Merge branch 'jn/parse-config-slot'
authorJunio C Hamano <gitster@pobox.com>
Mon, 20 Oct 2014 19:23:48 +0000 (12:23 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 20 Oct 2014 19:23:48 +0000 (12:23 -0700)
Code cleanup.

* jn/parse-config-slot:
color_parse: do not mention variable name in error message
pass config slots as pointers instead of offsets

1  2 
builtin/branch.c
builtin/clean.c
builtin/commit.c
builtin/config.c
builtin/for-each-ref.c
builtin/log.c
diff.c
log-tree.c
log-tree.h
pretty.c
diff --combined builtin/branch.c
index 67850975e7bebbf122ec961a527497c40c2c3f27,0a8ed9d22a410d51c44c3f93ade8bf746f071331..19a93a14d7dd04c2150ff43a8c0751d4a09baabb
@@@ -62,41 -62,38 +62,40 @@@ static unsigned char merge_filter_ref[2
  static struct string_list output = STRING_LIST_INIT_DUP;
  static unsigned int colopts;
  
- static int parse_branch_color_slot(const char *var, int ofs)
+ static int parse_branch_color_slot(const char *slot)
  {
-       if (!strcasecmp(var+ofs, "plain"))
+       if (!strcasecmp(slot, "plain"))
                return BRANCH_COLOR_PLAIN;
-       if (!strcasecmp(var+ofs, "reset"))
+       if (!strcasecmp(slot, "reset"))
                return BRANCH_COLOR_RESET;
-       if (!strcasecmp(var+ofs, "remote"))
+       if (!strcasecmp(slot, "remote"))
                return BRANCH_COLOR_REMOTE;
-       if (!strcasecmp(var+ofs, "local"))
+       if (!strcasecmp(slot, "local"))
                return BRANCH_COLOR_LOCAL;
-       if (!strcasecmp(var+ofs, "current"))
+       if (!strcasecmp(slot, "current"))
                return BRANCH_COLOR_CURRENT;
-       if (!strcasecmp(var+ofs, "upstream"))
+       if (!strcasecmp(slot, "upstream"))
                return BRANCH_COLOR_UPSTREAM;
        return -1;
  }
  
  static int git_branch_config(const char *var, const char *value, void *cb)
  {
 +      const char *slot_name;
 +
        if (starts_with(var, "column."))
                return git_column_config(var, value, "branch", &colopts);
        if (!strcmp(var, "color.branch")) {
                branch_use_color = git_config_colorbool(var, value);
                return 0;
        }
 -      if (starts_with(var, "color.branch.")) {
 -              int slot = parse_branch_color_slot(var + 13);
 +      if (skip_prefix(var, "color.branch.", &slot_name)) {
-               int slot = parse_branch_color_slot(var, slot_name - var);
++              int slot = parse_branch_color_slot(slot_name);
                if (slot < 0)
                        return 0;
                if (!value)
                        return config_error_nonbool(var);
-               color_parse(value, var, branch_colors[slot]);
-               return 0;
+               return color_parse(value, branch_colors[slot]);
        }
        return git_color_default_config(var, value, cb);
  }
@@@ -282,7 -279,6 +281,7 @@@ struct ref_item 
        char *dest;
        unsigned int kind, width;
        struct commit *commit;
 +      int ignore;
  };
  
  struct ref_list {
@@@ -337,18 -333,20 +336,18 @@@ static int append_ref(const char *refna
        static struct {
                int kind;
                const char *prefix;
 -              int pfxlen;
        } ref_kind[] = {
 -              { REF_LOCAL_BRANCH, "refs/heads/", 11 },
 -              { REF_REMOTE_BRANCH, "refs/remotes/", 13 },
 +              { REF_LOCAL_BRANCH, "refs/heads/" },
 +              { REF_REMOTE_BRANCH, "refs/remotes/" },
        };
  
        /* Detect kind */
        for (i = 0; i < ARRAY_SIZE(ref_kind); i++) {
                prefix = ref_kind[i].prefix;
 -              if (strncmp(refname, prefix, ref_kind[i].pfxlen))
 -                      continue;
 -              kind = ref_kind[i].kind;
 -              refname += ref_kind[i].pfxlen;
 -              break;
 +              if (skip_prefix(refname, prefix, &refname)) {
 +                      kind = ref_kind[i].kind;
 +                      break;
 +              }
        }
        if (ARRAY_SIZE(ref_kind) <= i)
                return 0;
        newitem->commit = commit;
        newitem->width = utf8_strwidth(refname);
        newitem->dest = resolve_symref(orig_refname, prefix);
 +      newitem->ignore = 0;
        /* adjust for "remotes/" */
        if (newitem->kind == REF_REMOTE_BRANCH &&
            ref_list->kinds != REF_REMOTE_BRANCH)
@@@ -486,6 -483,17 +485,6 @@@ static void fill_tracking_info(struct s
        free(ref);
  }
  
 -static int matches_merge_filter(struct commit *commit)
 -{
 -      int is_merged;
 -
 -      if (merge_filter == NO_FILTER)
 -              return 1;
 -
 -      is_merged = !!(commit->object.flags & UNINTERESTING);
 -      return (is_merged == (merge_filter == SHOW_MERGED));
 -}
 -
  static void add_verbose_info(struct strbuf *out, struct ref_item *item,
                             int verbose, int abbrev)
  {
@@@ -513,9 -521,10 +512,9 @@@ static void print_ref_item(struct ref_i
  {
        char c;
        int color;
 -      struct commit *commit = item->commit;
        struct strbuf out = STRBUF_INIT, name = STRBUF_INIT;
  
 -      if (!matches_merge_filter(commit))
 +      if (item->ignore)
                return;
  
        switch (item->kind) {
@@@ -565,7 -574,7 +564,7 @@@ static int calc_maxwidth(struct ref_lis
  {
        int i, w = 0;
        for (i = 0; i < refs->index; i++) {
 -              if (!matches_merge_filter(refs->list[i].commit))
 +              if (refs->list[i].ignore)
                        continue;
                if (refs->list[i].width > w)
                        w = refs->list[i].width;
@@@ -608,7 -617,6 +607,7 @@@ static void show_detached(struct ref_li
                item.kind = REF_LOCAL_BRANCH;
                item.dest = NULL;
                item.commit = head_commit;
 +              item.ignore = 0;
                if (item.width > ref_list->maxwidth)
                        ref_list->maxwidth = item.width;
                print_ref_item(&item, ref_list->maxwidth, ref_list->verbose, ref_list->abbrev, 1, "");
@@@ -644,23 -652,7 +643,23 @@@ static int print_ref_list(int kinds, in
                add_pending_object(&ref_list.revs,
                                   (struct object *) filter, "");
                ref_list.revs.limited = 1;
 -              prepare_revision_walk(&ref_list.revs);
 +
 +              if (prepare_revision_walk(&ref_list.revs))
 +                      die(_("revision walk setup failed"));
 +
 +              for (i = 0; i < ref_list.index; i++) {
 +                      struct ref_item *item = &ref_list.list[i];
 +                      struct commit *commit = item->commit;
 +                      int is_merged = !!(commit->object.flags & UNINTERESTING);
 +                      item->ignore = is_merged != (merge_filter == SHOW_MERGED);
 +              }
 +
 +              for (i = 0; i < ref_list.index; i++) {
 +                      struct ref_item *item = &ref_list.list[i];
 +                      clear_commit_marks(item->commit, ALL_REV_FLAGS);
 +              }
 +              clear_commit_marks(filter, ALL_REV_FLAGS);
 +
                if (verbose)
                        ref_list.maxwidth = calc_maxwidth(&ref_list);
        }
@@@ -872,10 -864,13 +871,10 @@@ int cmd_branch(int argc, const char **a
        head = resolve_refdup("HEAD", head_sha1, 0, NULL);
        if (!head)
                die(_("Failed to resolve HEAD as a valid ref."));
 -      if (!strcmp(head, "HEAD")) {
 +      if (!strcmp(head, "HEAD"))
                detached = 1;
 -      } else {
 -              if (!starts_with(head, "refs/heads/"))
 -                      die(_("HEAD not found below refs/heads!"));
 -              head += 11;
 -      }
 +      else if (!skip_prefix(head, "refs/heads/", &head))
 +              die(_("HEAD not found below refs/heads!"));
        hashcpy(merge_filter_ref, head_sha1);
  
  
diff --combined builtin/clean.c
index c35505ee6b4030eedc61861081fd21e655b2b3e4,035ea391a2ec0c9399fa9868888d32e84c93b227..77846762b55bd42f4ab97823ee1b6850dff81714
@@@ -67,7 -67,7 +67,7 @@@ struct menu_item 
        char hotkey;
        const char *title;
        int selected;
 -      int (*fn)();
 +      int (*fn)(void);
  };
  
  enum menu_stuff_type {
@@@ -100,8 -100,6 +100,8 @@@ static int parse_clean_color_slot(cons
  
  static int git_clean_config(const char *var, const char *value, void *cb)
  {
 +      const char *slot_name;
 +
        if (starts_with(var, "column."))
                return git_column_config(var, value, "clean", &colopts);
  
                clean_use_color = git_config_colorbool(var, value);
                return 0;
        }
 -      if (starts_with(var, "color.interactive.")) {
 -              int slot = parse_clean_color_slot(var +
 -                                                strlen("color.interactive."));
 +      if (skip_prefix(var, "color.interactive.", &slot_name)) {
 +              int slot = parse_clean_color_slot(slot_name);
                if (slot < 0)
                        return 0;
                if (!value)
                        return config_error_nonbool(var);
-               color_parse(value, var, clean_colors[slot]);
-               return 0;
+               return color_parse(value, clean_colors[slot]);
        }
  
        if (!strcmp(var, "clean.requireforce")) {
diff --combined builtin/commit.c
index 81dc622a3b72040daca199f05d0a3322f316e6f6,8dab44d93362553f4a9a55b66ba1d4a947795afd..60d35d0408c4900dfd86b24c86824a1e90da1f41
@@@ -6,7 -6,6 +6,7 @@@
   */
  
  #include "cache.h"
 +#include "lockfile.h"
  #include "cache-tree.h"
  #include "color.h"
  #include "dir.h"
@@@ -43,20 -42,7 +43,20 @@@ static const char * const builtin_statu
        NULL
  };
  
 -static const char implicit_ident_advice[] =
 +static const char implicit_ident_advice_noconfig[] =
 +N_("Your name and email address were configured automatically based\n"
 +"on your username and hostname. Please check that they are accurate.\n"
 +"You can suppress this message by setting them explicitly. Run the\n"
 +"following command and follow the instructions in your editor to edit\n"
 +"your configuration file:\n"
 +"\n"
 +"    git config --global --edit\n"
 +"\n"
 +"After doing this, you may fix the identity used for this commit with:\n"
 +"\n"
 +"    git commit --amend --reset-author\n");
 +
 +static const char implicit_ident_advice_config[] =
  N_("Your name and email address were configured automatically based\n"
  "on your username and hostname. Please check that they are accurate.\n"
  "You can suppress this message by setting them explicitly:\n"
@@@ -316,8 -302,8 +316,8 @@@ static void refresh_cache_or_die(int re
                die_resolve_conflict("commit");
  }
  
 -static char *prepare_index(int argc, const char **argv, const char *prefix,
 -                         const struct commit *current_head, int is_status)
 +static const char *prepare_index(int argc, const char **argv, const char *prefix,
 +                               const struct commit *current_head, int is_status)
  {
        struct string_list partial;
        struct pathspec pathspec;
                        die(_("unable to create temporary index"));
  
                old_index_env = getenv(INDEX_ENVIRONMENT);
 -              setenv(INDEX_ENVIRONMENT, index_lock.filename, 1);
 +              setenv(INDEX_ENVIRONMENT, index_lock.filename.buf, 1);
  
                if (interactive_add(argc, argv, prefix, patch_interactive) != 0)
                        die(_("interactive add failed"));
                        unsetenv(INDEX_ENVIRONMENT);
  
                discard_cache();
 -              read_cache_from(index_lock.filename);
 +              read_cache_from(index_lock.filename.buf);
 +              if (update_main_cache_tree(WRITE_TREE_SILENT) == 0) {
 +                      if (reopen_lock_file(&index_lock) < 0)
 +                              die(_("unable to write index file"));
 +                      if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
 +                              die(_("unable to update temporary index"));
 +              } else
 +                      warning(_("Failed to update main cache tree"));
  
                commit_style = COMMIT_NORMAL;
 -              return index_lock.filename;
 +              return index_lock.filename.buf;
        }
  
        /*
                if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
                        die(_("unable to write new_index file"));
                commit_style = COMMIT_NORMAL;
 -              return index_lock.filename;
 +              return index_lock.filename.buf;
        }
  
        /*
        if (!only && !pathspec.nr) {
                hold_locked_index(&index_lock, 1);
                refresh_cache_or_die(refresh_flags);
 -              if (active_cache_changed) {
 +              if (active_cache_changed
 +                  || !cache_tree_fully_valid(active_cache_tree)) {
                        update_main_cache_tree(WRITE_TREE_SILENT);
 +                      active_cache_changed = 1;
 +              }
 +              if (active_cache_changed) {
                        if (write_locked_index(&the_index, &index_lock,
                                               COMMIT_LOCK))
                                die(_("unable to write new_index file"));
        hold_locked_index(&index_lock, 1);
        add_remove_files(&partial);
        refresh_cache(REFRESH_QUIET);
 +      update_main_cache_tree(WRITE_TREE_SILENT);
        if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
                die(_("unable to write new_index file"));
  
                die(_("unable to write temporary index file"));
  
        discard_cache();
 -      read_cache_from(false_lock.filename);
 +      read_cache_from(false_lock.filename.buf);
  
 -      return false_lock.filename;
 +      return false_lock.filename.buf;
  }
  
  static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn,
@@@ -546,80 -520,77 +546,80 @@@ static int sane_ident_split(struct iden
        return 1;
  }
  
 -static int parse_force_date(const char *in, char *out, int len)
 +static int parse_force_date(const char *in, struct strbuf *out)
  {
 -      if (len < 1)
 -              return -1;
 -      *out++ = '@';
 -      len--;
 +      strbuf_addch(out, '@');
  
 -      if (parse_date(in, out, len) < 0) {
 +      if (parse_date(in, out) < 0) {
                int errors = 0;
                unsigned long t = approxidate_careful(in, &errors);
                if (errors)
                        return -1;
 -              snprintf(out, len, "%lu", t);
 +              strbuf_addf(out, "%lu", t);
        }
  
        return 0;
  }
  
 +static void set_ident_var(char **buf, char *val)
 +{
 +      free(*buf);
 +      *buf = val;
 +}
 +
 +static char *envdup(const char *var)
 +{
 +      const char *val = getenv(var);
 +      return val ? xstrdup(val) : NULL;
 +}
 +
  static void determine_author_info(struct strbuf *author_ident)
  {
        char *name, *email, *date;
        struct ident_split author;
 -      char date_buf[64];
  
 -      name = getenv("GIT_AUTHOR_NAME");
 -      email = getenv("GIT_AUTHOR_EMAIL");
 -      date = getenv("GIT_AUTHOR_DATE");
 +      name = envdup("GIT_AUTHOR_NAME");
 +      email = envdup("GIT_AUTHOR_EMAIL");
 +      date = envdup("GIT_AUTHOR_DATE");
  
        if (author_message) {
 -              const char *a, *lb, *rb, *eol;
 +              struct ident_split ident;
                size_t len;
 +              const char *a;
  
 -              a = strstr(author_message_buffer, "\nauthor ");
 +              a = find_commit_header(author_message_buffer, "author", &len);
                if (!a)
 -                      die(_("invalid commit: %s"), author_message);
 -
 -              lb = strchrnul(a + strlen("\nauthor "), '<');
 -              rb = strchrnul(lb, '>');
 -              eol = strchrnul(rb, '\n');
 -              if (!*lb || !*rb || !*eol)
 -                      die(_("invalid commit: %s"), author_message);
 -
 -              if (lb == a + strlen("\nauthor "))
 -                      /* \nauthor <foo@example.com> */
 -                      name = xcalloc(1, 1);
 -              else
 -                      name = xmemdupz(a + strlen("\nauthor "),
 -                                      (lb - strlen(" ") -
 -                                       (a + strlen("\nauthor "))));
 -              email = xmemdupz(lb + strlen("<"), rb - (lb + strlen("<")));
 -              len = eol - (rb + strlen("> "));
 -              date = xmalloc(len + 2);
 -              *date = '@';
 -              memcpy(date + 1, rb + strlen("> "), len);
 -              date[len + 1] = '\0';
 +                      die(_("commit '%s' lacks author header"), author_message);
 +              if (split_ident_line(&ident, a, len) < 0)
 +                      die(_("commit '%s' has malformed author line"), author_message);
 +
 +              set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));
 +              set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));
 +
 +              if (ident.date_begin) {
 +                      struct strbuf date_buf = STRBUF_INIT;
 +                      strbuf_addch(&date_buf, '@');
 +                      strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);
 +                      strbuf_addch(&date_buf, ' ');
 +                      strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);
 +                      set_ident_var(&date, strbuf_detach(&date_buf, NULL));
 +              }
        }
  
        if (force_author) {
 -              const char *lb = strstr(force_author, " <");
 -              const char *rb = strchr(force_author, '>');
 +              struct ident_split ident;
  
 -              if (!lb || !rb)
 +              if (split_ident_line(&ident, force_author, strlen(force_author)) < 0)
                        die(_("malformed --author parameter"));
 -              name = xstrndup(force_author, lb - force_author);
 -              email = xstrndup(lb + 2, rb - (lb + 2));
 +              set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));
 +              set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));
        }
  
        if (force_date) {
 -              if (parse_force_date(force_date, date_buf, sizeof(date_buf)))
 +              struct strbuf date_buf = STRBUF_INIT;
 +              if (parse_force_date(force_date, &date_buf))
                        die(_("invalid date format: %s"), force_date);
 -              date = date_buf;
 +              set_ident_var(&date, strbuf_detach(&date_buf, NULL));
        }
  
        strbuf_addstr(author_ident, fmt_ident(name, email, date, IDENT_STRICT));
                export_one("GIT_AUTHOR_EMAIL", author.mail_begin, author.mail_end, 0);
                export_one("GIT_AUTHOR_DATE", author.date_begin, author.tz_end, '@');
        }
 +
 +      free(name);
 +      free(email);
 +      free(date);
  }
  
  static void split_ident_or_die(struct ident_split *id, const struct strbuf *buf)
@@@ -1081,8 -1048,7 +1081,8 @@@ static const char *find_author_by_nickn
        revs.mailmap = &mailmap;
        read_mailmap(revs.mailmap, NULL);
  
 -      prepare_revision_walk(&revs);
 +      if (prepare_revision_walk(&revs))
 +              die(_("revision walk setup failed"));
        commit = get_revision(&revs);
        if (commit) {
                struct pretty_print_context ctx = {0};
@@@ -1272,22 -1238,21 +1272,21 @@@ static int dry_run_commit(int argc, con
        return commitable ? 0 : 1;
  }
  
- static int parse_status_slot(const char *var, int offset)
+ static int parse_status_slot(const char *slot)
  {
-       if (!strcasecmp(var+offset, "header"))
+       if (!strcasecmp(slot, "header"))
                return WT_STATUS_HEADER;
-       if (!strcasecmp(var+offset, "branch"))
+       if (!strcasecmp(slot, "branch"))
                return WT_STATUS_ONBRANCH;
-       if (!strcasecmp(var+offset, "updated")
-               || !strcasecmp(var+offset, "added"))
+       if (!strcasecmp(slot, "updated") || !strcasecmp(slot, "added"))
                return WT_STATUS_UPDATED;
-       if (!strcasecmp(var+offset, "changed"))
+       if (!strcasecmp(slot, "changed"))
                return WT_STATUS_CHANGED;
-       if (!strcasecmp(var+offset, "untracked"))
+       if (!strcasecmp(slot, "untracked"))
                return WT_STATUS_UNTRACKED;
-       if (!strcasecmp(var+offset, "nobranch"))
+       if (!strcasecmp(slot, "nobranch"))
                return WT_STATUS_NOBRANCH;
-       if (!strcasecmp(var+offset, "unmerged"))
+       if (!strcasecmp(slot, "unmerged"))
                return WT_STATUS_UNMERGED;
        return -1;
  }
  static int git_status_config(const char *k, const char *v, void *cb)
  {
        struct wt_status *s = cb;
 +      const char *slot_name;
  
        if (starts_with(k, "column."))
                return git_column_config(k, v, "status", &s->colopts);
                s->display_comment_prefix = git_config_bool(k, v);
                return 0;
        }
 -      if (starts_with(k, "status.color.") || starts_with(k, "color.status.")) {
 -              int slot = parse_status_slot(k + 13);
 +      if (skip_prefix(k, "status.color.", &slot_name) ||
 +          skip_prefix(k, "color.status.", &slot_name)) {
-               int slot = parse_status_slot(k, slot_name - k);
++              int slot = parse_status_slot(slot_name);
                if (slot < 0)
                        return 0;
                if (!v)
                        return config_error_nonbool(k);
-               color_parse(v, k, s->color_palette[slot]);
-               return 0;
+               return color_parse(v, s->color_palette[slot]);
        }
        if (!strcmp(k, "status.relativepaths")) {
                s->relative_paths = git_config_bool(k, v);
@@@ -1438,24 -1400,6 +1436,24 @@@ int cmd_status(int argc, const char **a
        return 0;
  }
  
 +static const char *implicit_ident_advice(void)
 +{
 +      char *user_config = NULL;
 +      char *xdg_config = NULL;
 +      int config_exists;
 +
 +      home_config_paths(&user_config, &xdg_config, "config");
 +      config_exists = file_exists(user_config) || file_exists(xdg_config);
 +      free(user_config);
 +      free(xdg_config);
 +
 +      if (config_exists)
 +              return _(implicit_ident_advice_config);
 +      else
 +              return _(implicit_ident_advice_noconfig);
 +
 +}
 +
  static void print_summary(const char *prefix, const unsigned char *sha1,
                          int initial_commit)
  {
                strbuf_addbuf_percentquote(&format, &committer_ident);
                if (advice_implicit_identity) {
                        strbuf_addch(&format, '\n');
 -                      strbuf_addstr(&format, _(implicit_ident_advice));
 +                      strbuf_addstr(&format, implicit_ident_advice());
                }
        }
        strbuf_release(&author_ident);
        diff_setup_done(&rev.diffopt);
  
        head = resolve_ref_unsafe("HEAD", junk_sha1, 0, NULL);
 -      printf("[%s%s ",
 -              starts_with(head, "refs/heads/") ?
 -                      head + 11 :
 -                      !strcmp(head, "HEAD") ?
 -                              _("detached HEAD") :
 -                              head,
 -              initial_commit ? _(" (root-commit)") : "");
 +      if (!strcmp(head, "HEAD"))
 +              head = _("detached HEAD");
 +      else
 +              skip_prefix(head, "refs/heads/", &head);
 +      printf("[%s%s ", head, initial_commit ? _(" (root-commit)") : "");
  
        if (!log_tree_commit(&rev, commit)) {
                rev.always_show_header = 1;
@@@ -1560,7 -1506,7 +1558,7 @@@ static int run_rewrite_hook(const unsig
  {
        /* oldsha1 SP newsha1 LF NUL */
        static char buf[2*40 + 3];
 -      struct child_process proc;
 +      struct child_process proc = CHILD_PROCESS_INIT;
        const char *argv[3];
        int code;
        size_t n;
        argv[1] = "amend";
        argv[2] = NULL;
  
 -      memset(&proc, 0, sizeof(proc));
        proc.argv = argv;
        proc.in = -1;
        proc.stdout_to_stderr = 1;
@@@ -1671,12 -1618,11 +1669,12 @@@ int cmd_commit(int argc, const char **a
        const char *index_file, *reflog_msg;
        char *nl;
        unsigned char sha1[20];
 -      struct ref_lock *ref_lock;
        struct commit_list *parents = NULL, **pptr = &parents;
        struct stat statbuf;
        struct commit *current_head = NULL;
        struct commit_extra_header *extra = NULL;
 +      struct ref_transaction *transaction;
 +      struct strbuf err = STRBUF_INIT;
  
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage_with_options(builtin_commit_usage, builtin_commit_options);
        strbuf_release(&author_ident);
        free_commit_extra_headers(extra);
  
 -      ref_lock = lock_any_ref_for_update("HEAD",
 -                                         !current_head
 -                                         ? NULL
 -                                         : current_head->object.sha1,
 -                                         0, NULL);
 -      if (!ref_lock) {
 -              rollback_index_files();
 -              die(_("cannot lock HEAD ref"));
 -      }
 -
        nl = strchr(sb.buf, '\n');
        if (nl)
                strbuf_setlen(&sb, nl + 1 - sb.buf);
        strbuf_insert(&sb, 0, reflog_msg, strlen(reflog_msg));
        strbuf_insert(&sb, strlen(reflog_msg), ": ", 2);
  
 -      if (write_ref_sha1(ref_lock, sha1, sb.buf) < 0) {
 +      transaction = ref_transaction_begin(&err);
 +      if (!transaction ||
 +          ref_transaction_update(transaction, "HEAD", sha1,
 +                                 current_head
 +                                 ? current_head->object.sha1 : NULL,
 +                                 0, !!current_head, &err) ||
 +          ref_transaction_commit(transaction, sb.buf, &err)) {
                rollback_index_files();
 -              die(_("cannot update HEAD ref"));
 +              die("%s", err.buf);
        }
 +      ref_transaction_free(transaction);
  
        unlink(git_path("CHERRY_PICK_HEAD"));
        unlink(git_path("REVERT_HEAD"));
  
        if (commit_index_files())
                die (_("Repository has been updated, but unable to write\n"
 -                   "new_index file. Check that disk is not full or quota is\n"
 +                   "new_index file. Check that disk is not full and quota is\n"
                     "not exceeded, and then \"git reset HEAD\" to recover."));
  
        rerere(0);
        if (!quiet)
                print_summary(prefix, sha1, !current_head);
  
 +      strbuf_release(&err);
        return 0;
  }
diff --combined builtin/config.c
index 37305e93e937ade83072501df6b5d07033ee89d3,842809b0b3b0907a883d651a72891a071367ab9e..8cc2604069cd008297081988ac807412ba9901a7
@@@ -296,7 -296,8 +296,8 @@@ static int git_get_color_config(const c
        if (!strcmp(var, get_color_slot)) {
                if (!value)
                        config_error_nonbool(var);
-               color_parse(value, var, parsed_color);
+               if (color_parse(value, parsed_color) < 0)
+                       return -1;
                get_color_found = 1;
        }
        return 0;
@@@ -309,8 -310,10 +310,10 @@@ static void get_color(const char *def_c
        git_config_with_options(git_get_color_config, NULL,
                                &given_config_source, respect_includes);
  
-       if (!get_color_found && def_color)
-               color_parse(def_color, "command line", parsed_color);
+       if (!get_color_found && def_color) {
+               if (color_parse(def_color, parsed_color) < 0)
+                       die(_("unable to parse default color value"));
+       }
  
        fputs(parsed_color, stdout);
  }
@@@ -445,20 -448,6 +448,20 @@@ static int get_urlmatch(const char *var
        return 0;
  }
  
 +static char *default_user_config(void)
 +{
 +      struct strbuf buf = STRBUF_INIT;
 +      strbuf_addf(&buf,
 +                  _("# This is Git's per-user configuration file.\n"
 +                    "[core]\n"
 +                    "# Please adapt and uncomment the following lines:\n"
 +                    "#        user = %s\n"
 +                    "#        email = %s\n"),
 +                  ident_default_name(),
 +                  ident_default_email());
 +      return strbuf_detach(&buf, NULL);
 +}
 +
  int cmd_config(int argc, const char **argv, const char *prefix)
  {
        int nongit = !startup_info->have_repository;
                }
        }
        else if (actions == ACTION_EDIT) {
 +              const char *config_file = given_config_source.file ?
 +                      given_config_source.file : git_path("config");
                check_argc(argc, 0, 0);
                if (!given_config_source.file && nongit)
                        die("not in a git directory");
                if (given_config_source.blob)
                        die("editing blobs is not supported");
                git_config(git_default_config, NULL);
 -              launch_editor(given_config_source.file ?
 -                            given_config_source.file : git_path("config"),
 -                            NULL, NULL);
 +              if (use_global_config) {
 +                      int fd = open(config_file, O_CREAT | O_EXCL | O_WRONLY, 0666);
 +                      if (fd) {
 +                              char *content = default_user_config();
 +                              write_str_in_full(fd, content);
 +                              free(content);
 +                              close(fd);
 +                      }
 +                      else if (errno != EEXIST)
 +                              die_errno(_("cannot create configuration file %s"), config_file);
 +              }
 +              launch_editor(config_file, NULL, NULL);
        }
        else if (actions == ACTION_SET) {
                int ret;
diff --combined builtin/for-each-ref.c
index fda0f047125f16f080288c1cda76b0e337784a54,d41920d382eae1e3e6ee92467a9591e4bea24d21..7ee86b3ae1f417704d4b1171a7d346dab3cd181e
@@@ -138,8 -138,10 +138,8 @@@ static int parse_atom(const char *atom
        /* Add it in, including the deref prefix */
        at = used_atom_cnt;
        used_atom_cnt++;
 -      used_atom = xrealloc(used_atom,
 -                           (sizeof *used_atom) * used_atom_cnt);
 -      used_atom_type = xrealloc(used_atom_type,
 -                                (sizeof(*used_atom_type) * used_atom_cnt));
 +      REALLOC_ARRAY(used_atom, used_atom_cnt);
 +      REALLOC_ARRAY(used_atom_type, used_atom_cnt);
        used_atom[at] = xmemdupz(atom, ep - atom);
        used_atom_type[at] = valid_atom[i].cmp_type;
        if (*atom == '*')
@@@ -631,7 -633,7 +631,7 @@@ static void populate_value(struct refin
        unsigned long size;
        const unsigned char *tagged;
  
 -      ref->value = xcalloc(sizeof(struct atom_value), used_atom_cnt);
 +      ref->value = xcalloc(used_atom_cnt, sizeof(struct atom_value));
  
        if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) {
                unsigned char unused1[20];
                } else if (starts_with(name, "color:")) {
                        char color[COLOR_MAXLEN] = "";
  
-                       color_parse(name + 6, "--format", color);
+                       if (color_parse(name + 6, color) < 0)
+                               die(_("unable to parse format"));
                        v->s = xstrdup(color);
                        continue;
                } else if (!strcmp(name, "flag")) {
@@@ -868,7 -871,8 +869,7 @@@ static int grab_single_ref(const char *
        ref->flag = flag;
  
        cnt = cb->grab_cnt;
 -      cb->grab_array = xrealloc(cb->grab_array,
 -                                sizeof(*cb->grab_array) * (cnt + 1));
 +      REALLOC_ARRAY(cb->grab_array, cnt + 1);
        cb->grab_array[cnt++] = ref;
        cb->grab_cnt = cnt;
        return 0;
@@@ -1004,7 -1008,8 +1005,8 @@@ static void show_ref(struct refinfo *in
                struct atom_value resetv;
                char color[COLOR_MAXLEN] = "";
  
-               color_parse("reset", "--format", color);
+               if (color_parse("reset", color) < 0)
+                       die("BUG: couldn't parse 'reset' as a color");
                resetv.s = color;
                print_value(&resetv, quote_style);
        }
diff --combined builtin/log.c
index 1202eba8b65c019662c5f6b82c13af947bd4d41c,4c5fc4bff5b955f712024a935b8b804fd7d5e363..68d5d30035442a1d12cf0fbf8a10dca0df2711be
@@@ -78,7 -78,7 +78,7 @@@ static int decorate_callback(const stru
                decoration_style = DECORATE_SHORT_REFS;
  
        if (decoration_style < 0)
 -              die("invalid --decorate option: %s", arg);
 +              die(_("invalid --decorate option: %s"), arg);
  
        decoration_given = 1;
  
@@@ -130,7 -130,7 +130,7 @@@ static void cmd_log_init_finish(int arg
                { OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"),
                  PARSE_OPT_OPTARG, decorate_callback},
                OPT_CALLBACK('L', NULL, &line_cb, "n,m:file",
 -                           "Process line range n,m in file, counting from 1",
 +                           N_("Process line range n,m in file, counting from 1"),
                             log_line_range_callback),
                OPT_END()
        };
  
        /* Any arguments at this point are not recognized */
        if (argc > 1)
 -              die("unrecognized argument: %s", argv[1]);
 +              die(_("unrecognized argument: %s"), argv[1]);
  
        memset(&w, 0, sizeof(w));
        userformat_find_requirements(NULL, &w);
@@@ -368,8 -368,6 +368,8 @@@ static int cmd_log_walk(struct rev_inf
  
  static int git_log_config(const char *var, const char *value, void *cb)
  {
 +      const char *slot_name;
 +
        if (!strcmp(var, "format.pretty"))
                return git_config_string(&fmt_pretty, var, value);
        if (!strcmp(var, "format.subjectprefix"))
                default_show_root = git_config_bool(var, value);
                return 0;
        }
 -      if (starts_with(var, "color.decorate."))
 -              return parse_decorate_color_config(var, var + 15, value);
 +      if (skip_prefix(var, "color.decorate.", &slot_name))
-               return parse_decorate_color_config(var, slot_name - var, value);
++              return parse_decorate_color_config(var, slot_name, value);
        if (!strcmp(var, "log.mailmap")) {
                use_mailmap_config = git_config_bool(var, value);
                return 0;
@@@ -449,13 -447,13 +449,13 @@@ static int show_blob_object(const unsig
                return stream_blob_to_fd(1, sha1, NULL, 0);
  
        if (get_sha1_with_context(obj_name, 0, sha1c, &obj_context))
 -              die("Not a valid object name %s", obj_name);
 +              die(_("Not a valid object name %s"), obj_name);
        if (!obj_context.path[0] ||
            !textconv_object(obj_context.path, obj_context.mode, sha1c, 1, &buf, &size))
                return stream_blob_to_fd(1, sha1, NULL, 0);
  
        if (!buf)
 -              die("git show %s: bad file", obj_name);
 +              die(_("git show %s: bad file"), obj_name);
  
        write_or_die(1, buf, size);
        return 0;
@@@ -866,7 -864,6 +866,7 @@@ static void add_branch_description(stru
                strbuf_addbuf(buf, &desc);
                strbuf_addch(buf, '\n');
        }
 +      strbuf_release(&desc);
  }
  
  static char *find_branch_name(struct rev_info *rev)
@@@ -1442,7 -1439,7 +1442,7 @@@ int cmd_format_patch(int argc, const ch
                        continue;
  
                nr++;
 -              list = xrealloc(list, nr * sizeof(list[0]));
 +              REALLOC_ARRAY(list, nr);
                list[nr - 1] = commit;
        }
        if (nr == 0)
diff --combined diff.c
index d7a5c81bb8545584ce5fe652dc42f2ee8bc1e2fd,4493dde7495c91346d9d164df63fbb61ed1bf00b..d1bd534caeaf662b0ee0d547d3aa2012310fff57
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -248,8 -248,7 +248,7 @@@ int git_diff_basic_config(const char *v
                        return 0;
                if (!value)
                        return config_error_nonbool(var);
-               color_parse(value, var, diff_colors[slot]);
-               return 0;
+               return color_parse(value, diff_colors[slot]);
        }
  
        /* like GNU diff's --suppress-blank-empty option  */
@@@ -376,7 -375,7 +375,7 @@@ static unsigned long diff_filespec_size
  {
        if (!DIFF_FILE_VALID(one))
                return 0;
 -      diff_populate_filespec(one, 1);
 +      diff_populate_filespec(one, CHECK_SIZE_ONLY);
        return one->size;
  }
  
@@@ -1910,11 -1909,11 +1909,11 @@@ static void show_dirstat(struct diff_op
                        diff_free_filespec_data(p->one);
                        diff_free_filespec_data(p->two);
                } else if (DIFF_FILE_VALID(p->one)) {
 -                      diff_populate_filespec(p->one, 1);
 +                      diff_populate_filespec(p->one, CHECK_SIZE_ONLY);
                        copied = added = 0;
                        diff_free_filespec_data(p->one);
                } else if (DIFF_FILE_VALID(p->two)) {
 -                      diff_populate_filespec(p->two, 1);
 +                      diff_populate_filespec(p->two, CHECK_SIZE_ONLY);
                        copied = 0;
                        added = p->two->size;
                        diff_free_filespec_data(p->two);
@@@ -2188,8 -2187,8 +2187,8 @@@ int diff_filespec_is_binary(struct diff
                        one->is_binary = one->driver->binary;
                else {
                        if (!one->data && DIFF_FILE_VALID(one))
 -                              diff_populate_filespec(one, 0);
 -                      if (one->data)
 +                              diff_populate_filespec(one, CHECK_BINARY);
 +                      if (one->is_binary == -1 && one->data)
                                one->is_binary = buffer_is_binary(one->data,
                                                one->size);
                        if (one->is_binary == -1)
@@@ -2324,19 -2323,6 +2323,19 @@@ static void builtin_diff(const char *na
        } else if (!DIFF_OPT_TST(o, TEXT) &&
            ( (!textconv_one && diff_filespec_is_binary(one)) ||
              (!textconv_two && diff_filespec_is_binary(two)) )) {
 +              if (!one->data && !two->data &&
 +                  S_ISREG(one->mode) && S_ISREG(two->mode) &&
 +                  !DIFF_OPT_TST(o, BINARY)) {
 +                      if (!hashcmp(one->sha1, two->sha1)) {
 +                              if (must_show_header)
 +                                      fprintf(o->file, "%s", header.buf);
 +                              goto free_ab_and_return;
 +                      }
 +                      fprintf(o->file, "%s", header.buf);
 +                      fprintf(o->file, "%sBinary files %s and %s differ\n",
 +                              line_prefix, lbl[0], lbl[1]);
 +                      goto free_ab_and_return;
 +              }
                if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
                        die("unable to read files to diff");
                /* Quite common confusing case */
@@@ -2681,9 -2667,8 +2680,9 @@@ static int diff_populate_gitlink(struc
   * grab the data for the blob (or file) for our own in-core comparison.
   * diff_filespec has data and size fields for this purpose.
   */
 -int diff_populate_filespec(struct diff_filespec *s, int size_only)
 +int diff_populate_filespec(struct diff_filespec *s, unsigned int flags)
  {
 +      int size_only = flags & CHECK_SIZE_ONLY;
        int err = 0;
        /*
         * demote FAIL to WARN to allow inspecting the situation
                }
                if (size_only)
                        return 0;
 +              if ((flags & CHECK_BINARY) &&
 +                  s->size > big_file_threshold && s->is_binary == -1) {
 +                      s->is_binary = 1;
 +                      return 0;
 +              }
                fd = open(s->path, O_RDONLY);
                if (fd < 0)
                        goto err_empty;
        }
        else {
                enum object_type type;
 -              if (size_only) {
 +              if (size_only || (flags & CHECK_BINARY)) {
                        type = sha1_object_info(s->sha1, &s->size);
                        if (type < 0)
                                die("unable to read %s", sha1_to_hex(s->sha1));
 -              } else {
 -                      s->data = read_sha1_file(s->sha1, &type, &s->size);
 -                      if (!s->data)
 -                              die("unable to read %s", sha1_to_hex(s->sha1));
 -                      s->should_free = 1;
 +                      if (size_only)
 +                              return 0;
 +                      if (s->size > big_file_threshold && s->is_binary == -1) {
 +                              s->is_binary = 1;
 +                              return 0;
 +                      }
                }
 +              s->data = read_sha1_file(s->sha1, &type, &s->size);
 +              if (!s->data)
 +                      die("unable to read %s", sha1_to_hex(s->sha1));
 +              s->should_free = 1;
        }
        return 0;
  }
@@@ -4712,8 -4687,8 +4711,8 @@@ static int diff_filespec_check_stat_unm
            !DIFF_FILE_VALID(p->two) ||
            (p->one->sha1_valid && p->two->sha1_valid) ||
            (p->one->mode != p->two->mode) ||
 -          diff_populate_filespec(p->one, 1) ||
 -          diff_populate_filespec(p->two, 1) ||
 +          diff_populate_filespec(p->one, CHECK_SIZE_ONLY) ||
 +          diff_populate_filespec(p->two, CHECK_SIZE_ONLY) ||
            (p->one->size != p->two->size) ||
            !diff_filespec_is_identical(p->one, p->two)) /* (2) */
                p->skip_stat_unmatch_result = 1;
@@@ -4955,7 -4930,7 +4954,7 @@@ static char *run_textconv(const char *p
        struct diff_tempfile *temp;
        const char *argv[3];
        const char **arg = argv;
 -      struct child_process child;
 +      struct child_process child = CHILD_PROCESS_INIT;
        struct strbuf buf = STRBUF_INIT;
        int err = 0;
  
        *arg++ = temp->name;
        *arg = NULL;
  
 -      memset(&child, 0, sizeof(child));
        child.use_shell = 1;
        child.argv = argv;
        child.out = -1;
diff --combined log-tree.c
index cff7ac1dbd8942f3013e6fa91d30d2ea33495854,a21ef30d0cb6a664665adf77a7c924572d600712..7f0890e4ac14348e78f7f1e305629fa745a79392
  #include "sequencer.h"
  #include "line-log.h"
  
 -struct decoration name_decoration = { "object names" };
 -
 -enum decoration_type {
 -      DECORATION_NONE = 0,
 -      DECORATION_REF_LOCAL,
 -      DECORATION_REF_REMOTE,
 -      DECORATION_REF_TAG,
 -      DECORATION_REF_STASH,
 -      DECORATION_REF_HEAD,
 -      DECORATION_GRAFTED,
 -};
 +static struct decoration name_decoration = { "object names" };
  
  static char decoration_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_RESET,
@@@ -56,15 -66,14 +56,14 @@@ static int parse_decorate_color_slot(co
        return -1;
  }
  
- int parse_decorate_color_config(const char *var, const int ofs, const char *value)
+ int parse_decorate_color_config(const char *var, const char *slot_name, const char *value)
  {
-       int slot = parse_decorate_color_slot(var + ofs);
+       int slot = parse_decorate_color_slot(slot_name);
        if (slot < 0)
                return 0;
        if (!value)
                return config_error_nonbool(var);
-       color_parse(value, var, decoration_colors[slot]);
-       return 0;
+       return color_parse(value, decoration_colors[slot]);
  }
  
  /*
  #define decorate_get_color_opt(o, ix) \
        decorate_get_color((o)->use_color, ix)
  
 -static void add_name_decoration(enum decoration_type type, const char *name, struct object *obj)
 +void add_name_decoration(enum decoration_type type, const char *name, struct object *obj)
  {
        int nlen = strlen(name);
 -      struct name_decoration *res = xmalloc(sizeof(struct name_decoration) + nlen);
 +      struct name_decoration *res = xmalloc(sizeof(*res) + nlen + 1);
        memcpy(res->name, name, nlen + 1);
        res->type = type;
        res->next = add_decoration(&name_decoration, obj, res);
  }
  
 +const struct name_decoration *get_name_decoration(const struct object *obj)
 +{
 +      return lookup_decoration(&name_decoration, obj);
 +}
 +
  static int add_ref_decoration(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
  {
        struct object *obj;
@@@ -174,25 -178,24 +173,25 @@@ static void show_children(struct rev_in
  }
  
  /*
 - * The caller makes sure there is no funny color before
 - * calling. format_decorations makes sure the same after return.
 + * The caller makes sure there is no funny color before calling.
 + * format_decorations_extended makes sure the same after return.
   */
 -void format_decorations(struct strbuf *sb,
 +void format_decorations_extended(struct strbuf *sb,
                        const struct commit *commit,
 -                      int use_color)
 +                      int use_color,
 +                      const char *prefix,
 +                      const char *separator,
 +                      const char *suffix)
  {
 -      const char *prefix;
 -      struct name_decoration *decoration;
 +      const struct name_decoration *decoration;
        const char *color_commit =
                diff_get_color(use_color, DIFF_COMMIT);
        const char *color_reset =
                decorate_get_color(use_color, DECORATION_NONE);
  
 -      decoration = lookup_decoration(&name_decoration, &commit->object);
 +      decoration = get_name_decoration(&commit->object);
        if (!decoration)
                return;
 -      prefix = " (";
        while (decoration) {
                strbuf_addstr(sb, color_commit);
                strbuf_addstr(sb, prefix);
                        strbuf_addstr(sb, "tag: ");
                strbuf_addstr(sb, decoration->name);
                strbuf_addstr(sb, color_reset);
 -              prefix = ", ";
 +              prefix = separator;
                decoration = decoration->next;
        }
        strbuf_addstr(sb, color_commit);
 -      strbuf_addch(sb, ')');
 +      strbuf_addstr(sb, suffix);
        strbuf_addstr(sb, color_reset);
  }
  
diff --combined log-tree.h
index b26160c4d64b041ed31826a61d4b898c8608c0a2,8cbefac573242cc6e1bfec100a3cb5e931a6e5c7..c8116e60cde34032da6f2044881d5a4970cd96fe
@@@ -7,19 -7,13 +7,19 @@@ struct log_info 
        struct commit *commit, *parent;
  };
  
- int parse_decorate_color_config(const char *var, const int ofs, const char *value);
+ int parse_decorate_color_config(const char *var, const char *slot_name, const char *value);
  void init_log_tree_opt(struct rev_info *);
  int log_tree_diff_flush(struct rev_info *);
  int log_tree_commit(struct rev_info *, struct commit *);
  int log_tree_opt_parse(struct rev_info *, const char **, int);
  void show_log(struct rev_info *opt);
 -void format_decorations(struct strbuf *sb, const struct commit *commit, int use_color);
 +void format_decorations_extended(struct strbuf *sb, const struct commit *commit,
 +                           int use_color,
 +                           const char *prefix,
 +                           const char *separator,
 +                           const char *suffix);
 +#define format_decorations(strbuf, commit, color) \
 +                           format_decorations_extended((strbuf), (commit), (color), " (", ", ", ")")
  void show_decorations(struct rev_info *opt, struct commit *commit);
  void log_write_email_headers(struct rev_info *opt, struct commit *commit,
                             const char **subject_p,
diff --combined pretty.c
index a181ac66875a158514bba48a21911adf9c5cd8be,6182ca9aed79503a4c24ed267b8cf69673cac16b..9d34d02db11bd6761d48484327fbc6d1704ad555
+++ b/pretty.c
@@@ -70,12 -70,11 +70,12 @@@ static int git_pretty_formats_config(co
  
        commit_format->name = xstrdup(name);
        commit_format->format = CMIT_FMT_USERFORMAT;
 -      git_config_string(&fmt, var, value);
 -      if (starts_with(fmt, "format:") || starts_with(fmt, "tformat:")) {
 -              commit_format->is_tformat = fmt[0] == 't';
 -              fmt = strchr(fmt, ':') + 1;
 -      } else if (strchr(fmt, '%'))
 +      if (git_config_string(&fmt, var, value))
 +              return -1;
 +
 +      if (skip_prefix(fmt, "format:", &fmt))
 +              commit_format->is_tformat = 0;
 +      else if (skip_prefix(fmt, "tformat:", &fmt) || strchr(fmt, '%'))
                commit_format->is_tformat = 1;
        else
                commit_format->is_alias = 1;
@@@ -156,12 -155,12 +156,12 @@@ void get_commit_format(const char *arg
                rev->commit_format = CMIT_FMT_DEFAULT;
                return;
        }
 -      if (starts_with(arg, "format:") || starts_with(arg, "tformat:")) {
 -              save_user_format(rev, strchr(arg, ':') + 1, arg[0] == 't');
 +      if (skip_prefix(arg, "format:", &arg)) {
 +              save_user_format(rev, arg, 0);
                return;
        }
  
 -      if (!*arg || strchr(arg, '%')) {
 +      if (!*arg || skip_prefix(arg, "tformat:", &arg) || strchr(arg, '%')) {
                save_user_format(rev, arg, 1);
                return;
        }
@@@ -553,11 -552,31 +553,11 @@@ static void add_merge_info(const struc
        strbuf_addch(sb, '\n');
  }
  
 -static char *get_header(const struct commit *commit, const char *msg,
 -                      const char *key)
 +static char *get_header(const char *msg, const char *key)
  {
 -      int key_len = strlen(key);
 -      const char *line = msg;
 -
 -      while (line) {
 -              const char *eol = strchrnul(line, '\n'), *next;
 -
 -              if (line == eol)
 -                      return NULL;
 -              if (!*eol) {
 -                      warning("malformed commit (header is missing newline): %s",
 -                              sha1_to_hex(commit->object.sha1));
 -                      next = NULL;
 -              } else
 -                      next = eol + 1;
 -              if (eol - line > key_len &&
 -                  !strncmp(line, key, key_len) &&
 -                  line[key_len] == ' ') {
 -                      return xmemdupz(line + key_len + 1, eol - line - key_len - 1);
 -              }
 -              line = next;
 -      }
 -      return NULL;
 +      size_t len;
 +      const char *v = find_commit_header(msg, key, &len);
 +      return v ? xmemdupz(v, len) : NULL;
  }
  
  static char *replace_encoding_header(char *buf, const char *encoding)
@@@ -603,10 -622,11 +603,10 @@@ const char *logmsg_reencode(const struc
  
        if (!output_encoding || !*output_encoding) {
                if (commit_encoding)
 -                      *commit_encoding =
 -                              get_header(commit, msg, "encoding");
 +                      *commit_encoding = get_header(msg, "encoding");
                return msg;
        }
 -      encoding = get_header(commit, msg, "encoding");
 +      encoding = get_header(msg, "encoding");
        if (commit_encoding)
                *commit_encoding = encoding;
        use_encoding = encoding ? encoding : utf8;
@@@ -716,12 -736,9 +716,12 @@@ static size_t format_person_part(struc
        case 'r':       /* date, relative */
                strbuf_addstr(sb, show_ident_date(&s, DATE_RELATIVE));
                return placeholder_len;
 -      case 'i':       /* date, ISO 8601 */
 +      case 'i':       /* date, ISO 8601-like */
                strbuf_addstr(sb, show_ident_date(&s, DATE_ISO8601));
                return placeholder_len;
 +      case 'I':       /* date, ISO 8601 strict */
 +              strbuf_addstr(sb, show_ident_date(&s, DATE_ISO8601_STRICT));
 +              return placeholder_len;
        }
  
  skip:
@@@ -808,19 -825,18 +808,19 @@@ static void parse_commit_header(struct 
        int i;
  
        for (i = 0; msg[i]; i++) {
 +              const char *name;
                int eol;
                for (eol = i; msg[eol] && msg[eol] != '\n'; eol++)
                        ; /* do nothing */
  
                if (i == eol) {
                        break;
 -              } else if (starts_with(msg + i, "author ")) {
 -                      context->author.off = i + 7;
 -                      context->author.len = eol - i - 7;
 -              } else if (starts_with(msg + i, "committer ")) {
 -                      context->committer.off = i + 10;
 -                      context->committer.len = eol - i - 10;
 +              } else if (skip_prefix(msg + i, "author ", &name)) {
 +                      context->author.off = name - msg;
 +                      context->author.len = msg + eol - name;
 +              } else if (skip_prefix(msg + i, "committer ", &name)) {
 +                      context->committer.off = name - msg;
 +                      context->committer.len = msg + eol - name;
                }
                i = eol;
        }
@@@ -951,8 -967,6 +951,8 @@@ static size_t parse_color(struct strbu
                          const char *placeholder,
                          struct format_commit_context *c)
  {
 +      const char *rest = placeholder;
 +
        if (placeholder[1] == '(') {
                const char *begin = placeholder + 2;
                const char *end = strchr(begin, ')');
  
                if (!end)
                        return 0;
 -              if (starts_with(begin, "auto,")) {
 +              if (skip_prefix(begin, "auto,", &begin)) {
                        if (!want_color(c->pretty_ctx->color))
                                return end - placeholder + 1;
 -                      begin += 5;
                }
-               color_parse_mem(begin,
-                               end - begin,
-                               "--pretty format", color);
+               if (color_parse_mem(begin, end - begin, color) < 0)
+                       die(_("unable to parse --pretty format"));
                strbuf_addstr(sb, color);
                return end - placeholder + 1;
        }
 -      if (starts_with(placeholder + 1, "red")) {
 +      if (skip_prefix(placeholder + 1, "red", &rest))
                strbuf_addstr(sb, GIT_COLOR_RED);
 -              return 4;
 -      } else if (starts_with(placeholder + 1, "green")) {
 +      else if (skip_prefix(placeholder + 1, "green", &rest))
                strbuf_addstr(sb, GIT_COLOR_GREEN);
 -              return 6;
 -      } else if (starts_with(placeholder + 1, "blue")) {
 +      else if (skip_prefix(placeholder + 1, "blue", &rest))
                strbuf_addstr(sb, GIT_COLOR_BLUE);
 -              return 5;
 -      } else if (starts_with(placeholder + 1, "reset")) {
 +      else if (skip_prefix(placeholder + 1, "reset", &rest))
                strbuf_addstr(sb, GIT_COLOR_RESET);
 -              return 6;
 -      } else
 -              return 0;
 +      return rest - placeholder;
  }
  
  static size_t parse_padding_placeholder(struct strbuf *sb,
@@@ -1175,10 -1194,6 +1174,10 @@@ static size_t format_commit_one(struct 
                load_ref_decorations(DECORATE_SHORT_REFS);
                format_decorations(sb, commit, c->auto_color);
                return 1;
 +      case 'D':
 +              load_ref_decorations(DECORATE_SHORT_REFS);
 +              format_decorations_extended(sb, commit, c->auto_color, "", ", ", "");
 +              return 1;
        case 'g':               /* reflog info */
                switch(placeholder[1]) {
                case 'd':       /* reflog selector */
@@@ -1377,7 -1392,9 +1376,7 @@@ static size_t format_and_pad_commit(str
                 * convert it back to chars
                 */
                padding = padding - len + local_sb.len;
 -              strbuf_grow(sb, padding);
 -              strbuf_setlen(sb, sb_len + padding);
 -              memset(sb->buf + sb_len, ' ', sb->len - sb_len);
 +              strbuf_addchars(sb, ' ', padding);
                memcpy(sb->buf + sb_len + offset, local_sb.buf,
                       local_sb.len);
        }
@@@ -1518,7 -1535,7 +1517,7 @@@ static void pp_header(struct pretty_pri
        int parents_shown = 0;
  
        for (;;) {
 -              const char *line = *msg_p;
 +              const char *name, *line = *msg_p;
                int linelen = get_one_line(*msg_p);
  
                if (!linelen)
                 * FULL shows both authors but not dates.
                 * FULLER shows both authors and dates.
                 */
 -              if (starts_with(line, "author ")) {
 +              if (skip_prefix(line, "author ", &name)) {
                        strbuf_grow(sb, linelen + 80);
 -                      pp_user_info(pp, "Author", sb, line + 7, encoding);
 +                      pp_user_info(pp, "Author", sb, name, encoding);
                }
 -              if (starts_with(line, "committer ") &&
 +              if (skip_prefix(line, "committer ", &name) &&
                    (pp->fmt == CMIT_FMT_FULL || pp->fmt == CMIT_FMT_FULLER)) {
                        strbuf_grow(sb, linelen + 80);
 -                      pp_user_info(pp, "Commit", sb, line + 10, encoding);
 +                      pp_user_info(pp, "Commit", sb, name, encoding);
                }
        }
  }
@@@ -1652,8 -1669,10 +1651,8 @@@ void pp_remainder(struct pretty_print_c
                first = 0;
  
                strbuf_grow(sb, linelen + indent + 20);
 -              if (indent) {
 -                      memset(sb->buf + sb->len, ' ', indent);
 -                      strbuf_setlen(sb, sb->len + indent);
 -              }
 +              if (indent)
 +                      strbuf_addchars(sb, ' ', indent);
                strbuf_add(sb, line, linelen);
                strbuf_addch(sb, '\n');
        }