Merge branch 'ct/advise-push-default'
authorJunio C Hamano <gitster@pobox.com>
Fri, 20 Apr 2012 22:50:37 +0000 (15:50 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 20 Apr 2012 22:50:37 +0000 (15:50 -0700)
Break down the cases in which "git push" fails due to non-ff into
three categories, and give separate advise messages for each case.

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 
builtin/push.c
cache.h
t/t5541-http-push.sh
transport.c
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 d7964c7eb523ce95944d559e6123df4c2b6d3934,57c3e48dda34c19711341042f96739594eafb239..5b170be2c0a8a97b5d7a0bc1a980afa4da9c40bd
@@@ -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
  '
  
@@@ -223,25 -222,5 +223,25 @@@ test_expect_success TTY 'quiet push' 
        test_cmp /dev/null 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)