Merge branch 'jh/status-no-ahead-behind'
authorJunio C Hamano <gitster@pobox.com>
Thu, 8 Mar 2018 20:36:24 +0000 (12:36 -0800)
committerJunio C Hamano <gitster@pobox.com>
Thu, 8 Mar 2018 20:36:24 +0000 (12:36 -0800)
"git status" can spend a lot of cycles to compute the relation
between the current branch and its upstream, which can now be
disabled with "--no-ahead-behind" option.

* jh/status-no-ahead-behind:
status: support --no-ahead-behind in long format
status: update short status to respect --no-ahead-behind
status: add --[no-]ahead-behind to status and commit for V2 format.
stat_tracking_info: return +1 when branches not equal

1  2 
Documentation/git-status.txt
builtin/checkout.c
builtin/commit.c
ref-filter.c
remote.c
remote.h
wt-status.c
wt-status.h
index f9c91c721e909291ba42c27c3fd521135b4869f0,b6ec10bf32769ce63de9ff3c93b2cc097ad0e0fb..6c230c0c7200412b988d233352e3411a9fb813a8
@@@ -130,6 -130,11 +130,11 @@@ ignored, then the directory is not show
        without options are equivalent to 'always' and 'never'
        respectively.
  
+ --ahead-behind::
+ --no-ahead-behind::
+       Display or do not display detailed ahead/behind counts for the
+       branch relative to its upstream branch.  Defaults to true.
  <pathspec>...::
        See the 'pathspec' entry in linkgit:gitglossary[7].
  
@@@ -149,15 -154,14 +154,15 @@@ the status.relativePaths config option 
  Short Format
  ~~~~~~~~~~~~
  
 -In the short-format, the status of each path is shown as
 +In the short-format, the status of each path is shown as one of these
 +forms
  
 -      XY PATH1 -> PATH2
 +      XY PATH
 +      XY ORIG_PATH -> PATH
  
 -where `PATH1` is the path in the `HEAD`, and the " `-> PATH2`" part is
 -shown only when `PATH1` corresponds to a different path in the
 -index/worktree (i.e. the file is renamed). The `XY` is a two-letter
 -status code.
 +where `ORIG_PATH` is where the renamed/copied contents came
 +from. `ORIG_PATH` is only shown when the entry is renamed or
 +copied. The `XY` is a two-letter status code.
  
  The fields (including the `->`) are separated from each other by a
  single space. If a filename contains whitespace or other nonprintable
@@@ -184,17 -188,15 +189,17 @@@ in which case `XY` are `!!`
  
      X          Y     Meaning
      -------------------------------------------------
 -              [MD]   not updated
 +           [AMD]   not updated
      M        [ MD]   updated in index
      A        [ MD]   added to index
 -    D         [ M]   deleted from index
 +    D                deleted from index
      R        [ MD]   renamed in index
      C        [ MD]   copied in index
      [MARC]           index and work tree matches
      [ MARC]     M    work tree changed since index
      [ MARC]     D    deleted in work tree
 +    [ D]        R    renamed in work tree
 +    [ D]        C    copied in work tree
      -------------------------------------------------
      D           D    unmerged, both deleted
      A           U    unmerged, added by us
@@@ -312,13 -314,13 +317,13 @@@ Renamed or copied entries have the foll
                of similarity between the source and target of the
                move or copy). For example "R100" or "C75".
      <path>      The pathname.  In a renamed/copied entry, this
 -              is the path in the index and in the working tree.
 +              is the target path.
      <sep>       When the `-z` option is used, the 2 pathnames are separated
                with a NUL (ASCII 0x00) byte; otherwise, a tab (ASCII 0x09)
                byte separates them.
 -    <origPath>  The pathname in the commit at HEAD.  This is only
 -              present in a renamed/copied entry, and tells
 -              where the renamed/copied contents came from.
 +    <origPath>  The pathname in the commit at HEAD or in the index.
 +              This is only present in a renamed/copied entry, and
 +              tells where the renamed/copied contents came from.
      --------------------------------------------------------
  
  Unmerged entries have the following format; the first character is
diff --combined builtin/checkout.c
index a52af2e50705312a6d42df8667bbe4f5dfab8c86,70d5785a5d95b17576546bfa027b44b12cdbfddd..8f4dfb104662baa3f32c350cf58e0f2b797dff29
@@@ -54,14 -54,14 +54,14 @@@ struct checkout_opts 
        struct tree *source_tree;
  };
  
 -static int post_checkout_hook(struct commit *old, struct commit *new,
 +static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit,
                              int changed)
  {
        return run_hook_le(NULL, "post-checkout",
 -                         oid_to_hex(old ? &old->object.oid : &null_oid),
 -                         oid_to_hex(new ? &new->object.oid : &null_oid),
 +                         oid_to_hex(old_commit ? &old_commit->object.oid : &null_oid),
 +                         oid_to_hex(new_commit ? &new_commit->object.oid : &null_oid),
                           changed ? "1" : "0", NULL);
 -      /* "new" can be NULL when checking out from the index before
 +      /* "new_commit" can be NULL when checking out from the index before
           a commit exists. */
  
  }
@@@ -227,7 -227,8 +227,7 @@@ static int checkout_merged(int pos, con
         * (it also writes the merge result to the object database even
         * when it may contain conflicts).
         */
 -      if (write_sha1_file(result_buf.ptr, result_buf.size,
 -                          blob_type, oid.hash))
 +      if (write_object_file(result_buf.ptr, result_buf.size, blob_type, &oid))
                die(_("Unable to add merge result for '%s'"), path);
        free(result_buf.ptr);
        ce = make_cache_entry(mode, oid.hash, path, 2, 0);
@@@ -471,8 -472,8 +471,8 @@@ static void setup_branch_path(struct br
  }
  
  static int merge_working_tree(const struct checkout_opts *opts,
 -                            struct branch_info *old,
 -                            struct branch_info *new,
 +                            struct branch_info *old_branch_info,
 +                            struct branch_info *new_branch_info,
                              int *writeout_error)
  {
        int ret;
  
        resolve_undo_clear();
        if (opts->force) {
 -              ret = reset_tree(new->commit->tree, opts, 1, writeout_error);
 +              ret = reset_tree(new_branch_info->commit->tree, opts, 1, writeout_error);
                if (ret)
                        return ret;
        } else {
                topts.initial_checkout = is_cache_unborn();
                topts.update = 1;
                topts.merge = 1;
 -              topts.gently = opts->merge && old->commit;
 +              topts.gently = opts->merge && old_branch_info->commit;
                topts.verbose_update = opts->show_progress;
                topts.fn = twoway_merge;
                if (opts->overwrite_ignore) {
                        topts.dir->flags |= DIR_SHOW_IGNORED;
                        setup_standard_excludes(topts.dir);
                }
 -              tree = parse_tree_indirect(old->commit ?
 -                                         &old->commit->object.oid :
 +              tree = parse_tree_indirect(old_branch_info->commit ?
 +                                         &old_branch_info->commit->object.oid :
                                           the_hash_algo->empty_tree);
                init_tree_desc(&trees[0], tree->buffer, tree->size);
 -              tree = parse_tree_indirect(&new->commit->object.oid);
 +              tree = parse_tree_indirect(&new_branch_info->commit->object.oid);
                init_tree_desc(&trees[1], tree->buffer, tree->size);
  
                ret = unpack_trees(2, trees, &topts);
                                return 1;
  
                        /*
 -                       * Without old->commit, the below is the same as
 +                       * Without old_branch_info->commit, the below is the same as
                         * the two-tree unpack we already tried and failed.
                         */
 -                      if (!old->commit)
 +                      if (!old_branch_info->commit)
                                return 1;
  
                        /* Do more real merge */
                        o.verbosity = 0;
                        work = write_tree_from_memory(&o);
  
 -                      ret = reset_tree(new->commit->tree, opts, 1,
 +                      ret = reset_tree(new_branch_info->commit->tree, opts, 1,
                                         writeout_error);
                        if (ret)
                                return ret;
 -                      o.ancestor = old->name;
 -                      o.branch1 = new->name;
 +                      o.ancestor = old_branch_info->name;
 +                      o.branch1 = new_branch_info->name;
                        o.branch2 = "local";
 -                      ret = merge_trees(&o, new->commit->tree, work,
 -                              old->commit->tree, &result);
 +                      ret = merge_trees(&o, new_branch_info->commit->tree, work,
 +                              old_branch_info->commit->tree, &result);
                        if (ret < 0)
                                exit(128);
 -                      ret = reset_tree(new->commit->tree, opts, 0,
 +                      ret = reset_tree(new_branch_info->commit->tree, opts, 0,
                                         writeout_error);
                        strbuf_release(&o.obuf);
                        if (ret)
                die(_("unable to write new index file"));
  
        if (!opts->force && !opts->quiet)
 -              show_local_changes(&new->commit->object, &opts->diff_options);
 +              show_local_changes(&new_branch_info->commit->object, &opts->diff_options);
  
        return 0;
  }
  
 -static void report_tracking(struct branch_info *new)
 +static void report_tracking(struct branch_info *new_branch_info)
  {
        struct strbuf sb = STRBUF_INIT;
 -      struct branch *branch = branch_get(new->name);
 +      struct branch *branch = branch_get(new_branch_info->name);
  
-       if (!format_tracking_info(branch, &sb))
+       if (!format_tracking_info(branch, &sb, AHEAD_BEHIND_FULL))
                return;
        fputs(sb.buf, stdout);
        strbuf_release(&sb);
  }
  
  static void update_refs_for_switch(const struct checkout_opts *opts,
 -                                 struct branch_info *old,
 -                                 struct branch_info *new)
 +                                 struct branch_info *old_branch_info,
 +                                 struct branch_info *new_branch_info)
  {
        struct strbuf msg = STRBUF_INIT;
        const char *old_desc, *reflog_msg;
                        free(refname);
                }
                else
 -                      create_branch(opts->new_branch, new->name,
 +                      create_branch(opts->new_branch, new_branch_info->name,
                                      opts->new_branch_force ? 1 : 0,
                                      opts->new_branch_force ? 1 : 0,
                                      opts->new_branch_log,
                                      opts->quiet,
                                      opts->track);
 -              new->name = opts->new_branch;
 -              setup_branch_path(new);
 +              new_branch_info->name = opts->new_branch;
 +              setup_branch_path(new_branch_info);
        }
  
 -      old_desc = old->name;
 -      if (!old_desc && old->commit)
 -              old_desc = oid_to_hex(&old->commit->object.oid);
 +      old_desc = old_branch_info->name;
 +      if (!old_desc && old_branch_info->commit)
 +              old_desc = oid_to_hex(&old_branch_info->commit->object.oid);
  
        reflog_msg = getenv("GIT_REFLOG_ACTION");
        if (!reflog_msg)
                strbuf_addf(&msg, "checkout: moving from %s to %s",
 -                      old_desc ? old_desc : "(invalid)", new->name);
 +                      old_desc ? old_desc : "(invalid)", new_branch_info->name);
        else
                strbuf_insert(&msg, 0, reflog_msg, strlen(reflog_msg));
  
 -      if (!strcmp(new->name, "HEAD") && !new->path && !opts->force_detach) {
 +      if (!strcmp(new_branch_info->name, "HEAD") && !new_branch_info->path && !opts->force_detach) {
                /* Nothing to do. */
 -      } else if (opts->force_detach || !new->path) {  /* No longer on any branch. */
 -              update_ref(msg.buf, "HEAD", &new->commit->object.oid, NULL,
 +      } else if (opts->force_detach || !new_branch_info->path) {      /* No longer on any branch. */
 +              update_ref(msg.buf, "HEAD", &new_branch_info->commit->object.oid, NULL,
                           REF_NO_DEREF, UPDATE_REFS_DIE_ON_ERR);
                if (!opts->quiet) {
 -                      if (old->path &&
 +                      if (old_branch_info->path &&
                            advice_detached_head && !opts->force_detach)
 -                              detach_advice(new->name);
 -                      describe_detached_head(_("HEAD is now at"), new->commit);
 +                              detach_advice(new_branch_info->name);
 +                      describe_detached_head(_("HEAD is now at"), new_branch_info->commit);
                }
 -      } else if (new->path) { /* Switch branches. */
 -              if (create_symref("HEAD", new->path, msg.buf) < 0)
 +      } else if (new_branch_info->path) {     /* Switch branches. */
 +              if (create_symref("HEAD", new_branch_info->path, msg.buf) < 0)
                        die(_("unable to update HEAD"));
                if (!opts->quiet) {
 -                      if (old->path && !strcmp(new->path, old->path)) {
 +                      if (old_branch_info->path && !strcmp(new_branch_info->path, old_branch_info->path)) {
                                if (opts->new_branch_force)
                                        fprintf(stderr, _("Reset branch '%s'\n"),
 -                                              new->name);
 +                                              new_branch_info->name);
                                else
                                        fprintf(stderr, _("Already on '%s'\n"),
 -                                              new->name);
 +                                              new_branch_info->name);
                        } else if (opts->new_branch) {
                                if (opts->branch_exists)
 -                                      fprintf(stderr, _("Switched to and reset branch '%s'\n"), new->name);
 +                                      fprintf(stderr, _("Switched to and reset branch '%s'\n"), new_branch_info->name);
                                else
 -                                      fprintf(stderr, _("Switched to a new branch '%s'\n"), new->name);
 +                                      fprintf(stderr, _("Switched to a new branch '%s'\n"), new_branch_info->name);
                        } else {
                                fprintf(stderr, _("Switched to branch '%s'\n"),
 -                                      new->name);
 +                                      new_branch_info->name);
                        }
                }
 -              if (old->path && old->name) {
 -                      if (!ref_exists(old->path) && reflog_exists(old->path))
 -                              delete_reflog(old->path);
 +              if (old_branch_info->path && old_branch_info->name) {
 +                      if (!ref_exists(old_branch_info->path) && reflog_exists(old_branch_info->path))
 +                              delete_reflog(old_branch_info->path);
                }
        }
        remove_branch_state();
        strbuf_release(&msg);
        if (!opts->quiet &&
 -          (new->path || (!opts->force_detach && !strcmp(new->name, "HEAD"))))
 -              report_tracking(new);
 +          (new_branch_info->path || (!opts->force_detach && !strcmp(new_branch_info->name, "HEAD"))))
 +              report_tracking(new_branch_info);
  }
  
  static int add_pending_uninteresting_ref(const char *refname,
@@@ -786,10 -787,11 +786,10 @@@ static void suggest_reattach(struct com
   * HEAD.  If it is not reachable from any ref, this is the last chance
   * for the user to do so without resorting to reflog.
   */
 -static void orphaned_commit_warning(struct commit *old, struct commit *new)
 +static void orphaned_commit_warning(struct commit *old_commit, struct commit *new_commit)
  {
        struct rev_info revs;
 -      struct object *object = &old->object;
 -      struct object_array refs;
 +      struct object *object = &old_commit->object;
  
        init_revisions(&revs, NULL);
        setup_revisions(0, NULL, &revs, NULL);
        add_pending_object(&revs, object, oid_to_hex(&object->oid));
  
        for_each_ref(add_pending_uninteresting_ref, &revs);
 -      add_pending_oid(&revs, "HEAD", &new->object.oid, UNINTERESTING);
 +      add_pending_oid(&revs, "HEAD", &new_commit->object.oid, UNINTERESTING);
  
 -      /* Save pending objects, so they can be cleaned up later. */
 -      refs = revs.pending;
 -      revs.leak_pending = 1;
 -
 -      /*
 -       * prepare_revision_walk (together with .leak_pending = 1) makes us
 -       * the sole owner of the list of pending objects.
 -       */
        if (prepare_revision_walk(&revs))
                die(_("internal error in revision walk"));
 -      if (!(old->object.flags & UNINTERESTING))
 -              suggest_reattach(old, &revs);
 +      if (!(old_commit->object.flags & UNINTERESTING))
 +              suggest_reattach(old_commit, &revs);
        else
 -              describe_detached_head(_("Previous HEAD position was"), old);
 +              describe_detached_head(_("Previous HEAD position was"), old_commit);
  
        /* Clean up objects used, as they will be reused. */
 -      clear_commit_marks_for_object_array(&refs, ALL_REV_FLAGS);
 -
 -      object_array_clear(&refs);
 +      clear_commit_marks_all(ALL_REV_FLAGS);
  }
  
  static int switch_branches(const struct checkout_opts *opts,
 -                         struct branch_info *new)
 +                         struct branch_info *new_branch_info)
  {
        int ret = 0;
 -      struct branch_info old;
 +      struct branch_info old_branch_info;
        void *path_to_free;
        struct object_id rev;
        int flag, writeout_error = 0;
 -      memset(&old, 0, sizeof(old));
 -      old.path = path_to_free = resolve_refdup("HEAD", 0, &rev, &flag);
 -      if (old.path)
 -              old.commit = lookup_commit_reference_gently(&rev, 1);
 +      memset(&old_branch_info, 0, sizeof(old_branch_info));
 +      old_branch_info.path = path_to_free = resolve_refdup("HEAD", 0, &rev, &flag);
 +      if (old_branch_info.path)
 +              old_branch_info.commit = lookup_commit_reference_gently(&rev, 1);
        if (!(flag & REF_ISSYMREF))
 -              old.path = NULL;
 +              old_branch_info.path = NULL;
  
 -      if (old.path)
 -              skip_prefix(old.path, "refs/heads/", &old.name);
 +      if (old_branch_info.path)
 +              skip_prefix(old_branch_info.path, "refs/heads/", &old_branch_info.name);
  
 -      if (!new->name) {
 -              new->name = "HEAD";
 -              new->commit = old.commit;
 -              if (!new->commit)
 +      if (!new_branch_info->name) {
 +              new_branch_info->name = "HEAD";
 +              new_branch_info->commit = old_branch_info.commit;
 +              if (!new_branch_info->commit)
                        die(_("You are on a branch yet to be born"));
 -              parse_commit_or_die(new->commit);
 +              parse_commit_or_die(new_branch_info->commit);
        }
  
 -      ret = merge_working_tree(opts, &old, new, &writeout_error);
 +      ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);
        if (ret) {
                free(path_to_free);
                return ret;
        }
  
 -      if (!opts->quiet && !old.path && old.commit && new->commit != old.commit)
 -              orphaned_commit_warning(old.commit, new->commit);
 +      if (!opts->quiet && !old_branch_info.path && old_branch_info.commit && new_branch_info->commit != old_branch_info.commit)
 +              orphaned_commit_warning(old_branch_info.commit, new_branch_info->commit);
  
 -      update_refs_for_switch(opts, &old, new);
 +      update_refs_for_switch(opts, &old_branch_info, new_branch_info);
  
 -      ret = post_checkout_hook(old.commit, new->commit, 1);
 +      ret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);
        free(path_to_free);
        return ret || writeout_error;
  }
@@@ -869,7 -881,7 +869,7 @@@ static int git_checkout_config(const ch
  
  static int parse_branchname_arg(int argc, const char **argv,
                                int dwim_new_local_branch_ok,
 -                              struct branch_info *new,
 +                              struct branch_info *new_branch_info,
                                struct checkout_opts *opts,
                                struct object_id *rev)
  {
        argv++;
        argc--;
  
 -      new->name = arg;
 -      setup_branch_path(new);
 +      new_branch_info->name = arg;
 +      setup_branch_path(new_branch_info);
  
 -      if (!check_refname_format(new->path, 0) &&
 -          !read_ref(new->path, &branch_rev))
 +      if (!check_refname_format(new_branch_info->path, 0) &&
 +          !read_ref(new_branch_info->path, &branch_rev))
                oidcpy(rev, &branch_rev);
        else
 -              new->path = NULL; /* not an existing branch */
 +              new_branch_info->path = NULL; /* not an existing branch */
  
 -      new->commit = lookup_commit_reference_gently(rev, 1);
 -      if (!new->commit) {
 +      new_branch_info->commit = lookup_commit_reference_gently(rev, 1);
 +      if (!new_branch_info->commit) {
                /* not a commit */
                *source_tree = parse_tree_indirect(rev);
        } else {
 -              parse_commit_or_die(new->commit);
 -              *source_tree = new->commit->tree;
 +              parse_commit_or_die(new_branch_info->commit);
 +              *source_tree = new_branch_info->commit->tree;
        }
  
        if (!*source_tree)                   /* case (1): want a tree */
@@@ -1042,7 -1054,7 +1042,7 @@@ static int switch_unborn_to_new_branch(
  }
  
  static int checkout_branch(struct checkout_opts *opts,
 -                         struct branch_info *new)
 +                         struct branch_info *new_branch_info)
  {
        if (opts->pathspec.nr)
                die(_("paths cannot be used with switching branches"));
        } else if (opts->track == BRANCH_TRACK_UNSPECIFIED)
                opts->track = git_branch_track;
  
 -      if (new->name && !new->commit)
 +      if (new_branch_info->name && !new_branch_info->commit)
                die(_("Cannot switch branch to a non-commit '%s'"),
 -                  new->name);
 +                  new_branch_info->name);
  
 -      if (new->path && !opts->force_detach && !opts->new_branch &&
 +      if (new_branch_info->path && !opts->force_detach && !opts->new_branch &&
            !opts->ignore_other_worktrees) {
                int flag;
                char *head_ref = resolve_refdup("HEAD", 0, NULL, &flag);
                if (head_ref &&
 -                  (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)))
 -                      die_if_checked_out(new->path, 1);
 +                  (!(flag & REF_ISSYMREF) || strcmp(head_ref, new_branch_info->path)))
 +                      die_if_checked_out(new_branch_info->path, 1);
                free(head_ref);
        }
  
 -      if (!new->commit && opts->new_branch) {
 +      if (!new_branch_info->commit && opts->new_branch) {
                struct object_id rev;
                int flag;
  
                    (flag & REF_ISSYMREF) && is_null_oid(&rev))
                        return switch_unborn_to_new_branch(opts);
        }
 -      return switch_branches(opts, new);
 +      return switch_branches(opts, new_branch_info);
  }
  
  int cmd_checkout(int argc, const char **argv, const char *prefix)
  {
        struct checkout_opts opts;
 -      struct branch_info new;
 +      struct branch_info new_branch_info;
        char *conflict_style = NULL;
        int dwim_new_local_branch = 1;
        struct option options[] = {
        };
  
        memset(&opts, 0, sizeof(opts));
 -      memset(&new, 0, sizeof(new));
 +      memset(&new_branch_info, 0, sizeof(new_branch_info));
        opts.overwrite_ignore = 1;
        opts.prefix = prefix;
        opts.show_progress = -1;
                        opts.track == BRANCH_TRACK_UNSPECIFIED &&
                        !opts.new_branch;
                int n = parse_branchname_arg(argc, argv, dwim_ok,
 -                                           &new, &opts, &rev);
 +                                           &new_branch_info, &opts, &rev);
                argv += n;
                argc -= n;
        }
  
        UNLEAK(opts);
        if (opts.patch_mode || opts.pathspec.nr)
 -              return checkout_paths(&opts, new.name);
 +              return checkout_paths(&opts, new_branch_info.name);
        else
 -              return checkout_branch(&opts, &new);
 +              return checkout_branch(&opts, &new_branch_info);
  }
diff --combined builtin/commit.c
index e8e8d13be4016e94014e98bcbe3489fad373d204,bfcb88f2915e12fd10b1ed5e53f2aa904e66780e..c14f95dbf6b11826f11e728354e6c7107878a07a
@@@ -31,7 -31,9 +31,7 @@@
  #include "gpg-interface.h"
  #include "column.h"
  #include "sequencer.h"
 -#include "notes-utils.h"
  #include "mailmap.h"
 -#include "sigchain.h"
  
  static const char * const builtin_commit_usage[] = {
        N_("git commit [<options>] [--] <pathspec>..."),
@@@ -43,6 -45,31 +43,6 @@@ static const char * const builtin_statu
        NULL
  };
  
 -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"
 -"\n"
 -"    git config --global user.name \"Your Name\"\n"
 -"    git config --global user.email you@example.com\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 empty_amend_advice[] =
  N_("You asked to amend the most recent commit, but doing so would make\n"
  "it empty. You can repeat your command with --allow-empty, or you can\n"
@@@ -66,6 -93,8 +66,6 @@@ N_("If you wish to skip this commit, us
  "Then \"git cherry-pick --continue\" will resume cherry-picking\n"
  "the remaining commits.\n");
  
 -static GIT_PATH_FUNC(git_path_commit_editmsg, "COMMIT_EDITMSG")
 -
  static const char *use_message_buffer;
  static struct lock_file index_lock; /* real index */
  static struct lock_file false_lock; /* used only for partial commits */
@@@ -99,7 -128,12 +99,7 @@@ static char *sign_commit
   * if editor is used, and only the whitespaces if the message
   * is specified explicitly.
   */
 -static enum {
 -      CLEANUP_SPACE,
 -      CLEANUP_NONE,
 -      CLEANUP_SCISSORS,
 -      CLEANUP_ALL
 -} cleanup_mode;
 +static enum commit_msg_cleanup_mode cleanup_mode;
  static const char *cleanup_arg;
  
  static enum commit_whence whence;
@@@ -639,7 -673,7 +639,7 @@@ static int prepare_to_commit(const cha
        struct strbuf sb = STRBUF_INIT;
        const char *hook_arg1 = NULL;
        const char *hook_arg2 = NULL;
 -      int clean_message_contents = (cleanup_mode != CLEANUP_NONE);
 +      int clean_message_contents = (cleanup_mode != COMMIT_MSG_CLEANUP_NONE);
        int old_display_comment_prefix;
  
        /* This checks and barfs if author is badly specified */
                }
        }
  
 -      if (have_option_m) {
 +      if (have_option_m && !fixup_message) {
                strbuf_addbuf(&sb, &message);
                hook_arg1 = "message";
        } else if (logfile && !strcmp(logfile, "-")) {
                ctx.output_encoding = get_commit_output_encoding();
                format_commit_message(commit, "fixup! %s\n\n",
                                      &sb, &ctx);
 +              if (have_option_m)
 +                      strbuf_addbuf(&sb, &message);
                hook_arg1 = "message";
        } else if (!stat(git_path_merge_msg(), &statbuf)) {
                /*
                struct ident_split ci, ai;
  
                if (whence != FROM_COMMIT) {
 -                      if (cleanup_mode == CLEANUP_SCISSORS)
 +                      if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS)
                                wt_status_add_cut_line(s->fp);
                        status_printf_ln(s, GIT_COLOR_NORMAL,
                            whence == FROM_MERGE
                }
  
                fprintf(s->fp, "\n");
 -              if (cleanup_mode == CLEANUP_ALL)
 +              if (cleanup_mode == COMMIT_MSG_CLEANUP_ALL)
                        status_printf(s, GIT_COLOR_NORMAL,
                                _("Please enter the commit message for your changes."
                                  " Lines starting\nwith '%c' will be ignored, and an empty"
                                  " message aborts the commit.\n"), comment_line_char);
 -              else if (cleanup_mode == CLEANUP_SCISSORS && whence == FROM_COMMIT)
 +              else if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS &&
 +                       whence == FROM_COMMIT)
                        wt_status_add_cut_line(s->fp);
 -              else /* CLEANUP_SPACE, that is. */
 +              else /* COMMIT_MSG_CLEANUP_SPACE, that is. */
                        status_printf(s, GIT_COLOR_NORMAL,
                                _("Please enter the commit message for your changes."
                                  " Lines starting\n"
        return 1;
  }
  
 -static int rest_is_empty(struct strbuf *sb, int start)
 -{
 -      int i, eol;
 -      const char *nl;
 -
 -      /* Check if the rest is just whitespace and Signed-off-by's. */
 -      for (i = start; i < sb->len; i++) {
 -              nl = memchr(sb->buf + i, '\n', sb->len - i);
 -              if (nl)
 -                      eol = nl - sb->buf;
 -              else
 -                      eol = sb->len;
 -
 -              if (strlen(sign_off_header) <= eol - i &&
 -                  starts_with(sb->buf + i, sign_off_header)) {
 -                      i = eol;
 -                      continue;
 -              }
 -              while (i < eol)
 -                      if (!isspace(sb->buf[i++]))
 -                              return 0;
 -      }
 -
 -      return 1;
 -}
 -
 -/*
 - * Find out if the message in the strbuf contains only whitespace and
 - * Signed-off-by lines.
 - */
 -static int message_is_empty(struct strbuf *sb)
 -{
 -      if (cleanup_mode == CLEANUP_NONE && sb->len)
 -              return 0;
 -      return rest_is_empty(sb, 0);
 -}
 -
 -/*
 - * See if the user edited the message in the editor or left what
 - * was in the template intact
 - */
 -static int template_untouched(struct strbuf *sb)
 -{
 -      struct strbuf tmpl = STRBUF_INIT;
 -      const char *start;
 -
 -      if (cleanup_mode == CLEANUP_NONE && sb->len)
 -              return 0;
 -
 -      if (!template_file || strbuf_read_file(&tmpl, template_file, 0) <= 0)
 -              return 0;
 -
 -      strbuf_stripspace(&tmpl, cleanup_mode == CLEANUP_ALL);
 -      if (!skip_prefix(sb->buf, tmpl.buf, &start))
 -              start = sb->buf;
 -      strbuf_release(&tmpl);
 -      return rest_is_empty(sb, start - sb->buf);
 -}
 -
  static const char *find_author_by_nickname(const char *name)
  {
        struct rev_info revs;
@@@ -1061,6 -1151,9 +1061,9 @@@ static void finalize_deferred_config(st
                s->show_branch = status_deferred_config.show_branch;
        if (s->show_branch < 0)
                s->show_branch = 0;
+       if (s->ahead_behind_flags == AHEAD_BEHIND_UNSPECIFIED)
+               s->ahead_behind_flags = AHEAD_BEHIND_FULL;
  }
  
  static int parse_and_validate_options(int argc, const char *argv[],
                f++;
        if (f > 1)
                die(_("Only one of -c/-C/-F/--fixup can be used."));
 -      if (have_option_m && f > 0)
 -              die((_("Option -m cannot be combined with -c/-C/-F/--fixup.")));
 +      if (have_option_m && (edit_message || use_message || logfile))
 +              die((_("Option -m cannot be combined with -c/-C/-F.")));
        if (f || have_option_m)
                template_file = NULL;
        if (edit_message)
        if (argc == 0 && (also || (only && !amend && !allow_empty)))
                die(_("No paths with --include/--only does not make sense."));
        if (!cleanup_arg || !strcmp(cleanup_arg, "default"))
 -              cleanup_mode = use_editor ? CLEANUP_ALL : CLEANUP_SPACE;
 +              cleanup_mode = use_editor ? COMMIT_MSG_CLEANUP_ALL :
 +                                          COMMIT_MSG_CLEANUP_SPACE;
        else if (!strcmp(cleanup_arg, "verbatim"))
 -              cleanup_mode = CLEANUP_NONE;
 +              cleanup_mode = COMMIT_MSG_CLEANUP_NONE;
        else if (!strcmp(cleanup_arg, "whitespace"))
 -              cleanup_mode = CLEANUP_SPACE;
 +              cleanup_mode = COMMIT_MSG_CLEANUP_SPACE;
        else if (!strcmp(cleanup_arg, "strip"))
 -              cleanup_mode = CLEANUP_ALL;
 +              cleanup_mode = COMMIT_MSG_CLEANUP_ALL;
        else if (!strcmp(cleanup_arg, "scissors"))
 -              cleanup_mode = use_editor ? CLEANUP_SCISSORS : CLEANUP_SPACE;
 +              cleanup_mode = use_editor ? COMMIT_MSG_CLEANUP_SCISSORS :
 +                                          COMMIT_MSG_CLEANUP_SPACE;
        else
                die(_("Invalid cleanup mode %s"), cleanup_arg);
  
@@@ -1277,6 -1368,8 +1280,8 @@@ int cmd_status(int argc, const char **a
                         N_("show branch information")),
                OPT_BOOL(0, "show-stash", &s.show_stash,
                         N_("show stash information")),
+               OPT_BOOL(0, "ahead-behind", &s.ahead_behind_flags,
+                        N_("compute full ahead/behind values")),
                { OPTION_CALLBACK, 0, "porcelain", &status_format,
                  N_("version"), N_("machine-readable output"),
                  PARSE_OPT_OPTARG, opt_parse_porcelain },
        return 0;
  }
  
 -static const char *implicit_ident_advice(void)
 -{
 -      char *user_config = expand_user_path("~/.gitconfig", 0);
 -      char *xdg_config = xdg_config_home("config");
 -      int 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 struct object_id *oid,
 -                        int initial_commit)
 -{
 -      struct rev_info rev;
 -      struct commit *commit;
 -      struct strbuf format = STRBUF_INIT;
 -      const char *head;
 -      struct pretty_print_context pctx = {0};
 -      struct strbuf author_ident = STRBUF_INIT;
 -      struct strbuf committer_ident = STRBUF_INIT;
 -
 -      commit = lookup_commit(oid);
 -      if (!commit)
 -              die(_("couldn't look up newly created commit"));
 -      if (parse_commit(commit))
 -              die(_("could not parse newly created commit"));
 -
 -      strbuf_addstr(&format, "format:%h] %s");
 -
 -      format_commit_message(commit, "%an <%ae>", &author_ident, &pctx);
 -      format_commit_message(commit, "%cn <%ce>", &committer_ident, &pctx);
 -      if (strbuf_cmp(&author_ident, &committer_ident)) {
 -              strbuf_addstr(&format, "\n Author: ");
 -              strbuf_addbuf_percentquote(&format, &author_ident);
 -      }
 -      if (author_date_is_interesting()) {
 -              struct strbuf date = STRBUF_INIT;
 -              format_commit_message(commit, "%ad", &date, &pctx);
 -              strbuf_addstr(&format, "\n Date: ");
 -              strbuf_addbuf_percentquote(&format, &date);
 -              strbuf_release(&date);
 -      }
 -      if (!committer_ident_sufficiently_given()) {
 -              strbuf_addstr(&format, "\n Committer: ");
 -              strbuf_addbuf_percentquote(&format, &committer_ident);
 -              if (advice_implicit_identity) {
 -                      strbuf_addch(&format, '\n');
 -                      strbuf_addstr(&format, implicit_ident_advice());
 -              }
 -      }
 -      strbuf_release(&author_ident);
 -      strbuf_release(&committer_ident);
 -
 -      init_revisions(&rev, prefix);
 -      setup_revisions(0, NULL, &rev, NULL);
 -
 -      rev.diff = 1;
 -      rev.diffopt.output_format =
 -              DIFF_FORMAT_SHORTSTAT | DIFF_FORMAT_SUMMARY;
 -
 -      rev.verbose_header = 1;
 -      rev.show_root_diff = 1;
 -      get_commit_format(format.buf, &rev);
 -      rev.always_show_header = 0;
 -      rev.diffopt.detect_rename = 1;
 -      rev.diffopt.break_opt = 0;
 -      diff_setup_done(&rev.diffopt);
 -
 -      head = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
 -      if (!head)
 -              die_errno(_("unable to resolve HEAD after creating 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;
 -              rev.use_terminator = 1;
 -              log_tree_commit(&rev, commit);
 -      }
 -
 -      strbuf_release(&format);
 -}
 -
  static int git_commit_config(const char *k, const char *v, void *cb)
  {
        struct wt_status *s = cb;
        return git_status_config(k, v, s);
  }
  
 -static int run_rewrite_hook(const struct object_id *oldoid,
 -                          const struct object_id *newoid)
 -{
 -      struct child_process proc = CHILD_PROCESS_INIT;
 -      const char *argv[3];
 -      int code;
 -      struct strbuf sb = STRBUF_INIT;
 -
 -      argv[0] = find_hook("post-rewrite");
 -      if (!argv[0])
 -              return 0;
 -
 -      argv[1] = "amend";
 -      argv[2] = NULL;
 -
 -      proc.argv = argv;
 -      proc.in = -1;
 -      proc.stdout_to_stderr = 1;
 -
 -      code = start_command(&proc);
 -      if (code)
 -              return code;
 -      strbuf_addf(&sb, "%s %s\n", oid_to_hex(oldoid), oid_to_hex(newoid));
 -      sigchain_push(SIGPIPE, SIG_IGN);
 -      write_in_full(proc.in, sb.buf, sb.len);
 -      close(proc.in);
 -      strbuf_release(&sb);
 -      sigchain_pop(SIGPIPE);
 -      return finish_command(&proc);
 -}
 -
  int run_commit_hook(int editor_is_used, const char *index_file, const char *name, ...)
  {
        struct argv_array hook_env = ARGV_ARRAY_INIT;
@@@ -1437,6 -1653,8 +1442,8 @@@ int cmd_commit(int argc, const char **a
                OPT_SET_INT(0, "short", &status_format, N_("show status concisely"),
                            STATUS_FORMAT_SHORT),
                OPT_BOOL(0, "branch", &s.show_branch, N_("show branch information")),
+               OPT_BOOL(0, "ahead-behind", &s.ahead_behind_flags,
+                        N_("compute full ahead/behind values")),
                OPT_SET_INT(0, "porcelain", &status_format,
                            N_("machine-readable output"), STATUS_FORMAT_PORCELAIN),
                OPT_SET_INT(0, "long", &status_format,
        struct strbuf sb = STRBUF_INIT;
        struct strbuf author_ident = STRBUF_INIT;
        const char *index_file, *reflog_msg;
 -      char *nl;
        struct object_id oid;
        struct commit_list *parents = NULL;
        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"))
        }
  
        if (verbose || /* Truncate the message just before the diff, if any. */
 -          cleanup_mode == CLEANUP_SCISSORS)
 +          cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS)
                strbuf_setlen(&sb, wt_status_locate_end(sb.buf, sb.len));
 -      if (cleanup_mode != CLEANUP_NONE)
 -              strbuf_stripspace(&sb, cleanup_mode == CLEANUP_ALL);
 +      if (cleanup_mode != COMMIT_MSG_CLEANUP_NONE)
 +              strbuf_stripspace(&sb, cleanup_mode == COMMIT_MSG_CLEANUP_ALL);
  
 -      if (message_is_empty(&sb) && !allow_empty_message) {
 +      if (message_is_empty(&sb, cleanup_mode) && !allow_empty_message) {
                rollback_index_files();
                fprintf(stderr, _("Aborting commit due to empty commit message.\n"));
                exit(1);
        }
 -      if (template_untouched(&sb) && !allow_empty_message) {
 +      if (template_untouched(&sb, template_file, cleanup_mode) && !allow_empty_message) {
                rollback_index_files();
                fprintf(stderr, _("Aborting commit; you did not edit the message.\n"));
                exit(1);
                append_merge_tag_headers(parents, &tail);
        }
  
 -      if (commit_tree_extended(sb.buf, sb.len, active_cache_tree->oid.hash,
 -                       parents, oid.hash, author_ident.buf, sign_commit, extra)) {
 +      if (commit_tree_extended(sb.buf, sb.len, &active_cache_tree->oid,
 +                               parents, &oid, author_ident.buf, sign_commit,
 +                               extra)) {
                rollback_index_files();
                die(_("failed to write commit object"));
        }
        strbuf_release(&author_ident);
        free_commit_extra_headers(extra);
  
 -      nl = strchr(sb.buf, '\n');
 -      if (nl)
 -              strbuf_setlen(&sb, nl + 1 - sb.buf);
 -      else
 -              strbuf_addch(&sb, '\n');
 -      strbuf_insert(&sb, 0, reflog_msg, strlen(reflog_msg));
 -      strbuf_insert(&sb, strlen(reflog_msg), ": ", 2);
 -
 -      transaction = ref_transaction_begin(&err);
 -      if (!transaction ||
 -          ref_transaction_update(transaction, "HEAD", &oid,
 -                                 current_head
 -                                 ? &current_head->object.oid : &null_oid,
 -                                 0, sb.buf, &err) ||
 -          ref_transaction_commit(transaction, &err)) {
 +      if (update_head_with_reflog(current_head, &oid, reflog_msg, &sb,
 +                                  &err)) {
                rollback_index_files();
                die("%s", err.buf);
        }
 -      ref_transaction_free(transaction);
  
        unlink(git_path_cherry_pick_head());
        unlink(git_path_revert_head());
        rerere(0);
        run_commit_hook(use_editor, get_index_file(), "post-commit", NULL);
        if (amend && !no_post_rewrite) {
 -              struct notes_rewrite_cfg *cfg;
 -              cfg = init_copy_notes_for_rewrite("amend");
 -              if (cfg) {
 -                      /* we are amending, so current_head is not NULL */
 -                      copy_note_for_rewrite(cfg, &current_head->object.oid, &oid);
 -                      finish_copy_notes_for_rewrite(cfg, "Notes added by 'git commit --amend'");
 -              }
 -              run_rewrite_hook(&current_head->object.oid, &oid);
 +              commit_post_rewrite(current_head, &oid);
 +      }
 +      if (!quiet) {
 +              unsigned int flags = 0;
 +
 +              if (!current_head)
 +                      flags |= SUMMARY_INITIAL_COMMIT;
 +              if (author_date_is_interesting())
 +                      flags |= SUMMARY_SHOW_AUTHOR_DATE;
 +              print_commit_summary(prefix, &oid, flags);
        }
 -      if (!quiet)
 -              print_summary(prefix, &oid, !current_head);
  
        UNLEAK(err);
        UNLEAK(sb);
diff --combined ref-filter.c
index 99a45beb14ea881390051a94f7254732446ace77,091144e67860065f9864301f9cd2637d2a6419ac..ac9ac6b0c136ae7cdf463abb9638feb6610250cd
@@@ -529,12 -529,12 +529,12 @@@ static void end_align_handler(struct re
  
  static void align_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
  {
 -      struct ref_formatting_stack *new;
 +      struct ref_formatting_stack *new_stack;
  
        push_stack_element(&state->stack);
 -      new = state->stack;
 -      new->at_end = end_align_handler;
 -      new->at_end_data = &atomv->atom->u.align;
 +      new_stack = state->stack;
 +      new_stack->at_end = end_align_handler;
 +      new_stack->at_end_data = &atomv->atom->u.align;
  }
  
  static void if_then_else_handler(struct ref_formatting_stack **stack)
  
  static void if_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
  {
 -      struct ref_formatting_stack *new;
 +      struct ref_formatting_stack *new_stack;
        struct if_then_else *if_then_else = xcalloc(sizeof(struct if_then_else), 1);
  
        if_then_else->str = atomv->atom->u.if_then_else.str;
        if_then_else->cmp_status = atomv->atom->u.if_then_else.cmp_status;
  
        push_stack_element(&state->stack);
 -      new = state->stack;
 -      new->at_end = if_then_else_handler;
 -      new->at_end_data = if_then_else;
 +      new_stack = state->stack;
 +      new_stack->at_end = if_then_else_handler;
 +      new_stack->at_end_data = if_then_else;
  }
  
  static int is_empty(const char *s)
@@@ -769,7 -769,7 +769,7 @@@ static void grab_common_values(struct a
                if (deref)
                        name++;
                if (!strcmp(name, "objecttype"))
 -                      v->s = typename(obj->type);
 +                      v->s = type_name(obj->type);
                else if (!strcmp(name, "objectsize")) {
                        v->value = sz;
                        v->s = xstrfmt("%lu", sz);
@@@ -795,7 -795,7 +795,7 @@@ static void grab_tag_values(struct atom
                if (!strcmp(name, "tag"))
                        v->s = tag->tag;
                else if (!strcmp(name, "type") && tag->tagged)
 -                      v->s = typename(tag->tagged->type);
 +                      v->s = type_name(tag->tagged->type);
                else if (!strcmp(name, "object") && tag->tagged)
                        v->s = xstrdup(oid_to_hex(&tag->tagged->oid));
        }
@@@ -1249,8 -1249,8 +1249,8 @@@ static void fill_remote_ref_details(str
        if (atom->u.remote_ref.option == RR_REF)
                *s = show_ref(&atom->u.remote_ref.refname, refname);
        else if (atom->u.remote_ref.option == RR_TRACK) {
-               if (stat_tracking_info(branch, &num_ours,
-                                      &num_theirs, NULL)) {
+               if (stat_tracking_info(branch, &num_ours, &num_theirs,
+                                      NULL, AHEAD_BEHIND_FULL) < 0) {
                        *s = xstrdup(msgs.gone);
                } else if (!num_ours && !num_theirs)
                        *s = "";
                        free((void *)to_free);
                }
        } else if (atom->u.remote_ref.option == RR_TRACKSHORT) {
-               if (stat_tracking_info(branch, &num_ours,
-                                      &num_theirs, NULL))
+               if (stat_tracking_info(branch, &num_ours, &num_theirs,
+                                      NULL, AHEAD_BEHIND_FULL) < 0)
                        return;
  
                if (!num_ours && !num_theirs)
@@@ -1995,7 -1995,8 +1995,7 @@@ static void do_merge_filter(struct ref_
                        free_array_item(item);
        }
  
 -      for (i = 0; i < old_nr; i++)
 -              clear_commit_marks(to_clear[i], ALL_REV_FLAGS);
 +      clear_commit_marks_many(old_nr, to_clear, ALL_REV_FLAGS);
        clear_commit_marks(filter->merge_commit, ALL_REV_FLAGS);
        free(to_clear);
  }
diff --combined remote.c
index a9b4853e65341b20688dea16ac31ab1606bc0234,d656e33c960f5ecaf8f9a7f95ed2a23d9b06223e..c10d87c24615e9d6497b46a69a82a71d3c1735a6
+++ b/remote.c
@@@ -22,7 -22,6 +22,7 @@@ static struct refspec s_tag_refspec = 
        "refs/tags/*"
  };
  
 +/* See TAG_REFSPEC for the string version */
  const struct refspec *tag_refspec = &s_tag_refspec;
  
  struct counted_string {
@@@ -104,17 -103,6 +104,17 @@@ static void add_fetch_refspec(struct re
        remote->fetch_refspec[remote->fetch_refspec_nr++] = ref;
  }
  
 +void add_prune_tags_to_fetch_refspec(struct remote *remote)
 +{
 +      int nr = remote->fetch_refspec_nr;
 +      int bufsize = nr  + 1;
 +      int size = sizeof(struct refspec);
 +
 +      remote->fetch = xrealloc(remote->fetch, size  * bufsize);
 +      memcpy(&remote->fetch[nr], tag_refspec, size);
 +      add_fetch_refspec(remote, xstrdup(TAG_REFSPEC));
 +}
 +
  static void add_url(struct remote *remote, const char *url)
  {
        ALLOC_GROW(remote->url, remote->url_nr + 1, remote->url_alloc);
@@@ -185,7 -173,6 +185,7 @@@ static struct remote *make_remote(cons
  
        ret = xcalloc(1, sizeof(struct remote));
        ret->prune = -1;  /* unspecified */
 +      ret->prune_tags = -1;  /* unspecified */
        ALLOC_GROW(remotes, remotes_nr + 1, remotes_alloc);
        remotes[remotes_nr++] = ret;
        ret->name = xstrndup(name, len);
@@@ -404,8 -391,6 +404,8 @@@ static int handle_config(const char *ke
                remote->skip_default_update = git_config_bool(key, value);
        else if (!strcmp(subkey, "prune"))
                remote->prune = git_config_bool(key, value);
 +      else if (!strcmp(subkey, "prunetags"))
 +              remote->prune_tags = git_config_bool(key, value);
        else if (!strcmp(subkey, "url")) {
                const char *v;
                if (git_config_string(&v, key, value))
@@@ -1985,33 -1970,33 +1985,33 @@@ static void unmark_and_free(struct comm
  int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid)
  {
        struct object *o;
 -      struct commit *old, *new;
 +      struct commit *old_commit, *new_commit;
        struct commit_list *list, *used;
        int found = 0;
  
        /*
 -       * Both new and old must be commit-ish and new is descendant of
 -       * old.  Otherwise we require --force.
 +       * Both new_commit and old_commit must be commit-ish and new_commit is descendant of
 +       * old_commit.  Otherwise we require --force.
         */
        o = deref_tag(parse_object(old_oid), NULL, 0);
        if (!o || o->type != OBJ_COMMIT)
                return 0;
 -      old = (struct commit *) o;
 +      old_commit = (struct commit *) o;
  
        o = deref_tag(parse_object(new_oid), NULL, 0);
        if (!o || o->type != OBJ_COMMIT)
                return 0;
 -      new = (struct commit *) o;
 +      new_commit = (struct commit *) o;
  
 -      if (parse_commit(new) < 0)
 +      if (parse_commit(new_commit) < 0)
                return 0;
  
        used = list = NULL;
 -      commit_list_insert(new, &list);
 +      commit_list_insert(new_commit, &list);
        while (list) {
 -              new = pop_most_recent_commit(&list, TMP_MARK);
 -              commit_list_insert(new, &used);
 -              if (new == old) {
 +              new_commit = pop_most_recent_commit(&list, TMP_MARK);
 +              commit_list_insert(new_commit, &used);
 +              if (new_commit == old_commit) {
                        found = 1;
                        break;
                }
  }
  
  /*
-  * Compare a branch with its upstream, and save their differences (number
-  * of commits) in *num_ours and *num_theirs. The name of the upstream branch
-  * (or NULL if no upstream is defined) is returned via *upstream_name, if it
-  * is not itself NULL.
+  * Lookup the upstream branch for the given branch and if present, optionally
+  * compute the commit ahead/behind values for the pair.
+  *
+  * If abf is AHEAD_BEHIND_FULL, compute the full ahead/behind and return the
+  * counts in *num_ours and *num_theirs.  If abf is AHEAD_BEHIND_QUICK, skip
+  * the (potentially expensive) a/b computation (*num_ours and *num_theirs are
+  * set to zero).
+  *
+  * The name of the upstream branch (or NULL if no upstream is defined) is
+  * returned via *upstream_name, if it is not itself NULL.
   *
   * Returns -1 if num_ours and num_theirs could not be filled in (e.g., no
-  * upstream defined, or ref does not exist), 0 otherwise.
+  * upstream defined, or ref does not exist).  Returns 0 if the commits are
+  * identical.  Returns 1 if commits are different.
   */
  int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
-                      const char **upstream_name)
+                      const char **upstream_name, enum ahead_behind_flags abf)
  {
        struct object_id oid;
        struct commit *ours, *theirs;
        if (!ours)
                return -1;
  
+       *num_theirs = *num_ours = 0;
        /* are we the same? */
-       if (theirs == ours) {
-               *num_theirs = *num_ours = 0;
+       if (theirs == ours)
                return 0;
-       }
+       if (abf == AHEAD_BEHIND_QUICK)
+               return 1;
+       if (abf != AHEAD_BEHIND_FULL)
+               BUG("stat_tracking_info: invalid abf '%d'", abf);
  
        /* Run "rev-list --left-right ours...theirs" internally... */
        argv_array_push(&argv, ""); /* ignored */
                die("revision walk setup failed");
  
        /* ... and count the commits on each side. */
-       *num_ours = 0;
-       *num_theirs = 0;
        while (1) {
                struct commit *c = get_revision(&revs);
                if (!c)
        clear_commit_marks(theirs, ALL_REV_FLAGS);
  
        argv_array_clear(&argv);
-       return 0;
+       return 1;
  }
  
  /*
   * Return true when there is anything to report, otherwise false.
   */
- int format_tracking_info(struct branch *branch, struct strbuf *sb)
+ int format_tracking_info(struct branch *branch, struct strbuf *sb,
+                        enum ahead_behind_flags abf)
  {
-       int ours, theirs;
+       int ours, theirs, sti;
        const char *full_base;
        char *base;
        int upstream_is_gone = 0;
  
-       if (stat_tracking_info(branch, &ours, &theirs, &full_base) < 0) {
+       sti = stat_tracking_info(branch, &ours, &theirs, &full_base, abf);
+       if (sti < 0) {
                if (!full_base)
                        return 0;
                upstream_is_gone = 1;
                if (advice_status_hints)
                        strbuf_addstr(sb,
                                _("  (use \"git branch --unset-upstream\" to fixup)\n"));
-       } else if (!ours && !theirs) {
+       } else if (!sti) {
                strbuf_addf(sb,
                        _("Your branch is up to date with '%s'.\n"),
                        base);
+       } else if (abf == AHEAD_BEHIND_QUICK) {
+               strbuf_addf(sb,
+                           _("Your branch and '%s' refer to different commits.\n"),
+                           base);
+               if (advice_status_hints)
+                       strbuf_addf(sb, _("  (use \"%s\" for details)\n"),
+                                   "git status --ahead-behind");
        } else if (!theirs) {
                strbuf_addf(sb,
                        Q_("Your branch is ahead of '%s' by %d commit.\n",
diff --combined remote.h
index 271afe1bab4d562ee6daf2d47071be765051237f,6360a9db69bb423476b53d49cdc244b18bb3d8f1..f09c01969d6b0d701140ceb9cb2e8f9e68533c96
+++ b/remote.h
@@@ -47,7 -47,6 +47,7 @@@ struct remote 
        int skip_default_update;
        int mirror;
        int prune;
 +      int prune_tags;
  
        const char *receivepack;
        const char *uploadpack;
@@@ -258,10 -257,18 +258,18 @@@ enum match_refs_flags 
        MATCH_REFS_FOLLOW_TAGS  = (1 << 3)
  };
  
+ /* Flags for --ahead-behind option. */
+ enum ahead_behind_flags {
+       AHEAD_BEHIND_UNSPECIFIED = -1,
+       AHEAD_BEHIND_QUICK       =  0,  /* just eq/neq reporting */
+       AHEAD_BEHIND_FULL        =  1,  /* traditional a/b reporting */
+ };
  /* Reporting of tracking info */
  int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
-                      const char **upstream_name);
- int format_tracking_info(struct branch *branch, struct strbuf *sb);
+                      const char **upstream_name, enum ahead_behind_flags abf);
+ int format_tracking_info(struct branch *branch, struct strbuf *sb,
+                        enum ahead_behind_flags abf);
  
  struct ref *get_local_heads(void);
  /*
@@@ -298,8 -305,4 +306,8 @@@ extern int parseopt_push_cas_option(con
  extern int is_empty_cas(const struct push_cas_option *);
  void apply_push_cas(struct push_cas_option *, struct remote *, struct ref *);
  
 +#define TAG_REFSPEC "refs/tags/*:refs/tags/*"
 +
 +void add_prune_tags_to_fetch_refspec(struct remote *remote);
 +
  #endif
diff --combined wt-status.c
index f5debcd2b4f05c50d5e70efc95d10d95ca6372cd,e4555c7fa724947483c6c72875c4d69c43fba171..66f4234af1149618b47786f3b623916be7c02c74
@@@ -136,6 -136,7 +136,7 @@@ void wt_status_prepare(struct wt_statu
        s->ignored.strdup_strings = 1;
        s->show_branch = -1;  /* unspecified */
        s->show_stash = 0;
+       s->ahead_behind_flags = AHEAD_BEHIND_UNSPECIFIED;
        s->display_comment_prefix = 0;
  }
  
@@@ -360,6 -361,8 +361,6 @@@ static void wt_longstatus_print_change_
        switch (change_type) {
        case WT_STATUS_UPDATED:
                status = d->index_status;
 -              if (d->head_path)
 -                      one_name = d->head_path;
                break;
        case WT_STATUS_CHANGED:
                if (d->new_submodule_commits || d->dirty_submodule) {
                    change_type);
        }
  
 +      /*
 +       * Only pick up the rename it's relevant. If the rename is for
 +       * the changed section and we're printing the updated section,
 +       * ignore it.
 +       */
 +      if (d->rename_status == status)
 +              one_name = d->rename_source;
 +
        one = quote_path(one_name, s->prefix, &onebuf);
        two = quote_path(two_name, s->prefix, &twobuf);
  
                die("BUG: unhandled diff status %c", status);
        len = label_width - utf8_strwidth(what);
        assert(len >= 0);
 -      if (status == DIFF_STATUS_COPIED || status == DIFF_STATUS_RENAMED)
 +      if (one_name != two_name)
                status_printf_more(s, c, "%s%.*s%s -> %s",
                                   what, len, padding, one, two);
        else
        strbuf_release(&twobuf);
  }
  
 -static char short_submodule_status(struct wt_status_change_data *d) {
 +static char short_submodule_status(struct wt_status_change_data *d)
 +{
        if (d->new_submodule_commits)
                return 'M';
        if (d->dirty_submodule & DIRTY_SUBMODULE_MODIFIED)
@@@ -439,7 -433,7 +440,7 @@@ static void wt_status_collect_changed_c
                struct wt_status_change_data *d;
  
                p = q->queue[i];
 -              it = string_list_insert(&s->change, p->one->path);
 +              it = string_list_insert(&s->change, p->two->path);
                d = it->util;
                if (!d) {
                        d = xcalloc(1, sizeof(*d));
                        /* mode_worktree is zero for a delete. */
                        break;
  
 +              case DIFF_STATUS_COPIED:
 +              case DIFF_STATUS_RENAMED:
 +                      if (d->rename_status)
 +                              die("BUG: multiple renames on the same target? how?");
 +                      d->rename_source = xstrdup(p->one->path);
 +                      d->rename_score = p->score * 100 / MAX_SCORE;
 +                      d->rename_status = p->status;
 +                      /* fallthru */
                case DIFF_STATUS_MODIFIED:
                case DIFF_STATUS_TYPE_CHANGED:
                case DIFF_STATUS_UNMERGED:
                        oidcpy(&d->oid_index, &p->one->oid);
                        break;
  
 -              case DIFF_STATUS_UNKNOWN:
 -                      die("BUG: worktree status unknown???");
 +              default:
 +                      die("BUG: unhandled diff-files status '%c'", p->status);
                        break;
                }
  
@@@ -545,11 -531,8 +546,11 @@@ static void wt_status_collect_updated_c
  
                case DIFF_STATUS_COPIED:
                case DIFF_STATUS_RENAMED:
 -                      d->head_path = xstrdup(p->one->path);
 -                      d->score = p->score * 100 / MAX_SCORE;
 +                      if (d->rename_status)
 +                              die("BUG: multiple renames on the same target? how?");
 +                      d->rename_source = xstrdup(p->one->path);
 +                      d->rename_score = p->score * 100 / MAX_SCORE;
 +                      d->rename_status = p->status;
                        /* fallthru */
                case DIFF_STATUS_MODIFIED:
                case DIFF_STATUS_TYPE_CHANGED:
                         * values in these fields.
                         */
                        break;
 +
 +              default:
 +                      die("BUG: unhandled diff-index status '%c'", p->status);
 +                      break;
                }
        }
  }
@@@ -624,7 -603,7 +625,7 @@@ static void wt_status_collect_changes_i
        rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
        rev.diffopt.format_callback = wt_status_collect_updated_cb;
        rev.diffopt.format_callback_data = s;
 -      rev.diffopt.detect_rename = 1;
 +      rev.diffopt.detect_rename = DIFF_DETECT_RENAME;
        rev.diffopt.rename_limit = 200;
        rev.diffopt.break_opt = 0;
        copy_pathspec(&rev.prune_data, &s->pathspec);
@@@ -984,7 -963,7 +985,7 @@@ static void wt_longstatus_print_verbose
        setup_revisions(0, NULL, &rev, &opt);
  
        rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
 -      rev.diffopt.detect_rename = 1;
 +      rev.diffopt.detect_rename = DIFF_DETECT_RENAME;
        rev.diffopt.file = s->fp;
        rev.diffopt.close_file = 0;
        /*
@@@ -1032,7 -1011,7 +1033,7 @@@ static void wt_longstatus_print_trackin
        if (!skip_prefix(s->branch, "refs/heads/", &branch_name))
                return;
        branch = branch_get(branch_name);
-       if (!format_tracking_info(branch, &sb))
+       if (!format_tracking_info(branch, &sb, s->ahead_behind_flags))
                return;
  
        i = 0;
@@@ -1741,14 -1720,13 +1742,14 @@@ static void wt_shortstatus_status(struc
        putchar(' ');
        if (s->null_termination) {
                fprintf(stdout, "%s%c", it->string, 0);
 -              if (d->head_path)
 -                      fprintf(stdout, "%s%c", d->head_path, 0);
 +              if (d->rename_source)
 +                      fprintf(stdout, "%s%c", d->rename_source, 0);
        } else {
                struct strbuf onebuf = STRBUF_INIT;
                const char *one;
 -              if (d->head_path) {
 -                      one = quote_path(d->head_path, s->prefix, &onebuf);
 +
 +              if (d->rename_source) {
 +                      one = quote_path(d->rename_source, s->prefix, &onebuf);
                        if (*one != '"' && strchr(one, ' ') != NULL) {
                                putchar('"');
                                strbuf_addch(&onebuf, '"');
@@@ -1793,7 -1771,7 +1794,7 @@@ static void wt_shortstatus_print_tracki
        const char *base;
        char *short_base;
        const char *branch_name;
-       int num_ours, num_theirs;
+       int num_ours, num_theirs, sti;
        int upstream_is_gone = 0;
  
        color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "## ");
  
        color_fprintf(s->fp, branch_color_local, "%s", branch_name);
  
-       if (stat_tracking_info(branch, &num_ours, &num_theirs, &base) < 0) {
+       sti = stat_tracking_info(branch, &num_ours, &num_theirs, &base,
+                                s->ahead_behind_flags);
+       if (sti < 0) {
                if (!base)
                        goto conclude;
  
        color_fprintf(s->fp, branch_color_remote, "%s", short_base);
        free(short_base);
  
-       if (!upstream_is_gone && !num_ours && !num_theirs)
+       if (!upstream_is_gone && !sti)
                goto conclude;
  
        color_fprintf(s->fp, header_color, " [");
        if (upstream_is_gone) {
                color_fprintf(s->fp, header_color, LABEL(N_("gone")));
+       } else if (s->ahead_behind_flags == AHEAD_BEHIND_QUICK) {
+               color_fprintf(s->fp, header_color, LABEL(N_("different")));
        } else if (!num_ours) {
                color_fprintf(s->fp, header_color, LABEL(N_("behind ")));
                color_fprintf(s->fp, branch_color_remote, "%d", num_theirs);
@@@ -1905,18 -1887,19 +1910,19 @@@ static void wt_porcelain_print(struct w
   *
   *    <upstream> ::= the upstream branch name, when set.
   *
-  *       <ahead> ::= integer ahead value, when upstream set
-  *                   and the commit is present (not gone).
-  *
-  *      <behind> ::= integer behind value, when upstream set
-  *                   and commit is present.
+  *       <ahead> ::= integer ahead value or '?'.
   *
+  *      <behind> ::= integer behind value or '?'.
   *
   * The end-of-line is defined by the -z flag.
   *
   *                 <eol> ::= NUL when -z,
   *                           LF when NOT -z.
   *
+  * When an upstream is set and present, the 'branch.ab' line will
+  * be printed with the ahead/behind counts for the branch and the
+  * upstream.  When AHEAD_BEHIND_QUICK is requested and the branches
+  * are different, '?' will be substituted for the actual count.
   */
  static void wt_porcelain_v2_print_tracking(struct wt_status *s)
  {
                /* Lookup stats on the upstream tracking branch, if set. */
                branch = branch_get(branch_name);
                base = NULL;
-               ab_info = (stat_tracking_info(branch, &nr_ahead, &nr_behind, &base) == 0);
+               ab_info = stat_tracking_info(branch, &nr_ahead, &nr_behind,
+                                            &base, s->ahead_behind_flags);
                if (base) {
                        base = shorten_unambiguous_ref(base, 0);
                        fprintf(s->fp, "# branch.upstream %s%c", base, eol);
                        free((char *)base);
  
-                       if (ab_info)
-                               fprintf(s->fp, "# branch.ab +%d -%d%c", nr_ahead, nr_behind, eol);
+                       if (ab_info > 0) {
+                               /* different */
+                               if (nr_ahead || nr_behind)
+                                       fprintf(s->fp, "# branch.ab +%d -%d%c",
+                                               nr_ahead, nr_behind, eol);
+                               else
+                                       fprintf(s->fp, "# branch.ab +? -?%c",
+                                               eol);
+                       } else if (!ab_info) {
+                               /* same */
+                               fprintf(s->fp, "# branch.ab +0 -0%c", eol);
+                       }
                }
        }
  
@@@ -2053,10 -2047,10 +2070,10 @@@ static void wt_porcelain_v2_print_chang
        struct wt_status *s)
  {
        struct wt_status_change_data *d = it->util;
 -      struct strbuf buf_index = STRBUF_INIT;
 -      struct strbuf buf_head = STRBUF_INIT;
 -      const char *path_index = NULL;
 -      const char *path_head = NULL;
 +      struct strbuf buf = STRBUF_INIT;
 +      struct strbuf buf_from = STRBUF_INIT;
 +      const char *path = NULL;
 +      const char *path_from = NULL;
        char key[3];
        char submodule_token[5];
        char sep_char, eol_char;
                 */
                sep_char = '\0';
                eol_char = '\0';
 -              path_index = it->string;
 -              path_head = d->head_path;
 +              path = it->string;
 +              path_from = d->rename_source;
        } else {
                /*
                 * Path(s) are C-quoted if necessary. Current path is ALWAYS first.
                 */
                sep_char = '\t';
                eol_char = '\n';
 -              path_index = quote_path(it->string, s->prefix, &buf_index);
 -              if (d->head_path)
 -                      path_head = quote_path(d->head_path, s->prefix, &buf_head);
 +              path = quote_path(it->string, s->prefix, &buf);
 +              if (d->rename_source)
 +                      path_from = quote_path(d->rename_source, s->prefix, &buf_from);
        }
  
 -      if (path_head)
 +      if (path_from)
                fprintf(s->fp, "2 %s %s %06o %06o %06o %s %s %c%d %s%c%s%c",
                                key, submodule_token,
                                d->mode_head, d->mode_index, d->mode_worktree,
                                oid_to_hex(&d->oid_head), oid_to_hex(&d->oid_index),
 -                              key[0], d->score,
 -                              path_index, sep_char, path_head, eol_char);
 +                              d->rename_status, d->rename_score,
 +                              path, sep_char, path_from, eol_char);
        else
                fprintf(s->fp, "1 %s %s %06o %06o %06o %s %s %s%c",
                                key, submodule_token,
                                d->mode_head, d->mode_index, d->mode_worktree,
                                oid_to_hex(&d->oid_head), oid_to_hex(&d->oid_index),
 -                              path_index, eol_char);
 +                              path, eol_char);
  
 -      strbuf_release(&buf_index);
 -      strbuf_release(&buf_head);
 +      strbuf_release(&buf);
 +      strbuf_release(&buf_from);
  }
  
  /*
diff --combined wt-status.h
index 3f84d5c29ff270596a894e2d42843b03d7aa37c5,b91267455b9b9aa0b4673ce7c6206166a9abc885..ea2456daf24a4a74b3d85b041527ce5f99dc2695
@@@ -5,6 -5,7 +5,7 @@@
  #include "string-list.h"
  #include "color.h"
  #include "pathspec.h"
+ #include "remote.h"
  
  struct worktree;
  
@@@ -44,11 -45,10 +45,11 @@@ struct wt_status_change_data 
        int worktree_status;
        int index_status;
        int stagemask;
 -      int score;
        int mode_head, mode_index, mode_worktree;
        struct object_id oid_head, oid_index;
 -      char *head_path;
 +      int rename_status;
 +      int rename_score;
 +      char *rename_source;
        unsigned dirty_submodule       : 2;
        unsigned new_submodule_commits : 1;
  };
@@@ -87,6 -87,7 +88,7 @@@ struct wt_status 
        int show_branch;
        int show_stash;
        int hints;
+       enum ahead_behind_flags ahead_behind_flags;
  
        enum wt_status_format status_format;
        unsigned char sha1_commit[GIT_MAX_RAWSZ]; /* when not Initial */