push: add an advice on unqualified <dst> push

Add an advice to the recently improved error message added in f8aae12034 ("push: allow unqualified dest refspecs to DWIM", 2008-04-23). Now with advice.pushUnqualifiedRefName=true (on by default) we show a hint about how to proceed: $ ./git-push avar v2.19.0^{commit}:newbranch -n error: The destination you provided is not a full refname (i.e., starting with "refs/"). We tried to guess what you meant by: - Looking for a ref that matches 'newbranch' on the remote side. - Checking if the <src> being pushed ('v2.19.0^{commit}') is a ref in "refs/{heads,tags}/". If so we add a corresponding refs/{heads,tags}/ prefix on the remote side. Neither worked, so we gave up. You must fully qualify the ref. hint: The <src> part of the refspec is a commit object. hint: Did you mean to create a new branch by pushing to hint: 'v2.19.0^{commit}:refs/heads/newbranch'? error: failed to push some refs to 'git@github.com:avar/git.git' When trying to push a tag, tree or a blob we suggest that perhaps the user meant to push them to refs/tags/ instead. The if/else duplication for all of OBJ_{COMMIT,TAG,TREE,BLOB} is unfortunate, but is required to correctly mark the messages for translation. See the discussion in <87r2gxebsi.fsf@evledraar.gmail.com> about that. Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Ævar Arnfjörð Bjarmason committed Nov 13, 2018 at 19:52 UTC dd8dd300c636bf0a1352379aed88d9434a6ac7bb
5 files changed +75
Documentation/config/advice.txt
+7
@@ -30,6 +30,13 @@ advice.*::
30 tries to overwrite a remote ref that points at an
31 object that is not a commit-ish, or make the remote
32 ref point at an object that is not a commit-ish.
33 + pushUnqualifiedRefname::
34 + Shown when linkgit:git-push[1] gives up trying to
35 + guess based on the source and destination refs what
36 + remote ref namespace the source belongs in, but where
37 + we can still suggest that the user push to either
38 + refs/heads/* or refs/tags/* based on the type of the
39 + source object.
40 statusHints::
41 Show directions on how to proceed from the current
42 state in the output of linkgit:git-status[1], in
advice.c
+2
@@ -9,6 +9,7 @@ int advice_push_non_ff_matching = 1;
9 int advice_push_already_exists = 1;
10 int advice_push_fetch_first = 1;
11 int advice_push_needs_force = 1;
12 +int advice_push_unqualified_ref_name = 1;
13 int advice_status_hints = 1;
14 int advice_status_u_option = 1;
15 int advice_commit_before_merge = 1;
@@ -63,6 +64,7 @@ static struct {
64 { "pushAlreadyExists", &advice_push_already_exists },
65 { "pushFetchFirst", &advice_push_fetch_first },
66 { "pushNeedsForce", &advice_push_needs_force },
67 + { "pushUnqualifiedRefName", &advice_push_unqualified_ref_name },
68 { "statusHints", &advice_status_hints },
69 { "statusUoption", &advice_status_u_option },
70 { "commitBeforeMerge", &advice_commit_before_merge },
advice.h
+1
@@ -9,6 +9,7 @@ extern int advice_push_non_ff_matching;
9 extern int advice_push_already_exists;
10 extern int advice_push_fetch_first;
11 extern int advice_push_needs_force;
12 +extern int advice_push_unqualified_ref_name;
13 extern int advice_status_hints;
14 extern int advice_status_u_option;
15 extern int advice_commit_before_merge;
remote.c
+37
@@ -13,6 +13,7 @@
13 #include "mergesort.h"
14 #include "argv-array.h"
15 #include "commit-reach.h"
16 +#include "advice.h"
17
18 enum map_direction { FROM_SRC, FROM_DST };
19
@@ -1008,6 +1009,9 @@ static int match_explicit_lhs(struct ref *src,
1009 static void show_push_unqualified_ref_name_error(const char *dst_value,
1010 const char *matched_src_name)
1011 {
1012 + struct object_id oid;
1013 + enum object_type type;
1014 +
1015 /*
1016 * TRANSLATORS: "matches '%s'%" is the <dst> part of "git push
1017 * <remote> <src>:<dst>" push, and "being pushed ('%s')" is
@@ -1023,6 +1027,39 @@ static void show_push_unqualified_ref_name_error(const char *dst_value,
1027 "\n"
1028 "Neither worked, so we gave up. You must fully qualify the ref."),
1029 dst_value, matched_src_name);
1030 +
1031 + if (!advice_push_unqualified_ref_name)
1032 + return;
1033 +
1034 + if (get_oid(matched_src_name, &oid))
1035 + BUG("'%s' is not a valid object, "
1036 + "match_explicit_lhs() should catch this!",
1037 + matched_src_name);
1038 + type = oid_object_info(the_repository, &oid, NULL);
1039 + if (type == OBJ_COMMIT) {
1040 + advise(_("The <src> part of the refspec is a commit object.\n"
1041 + "Did you mean to create a new branch by pushing to\n"
1042 + "'%s:refs/heads/%s'?"),
1043 + matched_src_name, dst_value);
1044 + } else if (type == OBJ_TAG) {
1045 + advise(_("The <src> part of the refspec is a tag object.\n"
1046 + "Did you mean to create a new tag by pushing to\n"
1047 + "'%s:refs/tags/%s'?"),
1048 + matched_src_name, dst_value);
1049 + } else if (type == OBJ_TREE) {
1050 + advise(_("The <src> part of the refspec is a tree object.\n"
1051 + "Did you mean to tag a new tree by pushing to\n"
1052 + "'%s:refs/tags/%s'?"),
1053 + matched_src_name, dst_value);
1054 + } else if (type == OBJ_BLOB) {
1055 + advise(_("The <src> part of the refspec is a blob object.\n"
1056 + "Did you mean to tag a new blob by pushing to\n"
1057 + "'%s:refs/tags/%s'?"),
1058 + matched_src_name, dst_value);
1059 + } else {
1060 + BUG("'%s' should be commit/tag/tree/blob, is '%d'",
1061 + matched_src_name, type);
1062 + }
1063 }
1064
1065 static int match_explicit(struct ref *src, struct ref *dst,
t/t5505-remote.sh
+28
@@ -1222,4 +1222,32 @@ test_expect_success 'add remote matching the "insteadOf" URL' '
1222 git remote add backup xyz@example.com
1223 '
1224
1225 +test_expect_success 'unqualified <dst> refspec DWIM and advice' '
1226 + test_when_finished "(cd test && git tag -d some-tag)" &&
1227 + (
1228 + cd test &&
1229 + git tag -a -m "Some tag" some-tag master &&
1230 + exit_with=true &&
1231 + for type in commit tag tree blob
1232 + do
1233 + if test "$type" = "blob"
1234 + then
1235 + oid=$(git rev-parse some-tag:file)
1236 + else
1237 + oid=$(git rev-parse some-tag^{$type})
1238 + fi &&
1239 + test_must_fail git push origin $oid:dst 2>err &&
1240 + test_i18ngrep "error: The destination you" err &&
1241 + test_i18ngrep "hint: Did you mean" err &&
1242 + test_must_fail git -c advice.pushUnqualifiedRefName=false \
1243 + push origin $oid:dst 2>err &&
1244 + test_i18ngrep "error: The destination you" err &&
1245 + test_i18ngrep ! "hint: Did you mean" err ||
1246 + exit_with=false
1247 + done &&
1248 + $exit_with
1249 + )
1250 +'
1251 +
1252 +
1253 test_done