cf7947b9c9c37cd0591effc7f3653c1ce2dd723d
   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
  10struct decoration name_decoration = { "object names" };
  11
  12static void add_name_decoration(const char *prefix, const char *name, struct object *obj)
  13{
  14        int plen = strlen(prefix);
  15        int nlen = strlen(name);
  16        struct name_decoration *res = xmalloc(sizeof(struct name_decoration) + plen + nlen);
  17        memcpy(res->name, prefix, plen);
  18        memcpy(res->name + plen, name, nlen + 1);
  19        res->next = add_decoration(&name_decoration, obj, res);
  20}
  21
  22static int add_ref_decoration(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
  23{
  24        struct object *obj = parse_object(sha1);
  25        if (!obj)
  26                return 0;
  27        add_name_decoration("", refname, obj);
  28        while (obj->type == OBJ_TAG) {
  29                obj = ((struct tag *)obj)->tagged;
  30                if (!obj)
  31                        break;
  32                add_name_decoration("tag: ", refname, obj);
  33        }
  34        return 0;
  35}
  36
  37void load_ref_decorations(void)
  38{
  39        static int loaded;
  40        if (!loaded) {
  41                loaded = 1;
  42                for_each_ref(add_ref_decoration, NULL);
  43        }
  44}
  45
  46static void show_parents(struct commit *commit, int abbrev)
  47{
  48        struct commit_list *p;
  49        for (p = commit->parents; p ; p = p->next) {
  50                struct commit *parent = p->item;
  51                printf(" %s", diff_unique_abbrev(parent->object.sha1, abbrev));
  52        }
  53}
  54
  55void show_decorations(struct rev_info *opt, struct commit *commit)
  56{
  57        const char *prefix;
  58        struct name_decoration *decoration;
  59
  60        if (opt->show_source && commit->util)
  61                printf(" %s", (char *) commit->util);
  62        decoration = lookup_decoration(&name_decoration, &commit->object);
  63        if (!decoration)
  64                return;
  65        prefix = " (";
  66        while (decoration) {
  67                printf("%s%s", prefix, decoration->name);
  68                prefix = ", ";
  69                decoration = decoration->next;
  70        }
  71        putchar(')');
  72}
  73
  74/*
  75 * Search for "^[-A-Za-z]+: [^@]+@" pattern. It usually matches
  76 * Signed-off-by: and Acked-by: lines.
  77 */
  78static int detect_any_signoff(char *letter, int size)
  79{
  80        char ch, *cp;
  81        int seen_colon = 0;
  82        int seen_at = 0;
  83        int seen_name = 0;
  84        int seen_head = 0;
  85
  86        cp = letter + size;
  87        while (letter <= --cp && (ch = *cp) == '\n')
  88                continue;
  89
  90        while (letter <= cp) {
  91                ch = *cp--;
  92                if (ch == '\n')
  93                        break;
  94
  95                if (!seen_at) {
  96                        if (ch == '@')
  97                                seen_at = 1;
  98                        continue;
  99                }
 100                if (!seen_colon) {
 101                        if (ch == '@')
 102                                return 0;
 103                        else if (ch == ':')
 104                                seen_colon = 1;
 105                        else
 106                                seen_name = 1;
 107                        continue;
 108                }
 109                if (('A' <= ch && ch <= 'Z') ||
 110                    ('a' <= ch && ch <= 'z') ||
 111                    ch == '-') {
 112                        seen_head = 1;
 113                        continue;
 114                }
 115                /* no empty last line doesn't match */
 116                return 0;
 117        }
 118        return seen_head && seen_name;
 119}
 120
 121static void append_signoff(struct strbuf *sb, const char *signoff)
 122{
 123        static const char signed_off_by[] = "Signed-off-by: ";
 124        size_t signoff_len = strlen(signoff);
 125        int has_signoff = 0;
 126        char *cp;
 127
 128        cp = sb->buf;
 129
 130        /* First see if we already have the sign-off by the signer */
 131        while ((cp = strstr(cp, signed_off_by))) {
 132
 133                has_signoff = 1;
 134
 135                cp += strlen(signed_off_by);
 136                if (cp + signoff_len >= sb->buf + sb->len)
 137                        break;
 138                if (strncmp(cp, signoff, signoff_len))
 139                        continue;
 140                if (!isspace(cp[signoff_len]))
 141                        continue;
 142                /* we already have him */
 143                return;
 144        }
 145
 146        if (!has_signoff)
 147                has_signoff = detect_any_signoff(sb->buf, sb->len);
 148
 149        if (!has_signoff)
 150                strbuf_addch(sb, '\n');
 151
 152        strbuf_addstr(sb, signed_off_by);
 153        strbuf_add(sb, signoff, signoff_len);
 154        strbuf_addch(sb, '\n');
 155}
 156
 157static unsigned int digits_in_number(unsigned int number)
 158{
 159        unsigned int i = 10, result = 1;
 160        while (i <= number) {
 161                i *= 10;
 162                result++;
 163        }
 164        return result;
 165}
 166
 167static int has_non_ascii(const char *s)
 168{
 169        int ch;
 170        if (!s)
 171                return 0;
 172        while ((ch = *s++) != '\0') {
 173                if (non_ascii(ch))
 174                        return 1;
 175        }
 176        return 0;
 177}
 178
 179void log_write_email_headers(struct rev_info *opt, const char *name,
 180                             const char **subject_p,
 181                             const char **extra_headers_p,
 182                             int *need_8bit_cte_p)
 183{
 184        const char *subject = NULL;
 185        const char *extra_headers = opt->extra_headers;
 186
 187        *need_8bit_cte_p = 0; /* unknown */
 188        if (opt->total > 0) {
 189                static char buffer[64];
 190                snprintf(buffer, sizeof(buffer),
 191                         "Subject: [%s %0*d/%d] ",
 192                         opt->subject_prefix,
 193                         digits_in_number(opt->total),
 194                         opt->nr, opt->total);
 195                subject = buffer;
 196        } else if (opt->total == 0 && opt->subject_prefix && *opt->subject_prefix) {
 197                static char buffer[256];
 198                snprintf(buffer, sizeof(buffer),
 199                         "Subject: [%s] ",
 200                         opt->subject_prefix);
 201                subject = buffer;
 202        } else {
 203                subject = "Subject: ";
 204        }
 205
 206        printf("From %s Mon Sep 17 00:00:00 2001\n", name);
 207        graph_show_oneline(opt->graph);
 208        if (opt->message_id) {
 209                printf("Message-Id: <%s>\n", opt->message_id);
 210                graph_show_oneline(opt->graph);
 211        }
 212        if (opt->ref_message_id) {
 213                printf("In-Reply-To: <%s>\nReferences: <%s>\n",
 214                       opt->ref_message_id, opt->ref_message_id);
 215                graph_show_oneline(opt->graph);
 216        }
 217        if (opt->mime_boundary) {
 218                static char subject_buffer[1024];
 219                static char buffer[1024];
 220                *need_8bit_cte_p = -1; /* NEVER */
 221                snprintf(subject_buffer, sizeof(subject_buffer) - 1,
 222                         "%s"
 223                         "MIME-Version: 1.0\n"
 224                         "Content-Type: multipart/mixed;"
 225                         " boundary=\"%s%s\"\n"
 226                         "\n"
 227                         "This is a multi-part message in MIME "
 228                         "format.\n"
 229                         "--%s%s\n"
 230                         "Content-Type: text/plain; "
 231                         "charset=UTF-8; format=fixed\n"
 232                         "Content-Transfer-Encoding: 8bit\n\n",
 233                         extra_headers ? extra_headers : "",
 234                         mime_boundary_leader, opt->mime_boundary,
 235                         mime_boundary_leader, opt->mime_boundary);
 236                extra_headers = subject_buffer;
 237
 238                snprintf(buffer, sizeof(buffer) - 1,
 239                         "\n--%s%s\n"
 240                         "Content-Type: text/x-patch;"
 241                         " name=\"%s.diff\"\n"
 242                         "Content-Transfer-Encoding: 8bit\n"
 243                         "Content-Disposition: %s;"
 244                         " filename=\"%s.diff\"\n\n",
 245                         mime_boundary_leader, opt->mime_boundary,
 246                         name,
 247                         opt->no_inline ? "attachment" : "inline",
 248                         name);
 249                opt->diffopt.stat_sep = buffer;
 250        }
 251        *subject_p = subject;
 252        *extra_headers_p = extra_headers;
 253}
 254
 255void show_log(struct rev_info *opt)
 256{
 257        struct strbuf msgbuf = STRBUF_INIT;
 258        struct log_info *log = opt->loginfo;
 259        struct commit *commit = log->commit, *parent = log->parent;
 260        int abbrev = opt->diffopt.abbrev;
 261        int abbrev_commit = opt->abbrev_commit ? opt->abbrev : 40;
 262        const char *subject = NULL, *extra_headers = opt->extra_headers;
 263        int need_8bit_cte = 0;
 264
 265        opt->loginfo = NULL;
 266        if (!opt->verbose_header) {
 267                graph_show_commit(opt->graph);
 268
 269                if (!opt->graph) {
 270                        if (commit->object.flags & BOUNDARY)
 271                                putchar('-');
 272                        else if (commit->object.flags & UNINTERESTING)
 273                                putchar('^');
 274                        else if (opt->left_right) {
 275                                if (commit->object.flags & SYMMETRIC_LEFT)
 276                                        putchar('<');
 277                                else
 278                                        putchar('>');
 279                        }
 280                }
 281                fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit), stdout);
 282                if (opt->print_parents)
 283                        show_parents(commit, abbrev_commit);
 284                show_decorations(opt, commit);
 285                if (opt->graph && !graph_is_commit_finished(opt->graph)) {
 286                        putchar('\n');
 287                        graph_show_remainder(opt->graph);
 288                }
 289                putchar(opt->diffopt.line_termination);
 290                return;
 291        }
 292
 293        /*
 294         * If use_terminator is set, add a newline at the end of the entry.
 295         * Otherwise, add a diffopt.line_termination character before all
 296         * entries but the first.  (IOW, as a separator between entries)
 297         */
 298        if (opt->shown_one && !opt->use_terminator) {
 299                /*
 300                 * If entries are separated by a newline, the output
 301                 * should look human-readable.  If the last entry ended
 302                 * with a newline, print the graph output before this
 303                 * newline.  Otherwise it will end up as a completely blank
 304                 * line and will look like a gap in the graph.
 305                 *
 306                 * If the entry separator is not a newline, the output is
 307                 * primarily intended for programmatic consumption, and we
 308                 * never want the extra graph output before the entry
 309                 * separator.
 310                 */
 311                if (opt->diffopt.line_termination == '\n' &&
 312                    !opt->missing_newline)
 313                        graph_show_padding(opt->graph);
 314                putchar(opt->diffopt.line_termination);
 315        }
 316        opt->shown_one = 1;
 317
 318        /*
 319         * If the history graph was requested,
 320         * print the graph, up to this commit's line
 321         */
 322        graph_show_commit(opt->graph);
 323
 324        /*
 325         * Print header line of header..
 326         */
 327
 328        if (opt->commit_format == CMIT_FMT_EMAIL) {
 329                log_write_email_headers(opt, sha1_to_hex(commit->object.sha1),
 330                                        &subject, &extra_headers,
 331                                        &need_8bit_cte);
 332        } else if (opt->commit_format != CMIT_FMT_USERFORMAT) {
 333                fputs(diff_get_color_opt(&opt->diffopt, DIFF_COMMIT), stdout);
 334                if (opt->commit_format != CMIT_FMT_ONELINE)
 335                        fputs("commit ", stdout);
 336
 337                if (!opt->graph) {
 338                        if (commit->object.flags & BOUNDARY)
 339                                putchar('-');
 340                        else if (commit->object.flags & UNINTERESTING)
 341                                putchar('^');
 342                        else if (opt->left_right) {
 343                                if (commit->object.flags & SYMMETRIC_LEFT)
 344                                        putchar('<');
 345                                else
 346                                        putchar('>');
 347                        }
 348                }
 349                fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit),
 350                      stdout);
 351                if (opt->print_parents)
 352                        show_parents(commit, abbrev_commit);
 353                if (parent)
 354                        printf(" (from %s)",
 355                               diff_unique_abbrev(parent->object.sha1,
 356                                                  abbrev_commit));
 357                show_decorations(opt, commit);
 358                printf("%s", diff_get_color_opt(&opt->diffopt, DIFF_RESET));
 359                if (opt->commit_format == CMIT_FMT_ONELINE) {
 360                        putchar(' ');
 361                } else {
 362                        putchar('\n');
 363                        graph_show_oneline(opt->graph);
 364                }
 365                if (opt->reflog_info) {
 366                        /*
 367                         * setup_revisions() ensures that opt->reflog_info
 368                         * and opt->graph cannot both be set,
 369                         * so we don't need to worry about printing the
 370                         * graph info here.
 371                         */
 372                        show_reflog_message(opt->reflog_info,
 373                                    opt->commit_format == CMIT_FMT_ONELINE,
 374                                    opt->date_mode);
 375                        if (opt->commit_format == CMIT_FMT_ONELINE)
 376                                return;
 377                }
 378        }
 379
 380        if (!commit->buffer)
 381                return;
 382
 383        /*
 384         * And then the pretty-printed message itself
 385         */
 386        if (need_8bit_cte >= 0)
 387                need_8bit_cte = has_non_ascii(opt->add_signoff);
 388        pretty_print_commit(opt->commit_format, commit, &msgbuf,
 389                            abbrev, subject, extra_headers, opt->date_mode,
 390                            need_8bit_cte);
 391
 392        if (opt->add_signoff)
 393                append_signoff(&msgbuf, opt->add_signoff);
 394        if (opt->show_log_size) {
 395                printf("log size %i\n", (int)msgbuf.len);
 396                graph_show_oneline(opt->graph);
 397        }
 398
 399        /*
 400         * Set opt->missing_newline if msgbuf doesn't
 401         * end in a newline (including if it is empty)
 402         */
 403        if (!msgbuf.len || msgbuf.buf[msgbuf.len - 1] != '\n')
 404                opt->missing_newline = 1;
 405        else
 406                opt->missing_newline = 0;
 407
 408        if (opt->graph)
 409                graph_show_commit_msg(opt->graph, &msgbuf);
 410        else
 411                fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout);
 412        if (opt->use_terminator) {
 413                if (!opt->missing_newline)
 414                        graph_show_padding(opt->graph);
 415                putchar('\n');
 416        }
 417
 418        strbuf_release(&msgbuf);
 419}
 420
 421int log_tree_diff_flush(struct rev_info *opt)
 422{
 423        diffcore_std(&opt->diffopt);
 424
 425        if (diff_queue_is_empty()) {
 426                int saved_fmt = opt->diffopt.output_format;
 427                opt->diffopt.output_format = DIFF_FORMAT_NO_OUTPUT;
 428                diff_flush(&opt->diffopt);
 429                opt->diffopt.output_format = saved_fmt;
 430                return 0;
 431        }
 432
 433        if (opt->loginfo && !opt->no_commit_id) {
 434                /* When showing a verbose header (i.e. log message),
 435                 * and not in --pretty=oneline format, we would want
 436                 * an extra newline between the end of log and the
 437                 * output for readability.
 438                 */
 439                show_log(opt);
 440                if ((opt->diffopt.output_format & ~DIFF_FORMAT_NO_OUTPUT) &&
 441                    opt->verbose_header &&
 442                    opt->commit_format != CMIT_FMT_ONELINE) {
 443                        int pch = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH;
 444                        if ((pch & opt->diffopt.output_format) == pch)
 445                                printf("---");
 446                        putchar('\n');
 447                }
 448        }
 449        diff_flush(&opt->diffopt);
 450        return 1;
 451}
 452
 453static int do_diff_combined(struct rev_info *opt, struct commit *commit)
 454{
 455        unsigned const char *sha1 = commit->object.sha1;
 456
 457        diff_tree_combined_merge(sha1, opt->dense_combined_merges, opt);
 458        return !opt->loginfo;
 459}
 460
 461/*
 462 * Show the diff of a commit.
 463 *
 464 * Return true if we printed any log info messages
 465 */
 466static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log_info *log)
 467{
 468        int showed_log;
 469        struct commit_list *parents;
 470        unsigned const char *sha1 = commit->object.sha1;
 471
 472        if (!opt->diff && !DIFF_OPT_TST(&opt->diffopt, EXIT_WITH_STATUS))
 473                return 0;
 474
 475        /* Root commit? */
 476        parents = commit->parents;
 477        if (!parents) {
 478                if (opt->show_root_diff) {
 479                        diff_root_tree_sha1(sha1, "", &opt->diffopt);
 480                        log_tree_diff_flush(opt);
 481                }
 482                return !opt->loginfo;
 483        }
 484
 485        /* More than one parent? */
 486        if (parents && parents->next) {
 487                if (opt->ignore_merges)
 488                        return 0;
 489                else if (opt->combine_merges)
 490                        return do_diff_combined(opt, commit);
 491
 492                /* If we show individual diffs, show the parent info */
 493                log->parent = parents->item;
 494        }
 495
 496        showed_log = 0;
 497        for (;;) {
 498                struct commit *parent = parents->item;
 499
 500                diff_tree_sha1(parent->object.sha1, sha1, "", &opt->diffopt);
 501                log_tree_diff_flush(opt);
 502
 503                showed_log |= !opt->loginfo;
 504
 505                /* Set up the log info for the next parent, if any.. */
 506                parents = parents->next;
 507                if (!parents)
 508                        break;
 509                log->parent = parents->item;
 510                opt->loginfo = log;
 511        }
 512        return showed_log;
 513}
 514
 515int log_tree_commit(struct rev_info *opt, struct commit *commit)
 516{
 517        struct log_info log;
 518        int shown;
 519
 520        log.commit = commit;
 521        log.parent = NULL;
 522        opt->loginfo = &log;
 523
 524        shown = log_tree_diff(opt, commit, &log);
 525        if (!shown && opt->loginfo && opt->always_show_header) {
 526                log.parent = NULL;
 527                show_log(opt);
 528                shown = 1;
 529        }
 530        opt->loginfo = NULL;
 531        maybe_flush_or_die(stdout, "stdout");
 532        return shown;
 533}