Message ID | 20181026230741.23321-6-avarab@gmail.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | None | expand |
Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes: > + if (!advice_push_unqualified_ref_name) > + return; > + > + if (get_oid(matched_src_name, &oid)) > + BUG("'%s' is not a valid object, " > + "match_explicit_lhs() should catch this!", > + matched_src_name); > + type = oid_object_info(the_repository, &oid, NULL); > + if (type == OBJ_COMMIT) { > + advise(_("The <src> part of the refspec is a commit object.\n" > + "Did you mean to create a new branch by pushing to\n" > + "'%s:refs/heads/%s'?"), > + matched_src_name, dst_value); Good, except that "git push $there notes/commits^0:newnotes" may not want to become a branch and neither may "git push $there stash:wip", I think it is a reasonable piece of advice we'd give by default. I do not think it is worth the effort of inspecting the tree of the commit object to special case notes and stash ;-) > + } else if (type == OBJ_TAG) { > + advise(_("The <src> part of the refspec is a tag object.\n" > + "Did you mean to create a new tag by pushing to\n" > + "'%s:refs/tags/%s'?"), > + matched_src_name, dst_value); Good. > + } else if (type == OBJ_TREE) { > + advise(_("The <src> part of the refspec is a tree object.\n" > + "Did you mean to tag a new tree by pushing to\n" > + "'%s:refs/tags/%s'?"), > + matched_src_name, dst_value); > + } else if (type == OBJ_BLOB) { > + advise(_("The <src> part of the refspec is a blob object.\n" > + "Did you mean to tag a new blob by pushing to\n" > + "'%s:refs/tags/%s'?"), > + matched_src_name, dst_value); These two are questionable, but assuming that heads and tags are the only two hiearchies people would push into, they are acceptable choices. > + } else { > + BUG("'%s' should be commit/tag/tree/blob, is '%d'", > + matched_src_name, type); > + } > } > > static int match_explicit(struct ref *src, struct ref *dst, > diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh > index d2a2cdd453..2e58721f98 100755 > --- a/t/t5505-remote.sh > +++ b/t/t5505-remote.sh > @@ -1222,4 +1222,29 @@ test_expect_success 'add remote matching the "insteadOf" URL' ' > git remote add backup xyz@example.com > ' > > +test_expect_success 'unqualified <dst> refspec DWIM and advice' ' > + test_when_finished "(cd test && git tag -d some-tag)" && > + ( > + cd test && > + git tag -a -m "Some tag" some-tag master && > + for type in commit tag tree blob > + do > + if test "$type" = "blob" > + then > + oid=$(git rev-parse some-tag:file) > + else > + oid=$(git rev-parse some-tag^{$type}) > + fi && > + test_must_fail git push origin $oid:dst 2>err && > + test_i18ngrep "error: The destination you" err && > + test_i18ngrep "hint: Did you mean" err && > + test_must_fail git -c advice.pushUnqualifiedRefName=false \ > + push origin $oid:dst 2>err && > + test_i18ngrep "error: The destination you" err && > + test_i18ngrep ! "hint: Did you mean" err Any failure in the &&-chain (or the last grep) would not terminate the for loop, so for the purpose of determining the success of this test_expect_success, the last "blob" iteration is the only thing that matters. Which is probably not what you want. If testing all of these is important, then you can do this: ( exit_with=true && for type in ... do ... many ... && ... many ... && ... tests ... || exit_with=false done $exit_with ) or just say "|| exit" and leave the loop (and the subprocess running it) early on the first failure. > + done > + ) > +' > + > + > test_done
On Mon, Oct 29, 2018 at 02:14:02PM +0900, Junio C Hamano wrote: > Any failure in the &&-chain (or the last grep) would not terminate > the for loop, so for the purpose of determining the success of this > test_expect_success, the last "blob" iteration is the only thing > that matters. > > Which is probably not what you want. If testing all of these is > important, then you can do this: > > ( > exit_with=true && > for type in ... > do > ... many ... && > ... many ... && > ... tests ... || > exit_with=false > done > $exit_with > ) > > or just say "|| exit" and leave the loop (and the subprocess running > it) early on the first failure. You can skip the subshell and just "|| return 1" from the chain inside the loop. We run the test snippets inside an extra layer of function-call exactly to allow that. -Peff
diff --git a/Documentation/config.txt b/Documentation/config.txt index 552827935a..8ca465702e 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -320,6 +320,13 @@ advice.*:: tries to overwrite a remote ref that points at an object that is not a commit-ish, or make the remote ref point at an object that is not a commit-ish. + pushUnqualifiedRefname:: + Shown when linkgit:git-push[1] gives up trying to + guess based on the source and destination refs what + remote ref namespace the source belongs in, but where + we can still suggest that the user push to either + refs/heads/* or refs/tags/* based on the type of the + source object. statusHints:: Show directions on how to proceed from the current state in the output of linkgit:git-status[1], in diff --git a/advice.c b/advice.c index 3561cd64e9..3089a4ca65 100644 --- a/advice.c +++ b/advice.c @@ -9,6 +9,7 @@ int advice_push_non_ff_matching = 1; int advice_push_already_exists = 1; int advice_push_fetch_first = 1; int advice_push_needs_force = 1; +int advice_push_unqualified_ref_name = 1; int advice_status_hints = 1; int advice_status_u_option = 1; int advice_commit_before_merge = 1; @@ -62,6 +63,7 @@ static struct { { "pushAlreadyExists", &advice_push_already_exists }, { "pushFetchFirst", &advice_push_fetch_first }, { "pushNeedsForce", &advice_push_needs_force }, + { "pushUnqualifiedRefName", &advice_push_unqualified_ref_name }, { "statusHints", &advice_status_hints }, { "statusUoption", &advice_status_u_option }, { "commitBeforeMerge", &advice_commit_before_merge }, diff --git a/advice.h b/advice.h index ab24df0fd0..9a2f8b5226 100644 --- a/advice.h +++ b/advice.h @@ -9,6 +9,7 @@ extern int advice_push_non_ff_matching; extern int advice_push_already_exists; extern int advice_push_fetch_first; extern int advice_push_needs_force; +extern int advice_push_unqualified_ref_name; extern int advice_status_hints; extern int advice_status_u_option; extern int advice_commit_before_merge; diff --git a/remote.c b/remote.c index c7a0b9c46f..93f802509d 100644 --- a/remote.c +++ b/remote.c @@ -13,6 +13,7 @@ #include "mergesort.h" #include "argv-array.h" #include "commit-reach.h" +#include "advice.h" enum map_direction { FROM_SRC, FROM_DST }; @@ -1008,6 +1009,9 @@ static int match_explicit_lhs(struct ref *src, static void show_push_unqualified_ref_name_error(const char *dst_value, const char *matched_src_name) { + struct object_id oid; + enum object_type type; + /* * TRANSLATORS: "matches '%s'%" is the <dst> part of "git push * <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, "\n" "Neither worked, so we gave up. You must fully-qualify the ref."), dst_value, matched_src_name); + + if (!advice_push_unqualified_ref_name) + return; + + if (get_oid(matched_src_name, &oid)) + BUG("'%s' is not a valid object, " + "match_explicit_lhs() should catch this!", + matched_src_name); + type = oid_object_info(the_repository, &oid, NULL); + if (type == OBJ_COMMIT) { + advise(_("The <src> part of the refspec is a commit object.\n" + "Did you mean to create a new branch by pushing to\n" + "'%s:refs/heads/%s'?"), + matched_src_name, dst_value); + } else if (type == OBJ_TAG) { + advise(_("The <src> part of the refspec is a tag object.\n" + "Did you mean to create a new tag by pushing to\n" + "'%s:refs/tags/%s'?"), + matched_src_name, dst_value); + } else if (type == OBJ_TREE) { + advise(_("The <src> part of the refspec is a tree object.\n" + "Did you mean to tag a new tree by pushing to\n" + "'%s:refs/tags/%s'?"), + matched_src_name, dst_value); + } else if (type == OBJ_BLOB) { + advise(_("The <src> part of the refspec is a blob object.\n" + "Did you mean to tag a new blob by pushing to\n" + "'%s:refs/tags/%s'?"), + matched_src_name, dst_value); + } else { + BUG("'%s' should be commit/tag/tree/blob, is '%d'", + matched_src_name, type); + } } static int match_explicit(struct ref *src, struct ref *dst, diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index d2a2cdd453..2e58721f98 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -1222,4 +1222,29 @@ test_expect_success 'add remote matching the "insteadOf" URL' ' git remote add backup xyz@example.com ' +test_expect_success 'unqualified <dst> refspec DWIM and advice' ' + test_when_finished "(cd test && git tag -d some-tag)" && + ( + cd test && + git tag -a -m "Some tag" some-tag master && + for type in commit tag tree blob + do + if test "$type" = "blob" + then + oid=$(git rev-parse some-tag:file) + else + oid=$(git rev-parse some-tag^{$type}) + fi && + test_must_fail git push origin $oid:dst 2>err && + test_i18ngrep "error: The destination you" err && + test_i18ngrep "hint: Did you mean" err && + test_must_fail git -c advice.pushUnqualifiedRefName=false \ + push origin $oid:dst 2>err && + test_i18ngrep "error: The destination you" err && + test_i18ngrep ! "hint: Did you mean" err + done + ) +' + + test_done
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> --- Documentation/config.txt | 7 +++++++ advice.c | 2 ++ advice.h | 1 + remote.c | 37 +++++++++++++++++++++++++++++++++++++ t/t5505-remote.sh | 25 +++++++++++++++++++++++++ 5 files changed, 72 insertions(+)