Merge branch 'jk/parseopt-string-list' into jk/string-list-static-init
authorJunio C Hamano <gitster@pobox.com>
Mon, 13 Jun 2016 17:37:48 +0000 (10:37 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 13 Jun 2016 17:37:48 +0000 (10:37 -0700)
* jk/parseopt-string-list:
blame,shortlog: don't make local option variables static
interpret-trailers: don't duplicate option strings
parse_opt_string_list: stop allocating new strings

1  2 
builtin/blame.c
builtin/interpret-trailers.c
builtin/shortlog.c
parse-options-cb.c
diff --combined builtin/blame.c
index 21f42b0b62b81b637f1cc9589cd6c0306a93d05e,9b1701d31494c5fe6d1c473b743ac30a6f00b8a8..80d24315b3ae3b7bbf3674356673b1af37fc4084
@@@ -28,7 -28,6 +28,7 @@@
  #include "line-range.h"
  #include "line-log.h"
  #include "dir.h"
 +#include "progress.h"
  
  static char blame_usage[] = N_("git blame [<options>] [<rev-opts>] [<rev>] [--] <file>");
  
@@@ -51,7 -50,6 +51,7 @@@ static int incremental
  static int xdl_opts;
  static int abbrev = -1;
  static int no_whole_file_rename;
 +static int show_progress;
  
  static struct date_mode blame_date_mode = { DATE_ISO8601 };
  static size_t blame_date_width;
@@@ -129,11 -127,6 +129,11 @@@ struct origin 
        char path[FLEX_ARRAY];
  };
  
 +struct progress_info {
 +      struct progress *progress;
 +      int blamed_lines;
 +};
 +
  static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b, long ctxlen,
                      xdl_emit_hunk_consume_func_t hunk_func, void *cb_data)
  {
@@@ -466,11 -459,12 +466,11 @@@ static void queue_blames(struct scorebo
  static struct origin *make_origin(struct commit *commit, const char *path)
  {
        struct origin *o;
 -      o = xcalloc(1, sizeof(*o) + strlen(path) + 1);
 +      FLEX_ALLOC_STR(o, path, path);
        o->commit = commit;
        o->refcnt = 1;
        o->next = commit->util;
        commit->util = o;
 -      strcpy(o->path, path);
        return o;
  }
  
@@@ -511,7 -505,7 +511,7 @@@ static int fill_blob_sha1_and_mode(stru
  {
        if (!is_null_sha1(origin->blob_sha1))
                return 0;
 -      if (get_tree_entry(origin->commit->object.sha1,
 +      if (get_tree_entry(origin->commit->object.oid.hash,
                           origin->path,
                           origin->blob_sha1, &origin->mode))
                goto error_out;
@@@ -562,11 -556,11 +562,11 @@@ static struct origin *find_origin(struc
                       PATHSPEC_LITERAL_PATH, "", paths);
        diff_setup_done(&diff_opts);
  
 -      if (is_null_sha1(origin->commit->object.sha1))
 -              do_diff_cache(parent->tree->object.sha1, &diff_opts);
 +      if (is_null_oid(&origin->commit->object.oid))
 +              do_diff_cache(parent->tree->object.oid.hash, &diff_opts);
        else
 -              diff_tree_sha1(parent->tree->object.sha1,
 -                             origin->commit->tree->object.sha1,
 +              diff_tree_sha1(parent->tree->object.oid.hash,
 +                             origin->commit->tree->object.oid.hash,
                               "", &diff_opts);
        diffcore_std(&diff_opts);
  
@@@ -632,11 -626,11 +632,11 @@@ static struct origin *find_rename(struc
        diff_opts.single_follow = origin->path;
        diff_setup_done(&diff_opts);
  
 -      if (is_null_sha1(origin->commit->object.sha1))
 -              do_diff_cache(parent->tree->object.sha1, &diff_opts);
 +      if (is_null_oid(&origin->commit->object.oid))
 +              do_diff_cache(parent->tree->object.oid.hash, &diff_opts);
        else
 -              diff_tree_sha1(parent->tree->object.sha1,
 -                             origin->commit->tree->object.sha1,
 +              diff_tree_sha1(parent->tree->object.oid.hash,
 +                             origin->commit->tree->object.oid.hash,
                               "", &diff_opts);
        diffcore_std(&diff_opts);
  
@@@ -982,8 -976,8 +982,8 @@@ static void pass_blame_to_parent(struc
  
        if (diff_hunks(&file_p, &file_o, 0, blame_chunk_cb, &d))
                die("unable to generate diff (%s -> %s)",
 -                  sha1_to_hex(parent->commit->object.sha1),
 -                  sha1_to_hex(target->commit->object.sha1));
 +                  oid_to_hex(&parent->commit->object.oid),
 +                  oid_to_hex(&target->commit->object.oid));
        /* The rest are the same as the parent */
        blame_chunk(&d.dstq, &d.srcq, INT_MAX, d.offset, INT_MAX, parent);
        *d.dstq = NULL;
@@@ -1131,7 -1125,7 +1131,7 @@@ static void find_copy_in_blob(struct sc
        memset(split, 0, sizeof(struct blame_entry [3]));
        if (diff_hunks(file_p, &file_o, 1, handle_split_cb, &d))
                die("unable to generate diff (%s)",
 -                  sha1_to_hex(parent->commit->object.sha1));
 +                  oid_to_hex(&parent->commit->object.oid));
        /* remainder, if any, all match the preimage */
        handle_split(sb, ent, d.tlno, d.plno, ent->num_lines, parent, split);
  }
@@@ -1280,11 -1274,11 +1280,11 @@@ static void find_copy_in_parent(struct 
                && (!porigin || strcmp(target->path, porigin->path))))
                DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER);
  
 -      if (is_null_sha1(target->commit->object.sha1))
 -              do_diff_cache(parent->tree->object.sha1, &diff_opts);
 +      if (is_null_oid(&target->commit->object.oid))
 +              do_diff_cache(parent->tree->object.oid.hash, &diff_opts);
        else
 -              diff_tree_sha1(parent->tree->object.sha1,
 -                             target->commit->tree->object.sha1,
 +              diff_tree_sha1(parent->tree->object.oid.hash,
 +                             target->commit->tree->object.oid.hash,
                               "", &diff_opts);
  
        if (!DIFF_OPT_TST(&diff_opts, FIND_COPIES_HARDER))
@@@ -1695,7 -1689,7 +1695,7 @@@ static void get_commit_info(struct comm
        if (len)
                strbuf_add(&ret->summary, subject, len);
        else
 -              strbuf_addf(&ret->summary, "(%s)", sha1_to_hex(commit->object.sha1));
 +              strbuf_addf(&ret->summary, "(%s)", oid_to_hex(&commit->object.oid));
  
        unuse_commit_buffer(commit, message);
  }
@@@ -1738,7 -1732,7 +1738,7 @@@ static int emit_one_suspect_detail(stru
                printf("boundary\n");
        if (suspect->previous) {
                struct origin *prev = suspect->previous;
 -              printf("previous %s ", sha1_to_hex(prev->commit->object.sha1));
 +              printf("previous %s ", oid_to_hex(&prev->commit->object.oid));
                write_name_quoted(prev->path, stdout, '\n');
        }
  
   * The blame_entry is found to be guilty for the range.
   * Show it in incremental output.
   */
 -static void found_guilty_entry(struct blame_entry *ent)
 +static void found_guilty_entry(struct blame_entry *ent,
 +                         struct progress_info *pi)
  {
        if (incremental) {
                struct origin *suspect = ent->suspect;
  
                printf("%s %d %d %d\n",
 -                     sha1_to_hex(suspect->commit->object.sha1),
 +                     oid_to_hex(&suspect->commit->object.oid),
                       ent->s_lno + 1, ent->lno + 1, ent->num_lines);
                emit_one_suspect_detail(suspect, 0);
                write_filename_info(suspect->path);
                maybe_flush_or_die(stdout, "stdout");
        }
 +      pi->blamed_lines += ent->num_lines;
 +      display_progress(pi->progress, pi->blamed_lines);
  }
  
  /*
@@@ -1776,11 -1767,6 +1776,11 @@@ static void assign_blame(struct scorebo
  {
        struct rev_info *revs = sb->revs;
        struct commit *commit = prio_queue_get(&sb->commits);
 +      struct progress_info pi = { NULL, 0 };
 +
 +      if (show_progress)
 +              pi.progress = start_progress_delay(_("Blaming lines"),
 +                                                 sb->num_lines, 50, 1);
  
        while (commit) {
                struct blame_entry *ent;
                        suspect->guilty = 1;
                        for (;;) {
                                struct blame_entry *next = ent->next;
 -                              found_guilty_entry(ent);
 +                              found_guilty_entry(ent, &pi);
                                if (next) {
                                        ent = next;
                                        continue;
                if (DEBUG) /* sanity */
                        sanity_check_refcnt(sb);
        }
 +
 +      stop_progress(&pi.progress);
  }
  
  static const char *format_time(unsigned long time, const char *tz_str,
@@@ -1895,9 -1879,9 +1895,9 @@@ static void emit_porcelain(struct score
        int cnt;
        const char *cp;
        struct origin *suspect = ent->suspect;
 -      char hex[41];
 +      char hex[GIT_SHA1_HEXSZ + 1];
  
 -      strcpy(hex, sha1_to_hex(suspect->commit->object.sha1));
 +      sha1_to_hex_r(hex, suspect->commit->object.oid.hash);
        printf("%s %d %d %d\n",
               hex,
               ent->s_lno + 1,
@@@ -1933,11 -1917,11 +1933,11 @@@ static void emit_other(struct scoreboar
        const char *cp;
        struct origin *suspect = ent->suspect;
        struct commit_info ci;
 -      char hex[41];
 +      char hex[GIT_SHA1_HEXSZ + 1];
        int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP);
  
        get_commit_info(suspect->commit, &ci, 1);
 -      strcpy(hex, sha1_to_hex(suspect->commit->object.sha1));
 +      sha1_to_hex_r(hex, suspect->commit->object.oid.hash);
  
        cp = nth_line(sb, ent->lno);
        for (cnt = 0; cnt < ent->num_lines; cnt++) {
@@@ -2057,8 -2041,7 +2057,8 @@@ static int prepare_lines(struct scorebo
        for (p = buf; p < end; p = get_next_line(p, end))
                num++;
  
 -      sb->lineno = lineno = xmalloc(sizeof(*sb->lineno) * (num + 1));
 +      ALLOC_ARRAY(sb->lineno, num + 1);
 +      lineno = sb->lineno;
  
        for (p = buf; p < end; p = get_next_line(p, end))
                *lineno++ = p - buf;
@@@ -2093,7 -2076,7 +2093,7 @@@ static int read_ancestry(const char *gr
  
  static int update_auto_abbrev(int auto_abbrev, struct origin *suspect)
  {
 -      const char *uniq = find_unique_abbrev(suspect->commit->object.sha1,
 +      const char *uniq = find_unique_abbrev(suspect->commit->object.oid.hash,
                                              auto_abbrev);
        int len = strlen(uniq);
        if (auto_abbrev < len)
@@@ -2169,7 -2152,7 +2169,7 @@@ static void sanity_check_refcnt(struct 
                if (ent->suspect->refcnt <= 0) {
                        fprintf(stderr, "%s in %s has negative refcnt %d\n",
                                ent->suspect->path,
 -                              sha1_to_hex(ent->suspect->commit->object.sha1),
 +                              oid_to_hex(&ent->suspect->commit->object.oid),
                                ent->suspect->refcnt);
                        baa = 1;
                }
@@@ -2232,7 -2215,7 +2232,7 @@@ static void verify_working_tree_path(st
        struct commit_list *parents;
  
        for (parents = work_tree->parents; parents; parents = parents->next) {
 -              const unsigned char *commit_sha1 = parents->item->object.sha1;
 +              const unsigned char *commit_sha1 = parents->item->object.oid.hash;
                unsigned char blob_sha1[20];
                unsigned mode;
  
@@@ -2307,7 -2290,6 +2307,7 @@@ static struct commit *fake_working_tree
        unsigned mode;
        struct strbuf msg = STRBUF_INIT;
  
 +      read_cache();
        time(&now);
        commit = alloc_commit_node();
        commit->object.parsed = 1;
        strbuf_addstr(&msg, "tree 0000000000000000000000000000000000000000\n");
        for (parent = commit->parents; parent; parent = parent->next)
                strbuf_addf(&msg, "parent %s\n",
 -                          sha1_to_hex(parent->item->object.sha1));
 +                          oid_to_hex(&parent->item->object.oid));
        strbuf_addf(&msg,
                    "author %s\n"
                    "committer %s\n\n"
        ce->ce_mode = create_ce_mode(mode);
        add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
  
 -      /*
 -       * We are not going to write this out, so this does not matter
 -       * right now, but someday we might optimize diff-index --cached
 -       * with cache-tree information.
 -       */
        cache_tree_invalidate_path(&the_index, path);
  
        return commit;
  }
  
 -static char *prepare_final(struct scoreboard *sb)
 +static struct commit *find_single_final(struct rev_info *revs,
 +                                      const char **name_p)
  {
        int i;
 -      const char *final_commit_name = NULL;
 -      struct rev_info *revs = sb->revs;
 +      struct commit *found = NULL;
 +      const char *name = NULL;
  
 -      /*
 -       * There must be one and only one positive commit in the
 -       * revs->pending array.
 -       */
        for (i = 0; i < revs->pending.nr; i++) {
                struct object *obj = revs->pending.objects[i].item;
                if (obj->flags & UNINTERESTING)
                        obj = deref_tag(obj, NULL, 0);
                if (obj->type != OBJ_COMMIT)
                        die("Non commit %s?", revs->pending.objects[i].name);
 -              if (sb->final)
 +              if (found)
                        die("More than one commit to dig from %s and %s?",
 -                          revs->pending.objects[i].name,
 -                          final_commit_name);
 -              sb->final = (struct commit *) obj;
 -              final_commit_name = revs->pending.objects[i].name;
 +                          revs->pending.objects[i].name, name);
 +              found = (struct commit *)obj;
 +              name = revs->pending.objects[i].name;
        }
 -      return xstrdup_or_null(final_commit_name);
 +      if (name_p)
 +              *name_p = name;
 +      return found;
 +}
 +
 +static char *prepare_final(struct scoreboard *sb)
 +{
 +      const char *name;
 +      sb->final = find_single_final(sb->revs, &name);
 +      return xstrdup_or_null(name);
  }
  
  static char *prepare_initial(struct scoreboard *sb)
@@@ -2520,19 -2502,17 +2520,19 @@@ int cmd_blame(int argc, const char **ar
        long dashdash_pos, lno;
        char *final_commit_name = NULL;
        enum object_type type;
 +      struct commit *final_commit = NULL;
  
-       static struct string_list range_list;
-       static int output_option = 0, opt = 0;
-       static int show_stats = 0;
-       static const char *revs_file = NULL;
-       static const char *contents_from = NULL;
-       static const struct option options[] = {
+       struct string_list range_list = STRING_LIST_INIT_NODUP;
+       int output_option = 0, opt = 0;
+       int show_stats = 0;
+       const char *revs_file = NULL;
+       const char *contents_from = NULL;
+       const struct option options[] = {
                OPT_BOOL(0, "incremental", &incremental, N_("Show blame entries as we find them, incrementally")),
                OPT_BOOL('b', NULL, &blank_boundary, N_("Show blank SHA-1 for boundary commits (Default: off)")),
                OPT_BOOL(0, "root", &show_root, N_("Do not treat root commits as boundaries (Default: off)")),
                OPT_BOOL(0, "show-stats", &show_stats, N_("Show work cost statistics")),
 +              OPT_BOOL(0, "progress", &show_progress, N_("Force progress reporting")),
                OPT_BIT(0, "score-debug", &output_option, N_("Show output score for blame entries"), OUTPUT_SHOW_SCORE),
                OPT_BIT('f', "show-name", &output_option, N_("Show original filename (Default: auto)"), OUTPUT_SHOW_NAME),
                OPT_BIT('n', "show-number", &output_option, N_("Show original linenumber (Default: off)"), OUTPUT_SHOW_NUMBER),
  
        save_commit_buffer = 0;
        dashdash_pos = 0;
 +      show_progress = -1;
  
        parse_options_start(&ctx, argc, argv, prefix, options,
                            PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0);
@@@ -2593,13 -2572,6 +2593,13 @@@ parse_done
        DIFF_OPT_CLR(&revs.diffopt, FOLLOW_RENAMES);
        argc = parse_options_end(&ctx);
  
 +      if (incremental || (output_option & OUTPUT_PORCELAIN)) {
 +              if (show_progress > 0)
 +                      die("--progress can't be used with --incremental or porcelain formats");
 +              show_progress = 0;
 +      } else if (show_progress < 0)
 +              show_progress = isatty(2);
 +
        if (0 < abbrev)
                /* one more abbrev length is needed for the boundary commit */
                abbrev++;
                   fewer display columns. */
                blame_date_width = utf8_strwidth(_("4 years, 11 months ago")) + 1; /* add the null */
                break;
 -      case DATE_LOCAL:
        case DATE_NORMAL:
                blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700");
                break;
        }
        else if (contents_from)
                die("--contents and --reverse do not blend well.");
 -      else if (revs.first_parent_only)
 -              die("combining --first-parent and --reverse is not supported");
        else {
                final_commit_name = prepare_initial(&sb);
                sb.commits.compare = compare_commits_by_reverse_commit_date;
 +              if (revs.first_parent_only)
 +                      revs.children.name = NULL;
        }
  
        if (!sb.final) {
        else if (contents_from)
                die("Cannot use --contents with final commit object name");
  
 +      if (reverse && revs.first_parent_only) {
 +              final_commit = find_single_final(sb.revs, NULL);
 +              if (!final_commit)
 +                      die("--reverse and --first-parent together require specified latest commit");
 +      }
 +
        /*
         * If we have bottom, this will mark the ancestors of the
         * bottom commits we would reach while traversing as
        if (prepare_revision_walk(&revs))
                die(_("revision walk setup failed"));
  
 -      if (is_null_sha1(sb.final->object.sha1)) {
 +      if (reverse && revs.first_parent_only) {
 +              struct commit *c = final_commit;
 +
 +              sb.revs->children.name = "children";
 +              while (c->parents &&
 +                     oidcmp(&c->object.oid, &sb.final->object.oid)) {
 +                      struct commit_list *l = xcalloc(1, sizeof(*l));
 +
 +                      l->item = c;
 +                      if (add_decoration(&sb.revs->children,
 +                                         &c->parents->item->object, l))
 +                              die("BUG: not unique item in first-parent chain");
 +                      c = c->parents->item;
 +              }
 +
 +              if (oidcmp(&c->object.oid, &sb.final->object.oid))
 +                      die("--reverse --first-parent together require range along first-parent chain");
 +      }
 +
 +      if (is_null_oid(&sb.final->object.oid)) {
                o = sb.final->util;
                sb.final_buf = xmemdupz(o->file.ptr, o->file.size);
                sb.final_buf_size = o->file.size;
  
        read_mailmap(&mailmap, NULL);
  
 +      assign_blame(&sb, opt);
 +
        if (!incremental)
                setup_pager();
  
 -      assign_blame(&sb, opt);
 -
        free(final_commit_name);
  
        if (incremental)
index b99ae4be8875ad8fb41fd8be2a84b6191681a4e4,b75e95311177f1db1907e44b36317a94d28ee38d..175f14797b101d22ead9d1008744440da66a7c1c
  #include "trailer.h"
  
  static const char * const git_interpret_trailers_usage[] = {
 -      N_("git interpret-trailers [--trim-empty] [(--trailer <token>[(=|:)<value>])...] [<file>...]"),
 +      N_("git interpret-trailers [--in-place] [--trim-empty] [(--trailer <token>[(=|:)<value>])...] [<file>...]"),
        NULL
  };
  
  int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)
  {
 +      int in_place = 0;
        int trim_empty = 0;
-       struct string_list trailers = STRING_LIST_INIT_DUP;
+       struct string_list trailers = STRING_LIST_INIT_NODUP;
  
        struct option options[] = {
 +              OPT_BOOL(0, "in-place", &in_place, N_("edit files in place")),
                OPT_BOOL(0, "trim-empty", &trim_empty, N_("trim empty trailers")),
                OPT_STRING_LIST(0, "trailer", &trailers, N_("trailer"),
                                N_("trailer(s) to add")),
        if (argc) {
                int i;
                for (i = 0; i < argc; i++)
 -                      process_trailers(argv[i], trim_empty, &trailers);
 -      } else
 -              process_trailers(NULL, trim_empty, &trailers);
 +                      process_trailers(argv[i], in_place, trim_empty, &trailers);
 +      } else {
 +              if (in_place)
 +                      die(_("no input file given for in-place editing"));
 +              process_trailers(NULL, in_place, trim_empty, &trailers);
 +      }
  
        string_list_clear(&trailers, 0);
  
diff --combined builtin/shortlog.c
index bfc082e58467953c1e4c96fd27a884abea4f5127,cb3e89cf1d540bb49eeda302f04db9e3409a3cbb..f83984e8a1264054b5bd32b3d559c75708a4f428
@@@ -14,26 -14,7 +14,26 @@@ static char const * const shortlog_usag
        NULL
  };
  
 -static int compare_by_number(const void *a1, const void *a2)
 +/*
 + * The util field of our string_list_items will contain one of two things:
 + *
 + *   - if --summary is not in use, it will point to a string list of the
 + *     oneline subjects assigned to this author
 + *
 + *   - if --summary is in use, we don't need that list; we only need to know
 + *     its size. So we abuse the pointer slot to store our integer counter.
 + *
 + *  This macro accesses the latter.
 + */
 +#define UTIL_TO_INT(x) ((intptr_t)(x)->util)
 +
 +static int compare_by_counter(const void *a1, const void *a2)
 +{
 +      const struct string_list_item *i1 = a1, *i2 = a2;
 +      return UTIL_TO_INT(i2) - UTIL_TO_INT(i1);
 +}
 +
 +static int compare_by_list(const void *a1, const void *a2)
  {
        const struct string_list_item *i1 = a1, *i2 = a2;
        const struct string_list *l1 = i1->util, *l2 = i2->util;
@@@ -50,9 -31,13 +50,9 @@@ static void insert_one_record(struct sh
                              const char *author,
                              const char *oneline)
  {
 -      const char *dot3 = log->common_repo_prefix;
 -      char *buffer, *p;
        struct string_list_item *item;
        const char *mailbuf, *namebuf;
        size_t namelen, maillen;
 -      const char *eol;
 -      struct strbuf subject = STRBUF_INIT;
        struct strbuf namemailbuf = STRBUF_INIT;
        struct ident_split ident;
  
                strbuf_addf(&namemailbuf, " <%.*s>", (int)maillen, mailbuf);
  
        item = string_list_insert(&log->list, namemailbuf.buf);
 -      if (item->util == NULL)
 -              item->util = xcalloc(1, sizeof(struct string_list));
 -
 -      /* Skip any leading whitespace, including any blank lines. */
 -      while (*oneline && isspace(*oneline))
 -              oneline++;
 -      eol = strchr(oneline, '\n');
 -      if (!eol)
 -              eol = oneline + strlen(oneline);
 -      if (starts_with(oneline, "[PATCH")) {
 -              char *eob = strchr(oneline, ']');
 -              if (eob && (!eol || eob < eol))
 -                      oneline = eob + 1;
 -      }
 -      while (*oneline && isspace(*oneline) && *oneline != '\n')
 -              oneline++;
 -      format_subject(&subject, oneline, " ");
 -      buffer = strbuf_detach(&subject, NULL);
 -
 -      if (dot3) {
 -              int dot3len = strlen(dot3);
 -              if (dot3len > 5) {
 -                      while ((p = strstr(buffer, dot3)) != NULL) {
 -                              int taillen = strlen(p) - dot3len;
 -                              memcpy(p, "/.../", 5);
 -                              memmove(p + 5, p + dot3len, taillen + 1);
 +
 +      if (log->summary)
 +              item->util = (void *)(UTIL_TO_INT(item) + 1);
 +      else {
 +              const char *dot3 = log->common_repo_prefix;
 +              char *buffer, *p;
 +              struct strbuf subject = STRBUF_INIT;
 +              const char *eol;
 +
 +              /* Skip any leading whitespace, including any blank lines. */
 +              while (*oneline && isspace(*oneline))
 +                      oneline++;
 +              eol = strchr(oneline, '\n');
 +              if (!eol)
 +                      eol = oneline + strlen(oneline);
 +              if (starts_with(oneline, "[PATCH")) {
 +                      char *eob = strchr(oneline, ']');
 +                      if (eob && (!eol || eob < eol))
 +                              oneline = eob + 1;
 +              }
 +              while (*oneline && isspace(*oneline) && *oneline != '\n')
 +                      oneline++;
 +              format_subject(&subject, oneline, " ");
 +              buffer = strbuf_detach(&subject, NULL);
 +
 +              if (dot3) {
 +                      int dot3len = strlen(dot3);
 +                      if (dot3len > 5) {
 +                              while ((p = strstr(buffer, dot3)) != NULL) {
 +                                      int taillen = strlen(p) - dot3len;
 +                                      memcpy(p, "/.../", 5);
 +                                      memmove(p + 5, p + dot3len, taillen + 1);
 +                              }
                        }
                }
 -      }
  
 -      string_list_append(item->util, buffer);
 +              if (item->util == NULL)
 +                      item->util = xcalloc(1, sizeof(struct string_list));
 +              string_list_append(item->util, buffer);
 +      }
  }
  
  static void read_from_stdin(struct shortlog *log)
  {
 -      char author[1024], oneline[1024];
 +      struct strbuf author = STRBUF_INIT;
 +      struct strbuf oneline = STRBUF_INIT;
  
 -      while (fgets(author, sizeof(author), stdin) != NULL) {
 -              if (!(author[0] == 'A' || author[0] == 'a') ||
 -                  !starts_with(author + 1, "uthor: "))
 +      while (strbuf_getline_lf(&author, stdin) != EOF) {
 +              const char *v;
 +              if (!skip_prefix(author.buf, "Author: ", &v) &&
 +                  !skip_prefix(author.buf, "author ", &v))
                        continue;
 -              while (fgets(oneline, sizeof(oneline), stdin) &&
 -                     oneline[0] != '\n')
 +              while (strbuf_getline_lf(&oneline, stdin) != EOF &&
 +                     oneline.len)
                        ; /* discard headers */
 -              while (fgets(oneline, sizeof(oneline), stdin) &&
 -                     oneline[0] == '\n')
 +              while (strbuf_getline_lf(&oneline, stdin) != EOF &&
 +                     !oneline.len)
                        ; /* discard blanks */
 -              insert_one_record(log, author + 8, oneline);
 +              insert_one_record(log, v, oneline.buf);
        }
 +      strbuf_release(&author);
 +      strbuf_release(&oneline);
  }
  
  void shortlog_add_commit(struct shortlog *log, struct commit *commit)
  {
 -      const char *author = NULL, *buffer;
 -      struct strbuf buf = STRBUF_INIT;
 -      struct strbuf ufbuf = STRBUF_INIT;
 -
 -      pp_commit_easy(CMIT_FMT_RAW, commit, &buf);
 -      buffer = buf.buf;
 -      while (*buffer && *buffer != '\n') {
 -              const char *eol = strchr(buffer, '\n');
 -
 -              if (eol == NULL)
 -                      eol = buffer + strlen(buffer);
 +      struct strbuf author = STRBUF_INIT;
 +      struct strbuf oneline = STRBUF_INIT;
 +      struct pretty_print_context ctx = {0};
 +
 +      ctx.fmt = CMIT_FMT_USERFORMAT;
 +      ctx.abbrev = log->abbrev;
 +      ctx.subject = "";
 +      ctx.after_subject = "";
 +      ctx.date_mode.type = DATE_NORMAL;
 +      ctx.output_encoding = get_log_output_encoding();
 +
 +      format_commit_message(commit, "%an <%ae>", &author, &ctx);
 +      if (!log->summary) {
 +              if (log->user_format)
 +                      pretty_print_commit(&ctx, commit, &oneline);
                else
 -                      eol++;
 -
 -              if (starts_with(buffer, "author "))
 -                      author = buffer + 7;
 -              buffer = eol;
 -      }
 -      if (!author) {
 -              warning(_("Missing author: %s"),
 -                  sha1_to_hex(commit->object.sha1));
 -              return;
 +                      format_commit_message(commit, "%s", &oneline, &ctx);
        }
 -      if (log->user_format) {
 -              struct pretty_print_context ctx = {0};
 -              ctx.fmt = CMIT_FMT_USERFORMAT;
 -              ctx.abbrev = log->abbrev;
 -              ctx.subject = "";
 -              ctx.after_subject = "";
 -              ctx.date_mode.type = DATE_NORMAL;
 -              ctx.output_encoding = get_log_output_encoding();
 -              pretty_print_commit(&ctx, commit, &ufbuf);
 -              buffer = ufbuf.buf;
 -      } else if (*buffer) {
 -              buffer++;
 -      }
 -      insert_one_record(log, author, !*buffer ? "<none>" : buffer);
 -      strbuf_release(&ufbuf);
 -      strbuf_release(&buf);
 +
 +      insert_one_record(log, author.buf, oneline.len ? oneline.buf : "<none>");
 +
 +      strbuf_release(&author);
 +      strbuf_release(&oneline);
  }
  
  static void get_from_rev(struct rev_info *rev, struct shortlog *log)
@@@ -233,11 -221,11 +233,11 @@@ void shortlog_init(struct shortlog *log
  
  int cmd_shortlog(int argc, const char **argv, const char *prefix)
  {
-       static struct shortlog log;
-       static struct rev_info rev;
+       struct shortlog log = { STRING_LIST_INIT_NODUP };
+       struct rev_info rev;
        int nongit = !startup_info->have_repository;
  
-       static const struct option options[] = {
+       const struct option options[] = {
                OPT_BOOL('n', "numbered", &log.sort_by_number,
                         N_("sort output according to the number of commits per author")),
                OPT_BOOL('s', "summary", &log.summary,
@@@ -306,14 -294,14 +306,14 @@@ void shortlog_output(struct shortlog *l
  
        if (log->sort_by_number)
                qsort(log->list.items, log->list.nr, sizeof(struct string_list_item),
 -                      compare_by_number);
 +                    log->summary ? compare_by_counter : compare_by_list);
        for (i = 0; i < log->list.nr; i++) {
 -              struct string_list *onelines = log->list.items[i].util;
 -
 +              const struct string_list_item *item = &log->list.items[i];
                if (log->summary) {
 -                      printf("%6d\t%s\n", onelines->nr, log->list.items[i].string);
 +                      printf("%6d\t%s\n", (int)UTIL_TO_INT(item), item->string);
                } else {
 -                      printf("%s (%d):\n", log->list.items[i].string, onelines->nr);
 +                      struct string_list *onelines = item->util;
 +                      printf("%s (%d):\n", item->string, onelines->nr);
                        for (j = onelines->nr - 1; j >= 0; j--) {
                                const char *msg = onelines->items[j].string;
  
                                        printf("      %s\n", msg);
                        }
                        putchar('\n');
 +                      onelines->strdup_strings = 1;
 +                      string_list_clear(onelines, 0);
 +                      free(onelines);
                }
  
 -              onelines->strdup_strings = 1;
 -              string_list_clear(onelines, 0);
 -              free(onelines);
                log->list.items[i].util = NULL;
        }
  
diff --combined parse-options-cb.c
index 239898d946f06d102030569fd45780f523fdcd5a,4e8290181ff718a48296d6da551c182722691630..ba5acf3111d809a3c77314fb2c0852854620cfae
@@@ -5,7 -5,6 +5,7 @@@
  #include "color.h"
  #include "string-list.h"
  #include "argv-array.h"
 +#include "sha1-array.h"
  
  /*----- some often used options -----*/
  
@@@ -78,7 -77,7 +78,7 @@@ int parse_opt_verbosity_cb(const struc
        return 0;
  }
  
 -int parse_opt_with_commit(const struct option *opt, const char *arg, int unset)
 +int parse_opt_commits(const struct option *opt, const char *arg, int unset)
  {
        unsigned char sha1[20];
        struct commit *commit;
        return 0;
  }
  
 +int parse_opt_object_name(const struct option *opt, const char *arg, int unset)
 +{
 +      unsigned char sha1[20];
 +
 +      if (unset) {
 +              sha1_array_clear(opt->value);
 +              return 0;
 +      }
 +      if (!arg)
 +              return -1;
 +      if (get_sha1(arg, sha1))
 +              return error(_("malformed object name '%s'"), arg);
 +      sha1_array_append(opt->value, sha1);
 +      return 0;
 +}
 +
  int parse_opt_tertiary(const struct option *opt, const char *arg, int unset)
  {
        int *target = opt->value;
@@@ -144,7 -127,7 +144,7 @@@ int parse_opt_string_list(const struct 
        if (!arg)
                return -1;
  
-       string_list_append(v, xstrdup(arg));
+       string_list_append(v, arg);
        return 0;
  }