Merge branch 'ct/advise-push-default' into maint
authorJunio C Hamano <gitster@pobox.com>
Fri, 11 May 2012 18:18:43 +0000 (11:18 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 11 May 2012 18:18:43 +0000 (11:18 -0700)
The cases "git push" fails due to non-ff can be broken into three
categories; each case is given a separate advise message.

By Christopher Tiwald (2) and Jeff King (1)
* ct/advise-push-default:
Fix httpd tests that broke when non-ff push advice changed
clean up struct ref's nonfastforward field
push: Provide situational hints for non-fast-forward errors

1  2 
Documentation/config.txt
builtin/push.c
cache.h
t/t5541-http-push.sh
transport.c
diff --combined Documentation/config.txt
index e67c8ef36908c7cb572ebdbed1e21d920b4f547b,fb386abc514efef6cb94521c4f5546b5c51ab296..83ad8ebce01ab0cf1af1bb1b50fd5f5b37f0f292
@@@ -95,9 -95,7 +95,9 @@@ included file is expanded immediately, 
  found at the location of the include directive. If the value of the
  `include.path` variable is a relative path, the path is considered to be
  relative to the configuration file in which the include directive was
 -found. See below for examples.
 +found. The value of `include.path` is subject to tilde expansion: `{tilde}/`
 +is expanded to the value of `$HOME`, and `{tilde}user/` to the specified
 +user's home directory. See below for examples.
  
  Example
  ~~~~~~~
        [include]
                path = /path/to/foo.inc ; include by absolute path
                path = foo ; expand "foo" relative to the current file
 +              path = ~/foo ; expand "foo" in your $HOME directory
  
  Variables
  ~~~~~~~~~
@@@ -141,8 -138,23 +141,23 @@@ advice.*:
  +
  --
        pushNonFastForward::
-               Advice shown when linkgit:git-push[1] refuses
-               non-fast-forward refs.
+               Set this variable to 'false' if you want to disable
+               'pushNonFFCurrent', 'pushNonFFDefault', and
+               'pushNonFFMatching' simultaneously.
+       pushNonFFCurrent::
+               Advice shown when linkgit:git-push[1] fails due to a
+               non-fast-forward update to the current branch.
+       pushNonFFDefault::
+               Advice to set 'push.default' to 'upstream' or 'current'
+               when you ran linkgit:git-push[1] and pushed 'matching
+               refs' by default (i.e. you did not provide an explicit
+               refspec, and no 'push.default' configuration was set)
+               and it resulted in a non-fast-forward error.
+       pushNonFFMatching::
+               Advice shown when you ran linkgit:git-push[1] and pushed
+               'matching refs' explicitly (i.e. you used ':', or
+               specified a refspec that isn't your current branch) and
+               it resulted in a non-fast-forward error.
        statusHints::
                Directions on how to stage/unstage/add shown in the
                output of linkgit:git-status[1] and the template shown
diff --combined builtin/push.c
index b6c0fee4c6058a3aadef659a3e98bc9e83fb6398,8a14e4bcc12bc3ee2741fb1077f6513cd9eba99f..693671315ee11313ca98209ebf28fdeffb13f636
@@@ -24,6 -24,7 +24,7 @@@ static int progress = -1
  static const char **refspec;
  static int refspec_nr;
  static int refspec_alloc;
+ static int default_matching_used;
  
  static void add_refspec(const char *ref)
  {
@@@ -65,16 -66,6 +66,16 @@@ static void set_refspecs(const char **r
        }
  }
  
 +static int push_url_of_remote(struct remote *remote, const char ***url_p)
 +{
 +      if (remote->pushurl_nr) {
 +              *url_p = remote->pushurl;
 +              return remote->pushurl_nr;
 +      }
 +      *url_p = remote->url;
 +      return remote->url_nr;
 +}
 +
  static void setup_push_upstream(struct remote *remote)
  {
        struct strbuf refspec = STRBUF_INIT;
@@@ -86,7 -77,7 +87,7 @@@
                    "\n"
                    "    git push %s HEAD:<name-of-remote-branch>\n"),
                    remote->name);
 -      if (!branch->merge_nr || !branch->merge)
 +      if (!branch->merge_nr || !branch->merge || !branch->remote_name)
                die(_("The current branch %s has no upstream branch.\n"
                    "To push the current branch and set the remote as upstream, use\n"
                    "\n"
        if (branch->merge_nr != 1)
                die(_("The current branch %s has multiple upstream branches, "
                    "refusing to push."), branch->name);
 +      if (strcmp(branch->remote_name, remote->name))
 +              die(_("You are pushing to remote '%s', which is not the upstream of\n"
 +                    "your current branch '%s', without telling me what to push\n"
 +                    "to update which remote branch."),
 +                  remote->name, branch->name);
 +
        strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src);
        add_refspec(refspec.buf);
  }
@@@ -111,6 -96,9 +112,9 @@@ static void setup_default_push_refspecs
  {
        switch (push_default) {
        default:
+       case PUSH_DEFAULT_UNSPECIFIED:
+               default_matching_used = 1;
+               /* fallthru */
        case PUSH_DEFAULT_MATCHING:
                add_refspec(":");
                break;
        }
  }
  
+ static const char message_advice_pull_before_push[] =
+       N_("Updates were rejected because the tip of your current branch is behind\n"
+          "its remote counterpart. Merge the remote changes (e.g. 'git pull')\n"
+          "before pushing again.\n"
+          "See the 'Note about fast-forwards' in 'git push --help' for details.");
+ static const char message_advice_use_upstream[] =
+       N_("Updates were rejected because a pushed branch tip is behind its remote\n"
+          "counterpart. If you did not intend to push that branch, you may want to\n"
+          "specify branches to push or set the 'push.default' configuration\n"
+          "variable to 'current' or 'upstream' to push only the current branch.");
+ static const char message_advice_checkout_pull_push[] =
+       N_("Updates were rejected because a pushed branch tip is behind its remote\n"
+          "counterpart. Check out this branch and merge the remote changes\n"
+          "(e.g. 'git pull') before pushing again.\n"
+          "See the 'Note about fast-forwards' in 'git push --help' for details.");
+ static void advise_pull_before_push(void)
+ {
+       if (!advice_push_non_ff_current || !advice_push_nonfastforward)
+               return;
+       advise(_(message_advice_pull_before_push));
+ }
+ static void advise_use_upstream(void)
+ {
+       if (!advice_push_non_ff_default || !advice_push_nonfastforward)
+               return;
+       advise(_(message_advice_use_upstream));
+ }
+ static void advise_checkout_pull_push(void)
+ {
+       if (!advice_push_non_ff_matching || !advice_push_nonfastforward)
+               return;
+       advise(_(message_advice_checkout_pull_push));
+ }
  static int push_with_options(struct transport *transport, int flags)
  {
        int err;
                error(_("failed to push some refs to '%s'"), transport->url);
  
        err |= transport_disconnect(transport);
        if (!err)
                return 0;
  
-       if (nonfastforward && advice_push_nonfastforward) {
-               fprintf(stderr, _("To prevent you from losing history, non-fast-forward updates were rejected\n"
-                               "Merge the remote changes (e.g. 'git pull') before pushing again.  See the\n"
-                               "'Note about fast-forwards' section of 'git push --help' for details.\n"));
+       switch (nonfastforward) {
+       default:
+               break;
+       case NON_FF_HEAD:
+               advise_pull_before_push();
+               break;
+       case NON_FF_OTHER:
+               if (default_matching_used)
+                       advise_use_upstream();
+               else
+                       advise_checkout_pull_push();
+               break;
        }
  
        return 1;
@@@ -212,7 -246,13 +262,7 @@@ static int do_push(const char *repo, in
                        setup_default_push_refspecs(remote);
        }
        errs = 0;
 -      if (remote->pushurl_nr) {
 -              url = remote->pushurl;
 -              url_nr = remote->pushurl_nr;
 -      } else {
 -              url = remote->url;
 -              url_nr = remote->url_nr;
 -      }
 +      url_nr = push_url_of_remote(remote, &url);
        if (url_nr) {
                for (i = 0; i < url_nr; i++) {
                        struct transport *transport =
diff --combined cache.h
index 5e6eef9b4d0fca96fb0f90265f5f68d640f1583a,35f30752c612d4da31009eb0ef8a877743ee05b5..5bf59ff5c33919ac42ee79dea7911a773c1f698d
+++ b/cache.h
@@@ -625,7 -625,8 +625,8 @@@ enum push_default_type 
        PUSH_DEFAULT_NOTHING = 0,
        PUSH_DEFAULT_MATCHING,
        PUSH_DEFAULT_UPSTREAM,
-       PUSH_DEFAULT_CURRENT
+       PUSH_DEFAULT_CURRENT,
+       PUSH_DEFAULT_UNSPECIFIED
  };
  
  extern enum branch_track git_branch_track;
@@@ -708,19 -709,6 +709,19 @@@ static inline void hashclr(unsigned cha
  #define EMPTY_TREE_SHA1_BIN \
         ((const unsigned char *) EMPTY_TREE_SHA1_BIN_LITERAL)
  
 +#define EMPTY_BLOB_SHA1_HEX \
 +      "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"
 +#define EMPTY_BLOB_SHA1_BIN_LITERAL \
 +      "\xe6\x9d\xe2\x9b\xb2\xd1\xd6\x43\x4b\x8b" \
 +      "\x29\xae\x77\x5a\xd8\xc2\xe4\x8c\x53\x91"
 +#define EMPTY_BLOB_SHA1_BIN \
 +      ((const unsigned char *) EMPTY_BLOB_SHA1_BIN_LITERAL)
 +
 +static inline int is_empty_blob_sha1(const unsigned char *sha1)
 +{
 +      return !hashcmp(sha1, EMPTY_BLOB_SHA1_BIN);
 +}
 +
  int git_mkstemp(char *path, size_t n, const char *template);
  
  int git_mkstemps(char *path, size_t n, const char *template, int suffix_len);
@@@ -941,22 -929,6 +942,22 @@@ extern const char *fmt_name(const char 
  extern const char *git_editor(void);
  extern const char *git_pager(int stdout_is_tty);
  
 +struct ident_split {
 +      const char *name_begin;
 +      const char *name_end;
 +      const char *mail_begin;
 +      const char *mail_end;
 +      const char *date_begin;
 +      const char *date_end;
 +      const char *tz_begin;
 +      const char *tz_end;
 +};
 +/*
 + * Signals an success with 0, but time part of the result may be NULL
 + * if the input lacks timestamp and zone
 + */
 +extern int split_ident_line(struct ident_split *, const char *, int);
 +
  struct checkout {
        const char *base_dir;
        int base_dir_len;
@@@ -1305,6 -1277,4 +1306,6 @@@ extern struct startup_info *startup_inf
  /* builtin/merge.c */
  int checkout_fast_forward(const unsigned char *from, const unsigned char *to);
  
 +int sane_execvp(const char *file, char *const argv[]);
 +
  #endif /* CACHE_H */
diff --combined t/t5541-http-push.sh
index 1f5bfa2d063b41d8eece768946b6143ea57f287a,57c3e48dda34c19711341042f96739594eafb239..312e484090d1ceb392399768f2229ae553182fe7
@@@ -30,7 -30,6 +30,7 @@@ test_expect_success 'setup remote repos
        git clone --bare test_repo test_repo.git &&
        cd test_repo.git &&
        git config http.receivepack true &&
 +      git config core.logallrefupdates true &&
        ORIG_HEAD=$(git rev-parse --verify HEAD) &&
        cd - &&
        mv test_repo.git "$HTTPD_DOCUMENT_ROOT_PATH"
@@@ -168,7 -167,7 +168,7 @@@ test_expect_success 'push fails for non
  '
  
  test_expect_success 'push fails for non-fast-forward refs unmatched by remote helper: our output' '
-       test_i18ngrep "To prevent you from losing history, non-fast-forward updates were rejected" \
+       test_i18ngrep "Updates were rejected because" \
                output
  '
  
@@@ -216,55 -215,12 +216,55 @@@ test_expect_success 'push --mirror to r
        git push --mirror "$HTTPD_URL"/smart/alternates-mirror.git
  '
  
 -test_expect_success TTY 'quiet push' '
 +test_expect_success TTY 'push shows progress when stderr is a tty' '
 +      cd "$ROOT_PATH"/test_repo_clone &&
 +      test_commit noisy &&
 +      test_terminal git push >output 2>&1 &&
 +      grep "^Writing objects" output
 +'
 +
 +test_expect_success TTY 'push --quiet silences status and progress' '
        cd "$ROOT_PATH"/test_repo_clone &&
        test_commit quiet &&
 -      test_terminal git push --quiet --no-progress 2>&1 | tee output &&
 +      test_terminal git push --quiet >output 2>&1 &&
        test_cmp /dev/null output
  '
  
 +test_expect_success TTY 'push --no-progress silences progress but not status' '
 +      cd "$ROOT_PATH"/test_repo_clone &&
 +      test_commit no-progress &&
 +      test_terminal git push --no-progress >output 2>&1 &&
 +      grep "^To http" output &&
 +      ! grep "^Writing objects"
 +'
 +
 +test_expect_success 'push --progress shows progress to non-tty' '
 +      cd "$ROOT_PATH"/test_repo_clone &&
 +      test_commit progress &&
 +      git push --progress >output 2>&1 &&
 +      grep "^To http" output &&
 +      grep "^Writing objects" output
 +'
 +
 +test_expect_success 'http push gives sane defaults to reflog' '
 +      cd "$ROOT_PATH"/test_repo_clone &&
 +      test_commit reflog-test &&
 +      git push "$HTTPD_URL"/smart/test_repo.git &&
 +      git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git" \
 +              log -g -1 --format="%gn <%ge>" >actual &&
 +      echo "anonymous <anonymous@http.127.0.0.1>" >expect &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'http push respects GIT_COMMITTER_* in reflog' '
 +      cd "$ROOT_PATH"/test_repo_clone &&
 +      test_commit custom-reflog-test &&
 +      git push "$HTTPD_URL"/smart_custom_env/test_repo.git &&
 +      git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git" \
 +              log -g -1 --format="%gn <%ge>" >actual &&
 +      echo "Custom User <custom@example.com>" >expect &&
 +      test_cmp expect actual
 +'
 +
  stop_httpd
  test_done
diff --combined transport.c
index ea9dcb6612d92c123c66c2eee03bc933ccb2b13a,7864007c9c689c4674730b2bccf6f5212b726501..2dfac700b630aabb2d1014c51a08889b3a3bae82
@@@ -721,6 -721,10 +721,10 @@@ void transport_print_push_status(const 
  {
        struct ref *ref;
        int n = 0;
+       unsigned char head_sha1[20];
+       char *head;
+       head = resolve_refdup("HEAD", head_sha1, 1, NULL);
  
        if (verbose) {
                for (ref = refs; ref; ref = ref->next)
                    ref->status != REF_STATUS_UPTODATE &&
                    ref->status != REF_STATUS_OK)
                        n += print_one_push_status(ref, dest, n, porcelain);
-               if (ref->status == REF_STATUS_REJECT_NONFASTFORWARD)
-                       *nonfastforward = 1;
+               if (ref->status == REF_STATUS_REJECT_NONFASTFORWARD &&
+                   *nonfastforward != NON_FF_HEAD) {
+                       if (!strcmp(head, ref->name))
+                               *nonfastforward = NON_FF_HEAD;
+                       else
+                               *nonfastforward = NON_FF_OTHER;
+               }
        }
  }
  
@@@ -1154,7 -1163,7 +1163,7 @@@ int transport_disconnect(struct transpo
  }
  
  /*
 - * Strip username (and password) from an url and return
 + * Strip username (and password) from a URL and return
   * it in a newly allocated string.
   */
  char *transport_anonymize_url(const char *url)