Make read_patch_file work on a strbuf.
[gitweb.git] / log-tree.c
index 3d404824a172c34c7ce0224de3b355f6c97eb52f..3e5e6acfafb5345b8ed4f67599270e841b194ade 100644 (file)
 #include "diff.h"
 #include "commit.h"
 #include "log-tree.h"
+#include "reflog-walk.h"
 
-void init_log_tree_opt(struct log_tree_opt *opt)
+struct decoration name_decoration = { "object names" };
+
+static void show_parents(struct commit *commit, int abbrev)
 {
-       memset(opt, 0, sizeof *opt);
-       opt->ignore_merges = 1;
-       opt->header_prefix = "";
-       opt->commit_format = CMIT_FMT_RAW;
-       diff_setup(&opt->diffopt);
+       struct commit_list *p;
+       for (p = commit->parents; p ; p = p->next) {
+               struct commit *parent = p->item;
+               printf(" %s", diff_unique_abbrev(parent->object.sha1, abbrev));
+       }
 }
 
-int log_tree_opt_parse(struct log_tree_opt *opt, const char **av, int ac)
+static void show_decorations(struct commit *commit)
 {
-       const char *arg;
-       int cnt = diff_opt_parse(&opt->diffopt, av, ac);
-       if (0 < cnt)
-               return cnt;
-       arg = *av;
-       if (!strcmp(arg, "-r"))
-               opt->diffopt.recursive = 1;
-       else if (!strcmp(arg, "-t")) {
-               opt->diffopt.recursive = 1;
-               opt->diffopt.tree_in_recursive = 1;
+       const char *prefix;
+       struct name_decoration *decoration;
+
+       decoration = lookup_decoration(&name_decoration, &commit->object);
+       if (!decoration)
+               return;
+       prefix = " (";
+       while (decoration) {
+               printf("%s%s", prefix, decoration->name);
+               prefix = ", ";
+               decoration = decoration->next;
        }
-       else if (!strcmp(arg, "-m"))
-               opt->ignore_merges = 0;
-       else if (!strcmp(arg, "-c"))
-               opt->combine_merges = 1;
-       else if (!strcmp(arg, "--cc")) {
-               opt->dense_combined_merges = 1;
-               opt->combine_merges = 1;
+       putchar(')');
+}
+
+/*
+ * Search for "^[-A-Za-z]+: [^@]+@" pattern. It usually matches
+ * Signed-off-by: and Acked-by: lines.
+ */
+static int detect_any_signoff(char *letter, int size)
+{
+       char ch, *cp;
+       int seen_colon = 0;
+       int seen_at = 0;
+       int seen_name = 0;
+       int seen_head = 0;
+
+       cp = letter + size;
+       while (letter <= --cp && (ch = *cp) == '\n')
+               continue;
+
+       while (letter <= cp) {
+               ch = *cp--;
+               if (ch == '\n')
+                       break;
+
+               if (!seen_at) {
+                       if (ch == '@')
+                               seen_at = 1;
+                       continue;
+               }
+               if (!seen_colon) {
+                       if (ch == '@')
+                               return 0;
+                       else if (ch == ':')
+                               seen_colon = 1;
+                       else
+                               seen_name = 1;
+                       continue;
+               }
+               if (('A' <= ch && ch <= 'Z') ||
+                   ('a' <= ch && ch <= 'z') ||
+                   ch == '-') {
+                       seen_head = 1;
+                       continue;
+               }
+               /* no empty last line doesn't match */
+               return 0;
        }
-       else if (!strcmp(arg, "-v")) {
-               opt->verbose_header = 1;
-               opt->header_prefix = "diff-tree ";
+       return seen_head && seen_name;
+}
+
+static void append_signoff(struct strbuf *sb, const char *signoff)
+{
+       static const char signed_off_by[] = "Signed-off-by: ";
+       size_t signoff_len = strlen(signoff);
+       int has_signoff = 0;
+       char *cp;
+
+       cp = sb->buf;
+
+       /* First see if we already have the sign-off by the signer */
+       while ((cp = strstr(cp, signed_off_by))) {
+
+               has_signoff = 1;
+
+               cp += strlen(signed_off_by);
+               if (cp + signoff_len >= sb->buf + sb->len)
+                       break;
+               if (strncmp(cp, signoff, signoff_len))
+                       continue;
+               if (!isspace(cp[signoff_len]))
+                       continue;
+               /* we already have him */
+               return;
        }
-       else if (!strncmp(arg, "--pretty", 8)) {
-               opt->verbose_header = 1;
-               opt->header_prefix = "diff-tree ";
-               opt->commit_format = get_commit_format(arg+8);
+
+       if (!has_signoff)
+               has_signoff = detect_any_signoff(sb->buf, sb->len);
+
+       if (!has_signoff)
+               strbuf_addch(sb, '\n');
+
+       strbuf_addstr(sb, signed_off_by);
+       strbuf_add(sb, signoff, signoff_len);
+       strbuf_addch(sb, '\n');
+}
+
+static unsigned int digits_in_number(unsigned int number)
+{
+       unsigned int i = 10, result = 1;
+       while (i <= number) {
+               i *= 10;
+               result++;
        }
-       else if (!strcmp(arg, "--root"))
-               opt->show_root_diff = 1;
-       else if (!strcmp(arg, "--no-commit-id"))
-               opt->no_commit_id = 1;
-       else if (!strcmp(arg, "--always"))
-               opt->always_show_header = 1;
-       else
-               return 0;
-       return 1;
+       return result;
 }
 
-int log_tree_diff_flush(struct log_tree_opt *opt)
+void show_log(struct rev_info *opt, const char *sep)
+{
+       struct strbuf msgbuf;
+       struct log_info *log = opt->loginfo;
+       struct commit *commit = log->commit, *parent = log->parent;
+       int abbrev = opt->diffopt.abbrev;
+       int abbrev_commit = opt->abbrev_commit ? opt->abbrev : 40;
+       const char *extra;
+       const char *subject = NULL, *extra_headers = opt->extra_headers;
+
+       opt->loginfo = NULL;
+       if (!opt->verbose_header) {
+               if (opt->left_right) {
+                       if (commit->object.flags & BOUNDARY)
+                               putchar('-');
+                       else if (commit->object.flags & SYMMETRIC_LEFT)
+                               putchar('<');
+                       else
+                               putchar('>');
+               }
+               fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit), stdout);
+               if (opt->parents)
+                       show_parents(commit, abbrev_commit);
+               show_decorations(commit);
+               putchar(opt->diffopt.line_termination);
+               return;
+       }
+
+       /*
+        * The "oneline" format has several special cases:
+        *  - The pretty-printed commit lacks a newline at the end
+        *    of the buffer, but we do want to make sure that we
+        *    have a newline there. If the separator isn't already
+        *    a newline, add an extra one.
+        *  - unlike other log messages, the one-line format does
+        *    not have an empty line between entries.
+        */
+       extra = "";
+       if (*sep != '\n' && opt->commit_format == CMIT_FMT_ONELINE)
+               extra = "\n";
+       if (opt->shown_one && opt->commit_format != CMIT_FMT_ONELINE)
+               putchar(opt->diffopt.line_termination);
+       opt->shown_one = 1;
+
+       /*
+        * Print header line of header..
+        */
+
+       if (opt->commit_format == CMIT_FMT_EMAIL) {
+               char *sha1 = sha1_to_hex(commit->object.sha1);
+               if (opt->total > 0) {
+                       static char buffer[64];
+                       snprintf(buffer, sizeof(buffer),
+                                       "Subject: [%s %0*d/%d] ",
+                                       opt->subject_prefix,
+                                       digits_in_number(opt->total),
+                                       opt->nr, opt->total);
+                       subject = buffer;
+               } else if (opt->total == 0 && opt->subject_prefix && *opt->subject_prefix) {
+                       static char buffer[256];
+                       snprintf(buffer, sizeof(buffer),
+                                       "Subject: [%s] ",
+                                       opt->subject_prefix);
+                       subject = buffer;
+               } else {
+                       subject = "Subject: ";
+               }
+
+               printf("From %s Mon Sep 17 00:00:00 2001\n", sha1);
+               if (opt->message_id)
+                       printf("Message-Id: <%s>\n", opt->message_id);
+               if (opt->ref_message_id)
+                       printf("In-Reply-To: <%s>\nReferences: <%s>\n",
+                              opt->ref_message_id, opt->ref_message_id);
+               if (opt->mime_boundary) {
+                       static char subject_buffer[1024];
+                       static char buffer[1024];
+                       snprintf(subject_buffer, sizeof(subject_buffer) - 1,
+                                "%s"
+                                "MIME-Version: 1.0\n"
+                                "Content-Type: multipart/mixed;"
+                                " boundary=\"%s%s\"\n"
+                                "\n"
+                                "This is a multi-part message in MIME "
+                                "format.\n"
+                                "--%s%s\n"
+                                "Content-Type: text/plain; "
+                                "charset=UTF-8; format=fixed\n"
+                                "Content-Transfer-Encoding: 8bit\n\n",
+                                extra_headers ? extra_headers : "",
+                                mime_boundary_leader, opt->mime_boundary,
+                                mime_boundary_leader, opt->mime_boundary);
+                       extra_headers = subject_buffer;
+
+                       snprintf(buffer, sizeof(buffer) - 1,
+                                "--%s%s\n"
+                                "Content-Type: text/x-patch;"
+                                " name=\"%s.diff\"\n"
+                                "Content-Transfer-Encoding: 8bit\n"
+                                "Content-Disposition: %s;"
+                                " filename=\"%s.diff\"\n\n",
+                                mime_boundary_leader, opt->mime_boundary,
+                                sha1,
+                                opt->no_inline ? "attachment" : "inline",
+                                sha1);
+                       opt->diffopt.stat_sep = buffer;
+               }
+       } else if (opt->commit_format != CMIT_FMT_USERFORMAT) {
+               fputs(diff_get_color(opt->diffopt.color_diff, DIFF_COMMIT),
+                     stdout);
+               if (opt->commit_format != CMIT_FMT_ONELINE)
+                       fputs("commit ", stdout);
+               if (commit->object.flags & BOUNDARY)
+                       putchar('-');
+               else if (opt->left_right) {
+                       if (commit->object.flags & SYMMETRIC_LEFT)
+                               putchar('<');
+                       else
+                               putchar('>');
+               }
+               fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit),
+                     stdout);
+               if (opt->parents)
+                       show_parents(commit, abbrev_commit);
+               if (parent)
+                       printf(" (from %s)",
+                              diff_unique_abbrev(parent->object.sha1,
+                                                 abbrev_commit));
+               show_decorations(commit);
+               printf("%s",
+                      diff_get_color(opt->diffopt.color_diff, DIFF_RESET));
+               putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n');
+               if (opt->reflog_info) {
+                       show_reflog_message(opt->reflog_info,
+                                   opt->commit_format == CMIT_FMT_ONELINE,
+                                   opt->date_mode);
+                       if (opt->commit_format == CMIT_FMT_ONELINE) {
+                               printf("%s", sep);
+                               return;
+                       }
+               }
+       }
+
+       /*
+        * And then the pretty-printed message itself
+        */
+       strbuf_init(&msgbuf, 0);
+       pretty_print_commit(opt->commit_format, commit, &msgbuf,
+                                 abbrev, subject, extra_headers, opt->date_mode);
+
+       if (opt->add_signoff)
+               append_signoff(&msgbuf, opt->add_signoff);
+       if (opt->show_log_size)
+               printf("log size %i\n", (int)msgbuf.len);
+
+       printf("%s%s%s", msgbuf.buf, extra, sep);
+       strbuf_release(&msgbuf);
+}
+
+int log_tree_diff_flush(struct rev_info *opt)
 {
        diffcore_std(&opt->diffopt);
+
        if (diff_queue_is_empty()) {
                int saved_fmt = opt->diffopt.output_format;
                opt->diffopt.output_format = DIFF_FORMAT_NO_OUTPUT;
@@ -63,113 +295,104 @@ int log_tree_diff_flush(struct log_tree_opt *opt)
                opt->diffopt.output_format = saved_fmt;
                return 0;
        }
-       if (opt->header) {
-               if (!opt->no_commit_id)
-                       printf("%s%c", opt->header,
-                              opt->diffopt.line_termination);
-               opt->header = NULL;
+
+       if (opt->loginfo && !opt->no_commit_id) {
+               /* When showing a verbose header (i.e. log message),
+                * and not in --pretty=oneline format, we would want
+                * an extra newline between the end of log and the
+                * output for readability.
+                */
+               show_log(opt, opt->diffopt.msg_sep);
+               if (opt->verbose_header &&
+                   opt->commit_format != CMIT_FMT_ONELINE) {
+                       int pch = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH;
+                       if ((pch & opt->diffopt.output_format) == pch)
+                               printf("---");
+                       putchar('\n');
+               }
        }
        diff_flush(&opt->diffopt);
        return 1;
 }
 
-static int diff_root_tree(struct log_tree_opt *opt,
-                         const unsigned char *new, const char *base)
-{
-       int retval;
-       void *tree;
-       struct tree_desc empty, real;
-
-       tree = read_object_with_reference(new, tree_type, &real.size, NULL);
-       if (!tree)
-               die("unable to read root tree (%s)", sha1_to_hex(new));
-       real.buf = tree;
-
-       empty.buf = "";
-       empty.size = 0;
-       retval = diff_tree(&empty, &real, base, &opt->diffopt);
-       free(tree);
-       log_tree_diff_flush(opt);
-       return retval;
-}
-
-static const char *generate_header(struct log_tree_opt *opt,
-                                  const unsigned char *commit_sha1,
-                                  const unsigned char *parent_sha1,
-                                  const struct commit *commit)
-{
-       static char this_header[16384];
-       int offset;
-       unsigned long len;
-       int abbrev = opt->diffopt.abbrev;
-       const char *msg = commit->buffer;
-
-       if (!opt->verbose_header)
-               return sha1_to_hex(commit_sha1);
-
-       len = strlen(msg);
-
-       offset = sprintf(this_header, "%s%s ",
-                        opt->header_prefix,
-                        diff_unique_abbrev(commit_sha1, abbrev));
-       if (commit_sha1 != parent_sha1)
-               offset += sprintf(this_header + offset, "(from %s)\n",
-                                 parent_sha1
-                                 ? diff_unique_abbrev(parent_sha1, abbrev)
-                                 : "root");
-       else
-               offset += sprintf(this_header + offset, "(from parents)\n");
-       offset += pretty_print_commit(opt->commit_format, commit, len,
-                                     this_header + offset,
-                                     sizeof(this_header) - offset, abbrev);
-       if (opt->always_show_header) {
-               puts(this_header);
-               return NULL;
-       }
-       return this_header;
-}
-
-static int do_diff_combined(struct log_tree_opt *opt, struct commit *commit)
+static int do_diff_combined(struct rev_info *opt, struct commit *commit)
 {
        unsigned const char *sha1 = commit->object.sha1;
 
-       opt->header = generate_header(opt, sha1, sha1, commit);
-       opt->header = diff_tree_combined_merge(sha1, opt->header,
-                                               opt->dense_combined_merges,
-                                               &opt->diffopt);
-       if (!opt->header && opt->verbose_header)
-               opt->header_prefix = "\ndiff-tree ";
-       return 0;
+       diff_tree_combined_merge(sha1, opt->dense_combined_merges, opt);
+       return !opt->loginfo;
 }
 
-int log_tree_commit(struct log_tree_opt *opt, struct commit *commit)
+/*
+ * Show the diff of a commit.
+ *
+ * Return true if we printed any log info messages
+ */
+static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log_info *log)
 {
+       int showed_log;
        struct commit_list *parents;
        unsigned const char *sha1 = commit->object.sha1;
 
+       if (!opt->diff)
+               return 0;
+
        /* Root commit? */
-       if (opt->show_root_diff && !commit->parents) {
-               opt->header = generate_header(opt, sha1, NULL, commit);
-               diff_root_tree(opt, sha1, "");
+       parents = commit->parents;
+       if (!parents) {
+               if (opt->show_root_diff) {
+                       diff_root_tree_sha1(sha1, "", &opt->diffopt);
+                       log_tree_diff_flush(opt);
+               }
+               return !opt->loginfo;
        }
 
        /* More than one parent? */
-       if (commit->parents && commit->parents->next) {
+       if (parents && parents->next) {
                if (opt->ignore_merges)
                        return 0;
                else if (opt->combine_merges)
                        return do_diff_combined(opt, commit);
+
+               /* If we show individual diffs, show the parent info */
+               log->parent = parents->item;
        }
 
-       for (parents = commit->parents; parents; parents = parents->next) {
+       showed_log = 0;
+       for (;;) {
                struct commit *parent = parents->item;
-               unsigned const char *psha1 = parent->object.sha1;
-               opt->header = generate_header(opt, sha1, psha1, commit);
-               diff_tree_sha1(psha1, sha1, "", &opt->diffopt);
-               log_tree_diff_flush(opt);               
 
-               if (!opt->header && opt->verbose_header)
-                       opt->header_prefix = "\ndiff-tree ";
+               diff_tree_sha1(parent->object.sha1, sha1, "", &opt->diffopt);
+               log_tree_diff_flush(opt);
+
+               showed_log |= !opt->loginfo;
+
+               /* Set up the log info for the next parent, if any.. */
+               parents = parents->next;
+               if (!parents)
+                       break;
+               log->parent = parents->item;
+               opt->loginfo = log;
+       }
+       return showed_log;
+}
+
+int log_tree_commit(struct rev_info *opt, struct commit *commit)
+{
+       struct log_info log;
+       int shown;
+
+       log.commit = commit;
+       log.parent = NULL;
+       opt->loginfo = &log;
+
+       shown = log_tree_diff(opt, commit, &log);
+       if (!shown && opt->loginfo && opt->always_show_header) {
+               log.parent = NULL;
+               show_log(opt, "");
+               shown = 1;
        }
-       return 0;
+       opt->loginfo = NULL;
+       maybe_flush_or_die(stdout, "stdout");
+       return shown;
 }