ssh-upload: prevent buffer overrun
[gitweb.git] / log-tree.c
index 17e976a63753c11bb4e33565437cbfa118b01d62..35be33aaf73e0b53cb9ece09bba5f1e12de4b99d 100644 (file)
 #include "commit.h"
 #include "log-tree.h"
 
-void init_log_tree_opt(struct log_tree_opt *opt)
+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)
+/*
+ * 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)
 {
-       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;
+       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, "-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;
+       return seen_head && seen_name;
+}
+
+static int append_signoff(char *buf, int buf_sz, int at, const char *signoff)
+{
+       static const char signed_off_by[] = "Signed-off-by: ";
+       int signoff_len = strlen(signoff);
+       int has_signoff = 0;
+       char *cp = buf;
+
+       /* Do we have enough space to add it? */
+       if (buf_sz - at <= strlen(signed_off_by) + signoff_len + 3)
+               return at;
+
+       /* 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 >= buf + at)
+                       break;
+               if (strncmp(cp, signoff, signoff_len))
+                       continue;
+               if (!isspace(cp[signoff_len]))
+                       continue;
+               /* we already have him */
+               return at;
        }
-       else if (!strcmp(arg, "-v")) {
-               opt->verbose_header = 1;
-               opt->header_prefix = "diff-tree ";
+
+       if (!has_signoff)
+               has_signoff = detect_any_signoff(buf, at);
+
+       if (!has_signoff)
+               buf[at++] = '\n';
+
+       strcpy(buf + at, signed_off_by);
+       at += strlen(signed_off_by);
+       strcpy(buf + at, signoff);
+       at += signoff_len;
+       buf[at++] = '\n';
+       buf[at] = 0;
+       return at;
+}
+
+void show_log(struct rev_info *opt, const char *sep)
+{
+       static char this_header[16384];
+       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;
+       int len;
+       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);
+               putchar(opt->diffopt.line_termination);
+               return;
        }
-       else if (!strncmp(arg, "--pretty", 8)) {
-               opt->verbose_header = 1;
-               opt->header_prefix = "diff-tree ";
-               opt->commit_format = get_commit_format(arg+8);
+
+       /*
+        * 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('\n');
+       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: [PATCH %d/%d] ",
+                                       opt->nr, opt->total);
+                       subject = buffer;
+               } else if (opt->total == 0)
+                       subject = "Subject: [PATCH] ";
+               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;\n"
+                                " 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;\n"
+                                " name=\"%s.diff\"\n"
+                                "Content-Transfer-Encoding: 8bit\n"
+                                "Content-Disposition: inline;\n"
+                                " filename=\"%s.diff\"\n\n",
+                                mime_boundary_leader, opt->mime_boundary,
+                                sha1, sha1);
+                       opt->diffopt.stat_sep = buffer;
+               }
+       } else {
+               fputs(diff_get_color(opt->diffopt.color_diff, DIFF_COMMIT),
+                     stdout);
+               if (opt->commit_format != CMIT_FMT_ONELINE)
+                       fputs("commit ", stdout);
+               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);
+               if (parent)
+                       printf(" (from %s)",
+                              diff_unique_abbrev(parent->object.sha1,
+                                                 abbrev_commit));
+               printf("%s",
+                      diff_get_color(opt->diffopt.color_diff, DIFF_RESET));
+               putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n');
        }
-       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;
+
+       /*
+        * And then the pretty-printed message itself
+        */
+       len = pretty_print_commit(opt->commit_format, commit, ~0u, this_header,
+                                 sizeof(this_header), abbrev, subject,
+                                 extra_headers, opt->relative_date);
+
+       if (opt->add_signoff)
+               len = append_signoff(this_header, sizeof(this_header), len,
+                                    opt->add_signoff);
+       printf("%s%s%s", this_header, extra, sep);
 }
 
-int log_tree_diff_flush(struct log_tree_opt *opt)
+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,180 +249,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("---%c", opt->diffopt.line_termination);
+                       else
+                               putchar(opt->diffopt.line_termination);
+               }
        }
        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 0;
+       return showed_log;
 }
 
-int parse_whatchanged_opt(int ac, const char **av, struct whatchanged_opt *wcopt)
+int log_tree_commit(struct rev_info *opt, struct commit *commit)
 {
-       struct rev_info *rev = &wcopt->revopt;
-       struct log_tree_opt *opt = &wcopt->logopt;
-       const char **unrecognized = av+1;
-       int left = 1;
-
-       ac = setup_revisions(ac, av, rev, "HEAD");
-       if (!strcmp(av[0], "show"))
-               rev->no_walk = 1;
-       while (1 < ac) {
-               const char *arg = av[1];
-               if (!strncmp(arg, "--pretty", 8)) {
-                       opt->commit_format = get_commit_format(arg + 8);
-               }
-               else if (!strcmp(arg, "--no-abbrev")) {
-                       wcopt->abbrev = 0;
-               }
-               else if (!strcmp(arg, "--abbrev")) {
-                       wcopt->abbrev = DEFAULT_ABBREV;
-               }
-               else if (!strcmp(arg, "--abbrev-commit")) {
-                       wcopt->abbrev_commit = 1;
-               }
-               else if (!strncmp(arg, "--abbrev=", 9)) {
-                       wcopt->abbrev = strtoul(arg + 9, NULL, 10);
-                       if (wcopt->abbrev && wcopt->abbrev < MINIMUM_ABBREV)
-                               wcopt->abbrev = MINIMUM_ABBREV;
-                       else if (40 < wcopt->abbrev)
-                               wcopt->abbrev = 40;
-               }
-               else if (!strcmp(arg, "--full-diff")) {
-                       wcopt->do_diff = 1;
-                       wcopt->full_diff = 1;
-               }
-               else {
-                       int cnt = log_tree_opt_parse(opt, av+1, ac-1);
-                       if (0 < cnt) {
-                               wcopt->do_diff = 1;
-                               av += cnt;
-                               ac -= cnt;
-                               continue;
-                       }
-                       *unrecognized++ = arg;
-                       left++;
-               }
-               ac--; av++;
-       }
+       struct log_info log;
+       int shown;
+
+       log.commit = commit;
+       log.parent = NULL;
+       opt->loginfo = &log;
 
-       if (wcopt->do_diff) {
-               opt->diffopt.abbrev = wcopt->abbrev;
-               opt->verbose_header = 0;
-               opt->always_show_header = 0;
-               opt->no_commit_id = 1;
-               if (opt->combine_merges)
-                       opt->ignore_merges = 0;
-               if (opt->dense_combined_merges)
-                       opt->diffopt.output_format = DIFF_FORMAT_PATCH;
-               if (opt->diffopt.output_format == DIFF_FORMAT_PATCH)
-                       opt->diffopt.recursive = 1;
-               if (!wcopt->full_diff && rev->prune_data)
-                       diff_tree_setup_paths(rev->prune_data, &opt->diffopt);
-               diff_setup_done(&opt->diffopt);
+       shown = log_tree_diff(opt, commit, &log);
+       if (!shown && opt->loginfo && opt->always_show_header) {
+               log.parent = NULL;
+               show_log(opt, "");
+               shown = 1;
        }
-       return left;
+       opt->loginfo = NULL;
+       return shown;
 }