Merge branch 'jx/branch-vv-always-compare-with-upstream'
authorJunio C Hamano <gitster@pobox.com>
Fri, 20 Sep 2013 19:26:57 +0000 (12:26 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 20 Sep 2013 19:26:57 +0000 (12:26 -0700)
"git branch -v -v" (and "git status") did not distinguish among a
branch that does not build on any other branch, a branch that is in
sync with the branch it builds on, and a branch that is configured
to build on some other branch that no longer exists.

* jx/branch-vv-always-compare-with-upstream:
status: always show tracking branch even no change
branch: report invalid tracking branch as gone

1  2 
builtin/branch.c
remote.c
wt-status.c
diff --combined builtin/branch.c
index 0903763702a436dc8b3ffc89aa64b2598e257446,0539fda85a54663357d2580a78a275853440b7cc..ad0f86de540dc394199f71c03665d0cd76371c35
@@@ -423,19 -423,19 +423,19 @@@ static void fill_tracking_info(struct s
        char *ref = NULL;
        struct branch *branch = branch_get(branch_name);
        struct strbuf fancy = STRBUF_INIT;
+       int upstream_is_gone = 0;
  
-       if (!stat_tracking_info(branch, &ours, &theirs)) {
-               if (branch && branch->merge && branch->merge[0]->dst &&
-                   show_upstream_ref) {
-                       ref = shorten_unambiguous_ref(branch->merge[0]->dst, 0);
-                       if (want_color(branch_use_color))
-                               strbuf_addf(stat, "[%s%s%s] ",
-                                               branch_get_color(BRANCH_COLOR_UPSTREAM),
-                                               ref, branch_get_color(BRANCH_COLOR_RESET));
-                       else
-                               strbuf_addf(stat, "[%s] ", ref);
-               }
+       switch (stat_tracking_info(branch, &ours, &theirs)) {
+       case 0:
+               /* no base */
                return;
+       case -1:
+               /* with "gone" base */
+               upstream_is_gone = 1;
+               break;
+       default:
+               /* with base */
+               break;
        }
  
        if (show_upstream_ref) {
                        strbuf_addstr(&fancy, ref);
        }
  
-       if (!ours) {
-               if (ref)
+       if (upstream_is_gone) {
+               if (show_upstream_ref)
+                       strbuf_addf(stat, _("[%s: gone]"), fancy.buf);
+       } else if (!ours && !theirs) {
+               if (show_upstream_ref)
+                       strbuf_addf(stat, _("[%s]"), fancy.buf);
+       } else if (!ours) {
+               if (show_upstream_ref)
                        strbuf_addf(stat, _("[%s: behind %d]"), fancy.buf, theirs);
                else
                        strbuf_addf(stat, _("[behind %d]"), theirs);
  
        } else if (!theirs) {
-               if (ref)
+               if (show_upstream_ref)
                        strbuf_addf(stat, _("[%s: ahead %d]"), fancy.buf, ours);
                else
                        strbuf_addf(stat, _("[ahead %d]"), ours);
        } else {
-               if (ref)
+               if (show_upstream_ref)
                        strbuf_addf(stat, _("[%s: ahead %d, behind %d]"),
                                    fancy.buf, ours, theirs);
                else
@@@ -797,7 -803,7 +803,7 @@@ int cmd_branch(int argc, const char **a
                OPT_SET_INT( 0, "set-upstream",  &track, N_("change upstream info"),
                        BRANCH_TRACK_OVERRIDE),
                OPT_STRING('u', "set-upstream-to", &new_upstream, "upstream", "change the upstream info"),
 -              OPT_BOOLEAN(0, "unset-upstream", &unset_upstream, "Unset 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),
                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_BIT('M', NULL, &rename, N_("move/rename a branch, even if target exists"), 2),
 -              OPT_BOOLEAN(0, "list", &list, N_("list branch names")),
 -              OPT_BOOLEAN('l', "create-reflog", &reflog, N_("create the branch's reflog")),
 -              OPT_BOOLEAN(0, "edit-description", &edit_description,
 -                          N_("edit the description for the branch")),
 +              OPT_BOOL(0, "list", &list, N_("list branch names")),
 +              OPT_BOOL('l', "create-reflog", &reflog, N_("create the branch's reflog")),
 +              OPT_BOOL(0, "edit-description", &edit_description,
 +                       N_("edit the description for the branch")),
                OPT__FORCE(&force_create, N_("force creation (when already exists)")),
                {
                        OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref,
        if (with_commit || merge_filter != NO_FILTER)
                list = 1;
  
 -      if (!!delete + !!rename + !!force_create + !!list + !!new_upstream + !!unset_upstream > 1)
 +      if (!!delete + !!rename + !!force_create + !!new_upstream +
 +          list + unset_upstream > 1)
                usage_with_options(builtin_branch_usage, options);
  
        if (abbrev == -1)
diff --combined remote.c
index 24334679e0971e3af5e168d1cf7259da55d21d50,c972bf39e40d9f2c1c46d27831ef2a3a908a976b..e9fedfa918d6806532184b8735d2761951fd4a2e
+++ b/remote.c
@@@ -148,7 -148,6 +148,7 @@@ static struct remote *make_remote(cons
        }
  
        ret = xcalloc(1, sizeof(struct remote));
 +      ret->prune = -1;  /* unspecified */
        ALLOC_GROW(remotes, remotes_nr + 1, remotes_alloc);
        remotes[remotes_nr++] = ret;
        if (len)
@@@ -405,8 -404,6 +405,8 @@@ static int handle_config(const char *ke
                remote->skip_default_update = git_config_bool(key, value);
        else if (!strcmp(subkey, ".skipfetchall"))
                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, ".url")) {
                const char *v;
                if (git_config_string(&v, key, value))
@@@ -1305,14 -1302,6 +1305,14 @@@ static void add_missing_tags(struct re
        free(sent_tips.tip);
  }
  
 +struct ref *find_ref_by_name(const struct ref *list, const char *name)
 +{
 +      for ( ; list; list = list->next)
 +              if (!strcmp(list->name, name))
 +                      return (struct ref *)list;
 +      return NULL;
 +}
 +
  static void prepare_ref_index(struct string_list *ref_index, struct ref *ref)
  {
        for ( ; ref; ref = ref->next)
@@@ -1422,13 -1411,12 +1422,13 @@@ int match_push_refs(struct ref *src, st
  }
  
  void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
 -      int force_update)
 +                           int force_update)
  {
        struct ref *ref;
  
        for (ref = remote_refs; ref; ref = ref->next) {
                int force_ref_update = ref->force || force_update;
 +              int reject_reason = 0;
  
                if (ref->peer_ref)
                        hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
                }
  
                /*
 +               * Bypass the usual "must fast-forward" check but
 +               * replace it with a weaker "the old value must be
 +               * this value we observed".  If the remote ref has
 +               * moved and is now different from what we expect,
 +               * reject any push.
 +               *
 +               * It also is an error if the user told us to check
 +               * with the remote-tracking branch to find the value
 +               * to expect, but we did not have such a tracking
 +               * branch.
 +               */
 +              if (ref->expect_old_sha1) {
 +                      if (ref->expect_old_no_trackback ||
 +                          hashcmp(ref->old_sha1, ref->old_sha1_expect))
 +                              reject_reason = REF_STATUS_REJECT_STALE;
 +              }
 +
 +              /*
 +               * The usual "must fast-forward" rules.
 +               *
                 * Decide whether an individual refspec A:B can be
                 * pushed.  The push will succeed if any of the
                 * following are true:
                 *     passing the --force argument
                 */
  
 -              if (!ref->deletion && !is_null_sha1(ref->old_sha1)) {
 -                      int why = 0; /* why would this push require --force? */
 -
 +              else if (!ref->deletion && !is_null_sha1(ref->old_sha1)) {
                        if (!prefixcmp(ref->name, "refs/tags/"))
 -                              why = REF_STATUS_REJECT_ALREADY_EXISTS;
 +                              reject_reason = REF_STATUS_REJECT_ALREADY_EXISTS;
                        else if (!has_sha1_file(ref->old_sha1))
 -                              why = REF_STATUS_REJECT_FETCH_FIRST;
 +                              reject_reason = REF_STATUS_REJECT_FETCH_FIRST;
                        else if (!lookup_commit_reference_gently(ref->old_sha1, 1) ||
                                 !lookup_commit_reference_gently(ref->new_sha1, 1))
 -                              why = REF_STATUS_REJECT_NEEDS_FORCE;
 +                              reject_reason = REF_STATUS_REJECT_NEEDS_FORCE;
                        else if (!ref_newer(ref->new_sha1, ref->old_sha1))
 -                              why = REF_STATUS_REJECT_NONFASTFORWARD;
 -
 -                      if (!force_ref_update)
 -                              ref->status = why;
 -                      else if (why)
 -                              ref->forced_update = 1;
 +                              reject_reason = REF_STATUS_REJECT_NONFASTFORWARD;
                }
 +
 +              /*
 +               * "--force" will defeat any rejection implemented
 +               * by the rules above.
 +               */
 +              if (!force_ref_update)
 +                      ref->status = reject_reason;
 +              else if (reject_reason)
 +                      ref->forced_update = 1;
        }
  }
  
@@@ -1729,7 -1695,11 +1729,11 @@@ int ref_newer(const unsigned char *new_
  }
  
  /*
-  * Return true if there is anything to report, otherwise false.
+  * Compare a branch with its upstream, and save their differences (number
+  * of commits) in *num_ours and *num_theirs.
+  *
+  * Return 0 if branch has no upstream (no base), -1 if upstream is missing
+  * (with "gone" base), otherwise 1 (with base).
   */
  int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs)
  {
        const char *rev_argv[10], *base;
        int rev_argc;
  
-       /*
-        * Nothing to report unless we are marked to build on top of
-        * somebody else.
-        */
+       /* Cannot stat unless we are marked to build on top of somebody else. */
        if (!branch ||
            !branch->merge || !branch->merge[0] || !branch->merge[0]->dst)
                return 0;
  
-       /*
-        * If what we used to build on no longer exists, there is
-        * nothing to report.
-        */
+       /* Cannot stat if what we used to build on no longer exists */
        base = branch->merge[0]->dst;
        if (read_ref(base, sha1))
-               return 0;
+               return -1;
        theirs = lookup_commit_reference(sha1);
        if (!theirs)
-               return 0;
+               return -1;
  
        if (read_ref(branch->refname, sha1))
-               return 0;
+               return -1;
        ours = lookup_commit_reference(sha1);
        if (!ours)
-               return 0;
+               return -1;
  
        /* are we the same? */
-       if (theirs == ours)
-               return 0;
+       if (theirs == ours) {
+               *num_theirs = *num_ours = 0;
+               return 1;
+       }
  
        /* Run "rev-list --left-right ours...theirs" internally... */
        rev_argc = 0;
   */
  int format_tracking_info(struct branch *branch, struct strbuf *sb)
  {
-       int num_ours, num_theirs;
+       int ours, theirs;
        const char *base;
+       int upstream_is_gone = 0;
  
-       if (!stat_tracking_info(branch, &num_ours, &num_theirs))
+       switch (stat_tracking_info(branch, &ours, &theirs)) {
+       case 0:
+               /* no base */
                return 0;
+       case -1:
+               /* with "gone" base */
+               upstream_is_gone = 1;
+               break;
+       default:
+               /* with base */
+               break;
+       }
  
        base = branch->merge[0]->dst;
        base = shorten_unambiguous_ref(base, 0);
-       if (!num_theirs) {
+       if (upstream_is_gone) {
+               strbuf_addf(sb,
+                       _("Your branch is based on '%s', but the upstream is gone.\n"),
+                       base);
+               if (advice_status_hints)
+                       strbuf_addf(sb,
+                               _("  (use \"git branch --unset-upstream\" to fixup)\n"));
+       } else if (!ours && !theirs) {
+               strbuf_addf(sb,
+                       _("Your branch is up-to-date with '%s'.\n"),
+                       base);
+       } else if (!theirs) {
                strbuf_addf(sb,
                        Q_("Your branch is ahead of '%s' by %d commit.\n",
                           "Your branch is ahead of '%s' by %d commits.\n",
-                          num_ours),
-                       base, num_ours);
+                          ours),
+                       base, ours);
                if (advice_status_hints)
                        strbuf_addf(sb,
                                _("  (use \"git push\" to publish your local commits)\n"));
-       } else if (!num_ours) {
+       } else if (!ours) {
                strbuf_addf(sb,
                        Q_("Your branch is behind '%s' by %d commit, "
                               "and can be fast-forwarded.\n",
                           "Your branch is behind '%s' by %d commits, "
                               "and can be fast-forwarded.\n",
-                          num_theirs),
-                       base, num_theirs);
+                          theirs),
+                       base, theirs);
                if (advice_status_hints)
                        strbuf_addf(sb,
                                _("  (use \"git pull\" to update your local branch)\n"));
                           "Your branch and '%s' have diverged,\n"
                               "and have %d and %d different commits each, "
                               "respectively.\n",
-                          num_theirs),
-                       base, num_ours, num_theirs);
+                          theirs),
+                       base, ours, theirs);
                if (advice_status_hints)
                        strbuf_addf(sb,
                                _("  (use \"git pull\" to merge the remote branch into yours)\n"));
@@@ -1970,121 -1958,3 +1992,121 @@@ struct ref *get_stale_heads(struct refs
        string_list_clear(&ref_names, 0);
        return stale_refs;
  }
 +
 +/*
 + * Compare-and-swap
 + */
 +void clear_cas_option(struct push_cas_option *cas)
 +{
 +      int i;
 +
 +      for (i = 0; i < cas->nr; i++)
 +              free(cas->entry[i].refname);
 +      free(cas->entry);
 +      memset(cas, 0, sizeof(*cas));
 +}
 +
 +static struct push_cas *add_cas_entry(struct push_cas_option *cas,
 +                                    const char *refname,
 +                                    size_t refnamelen)
 +{
 +      struct push_cas *entry;
 +      ALLOC_GROW(cas->entry, cas->nr + 1, cas->alloc);
 +      entry = &cas->entry[cas->nr++];
 +      memset(entry, 0, sizeof(*entry));
 +      entry->refname = xmemdupz(refname, refnamelen);
 +      return entry;
 +}
 +
 +int parse_push_cas_option(struct push_cas_option *cas, const char *arg, int unset)
 +{
 +      const char *colon;
 +      struct push_cas *entry;
 +
 +      if (unset) {
 +              /* "--no-<option>" */
 +              clear_cas_option(cas);
 +              return 0;
 +      }
 +
 +      if (!arg) {
 +              /* just "--<option>" */
 +              cas->use_tracking_for_rest = 1;
 +              return 0;
 +      }
 +
 +      /* "--<option>=refname" or "--<option>=refname:value" */
 +      colon = strchrnul(arg, ':');
 +      entry = add_cas_entry(cas, arg, colon - arg);
 +      if (!*colon)
 +              entry->use_tracking = 1;
 +      else if (get_sha1(colon + 1, entry->expect))
 +              return error("cannot parse expected object name '%s'", colon + 1);
 +      return 0;
 +}
 +
 +int parseopt_push_cas_option(const struct option *opt, const char *arg, int unset)
 +{
 +      return parse_push_cas_option(opt->value, arg, unset);
 +}
 +
 +int is_empty_cas(const struct push_cas_option *cas)
 +{
 +      return !cas->use_tracking_for_rest && !cas->nr;
 +}
 +
 +/*
 + * Look at remote.fetch refspec and see if we have a remote
 + * tracking branch for the refname there.  Fill its current
 + * value in sha1[].
 + * If we cannot do so, return negative to signal an error.
 + */
 +static int remote_tracking(struct remote *remote, const char *refname,
 +                         unsigned char sha1[20])
 +{
 +      char *dst;
 +
 +      dst = apply_refspecs(remote->fetch, remote->fetch_refspec_nr, refname);
 +      if (!dst)
 +              return -1; /* no tracking ref for refname at remote */
 +      if (read_ref(dst, sha1))
 +              return -1; /* we know what the tracking ref is but we cannot read it */
 +      return 0;
 +}
 +
 +static void apply_cas(struct push_cas_option *cas,
 +                    struct remote *remote,
 +                    struct ref *ref)
 +{
 +      int i;
 +
 +      /* Find an explicit --<option>=<name>[:<value>] entry */
 +      for (i = 0; i < cas->nr; i++) {
 +              struct push_cas *entry = &cas->entry[i];
 +              if (!refname_match(entry->refname, ref->name, ref_rev_parse_rules))
 +                      continue;
 +              ref->expect_old_sha1 = 1;
 +              if (!entry->use_tracking)
 +                      hashcpy(ref->old_sha1_expect, cas->entry[i].expect);
 +              else if (remote_tracking(remote, ref->name, ref->old_sha1_expect))
 +                      ref->expect_old_no_trackback = 1;
 +              return;
 +      }
 +
 +      /* Are we using "--<option>" to cover all? */
 +      if (!cas->use_tracking_for_rest)
 +              return;
 +
 +      ref->expect_old_sha1 = 1;
 +      if (remote_tracking(remote, ref->name, ref->old_sha1_expect))
 +              ref->expect_old_no_trackback = 1;
 +}
 +
 +void apply_push_cas(struct push_cas_option *cas,
 +                  struct remote *remote,
 +                  struct ref *remote_refs)
 +{
 +      struct ref *ref;
 +      for (ref = remote_refs; ref; ref = ref->next)
 +              apply_cas(cas, remote, ref);
 +}
diff --combined wt-status.c
index ff4b32426a36db38fba9e4cc51e09a988efe34a6,c5e68174f97bd3f46b7aac2a65daea333a58851b..c8c2d7752470fbea4fd4c8f80e2fa2d7bc50eeda
@@@ -1,5 -1,4 +1,5 @@@
  #include "cache.h"
 +#include "pathspec.h"
  #include "wt-status.h"
  #include "object.h"
  #include "dir.h"
@@@ -439,7 -438,7 +439,7 @@@ static void wt_status_collect_changes_w
        }
        rev.diffopt.format_callback = wt_status_collect_changed_cb;
        rev.diffopt.format_callback_data = s;
 -      init_pathspec(&rev.prune_data, s->pathspec);
 +      copy_pathspec(&rev.prune_data, &s->pathspec);
        run_diff_files(&rev, 0);
  }
  
@@@ -464,20 -463,22 +464,20 @@@ static void wt_status_collect_changes_i
        rev.diffopt.detect_rename = 1;
        rev.diffopt.rename_limit = 200;
        rev.diffopt.break_opt = 0;
 -      init_pathspec(&rev.prune_data, s->pathspec);
 +      copy_pathspec(&rev.prune_data, &s->pathspec);
        run_diff_index(&rev, 1);
  }
  
  static void wt_status_collect_changes_initial(struct wt_status *s)
  {
 -      struct pathspec pathspec;
        int i;
  
 -      init_pathspec(&pathspec, s->pathspec);
        for (i = 0; i < active_nr; i++) {
                struct string_list_item *it;
                struct wt_status_change_data *d;
                const struct cache_entry *ce = active_cache[i];
  
 -              if (!ce_path_match(ce, &pathspec))
 +              if (!ce_path_match(ce, &s->pathspec))
                        continue;
                it = string_list_insert(&s->change, ce->name);
                d = it->util;
                else
                        d->index_status = DIFF_STATUS_ADDED;
        }
 -      free_pathspec(&pathspec);
  }
  
  static void wt_status_collect_untracked(struct wt_status *s)
                dir.flags |= DIR_SHOW_IGNORED_TOO;
        setup_standard_excludes(&dir);
  
 -      fill_directory(&dir, s->pathspec);
 +      fill_directory(&dir, &s->pathspec);
  
        for (i = 0; i < dir.nr; i++) {
                struct dir_entry *ent = dir.entries[i];
                if (cache_name_is_other(ent->name, ent->len) &&
 -                  match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))
 +                  match_pathspec_depth(&s->pathspec, ent->name, ent->len, 0, NULL))
                        string_list_insert(&s->untracked, ent->name);
                free(ent);
        }
        for (i = 0; i < dir.ignored_nr; i++) {
                struct dir_entry *ent = dir.ignored[i];
                if (cache_name_is_other(ent->name, ent->len) &&
 -                  match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))
 +                  match_pathspec_depth(&s->pathspec, ent->name, ent->len, 0, NULL))
                        string_list_insert(&s->ignored, ent->name);
                free(ent);
        }
@@@ -1363,6 -1365,7 +1363,7 @@@ static void wt_shortstatus_print_tracki
        const char *base;
        const char *branch_name;
        int num_ours, num_theirs;
+       int upstream_is_gone = 0;
  
        color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "## ");
  
        branch = branch_get(s->branch + 11);
        if (s->is_initial)
                color_fprintf(s->fp, header_color, _("Initial commit on "));
-       if (!stat_tracking_info(branch, &num_ours, &num_theirs)) {
-               color_fprintf(s->fp, branch_color_local, "%s", branch_name);
+       color_fprintf(s->fp, branch_color_local, "%s", branch_name);
+       switch (stat_tracking_info(branch, &num_ours, &num_theirs)) {
+       case 0:
+               /* no base */
                fputc(s->null_termination ? '\0' : '\n', s->fp);
                return;
+       case -1:
+               /* with "gone" base */
+               upstream_is_gone = 1;
+               break;
+       default:
+               /* with base */
+               break;
        }
  
        base = branch->merge[0]->dst;
        base = shorten_unambiguous_ref(base, 0);
-       color_fprintf(s->fp, branch_color_local, "%s", branch_name);
        color_fprintf(s->fp, header_color, "...");
        color_fprintf(s->fp, branch_color_remote, "%s", base);
  
+       if (!upstream_is_gone && !num_ours && !num_theirs) {
+               fputc(s->null_termination ? '\0' : '\n', s->fp);
+               return;
+       }
        color_fprintf(s->fp, header_color, " [");
-       if (!num_ours) {
+       if (upstream_is_gone) {
+               color_fprintf(s->fp, header_color, _("gone"));
+       } else if (!num_ours) {
                color_fprintf(s->fp, header_color, _("behind "));
                color_fprintf(s->fp, branch_color_remote, "%d", num_theirs);
        } else if (!num_theirs) {