Merge branch 'tk/stripspace'
authorJunio C Hamano <gitster@pobox.com>
Mon, 26 Oct 2015 22:55:20 +0000 (15:55 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 26 Oct 2015 22:55:20 +0000 (15:55 -0700)
The internal stripspace() function has been moved to where it
logically belongs to, i.e. strbuf API, and the command line parser
of "git stripspace" has been updated to use the parse_options API.

* tk/stripspace:
stripspace: use parse-options for command-line parsing
strbuf: make stripspace() part of strbuf

1  2 
builtin/am.c
builtin/branch.c
builtin/merge.c
builtin/notes.c
builtin/stripspace.c
builtin/tag.c
strbuf.c
strbuf.h
diff --combined builtin/am.c
index fc6d3d21aac54f9d820e3cae60e08d81366787be,fbe9152911e32f4db4391a068051c6e1c1170cd1..4e396c832139aa3c4ae4e68e9d265b0107817aa9
@@@ -1343,7 -1343,7 +1343,7 @@@ static int parse_mail(struct am_state *
        strbuf_addstr(&msg, "\n\n");
        if (strbuf_read_file(&msg, am_path(state, "msg"), 0) < 0)
                die_errno(_("could not read '%s'"), am_path(state, "msg"));
-       stripspace(&msg, 0);
+       strbuf_stripspace(&msg, 0);
  
        if (state->signoff)
                am_signoff(&msg);
@@@ -1589,38 -1589,6 +1589,38 @@@ static int build_fake_ancestor(const st
        return 0;
  }
  
 +/**
 + * Do the three-way merge using fake ancestor, his tree constructed
 + * from the fake ancestor and the postimage of the patch, and our
 + * state.
 + */
 +static int run_fallback_merge_recursive(const struct am_state *state,
 +                                      unsigned char *orig_tree,
 +                                      unsigned char *our_tree,
 +                                      unsigned char *his_tree)
 +{
 +      struct child_process cp = CHILD_PROCESS_INIT;
 +      int status;
 +
 +      cp.git_cmd = 1;
 +
 +      argv_array_pushf(&cp.env_array, "GITHEAD_%s=%.*s",
 +                       sha1_to_hex(his_tree), linelen(state->msg), state->msg);
 +      if (state->quiet)
 +              argv_array_push(&cp.env_array, "GIT_MERGE_VERBOSITY=0");
 +
 +      argv_array_push(&cp.args, "merge-recursive");
 +      argv_array_push(&cp.args, sha1_to_hex(orig_tree));
 +      argv_array_push(&cp.args, "--");
 +      argv_array_push(&cp.args, sha1_to_hex(our_tree));
 +      argv_array_push(&cp.args, sha1_to_hex(his_tree));
 +
 +      status = run_command(&cp) ? (-1) : 0;
 +      discard_cache();
 +      read_cache();
 +      return status;
 +}
 +
  /**
   * Attempt a threeway merge, using index_path as the temporary index.
   */
@@@ -1628,6 -1596,10 +1628,6 @@@ static int fall_back_threeway(const str
  {
        unsigned char orig_tree[GIT_SHA1_RAWSZ], his_tree[GIT_SHA1_RAWSZ],
                      our_tree[GIT_SHA1_RAWSZ];
 -      const unsigned char *bases[1] = {orig_tree};
 -      struct merge_options o;
 -      struct commit *result;
 -      char *his_tree_name;
  
        if (get_sha1("HEAD", our_tree) < 0)
                hashcpy(our_tree, EMPTY_TREE_SHA1_BIN);
         * changes.
         */
  
 -      init_merge_options(&o);
 -
 -      o.branch1 = "HEAD";
 -      his_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg);
 -      o.branch2 = his_tree_name;
 -
 -      if (state->quiet)
 -              o.verbosity = 0;
 -
 -      if (merge_recursive_generic(&o, our_tree, his_tree, 1, bases, &result)) {
 +      if (run_fallback_merge_recursive(state, orig_tree, our_tree, his_tree)) {
                rerere(state->allow_rerere_autoupdate);
 -              free(his_tree_name);
                return error(_("Failed to merge in the changes."));
        }
  
 -      free(his_tree_name);
        return 0;
  }
  
@@@ -2225,17 -2208,6 +2225,17 @@@ enum resume_mode 
        RESUME_ABORT
  };
  
 +static int git_am_config(const char *k, const char *v, void *cb)
 +{
 +      int status;
 +
 +      status = git_gpg_config(k, v, NULL);
 +      if (status)
 +              return status;
 +
 +      return git_default_config(k, v, NULL);
 +}
 +
  int cmd_am(int argc, const char **argv, const char *prefix)
  {
        struct am_state state;
        int in_progress;
  
        const char * const usage[] = {
 -              N_("git am [options] [(<mbox>|<Maildir>)...]"),
 -              N_("git am [options] (--continue | --skip | --abort)"),
 +              N_("git am [<options>] [(<mbox>|<Maildir>)...]"),
 +              N_("git am [<options>] (--continue | --skip | --abort)"),
                NULL
        };
  
                OPT_END()
        };
  
 -      git_config(git_default_config, NULL);
 +      git_config(git_am_config, NULL);
  
        am_state_init(&state, git_path("rebase-apply"));
  
diff --combined builtin/branch.c
index 01f9530822d9f4c060e2bcfa6d80b03618f526d9,baaa44c1ae23b6dbf761e358eab6ffb51a02b871..b99a436ef36ecfcc70636741642fdcf07b5a5810
  #include "column.h"
  #include "utf8.h"
  #include "wt-status.h"
 +#include "ref-filter.h"
  
  static const char * const builtin_branch_usage[] = {
        N_("git branch [<options>] [-r | -a] [--merged | --no-merged]"),
        N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"),
        N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."),
        N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"),
 +      N_("git branch [<options>] [-r | -a] [--points-at]"),
        NULL
  };
  
 -#define REF_LOCAL_BRANCH    0x01
 -#define REF_REMOTE_BRANCH   0x02
 -
  static const char *head;
  static unsigned char head_sha1[20];
  
@@@ -51,6 -52,13 +51,6 @@@ enum color_branch 
        BRANCH_COLOR_UPSTREAM = 5
  };
  
 -static enum merge_filter {
 -      NO_FILTER = 0,
 -      SHOW_NOT_MERGED,
 -      SHOW_MERGED
 -} merge_filter;
 -static unsigned char merge_filter_ref[20];
 -
  static struct string_list output = STRING_LIST_INIT_DUP;
  static unsigned int colopts;
  
@@@ -113,7 -121,7 +113,7 @@@ static int branch_merged(int kind, cons
        void *reference_name_to_free = NULL;
        int merged;
  
 -      if (kind == REF_LOCAL_BRANCH) {
 +      if (kind == FILTER_REFS_BRANCHES) {
                struct branch *branch = branch_get(name);
                const char *upstream = branch_get_upstream(branch, NULL);
                unsigned char sha1[20];
@@@ -191,14 -199,14 +191,14 @@@ static int delete_branches(int argc, co
        struct strbuf bname = STRBUF_INIT;
  
        switch (kinds) {
 -      case REF_REMOTE_BRANCH:
 +      case FILTER_REFS_REMOTES:
                fmt = "refs/remotes/%s";
                /* For subsequent UI messages */
                remote_branch = 1;
  
                force = 1;
                break;
 -      case REF_LOCAL_BRANCH:
 +      case FILTER_REFS_BRANCHES:
                fmt = "refs/heads/%s";
                break;
        default:
                int flags = 0;
  
                strbuf_branchname(&bname, argv[i]);
 -              if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) {
 +              if (kinds == FILTER_REFS_BRANCHES && !strcmp(head, bname.buf)) {
                        error(_("Cannot delete the branch '%s' "
                              "which you are currently on."), bname.buf);
                        ret = 1;
        return(ret);
  }
  
 -struct ref_item {
 -      char *name;
 -      char *dest;
 -      unsigned int kind, width;
 -      struct commit *commit;
 -      int ignore;
 -};
 -
 -struct ref_list {
 -      struct rev_info revs;
 -      int index, alloc, maxwidth, verbose, abbrev;
 -      struct ref_item *list;
 -      struct commit_list *with_commit;
 -      int kinds;
 -};
 -
 -static char *resolve_symref(const char *src, const char *prefix)
 -{
 -      unsigned char sha1[20];
 -      int flag;
 -      const char *dst;
 -
 -      dst = resolve_ref_unsafe(src, 0, sha1, &flag);
 -      if (!(dst && (flag & REF_ISSYMREF)))
 -              return NULL;
 -      if (prefix)
 -              skip_prefix(dst, prefix, &dst);
 -      return xstrdup(dst);
 -}
 -
 -struct append_ref_cb {
 -      struct ref_list *ref_list;
 -      const char **pattern;
 -      int ret;
 -};
 -
 -static int match_patterns(const char **pattern, const char *refname)
 -{
 -      if (!*pattern)
 -              return 1; /* no pattern always matches */
 -      while (*pattern) {
 -              if (!wildmatch(*pattern, refname, 0, NULL))
 -                      return 1;
 -              pattern++;
 -      }
 -      return 0;
 -}
 -
 -static int append_ref(const char *refname, const struct object_id *oid, int flags, void *cb_data)
 -{
 -      struct append_ref_cb *cb = (struct append_ref_cb *)(cb_data);
 -      struct ref_list *ref_list = cb->ref_list;
 -      struct ref_item *newitem;
 -      struct commit *commit;
 -      int kind, i;
 -      const char *prefix, *orig_refname = refname;
 -
 -      static struct {
 -              int kind;
 -              const char *prefix;
 -      } ref_kind[] = {
 -              { 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 (skip_prefix(refname, prefix, &refname)) {
 -                      kind = ref_kind[i].kind;
 -                      break;
 -              }
 -      }
 -      if (ARRAY_SIZE(ref_kind) <= i)
 -              return 0;
 -
 -      /* Don't add types the caller doesn't want */
 -      if ((kind & ref_list->kinds) == 0)
 -              return 0;
 -
 -      if (!match_patterns(cb->pattern, refname))
 -              return 0;
 -
 -      commit = NULL;
 -      if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) {
 -              commit = lookup_commit_reference_gently(oid->hash, 1);
 -              if (!commit) {
 -                      cb->ret = error(_("branch '%s' does not point at a commit"), refname);
 -                      return 0;
 -              }
 -
 -              /* Filter with with_commit if specified */
 -              if (!is_descendant_of(commit, ref_list->with_commit))
 -                      return 0;
 -
 -              if (merge_filter != NO_FILTER)
 -                      add_pending_object(&ref_list->revs,
 -                                         (struct object *)commit, refname);
 -      }
 -
 -      ALLOC_GROW(ref_list->list, ref_list->index + 1, ref_list->alloc);
 -
 -      /* Record the new item */
 -      newitem = &(ref_list->list[ref_list->index++]);
 -      newitem->name = xstrdup(refname);
 -      newitem->kind = kind;
 -      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)
 -              newitem->width += 8;
 -      if (newitem->width > ref_list->maxwidth)
 -              ref_list->maxwidth = newitem->width;
 -
 -      return 0;
 -}
 -
 -static void free_ref_list(struct ref_list *ref_list)
 -{
 -      int i;
 -
 -      for (i = 0; i < ref_list->index; i++) {
 -              free(ref_list->list[i].name);
 -              free(ref_list->list[i].dest);
 -      }
 -      free(ref_list->list);
 -}
 -
 -static int ref_cmp(const void *r1, const void *r2)
 -{
 -      struct ref_item *c1 = (struct ref_item *)(r1);
 -      struct ref_item *c2 = (struct ref_item *)(r2);
 -
 -      if (c1->kind != c2->kind)
 -              return c1->kind - c2->kind;
 -      return strcmp(c1->name, c2->name);
 -}
 -
  static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
                int show_upstream_ref)
  {
        free(ref);
  }
  
 -static void add_verbose_info(struct strbuf *out, struct ref_item *item,
 -                           int verbose, int abbrev)
 +static void add_verbose_info(struct strbuf *out, struct ref_array_item *item,
 +                           struct ref_filter *filter, const char *refname)
  {
        struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT;
        const char *sub = _(" **** invalid ref ****");
                sub = subject.buf;
        }
  
 -      if (item->kind == REF_LOCAL_BRANCH)
 -              fill_tracking_info(&stat, item->name, verbose > 1);
 +      if (item->kind == FILTER_REFS_BRANCHES)
 +              fill_tracking_info(&stat, refname, filter->verbose > 1);
  
        strbuf_addf(out, " %s %s%s",
 -              find_unique_abbrev(item->commit->object.sha1, abbrev),
 +              find_unique_abbrev(item->commit->object.sha1, filter->abbrev),
                stat.buf, sub);
        strbuf_release(&stat);
        strbuf_release(&subject);
  }
  
 -static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
 -                         int abbrev, int current, char *prefix)
 +static char *get_head_description(void)
 +{
 +      struct strbuf desc = STRBUF_INIT;
 +      struct wt_status_state state;
 +      memset(&state, 0, sizeof(state));
 +      wt_status_get_state(&state, 1);
 +      if (state.rebase_in_progress ||
 +          state.rebase_interactive_in_progress)
 +              strbuf_addf(&desc, _("(no branch, rebasing %s)"),
 +                          state.branch);
 +      else if (state.bisect_in_progress)
 +              strbuf_addf(&desc, _("(no branch, bisect started on %s)"),
 +                          state.branch);
 +      else if (state.detached_from) {
 +              /* TRANSLATORS: make sure these match _("HEAD detached at ")
 +                 and _("HEAD detached from ") in wt-status.c */
 +              if (state.detached_at)
 +                      strbuf_addf(&desc, _("(HEAD detached at %s)"),
 +                              state.detached_from);
 +              else
 +                      strbuf_addf(&desc, _("(HEAD detached from %s)"),
 +                              state.detached_from);
 +      }
 +      else
 +              strbuf_addstr(&desc, _("(no branch)"));
 +      free(state.branch);
 +      free(state.onto);
 +      free(state.detached_from);
 +      return strbuf_detach(&desc, NULL);
 +}
 +
 +static void format_and_print_ref_item(struct ref_array_item *item, int maxwidth,
 +                                    struct ref_filter *filter, const char *remote_prefix)
  {
        char c;
 +      int current = 0;
        int color;
        struct strbuf out = STRBUF_INIT, name = STRBUF_INIT;
 -
 -      if (item->ignore)
 -              return;
 +      const char *prefix = "";
 +      const char *desc = item->refname;
 +      char *to_free = NULL;
  
        switch (item->kind) {
 -      case REF_LOCAL_BRANCH:
 -              color = BRANCH_COLOR_LOCAL;
 +      case FILTER_REFS_BRANCHES:
 +              skip_prefix(desc, "refs/heads/", &desc);
 +              if (!filter->detached && !strcmp(desc, head))
 +                      current = 1;
 +              else
 +                      color = BRANCH_COLOR_LOCAL;
                break;
 -      case REF_REMOTE_BRANCH:
 +      case FILTER_REFS_REMOTES:
 +              skip_prefix(desc, "refs/remotes/", &desc);
                color = BRANCH_COLOR_REMOTE;
 +              prefix = remote_prefix;
 +              break;
 +      case FILTER_REFS_DETACHED_HEAD:
 +              desc = to_free = get_head_description();
 +              current = 1;
                break;
        default:
                color = BRANCH_COLOR_PLAIN;
                color = BRANCH_COLOR_CURRENT;
        }
  
 -      strbuf_addf(&name, "%s%s", prefix, item->name);
 -      if (verbose) {
 +      strbuf_addf(&name, "%s%s", prefix, desc);
 +      if (filter->verbose) {
                int utf8_compensation = strlen(name.buf) - utf8_strwidth(name.buf);
                strbuf_addf(&out, "%c %s%-*s%s", c, branch_get_color(color),
                            maxwidth + utf8_compensation, name.buf,
                strbuf_addf(&out, "%c %s%s%s", c, branch_get_color(color),
                            name.buf, branch_get_color(BRANCH_COLOR_RESET));
  
 -      if (item->dest)
 -              strbuf_addf(&out, " -> %s", item->dest);
 -      else if (verbose)
 +      if (item->symref) {
 +              skip_prefix(item->symref, "refs/remotes/", &desc);
 +              strbuf_addf(&out, " -> %s", desc);
 +      }
 +      else if (filter->verbose)
                /* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */
 -              add_verbose_info(&out, item, verbose, abbrev);
 +              add_verbose_info(&out, item, filter, desc);
        if (column_active(colopts)) {
 -              assert(!verbose && "--column and --verbose are incompatible");
 +              assert(!filter->verbose && "--column and --verbose are incompatible");
                string_list_append(&output, out.buf);
        } else {
                printf("%s\n", out.buf);
        }
        strbuf_release(&name);
        strbuf_release(&out);
 +      free(to_free);
  }
  
 -static int calc_maxwidth(struct ref_list *refs)
 -{
 -      int i, w = 0;
 -      for (i = 0; i < refs->index; i++) {
 -              if (refs->list[i].ignore)
 -                      continue;
 -              if (refs->list[i].width > w)
 -                      w = refs->list[i].width;
 -      }
 -      return w;
 -}
 -
 -static char *get_head_description(void)
 -{
 -      struct strbuf desc = STRBUF_INIT;
 -      struct wt_status_state state;
 -      memset(&state, 0, sizeof(state));
 -      wt_status_get_state(&state, 1);
 -      if (state.rebase_in_progress ||
 -          state.rebase_interactive_in_progress)
 -              strbuf_addf(&desc, _("(no branch, rebasing %s)"),
 -                          state.branch);
 -      else if (state.bisect_in_progress)
 -              strbuf_addf(&desc, _("(no branch, bisect started on %s)"),
 -                          state.branch);
 -      else if (state.detached_from) {
 -              /* TRANSLATORS: make sure these match _("HEAD detached at ")
 -                 and _("HEAD detached from ") in wt-status.c */
 -              if (state.detached_at)
 -                      strbuf_addf(&desc, _("(HEAD detached at %s)"),
 -                              state.detached_from);
 -              else
 -                      strbuf_addf(&desc, _("(HEAD detached from %s)"),
 -                              state.detached_from);
 -      }
 -      else
 -              strbuf_addstr(&desc, _("(no branch)"));
 -      free(state.branch);
 -      free(state.onto);
 -      free(state.detached_from);
 -      return strbuf_detach(&desc, NULL);
 -}
 -
 -static void show_detached(struct ref_list *ref_list)
 +static int calc_maxwidth(struct ref_array *refs, int remote_bonus)
  {
 -      struct commit *head_commit = lookup_commit_reference_gently(head_sha1, 1);
 -
 -      if (head_commit && is_descendant_of(head_commit, ref_list->with_commit)) {
 -              struct ref_item item;
 -              item.name = get_head_description();
 -              item.width = utf8_strwidth(item.name);
 -              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, "");
 -              free(item.name);
 +      int i, max = 0;
 +      for (i = 0; i < refs->nr; i++) {
 +              struct ref_array_item *it = refs->items[i];
 +              const char *desc = it->refname;
 +              int w;
 +
 +              skip_prefix(it->refname, "refs/heads/", &desc);
 +              skip_prefix(it->refname, "refs/remotes/", &desc);
 +              w = utf8_strwidth(desc);
 +
 +              if (it->kind == FILTER_REFS_REMOTES)
 +                      w += remote_bonus;
 +              if (w > max)
 +                      max = w;
        }
 +      return max;
  }
  
 -static int print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit, const char **pattern)
 +static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sorting)
  {
        int i;
 -      struct append_ref_cb cb;
 -      struct ref_list ref_list;
 -
 -      memset(&ref_list, 0, sizeof(ref_list));
 -      ref_list.kinds = kinds;
 -      ref_list.verbose = verbose;
 -      ref_list.abbrev = abbrev;
 -      ref_list.with_commit = with_commit;
 -      if (merge_filter != NO_FILTER)
 -              init_revisions(&ref_list.revs, NULL);
 -      cb.ref_list = &ref_list;
 -      cb.pattern = pattern;
 -      cb.ret = 0;
 -      for_each_rawref(append_ref, &cb);
 -      if (merge_filter != NO_FILTER) {
 -              struct commit *filter;
 -              filter = lookup_commit_reference_gently(merge_filter_ref, 0);
 -              if (!filter)
 -                      die(_("object '%s' does not point to a commit"),
 -                          sha1_to_hex(merge_filter_ref));
 -
 -              filter->object.flags |= UNINTERESTING;
 -              add_pending_object(&ref_list.revs,
 -                                 (struct object *) filter, "");
 -              ref_list.revs.limited = 1;
 -
 -              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);
 -              }
 +      struct ref_array array;
 +      int maxwidth = 0;
 +      const char *remote_prefix = "";
  
 -              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 we are listing more than just remote branches,
 +       * then remote branches will have a "remotes/" prefix.
 +       * We need to account for this in the width.
 +       */
 +      if (filter->kind != FILTER_REFS_REMOTES)
 +              remote_prefix = "remotes/";
  
 -              if (verbose)
 -                      ref_list.maxwidth = calc_maxwidth(&ref_list);
 -      }
 +      memset(&array, 0, sizeof(array));
  
 -      qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp);
 -
 -      detached = (detached && (kinds & REF_LOCAL_BRANCH));
 -      if (detached && match_patterns(pattern, "HEAD"))
 -              show_detached(&ref_list);
 -
 -      for (i = 0; i < ref_list.index; i++) {
 -              int current = !detached &&
 -                      (ref_list.list[i].kind == REF_LOCAL_BRANCH) &&
 -                      !strcmp(ref_list.list[i].name, head);
 -              char *prefix = (kinds != REF_REMOTE_BRANCH &&
 -                              ref_list.list[i].kind == REF_REMOTE_BRANCH)
 -                              ? "remotes/" : "";
 -              print_ref_item(&ref_list.list[i], ref_list.maxwidth, verbose,
 -                             abbrev, current, prefix);
 -      }
 +      verify_ref_format("%(refname)%(symref)");
 +      filter_refs(&array, filter, filter->kind | FILTER_REFS_INCLUDE_BROKEN);
  
 -      free_ref_list(&ref_list);
 +      if (filter->verbose)
 +              maxwidth = calc_maxwidth(&array, strlen(remote_prefix));
  
 -      if (cb.ret)
 -              error(_("some refs could not be read"));
 +      /*
 +       * If no sorting parameter is given then we default to sorting
 +       * by 'refname'. This would give us an alphabetically sorted
 +       * array with the 'HEAD' ref at the beginning followed by
 +       * local branches 'refs/heads/...' and finally remote-tacking
 +       * branches 'refs/remotes/...'.
 +       */
 +      if (!sorting)
 +              sorting = ref_default_sorting();
 +      ref_array_sort(sorting, &array);
 +
 +      for (i = 0; i < array.nr; i++)
 +              format_and_print_ref_item(array.items[i], maxwidth, filter, remote_prefix);
  
 -      return cb.ret;
 +      ref_array_clear(&array);
  }
  
  static void rename_branch(const char *oldname, const char *newname, int force)
        strbuf_release(&newsection);
  }
  
 -static int opt_parse_merge_filter(const struct option *opt, const char *arg, int unset)
 -{
 -      merge_filter = ((opt->long_name[0] == 'n')
 -                      ? SHOW_NOT_MERGED
 -                      : SHOW_MERGED);
 -      if (unset)
 -              merge_filter = SHOW_NOT_MERGED; /* b/c for --no-merged */
 -      if (!arg)
 -              arg = "HEAD";
 -      if (get_sha1(arg, merge_filter_ref))
 -              die(_("malformed object name %s"), arg);
 -      return 0;
 -}
 -
  static const char edit_description[] = "BRANCH_DESCRIPTION";
  
  static int edit_branch_description(const char *branch_name)
                strbuf_release(&buf);
                return -1;
        }
-       stripspace(&buf, 1);
+       strbuf_stripspace(&buf, 1);
  
        strbuf_addf(&name, "branch.%s.description", branch_name);
        status = git_config_set(name.buf, buf.len ? buf.buf : NULL);
  int cmd_branch(int argc, const char **argv, const char *prefix)
  {
        int delete = 0, rename = 0, force = 0, list = 0;
 -      int verbose = 0, abbrev = -1, detached = 0;
        int reflog = 0, edit_description = 0;
        int quiet = 0, unset_upstream = 0;
        const char *new_upstream = NULL;
        enum branch_track track;
 -      int kinds = REF_LOCAL_BRANCH;
 -      struct commit_list *with_commit = NULL;
 +      struct ref_filter filter;
 +      static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
  
        struct option options[] = {
                OPT_GROUP(N_("Generic options")),
 -              OPT__VERBOSE(&verbose,
 +              OPT__VERBOSE(&filter.verbose,
                        N_("show hash and subject, give twice for upstream branch")),
                OPT__QUIET(&quiet, N_("suppress informational messages")),
                OPT_SET_INT('t', "track",  &track, N_("set up tracking mode (see git-pull(1))"),
                OPT_STRING('u', "set-upstream-to", &new_upstream, "upstream", "change the upstream info"),
                OPT_BOOL(0, "unset-upstream", &unset_upstream, "Unset the upstream info"),
                OPT__COLOR(&branch_use_color, N_("use colored output")),
 -              OPT_SET_INT('r', "remotes",     &kinds, N_("act on remote-tracking branches"),
 -                      REF_REMOTE_BRANCH),
 -              {
 -                      OPTION_CALLBACK, 0, "contains", &with_commit, N_("commit"),
 -                      N_("print only branches that contain the commit"),
 -                      PARSE_OPT_LASTARG_DEFAULT,
 -                      parse_opt_with_commit, (intptr_t)"HEAD",
 -              },
 -              {
 -                      OPTION_CALLBACK, 0, "with", &with_commit, N_("commit"),
 -                      N_("print only branches that contain the commit"),
 -                      PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT,
 -                      parse_opt_with_commit, (intptr_t) "HEAD",
 -              },
 -              OPT__ABBREV(&abbrev),
 +              OPT_SET_INT('r', "remotes",     &filter.kind, N_("act on remote-tracking branches"),
 +                      FILTER_REFS_REMOTES),
 +              OPT_CONTAINS(&filter.with_commit, N_("print only branches that contain the commit")),
 +              OPT_WITH(&filter.with_commit, N_("print only branches that contain the commit")),
 +              OPT__ABBREV(&filter.abbrev),
  
                OPT_GROUP(N_("Specific git-branch actions:")),
 -              OPT_SET_INT('a', "all", &kinds, N_("list both remote-tracking and local branches"),
 -                      REF_REMOTE_BRANCH | REF_LOCAL_BRANCH),
 +              OPT_SET_INT('a', "all", &filter.kind, N_("list both remote-tracking and local branches"),
 +                      FILTER_REFS_REMOTES | FILTER_REFS_BRANCHES),
                OPT_BIT('d', "delete", &delete, N_("delete fully merged branch"), 1),
                OPT_BIT('D', NULL, &delete, N_("delete branch (even if not merged)"), 2),
                OPT_BIT('m', "move", &rename, N_("move/rename a branch and its reflog"), 1),
                OPT_BOOL(0, "edit-description", &edit_description,
                         N_("edit the description for the branch")),
                OPT__FORCE(&force, N_("force creation, move/rename, deletion")),
 +              OPT_MERGED(&filter, N_("print only branches that are merged")),
 +              OPT_NO_MERGED(&filter, N_("print only branches that are not merged")),
 +              OPT_COLUMN(0, "column", &colopts, N_("list branches in columns")),
 +              OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"),
 +                           N_("field name to sort on"), &parse_opt_ref_sorting),
                {
 -                      OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref,
 -                      N_("commit"), N_("print only not merged branches"),
 -                      PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,
 -                      opt_parse_merge_filter, (intptr_t) "HEAD",
 -              },
 -              {
 -                      OPTION_CALLBACK, 0, "merged", &merge_filter_ref,
 -                      N_("commit"), N_("print only merged branches"),
 -                      PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,
 -                      opt_parse_merge_filter, (intptr_t) "HEAD",
 +                      OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"),
 +                      N_("print only branches of the object"), 0, parse_opt_object_name
                },
 -              OPT_COLUMN(0, "column", &colopts, N_("list branches in columns")),
                OPT_END(),
        };
  
 +      memset(&filter, 0, sizeof(filter));
 +      filter.kind = FILTER_REFS_BRANCHES;
 +      filter.abbrev = -1;
 +
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage_with_options(builtin_branch_usage, options);
  
        if (!head)
                die(_("Failed to resolve HEAD as a valid ref."));
        if (!strcmp(head, "HEAD"))
 -              detached = 1;
 +              filter.detached = 1;
        else if (!skip_prefix(head, "refs/heads/", &head))
                die(_("HEAD not found below refs/heads!"));
 -      hashcpy(merge_filter_ref, head_sha1);
 -
  
        argc = parse_options(argc, argv, prefix, options, builtin_branch_usage,
                             0);
        if (!delete && !rename && !edit_description && !new_upstream && !unset_upstream && argc == 0)
                list = 1;
  
 -      if (with_commit || merge_filter != NO_FILTER)
 +      if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr)
                list = 1;
  
        if (!!delete + !!rename + !!new_upstream +
            list + unset_upstream > 1)
                usage_with_options(builtin_branch_usage, options);
  
 -      if (abbrev == -1)
 -              abbrev = DEFAULT_ABBREV;
 +      if (filter.abbrev == -1)
 +              filter.abbrev = DEFAULT_ABBREV;
        finalize_colopts(&colopts, -1);
 -      if (verbose) {
 +      if (filter.verbose) {
                if (explicitly_enable_column(colopts))
                        die(_("--column and --verbose are incompatible"));
                colopts = 0;
        if (delete) {
                if (!argc)
                        die(_("branch name required"));
 -              return delete_branches(argc, argv, delete > 1, kinds, quiet);
 +              return delete_branches(argc, argv, delete > 1, filter.kind, quiet);
        } else if (list) {
 -              int ret = print_ref_list(kinds, detached, verbose, abbrev,
 -                                       with_commit, argv);
 +              /*  git branch --local also shows HEAD when it is detached */
 +              if ((filter.kind & FILTER_REFS_BRANCHES) && filter.detached)
 +                      filter.kind |= FILTER_REFS_DETACHED_HEAD;
 +              filter.name_patterns = argv;
 +              print_ref_list(&filter, sorting);
                print_columns(&output, colopts, NULL);
                string_list_clear(&output, 0);
 -              return ret;
 +              return 0;
        }
        else if (edit_description) {
                const char *branch_name;
                struct strbuf branch_ref = STRBUF_INIT;
  
                if (!argc) {
 -                      if (detached)
 +                      if (filter.detached)
                                die(_("Cannot give description to detached HEAD"));
                        branch_name = head;
                } else if (argc == 1)
                if (!branch)
                        die(_("no such branch '%s'"), argv[0]);
  
 -              if (kinds != REF_LOCAL_BRANCH)
 +              if (filter.kind != FILTER_REFS_BRANCHES)
                        die(_("-a and -r options to 'git branch' do not make sense with a branch name"));
  
                if (track == BRANCH_TRACK_OVERRIDE)
diff --combined builtin/merge.c
index a0a93282922ebb8c58b79ff365d859500041351a,e6741f3380d30adaf7b5a16688a4ccb3e6d6dd99..977ffff2876de4d817025bbf6d11ec251b674141
@@@ -806,7 -806,7 +806,7 @@@ static void prepare_to_commit(struct co
                        abort_commit(remoteheads, NULL);
        }
        read_merge_msg(&msg);
-       stripspace(&msg, 0 < option_edit);
+       strbuf_stripspace(&msg, 0 < option_edit);
        if (!msg.len)
                abort_commit(remoteheads, _("Empty commit message."));
        strbuf_release(&merge_msg);
@@@ -1319,13 -1319,13 +1319,13 @@@ int cmd_merge(int argc, const char **ar
        if (verify_signatures) {
                for (p = remoteheads; p; p = p->next) {
                        struct commit *commit = p->item;
 -                      char hex[41];
 +                      char hex[GIT_SHA1_HEXSZ + 1];
                        struct signature_check signature_check;
                        memset(&signature_check, 0, sizeof(signature_check));
  
                        check_commit_signature(commit, &signature_check);
  
 -                      strcpy(hex, find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
 +                      find_unique_abbrev_r(hex, commit->object.sha1, DEFAULT_ABBREV);
                        switch (signature_check.result) {
                        case 'G':
                                break;
                /* Again the most common case of merging one remote. */
                struct strbuf msg = STRBUF_INIT;
                struct commit *commit;
 -              char hex[41];
  
 -              strcpy(hex, find_unique_abbrev(head_commit->object.sha1, DEFAULT_ABBREV));
 -
 -              if (verbosity >= 0)
 -                      printf(_("Updating %s..%s\n"),
 -                              hex,
 -                              find_unique_abbrev(remoteheads->item->object.sha1,
 -                              DEFAULT_ABBREV));
 +              if (verbosity >= 0) {
 +                      char from[GIT_SHA1_HEXSZ + 1], to[GIT_SHA1_HEXSZ + 1];
 +                      find_unique_abbrev_r(from, head_commit->object.sha1,
 +                                            DEFAULT_ABBREV);
 +                      find_unique_abbrev_r(to, remoteheads->item->object.sha1,
 +                                            DEFAULT_ABBREV);
 +                      printf(_("Updating %s..%s\n"), from, to);
 +              }
                strbuf_addstr(&msg, "Fast-forward");
                if (have_message)
                        strbuf_addstr(&msg,
diff --combined builtin/notes.c
index 13c0af9155052499c15ecf2a147acd83bc042f42,bb23d5501c9a00818b880217d748c5fd1bcfe5cc..515cebbeb8a33eb982a1ddccdc9d69e09beefb1d
@@@ -19,7 -19,7 +19,7 @@@
  #include "string-list.h"
  #include "notes-merge.h"
  #include "notes-utils.h"
 -#include "branch.h"
 +#include "worktree.h"
  
  static const char * const git_notes_usage[] = {
        N_("git notes [--ref <notes-ref>] [list [<object>]]"),
@@@ -192,7 -192,7 +192,7 @@@ static void prepare_note_data(const uns
                if (launch_editor(d->edit_path, &d->buf, NULL)) {
                        die(_("Please supply the note contents using either -m or -F option"));
                }
-               stripspace(&d->buf, 1);
+               strbuf_stripspace(&d->buf, 1);
        }
  }
  
@@@ -215,7 -215,7 +215,7 @@@ static int parse_msg_arg(const struct o
        if (d->buf.len)
                strbuf_addch(&d->buf, '\n');
        strbuf_addstr(&d->buf, arg);
-       stripspace(&d->buf, 0);
+       strbuf_stripspace(&d->buf, 0);
  
        d->given = 1;
        return 0;
@@@ -232,7 -232,7 +232,7 @@@ static int parse_file_arg(const struct 
                        die_errno(_("cannot read '%s'"), arg);
        } else if (strbuf_read_file(&d->buf, arg, 1024) < 0)
                die_errno(_("could not open or read '%s'"), arg);
-       stripspace(&d->buf, 0);
+       strbuf_stripspace(&d->buf, 0);
  
        d->given = 1;
        return 0;
diff --combined builtin/stripspace.c
index 958567a0cd0f3b676f2a46a699b34a67d14f129a,a8b7a93b43e414b51c76a4853ff3b49394ab1d77..7ff8434f7c3cf71dbd77cbf868d5dc56dd943fdb
@@@ -1,71 -1,7 +1,7 @@@
  #include "builtin.h"
  #include "cache.h"
- /*
-  * Returns the length of a line, without trailing spaces.
-  *
-  * If the line ends with newline, it will be removed too.
-  */
- static size_t cleanup(char *line, size_t len)
- {
-       while (len) {
-               unsigned char c = line[len - 1];
-               if (!isspace(c))
-                       break;
-               len--;
-       }
-       return len;
- }
- /*
-  * Remove empty lines from the beginning and end
-  * and also trailing spaces from every line.
-  *
-  * Turn multiple consecutive empty lines between paragraphs
-  * into just one empty line.
-  *
-  * If the input has only empty lines and spaces,
-  * no output will be produced.
-  *
-  * If last line does not have a newline at the end, one is added.
-  *
-  * Enable skip_comments to skip every line starting with comment
-  * character.
-  */
- void stripspace(struct strbuf *sb, int skip_comments)
- {
-       int empties = 0;
-       size_t i, j, len, newlen;
-       char *eol;
-       /* We may have to add a newline. */
-       strbuf_grow(sb, 1);
-       for (i = j = 0; i < sb->len; i += len, j += newlen) {
-               eol = memchr(sb->buf + i, '\n', sb->len - i);
-               len = eol ? eol - (sb->buf + i) + 1 : sb->len - i;
-               if (skip_comments && len && sb->buf[i] == comment_line_char) {
-                       newlen = 0;
-                       continue;
-               }
-               newlen = cleanup(sb->buf + i, len);
-               /* Not just an empty line? */
-               if (newlen) {
-                       if (empties > 0 && j > 0)
-                               sb->buf[j++] = '\n';
-                       empties = 0;
-                       memmove(sb->buf + j, sb->buf + i, newlen);
-                       sb->buf[newlen + j++] = '\n';
-               } else {
-                       empties++;
-               }
-       }
-       strbuf_setlen(sb, j);
- }
+ #include "parse-options.h"
+ #include "strbuf.h"
  
  static void comment_lines(struct strbuf *buf)
  {
        free(msg);
  }
  
- static const char *usage_msg = "\n"
- "  git stripspace [-s | --strip-comments]\n"
- "  git stripspace [-c | --comment-lines]";
+ static const char * const stripspace_usage[] = {
 -      N_("git stripspace [-s | --strip-comments] < input"),
 -      N_("git stripspace [-c | --comment-lines] < input"),
++      N_("git stripspace [-s | --strip-comments]"),
++      N_("git stripspace [-c | --comment-lines]"),
+       NULL
+ };
+ enum stripspace_mode {
+       STRIP_DEFAULT = 0,
+       STRIP_COMMENTS,
+       COMMENT_LINES
+ };
  
  int cmd_stripspace(int argc, const char **argv, const char *prefix)
  {
        struct strbuf buf = STRBUF_INIT;
-       int strip_comments = 0;
-       enum { INVAL = 0, STRIP_SPACE = 1, COMMENT_LINES = 2 } mode = STRIP_SPACE;
-       if (argc == 2) {
-               if (!strcmp(argv[1], "-s") ||
-                   !strcmp(argv[1], "--strip-comments")) {
-                       strip_comments = 1;
-               } else if (!strcmp(argv[1], "-c") ||
-                          !strcmp(argv[1], "--comment-lines")) {
-                       mode = COMMENT_LINES;
-               } else {
-                       mode = INVAL;
-               }
-       } else if (argc > 1) {
-               mode = INVAL;
-       }
-       if (mode == INVAL)
-               usage(usage_msg);
-       if (strip_comments || mode == COMMENT_LINES)
+       enum stripspace_mode mode = STRIP_DEFAULT;
+       const struct option options[] = {
+               OPT_CMDMODE('s', "strip-comments", &mode,
+                           N_("skip and remove all lines starting with comment character"),
+                           STRIP_COMMENTS),
+               OPT_CMDMODE('c', "comment-lines", &mode,
+                           N_("prepend comment character and blank to each line"),
+                           COMMENT_LINES),
+               OPT_END()
+       };
+       argc = parse_options(argc, argv, prefix, options, stripspace_usage, 0);
+       if (argc)
+               usage_with_options(stripspace_usage, options);
+       if (mode == STRIP_COMMENTS || mode == COMMENT_LINES)
                git_config(git_default_config, NULL);
  
        if (strbuf_read(&buf, 0, 1024) < 0)
                die_errno("could not read the input");
  
-       if (mode == STRIP_SPACE)
-               stripspace(&buf, strip_comments);
+       if (mode == STRIP_DEFAULT || mode == STRIP_COMMENTS)
+               strbuf_stripspace(&buf, mode == STRIP_COMMENTS);
        else
                comment_lines(&buf);
  
diff --combined builtin/tag.c
index 9e17dca7cac30738c966d0e23258f56c0cb9bc70,f048cae0e0593866105574e685b97eb71023fbd2..566078773fe82a51d850a4def8d63835d6310b2e
  #include "gpg-interface.h"
  #include "sha1-array.h"
  #include "column.h"
 +#include "ref-filter.h"
  
  static const char * const git_tag_usage[] = {
        N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> [<head>]"),
        N_("git tag -d <tagname>..."),
        N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]"
 -              "\n\t\t[<pattern>...]"),
 +              "\n\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"),
        N_("git tag -v <tagname>..."),
        NULL
  };
  
 -#define STRCMP_SORT     0     /* must be zero */
 -#define VERCMP_SORT     1
 -#define SORT_MASK       0x7fff
 -#define REVERSE_SORT    0x8000
 -
 -static int tag_sort;
 -
 -struct tag_filter {
 -      const char **patterns;
 -      int lines;
 -      int sort;
 -      struct string_list tags;
 -      struct commit_list *with_commit;
 -};
 -
 -static struct sha1_array points_at;
  static unsigned int colopts;
  
 -static int match_pattern(const char **patterns, const char *ref)
 -{
 -      /* no pattern means match everything */
 -      if (!*patterns)
 -              return 1;
 -      for (; *patterns; patterns++)
 -              if (!wildmatch(*patterns, ref, 0, NULL))
 -                      return 1;
 -      return 0;
 -}
 -
 -static const unsigned char *match_points_at(const char *refname,
 -                                          const unsigned char *sha1)
 -{
 -      const unsigned char *tagged_sha1 = NULL;
 -      struct object *obj;
 -
 -      if (sha1_array_lookup(&points_at, sha1) >= 0)
 -              return sha1;
 -      obj = parse_object(sha1);
 -      if (!obj)
 -              die(_("malformed object at '%s'"), refname);
 -      if (obj->type == OBJ_TAG)
 -              tagged_sha1 = ((struct tag *)obj)->tagged->sha1;
 -      if (tagged_sha1 && sha1_array_lookup(&points_at, tagged_sha1) >= 0)
 -              return tagged_sha1;
 -      return NULL;
 -}
 -
 -static int in_commit_list(const struct commit_list *want, struct commit *c)
 -{
 -      for (; want; want = want->next)
 -              if (!hashcmp(want->item->object.sha1, c->object.sha1))
 -                      return 1;
 -      return 0;
 -}
 -
 -enum contains_result {
 -      CONTAINS_UNKNOWN = -1,
 -      CONTAINS_NO = 0,
 -      CONTAINS_YES = 1
 -};
 -
 -/*
 - * Test whether the candidate or one of its parents is contained in the list.
 - * Do not recurse to find out, though, but return -1 if inconclusive.
 - */
 -static enum contains_result contains_test(struct commit *candidate,
 -                          const struct commit_list *want)
 -{
 -      /* was it previously marked as containing a want commit? */
 -      if (candidate->object.flags & TMP_MARK)
 -              return 1;
 -      /* or marked as not possibly containing a want commit? */
 -      if (candidate->object.flags & UNINTERESTING)
 -              return 0;
 -      /* or are we it? */
 -      if (in_commit_list(want, candidate)) {
 -              candidate->object.flags |= TMP_MARK;
 -              return 1;
 -      }
 -
 -      if (parse_commit(candidate) < 0)
 -              return 0;
 -
 -      return -1;
 -}
 -
 -/*
 - * Mimicking the real stack, this stack lives on the heap, avoiding stack
 - * overflows.
 - *
 - * At each recursion step, the stack items points to the commits whose
 - * ancestors are to be inspected.
 - */
 -struct stack {
 -      int nr, alloc;
 -      struct stack_entry {
 -              struct commit *commit;
 -              struct commit_list *parents;
 -      } *stack;
 -};
 -
 -static void push_to_stack(struct commit *candidate, struct stack *stack)
 -{
 -      int index = stack->nr++;
 -      ALLOC_GROW(stack->stack, stack->nr, stack->alloc);
 -      stack->stack[index].commit = candidate;
 -      stack->stack[index].parents = candidate->parents;
 -}
 -
 -static enum contains_result contains(struct commit *candidate,
 -              const struct commit_list *want)
 -{
 -      struct stack stack = { 0, 0, NULL };
 -      int result = contains_test(candidate, want);
 -
 -      if (result != CONTAINS_UNKNOWN)
 -              return result;
 -
 -      push_to_stack(candidate, &stack);
 -      while (stack.nr) {
 -              struct stack_entry *entry = &stack.stack[stack.nr - 1];
 -              struct commit *commit = entry->commit;
 -              struct commit_list *parents = entry->parents;
 -
 -              if (!parents) {
 -                      commit->object.flags |= UNINTERESTING;
 -                      stack.nr--;
 -              }
 -              /*
 -               * If we just popped the stack, parents->item has been marked,
 -               * therefore contains_test will return a meaningful 0 or 1.
 -               */
 -              else switch (contains_test(parents->item, want)) {
 -              case CONTAINS_YES:
 -                      commit->object.flags |= TMP_MARK;
 -                      stack.nr--;
 -                      break;
 -              case CONTAINS_NO:
 -                      entry->parents = parents->next;
 -                      break;
 -              case CONTAINS_UNKNOWN:
 -                      push_to_stack(parents->item, &stack);
 -                      break;
 -              }
 -      }
 -      free(stack.stack);
 -      return contains_test(candidate, want);
 -}
 -
 -static void show_tag_lines(const struct object_id *oid, int lines)
 +static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, const char *format)
  {
 +      struct ref_array array;
 +      char *to_free = NULL;
        int i;
 -      unsigned long size;
 -      enum object_type type;
 -      char *buf, *sp, *eol;
 -      size_t len;
 -
 -      buf = read_sha1_file(oid->hash, &type, &size);
 -      if (!buf)
 -              die_errno("unable to read object %s", oid_to_hex(oid));
 -      if (type != OBJ_COMMIT && type != OBJ_TAG)
 -              goto free_return;
 -      if (!size)
 -              die("an empty %s object %s?",
 -                  typename(type), oid_to_hex(oid));
 -
 -      /* skip header */
 -      sp = strstr(buf, "\n\n");
 -      if (!sp)
 -              goto free_return;
 -
 -      /* only take up to "lines" lines, and strip the signature from a tag */
 -      if (type == OBJ_TAG)
 -              size = parse_signature(buf, size);
 -      for (i = 0, sp += 2; i < lines && sp < buf + size; i++) {
 -              if (i)
 -                      printf("\n    ");
 -              eol = memchr(sp, '\n', size - (sp - buf));
 -              len = eol ? eol - sp : size - (sp - buf);
 -              fwrite(sp, len, 1, stdout);
 -              if (!eol)
 -                      break;
 -              sp = eol + 1;
 -      }
 -free_return:
 -      free(buf);
 -}
  
 -static int show_reference(const char *refname, const struct object_id *oid,
 -                        int flag, void *cb_data)
 -{
 -      struct tag_filter *filter = cb_data;
 +      memset(&array, 0, sizeof(array));
  
 -      if (match_pattern(filter->patterns, refname)) {
 -              if (filter->with_commit) {
 -                      struct commit *commit;
 +      if (filter->lines == -1)
 +              filter->lines = 0;
  
 -                      commit = lookup_commit_reference_gently(oid->hash, 1);
 -                      if (!commit)
 -                              return 0;
 -                      if (!contains(commit, filter->with_commit))
 -                              return 0;
 -              }
 -
 -              if (points_at.nr && !match_points_at(refname, oid->hash))
 -                      return 0;
 -
 -              if (!filter->lines) {
 -                      if (filter->sort)
 -                              string_list_append(&filter->tags, refname);
 -                      else
 -                              printf("%s\n", refname);
 -                      return 0;
 -              }
 -              printf("%-15s ", refname);
 -              show_tag_lines(oid, filter->lines);
 -              putchar('\n');
 +      if (!format) {
 +              if (filter->lines) {
 +                      to_free = xstrfmt("%s %%(contents:lines=%d)",
 +                                        "%(align:15)%(refname:short)%(end)",
 +                                        filter->lines);
 +                      format = to_free;
 +              } else
 +                      format = "%(refname:short)";
        }
  
 -      return 0;
 -}
 +      verify_ref_format(format);
 +      filter_refs(&array, filter, FILTER_REFS_TAGS);
 +      ref_array_sort(sorting, &array);
  
 -static int sort_by_version(const void *a_, const void *b_)
 -{
 -      const struct string_list_item *a = a_;
 -      const struct string_list_item *b = b_;
 -      return versioncmp(a->string, b->string);
 -}
 +      for (i = 0; i < array.nr; i++)
 +              show_ref_array_item(array.items[i], format, 0);
 +      ref_array_clear(&array);
 +      free(to_free);
  
 -static int list_tags(const char **patterns, int lines,
 -                   struct commit_list *with_commit, int sort)
 -{
 -      struct tag_filter filter;
 -
 -      filter.patterns = patterns;
 -      filter.lines = lines;
 -      filter.sort = sort;
 -      filter.with_commit = with_commit;
 -      memset(&filter.tags, 0, sizeof(filter.tags));
 -      filter.tags.strdup_strings = 1;
 -
 -      for_each_tag_ref(show_reference, (void *)&filter);
 -      if (sort) {
 -              int i;
 -              if ((sort & SORT_MASK) == VERCMP_SORT)
 -                      qsort(filter.tags.items, filter.tags.nr,
 -                            sizeof(struct string_list_item), sort_by_version);
 -              if (sort & REVERSE_SORT)
 -                      for (i = filter.tags.nr - 1; i >= 0; i--)
 -                              printf("%s\n", filter.tags.items[i].string);
 -              else
 -                      for (i = 0; i < filter.tags.nr; i++)
 -                              printf("%s\n", filter.tags.items[i].string);
 -              string_list_clear(&filter.tags, 0);
 -      }
        return 0;
  }
  
@@@ -126,26 -348,35 +126,26 @@@ static const char tag_template_nocleanu
        "Lines starting with '%c' will be kept; you may remove them"
        " yourself if you want to.\n");
  
 -/*
 - * Parse a sort string, and return 0 if parsed successfully. Will return
 - * non-zero when the sort string does not parse into a known type. If var is
 - * given, the error message becomes a warning and includes information about
 - * the configuration value.
 - */
 -static int parse_sort_string(const char *var, const char *arg, int *sort)
 +/* Parse arg given and add it the ref_sorting array */
 +static int parse_sorting_string(const char *arg, struct ref_sorting **sorting_tail)
  {
 -      int type = 0, flags = 0;
 -
 -      if (skip_prefix(arg, "-", &arg))
 -              flags |= REVERSE_SORT;
 +      struct ref_sorting *s;
 +      int len;
  
 -      if (skip_prefix(arg, "version:", &arg) || skip_prefix(arg, "v:", &arg))
 -              type = VERCMP_SORT;
 -      else
 -              type = STRCMP_SORT;
 +      s = xcalloc(1, sizeof(*s));
 +      s->next = *sorting_tail;
 +      *sorting_tail = s;
  
 -      if (strcmp(arg, "refname")) {
 -              if (!var)
 -                      return error(_("unsupported sort specification '%s'"), arg);
 -              else {
 -                      warning(_("unsupported sort specification '%s' in variable '%s'"),
 -                              var, arg);
 -                      return -1;
 -              }
 +      if (*arg == '-') {
 +              s->reverse = 1;
 +              arg++;
        }
 +      if (skip_prefix(arg, "version:", &arg) ||
 +          skip_prefix(arg, "v:", &arg))
 +              s->version = 1;
  
 -      *sort = (type | flags);
 +      len = strlen(arg);
 +      s->atom = parse_ref_filter_atom(arg, arg+len);
  
        return 0;
  }
  static int git_tag_config(const char *var, const char *value, void *cb)
  {
        int status;
 +      struct ref_sorting **sorting_tail = (struct ref_sorting **)cb;
  
        if (!strcmp(var, "tag.sort")) {
                if (!value)
                        return config_error_nonbool(var);
 -              parse_sort_string(var, value, &tag_sort);
 +              parse_sorting_string(value, sorting_tail);
                return 0;
        }
  
@@@ -268,7 -498,7 +268,7 @@@ static void create_tag(const unsigned c
        }
  
        if (opt->cleanup_mode != CLEANUP_NONE)
-               stripspace(buf, opt->cleanup_mode == CLEANUP_ALL);
+               strbuf_stripspace(buf, opt->cleanup_mode == CLEANUP_ALL);
  
        if (!opt->message_given && !buf->len)
                die(_("no tag message?"));
@@@ -316,6 -546,30 +316,6 @@@ static int strbuf_check_tag_ref(struct 
        return check_refname_format(sb->buf, 0);
  }
  
 -static int parse_opt_points_at(const struct option *opt __attribute__((unused)),
 -                      const char *arg, int unset)
 -{
 -      unsigned char sha1[20];
 -
 -      if (unset) {
 -              sha1_array_clear(&points_at);
 -              return 0;
 -      }
 -      if (!arg)
 -              return error(_("switch 'points-at' requires an object"));
 -      if (get_sha1(arg, sha1))
 -              return error(_("malformed object name '%s'"), arg);
 -      sha1_array_append(&points_at, sha1);
 -      return 0;
 -}
 -
 -static int parse_opt_sort(const struct option *opt, const char *arg, int unset)
 -{
 -      int *sort = opt->value;
 -
 -      return parse_sort_string(NULL, arg, sort);
 -}
 -
  int cmd_tag(int argc, const char **argv, const char *prefix)
  {
        struct strbuf buf = STRBUF_INIT;
        const char *object_ref, *tag;
        struct create_tag_options opt;
        char *cleanup_arg = NULL;
 -      int annotate = 0, force = 0, lines = -1;
        int create_reflog = 0;
 +      int annotate = 0, force = 0;
        int cmdmode = 0;
        const char *msgfile = NULL, *keyid = NULL;
        struct msg_arg msg = { 0, STRBUF_INIT };
 -      struct commit_list *with_commit = NULL;
        struct ref_transaction *transaction;
        struct strbuf err = STRBUF_INIT;
 +      struct ref_filter filter;
 +      static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
 +      const char *format = NULL;
        struct option options[] = {
                OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'),
 -              { OPTION_INTEGER, 'n', NULL, &lines, N_("n"),
 +              { OPTION_INTEGER, 'n', NULL, &filter.lines, N_("n"),
                                N_("print <n> lines of each tag message"),
                                PARSE_OPT_OPTARG, NULL, 1 },
                OPT_CMDMODE('d', "delete", &cmdmode, N_("delete tags"), 'd'),
  
                OPT_GROUP(N_("Tag listing options")),
                OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")),
 +              OPT_CONTAINS(&filter.with_commit, N_("print only tags that contain the commit")),
 +              OPT_WITH(&filter.with_commit, N_("print only tags that contain the commit")),
 +              OPT_MERGED(&filter, N_("print only tags that are merged")),
 +              OPT_NO_MERGED(&filter, N_("print only tags that are not merged")),
 +              OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"),
 +                           N_("field name to sort on"), &parse_opt_ref_sorting),
                {
 -                      OPTION_CALLBACK, 0, "sort", &tag_sort, N_("type"), N_("sort tags"),
 -                      PARSE_OPT_NONEG, parse_opt_sort
 -              },
 -              {
 -                      OPTION_CALLBACK, 0, "contains", &with_commit, N_("commit"),
 -                      N_("print only tags that contain the commit"),
 -                      PARSE_OPT_LASTARG_DEFAULT,
 -                      parse_opt_with_commit, (intptr_t)"HEAD",
 -              },
 -              {
 -                      OPTION_CALLBACK, 0, "with", &with_commit, N_("commit"),
 -                      N_("print only tags that contain the commit"),
 -                      PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT,
 -                      parse_opt_with_commit, (intptr_t)"HEAD",
 -              },
 -              {
 -                      OPTION_CALLBACK, 0, "points-at", NULL, N_("object"),
 -                      N_("print only tags of the object"), 0, parse_opt_points_at
 +                      OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"),
 +                      N_("print only tags of the object"), 0, parse_opt_object_name
                },
 +              OPT_STRING(  0 , "format", &format, N_("format"), N_("format to use for the output")),
                OPT_END()
        };
  
 -      git_config(git_tag_config, NULL);
 +      git_config(git_tag_config, sorting_tail);
  
        memset(&opt, 0, sizeof(opt));
 +      memset(&filter, 0, sizeof(filter));
 +      filter.lines = -1;
  
        argc = parse_options(argc, argv, prefix, options, git_tag_usage, 0);
  
                usage_with_options(git_tag_usage, options);
  
        finalize_colopts(&colopts, -1);
 -      if (cmdmode == 'l' && lines != -1) {
 +      if (cmdmode == 'l' && filter.lines != -1) {
                if (explicitly_enable_column(colopts))
                        die(_("--column and -n are incompatible"));
                colopts = 0;
        }
 +      if (!sorting)
 +              sorting = ref_default_sorting();
        if (cmdmode == 'l') {
                int ret;
                if (column_active(colopts)) {
                        copts.padding = 2;
                        run_column_filter(colopts, &copts);
                }
 -              if (lines != -1 && tag_sort)
 -                      die(_("--sort and -n are incompatible"));
 -              ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit, tag_sort);
 +              filter.name_patterns = argv;
 +              ret = list_tags(&filter, sorting, format);
                if (column_active(colopts))
                        stop_column_filter();
                return ret;
        }
 -      if (lines != -1)
 +      if (filter.lines != -1)
                die(_("-n option is only allowed with -l."));
 -      if (with_commit)
 +      if (filter.with_commit)
                die(_("--contains option is only allowed with -l."));
 -      if (points_at.nr)
 +      if (filter.points_at.nr)
                die(_("--points-at option is only allowed with -l."));
 +      if (filter.merge_commit)
 +              die(_("--merged and --no-merged option are only allowed with -l"));
        if (cmdmode == 'd')
                return for_each_tag_name(argv, delete_tag);
        if (cmdmode == 'v')
diff --combined strbuf.c
index 107c45d29111a011a04daaa8fd0323ed9d973398,958387562184fe27c7e476ebb24d5547a55ae4ad..d76f0aed85c4ec6eafdcfd1a8ee2b22d3d20df96
+++ b/strbuf.c
@@@ -245,8 -245,8 +245,8 @@@ void strbuf_add_commented_lines(struct 
        static char prefix2[2];
  
        if (prefix1[0] != comment_line_char) {
 -              sprintf(prefix1, "%c ", comment_line_char);
 -              sprintf(prefix2, "%c", comment_line_char);
 +              xsnprintf(prefix1, sizeof(prefix1), "%c ", comment_line_char);
 +              xsnprintf(prefix2, sizeof(prefix2), "%c", comment_line_char);
        }
        add_lines(out, prefix1, prefix2, buf, size);
  }
@@@ -744,11 -744,68 +744,77 @@@ void strbuf_addftime(struct strbuf *sb
        strbuf_setlen(sb, sb->len + len);
  }
  
 +void strbuf_add_unique_abbrev(struct strbuf *sb, const unsigned char *sha1,
 +                            int abbrev_len)
 +{
 +      int r;
 +      strbuf_grow(sb, GIT_SHA1_HEXSZ + 1);
 +      r = find_unique_abbrev_r(sb->buf + sb->len, sha1, abbrev_len);
 +      strbuf_setlen(sb, sb->len + r);
 +}
++
+ /*
+  * Returns the length of a line, without trailing spaces.
+  *
+  * If the line ends with newline, it will be removed too.
+  */
+ static size_t cleanup(char *line, size_t len)
+ {
+       while (len) {
+               unsigned char c = line[len - 1];
+               if (!isspace(c))
+                       break;
+               len--;
+       }
+       return len;
+ }
+ /*
+  * Remove empty lines from the beginning and end
+  * and also trailing spaces from every line.
+  *
+  * Turn multiple consecutive empty lines between paragraphs
+  * into just one empty line.
+  *
+  * If the input has only empty lines and spaces,
+  * no output will be produced.
+  *
+  * If last line does not have a newline at the end, one is added.
+  *
+  * Enable skip_comments to skip every line starting with comment
+  * character.
+  */
+ void strbuf_stripspace(struct strbuf *sb, int skip_comments)
+ {
+       int empties = 0;
+       size_t i, j, len, newlen;
+       char *eol;
+       /* We may have to add a newline. */
+       strbuf_grow(sb, 1);
+       for (i = j = 0; i < sb->len; i += len, j += newlen) {
+               eol = memchr(sb->buf + i, '\n', sb->len - i);
+               len = eol ? eol - (sb->buf + i) + 1 : sb->len - i;
+               if (skip_comments && len && sb->buf[i] == comment_line_char) {
+                       newlen = 0;
+                       continue;
+               }
+               newlen = cleanup(sb->buf + i, len);
+               /* Not just an empty line? */
+               if (newlen) {
+                       if (empties > 0 && j > 0)
+                               sb->buf[j++] = '\n';
+                       empties = 0;
+                       memmove(sb->buf + j, sb->buf + i, newlen);
+                       sb->buf[newlen + j++] = '\n';
+               } else {
+                       empties++;
+               }
+       }
+       strbuf_setlen(sb, j);
+ }
diff --combined strbuf.h
index 0f9c8a72ba7cdb7adb34c96a8e04db776c18d743,5397d91d7ade0360077c3b2ceb9e6d3cd226820d..7123fca7aff5182b66af6d76991552081139467c
+++ b/strbuf.h
@@@ -418,7 -418,16 +418,16 @@@ extern void strbuf_add_absolute_path(st
   * Strip whitespace from a buffer. The second parameter controls if
   * comments are considered contents to be removed or not.
   */
- extern void stripspace(struct strbuf *buf, int skip_comments);
+ extern void strbuf_stripspace(struct strbuf *buf, int skip_comments);
+ /**
+  * Temporary alias until all topic branches have switched to use
+  * strbuf_stripspace directly.
+  */
+ static inline void stripspace(struct strbuf *buf, int skip_comments)
+ {
+       strbuf_stripspace(buf, skip_comments);
+ }
  
  static inline int strbuf_strip_suffix(struct strbuf *sb, const char *suffix)
  {
@@@ -474,14 -483,6 +483,14 @@@ static inline struct strbuf **strbuf_sp
   */
  extern void strbuf_list_free(struct strbuf **);
  
 +/**
 + * Add the abbreviation, as generated by find_unique_abbrev, of `sha1` to
 + * the strbuf `sb`.
 + */
 +extern void strbuf_add_unique_abbrev(struct strbuf *sb,
 +                                   const unsigned char *sha1,
 +                                   int abbrev_len);
 +
  /**
   * Launch the user preferred editor to edit a file and fill the buffer
   * with the file's contents upon the user completing their editing. The
@@@ -499,21 -500,10 +508,21 @@@ extern void strbuf_add_lines(struct str
   */
  extern void strbuf_addstr_xml_quoted(struct strbuf *sb, const char *s);
  
 +/**
 + * "Complete" the contents of `sb` by ensuring that either it ends with the
 + * character `term`, or it is empty.  This can be used, for example,
 + * to ensure that text ends with a newline, but without creating an empty
 + * blank line if there is no content in the first place.
 + */
 +static inline void strbuf_complete(struct strbuf *sb, char term)
 +{
 +      if (sb->len && sb->buf[sb->len - 1] != term)
 +              strbuf_addch(sb, term);
 +}
 +
  static inline void strbuf_complete_line(struct strbuf *sb)
  {
 -      if (sb->len && sb->buf[sb->len - 1] != '\n')
 -              strbuf_addch(sb, '\n');
 +      strbuf_complete(sb, '\n');
  }
  
  extern int strbuf_branchname(struct strbuf *sb, const char *name);