Re: [PATCH v3 5/8] push: add an advice on unqualified push

2018-11-02 Thread Jeff King
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


Re: [PATCH v3 5/8] push: add an advice on unqualified push

2018-10-28 Thread Junio C Hamano
Ævar Arnfjörð Bjarmason   writes:

> + if (!advice_push_unqualified_ref_name)
> + return;
> +
> + if (get_oid(matched_src_name, ))
> + BUG("'%s' is not a valid object, "
> + "match_explicit_lhs() should catch this!",
> + matched_src_name);
> + type = oid_object_info(the_repository, , NULL);
> + if (type == OBJ_COMMIT) {
> + advise(_("The  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  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  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  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 x...@example.com
>  '
>  
> +test_expect_success 'unqualified  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


[PATCH v3 5/8] push: add an advice on unqualified push

2018-10-26 Thread Ævar Arnfjörð Bjarmason
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  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  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 'g...@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@evledraar.gmail.com> about that.

Signed-off-by: Ævar Arnfjörð Bjarmason 
---
 Documentation/config.txt |  7 +++
 advice.c |  2 ++
 advice.h |  1 +
 remote.c | 37 +
 t/t5505-remote.sh| 25 +
 5 files changed, 72 insertions(+)

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", _push_already_exists },
{ "pushFetchFirst", _push_fetch_first },
{ "pushNeedsForce", _push_needs_force },
+   { "pushUnqualifiedRefName", _push_unqualified_ref_name },
{ "statusHints", _status_hints },
{ "statusUoption", _status_u_option },
{ "commitBeforeMerge", _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  part of "git push
 *  :" 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, ))
+   BUG("'%s' is not a valid object, "
+   "match_explicit_lhs() should catch this!",
+   matched_src_name);
+   type =