git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [GSoC][PATCH v2] ref-filter: Make "--contains <id>" less chatty if <id> is invalid
@ 2018-02-23 16:25 Paul-Sebastian Ungureanu
  2018-02-23 20:58 ` Junio C Hamano
  0 siblings, 1 reply; 4+ messages in thread
From: Paul-Sebastian Ungureanu @ 2018-02-23 16:25 UTC (permalink / raw)
  To: git; +Cc: Paul-Sebastian Ungureanu

Hello,
I have made the changes after review. This is the updated patch
based on what was discussed last time [1].

In this patch, I have fixed the same issue that was also seen
in "git branch" and "git for-reach-ref". I have also removed the
dead code that was left and updated the patches accordingly.

[1] https://public-inbox.org/git/20180219212130.4217-1-ungureanupaulsebastian@gmail.com/

Best regards,
Paul Ungureanu

https://public-inbox.org/git/20180219212130.4217-1-ungureanupaulsebastian@gmail.com/

---

Some git commands which use --contains <id> print the whole
help text if <id> is invalid. It should only show the error
message instead.

This patch applies to "git tag", "git branch", "git for-each-ref".

This bug was a side effect of looking up the commit in option
parser callback. When a error occurs in the option parser, the
full usage is shown. To fix this bug, the part related to
looking up the commit was moved outside of the option parser
to the ref-filter module.

Basically, the option parser only parses strings that represent
commits and the ref-filter performs the commit look-up. If an
error occurs during the option parsing, then it must be an invalid
argument and the user should be informed of usage, but if a error
occurs during ref-filtering, then it is a problem with the
argument.

Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com>
---
 builtin/branch.c       | 12 +++----
 builtin/for-each-ref.c |  4 +--
 builtin/tag.c          | 16 ++++-----
 parse-options.h        |  3 +-
 ref-filter.c           | 23 +++++++++++++
 ref-filter.h           |  3 ++
 t/tcontains.sh         | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 136 insertions(+), 17 deletions(-)
 create mode 100755 t/tcontains.sh

diff --git a/builtin/branch.c b/builtin/branch.c
index 8dcc2ed05..43442c12e 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -596,10 +596,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 		OPT__COLOR(&branch_use_color, N_("use colored output")),
 		OPT_SET_INT('r', "remotes",     &filter.kind, N_("act on remote-tracking branches"),
 			FILTER_REFS_REMOTES),
-		OPT_CONTAINS(&filter.with_commit, N_("print only branches that contain the commit")),
-		OPT_NO_CONTAINS(&filter.no_commit, N_("print only branches that don't contain the commit")),
-		OPT_WITH(&filter.with_commit, N_("print only branches that contain the commit")),
-		OPT_WITHOUT(&filter.no_commit, N_("print only branches that don't contain the commit")),
+		OPT_CONTAINS(&filter.with_commit_strs, N_("print only branches that contain the commit")),
+		OPT_NO_CONTAINS(&filter.no_commit_strs, N_("print only branches that don't contain the commit")),
+		OPT_WITH(&filter.with_commit_strs, N_("print only branches that contain the commit")),
+		OPT_WITHOUT(&filter.no_commit_strs, N_("print only branches that don't contain the commit")),
 		OPT__ABBREV(&filter.abbrev),
 
 		OPT_GROUP(N_("Specific git-branch actions:")),
@@ -657,8 +657,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 	if (!delete && !rename && !copy && !edit_description && !new_upstream && !unset_upstream && argc == 0)
 		list = 1;
 
-	if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr ||
-	    filter.no_commit)
+	if (filter.with_commit_strs.nr || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr ||
+	    filter.no_commit_strs.nr)
 		list = 1;
 
 	if (!!delete + !!rename + !!copy + !!new_upstream +
diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
index e931be9ce..deb9a779a 100644
--- a/builtin/for-each-ref.c
+++ b/builtin/for-each-ref.c
@@ -44,8 +44,8 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
 			     parse_opt_object_name),
 		OPT_MERGED(&filter, N_("print only refs that are merged")),
 		OPT_NO_MERGED(&filter, N_("print only refs that are not merged")),
-		OPT_CONTAINS(&filter.with_commit, N_("print only refs which contain the commit")),
-		OPT_NO_CONTAINS(&filter.no_commit, N_("print only refs which don't contain the commit")),
+		OPT_CONTAINS(&filter.with_commit_strs, N_("print only refs which contain the commit")),
+		OPT_NO_CONTAINS(&filter.no_commit_strs, N_("print only refs which don't contain the commit")),
 		OPT_BOOL(0, "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
 		OPT_END(),
 	};
diff --git a/builtin/tag.c b/builtin/tag.c
index 8885e21dd..6be7f53ae 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -396,10 +396,10 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 
 		OPT_GROUP(N_("Tag listing options")),
 		OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")),
-		OPT_CONTAINS(&filter.with_commit, N_("print only tags that contain the commit")),
-		OPT_NO_CONTAINS(&filter.no_commit, N_("print only tags that don't contain the commit")),
-		OPT_WITH(&filter.with_commit, N_("print only tags that contain the commit")),
-		OPT_WITHOUT(&filter.no_commit, N_("print only tags that don't contain the commit")),
+		OPT_CONTAINS(&filter.with_commit_strs, N_("print only tags that contain the commit")),
+		OPT_NO_CONTAINS(&filter.no_commit_strs, N_("print only tags that don't contain the commit")),
+		OPT_WITH(&filter.with_commit_strs, N_("print only tags that contain the commit")),
+		OPT_WITHOUT(&filter.no_commit_strs, N_("print only tags that don't contain the commit")),
 		OPT_MERGED(&filter, N_("print only tags that are merged")),
 		OPT_NO_MERGED(&filter, N_("print only tags that are not merged")),
 		OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"),
@@ -435,8 +435,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 	if (!cmdmode) {
 		if (argc == 0)
 			cmdmode = 'l';
-		else if (filter.with_commit || filter.no_commit ||
-			 filter.points_at.nr || filter.merge_commit ||
+		else if (filter.points_at.nr || filter.merge_commit ||
+			 filter.with_commit_strs.nr || filter.no_commit_strs.nr ||
 			 filter.lines != -1)
 			cmdmode = 'l';
 	}
@@ -473,9 +473,9 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 	}
 	if (filter.lines != -1)
 		die(_("-n option is only allowed in list mode"));
-	if (filter.with_commit)
+	if (filter.with_commit_strs.nr)
 		die(_("--contains option is only allowed in list mode"));
-	if (filter.no_commit)
+	if (filter.no_commit_strs.nr)
 		die(_("--no-contains option is only allowed in list mode"));
 	if (filter.points_at.nr)
 		die(_("--points-at option is only allowed in list mode"));
diff --git a/parse-options.h b/parse-options.h
index af711227a..4b4734f2e 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -256,8 +256,9 @@ extern int parse_opt_passthru_argv(const struct option *, const char *, int);
 #define _OPT_CONTAINS_OR_WITH(name, variable, help, flag) \
 	{ OPTION_CALLBACK, 0, name, (variable), N_("commit"), (help), \
 	  PARSE_OPT_LASTARG_DEFAULT | flag, \
-	  parse_opt_commits, (intptr_t) "HEAD" \
+	  parse_opt_string_list, (intptr_t) "HEAD" \
 	}
+
 #define OPT_CONTAINS(v, h) _OPT_CONTAINS_OR_WITH("contains", v, h, PARSE_OPT_NONEG)
 #define OPT_NO_CONTAINS(v, h) _OPT_CONTAINS_OR_WITH("no-contains", v, h, PARSE_OPT_NONEG)
 #define OPT_WITH(v, h) _OPT_CONTAINS_OR_WITH("with", v, h, PARSE_OPT_HIDDEN | PARSE_OPT_NONEG)
diff --git a/ref-filter.c b/ref-filter.c
index f9e25aea7..aa282a27f 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -2000,6 +2000,25 @@ static void do_merge_filter(struct ref_filter_cbdata *ref_cbdata)
 	free(to_clear);
 }
 
+int add_str_to_commit_list(struct string_list_item *item, void *commit_list)
+{
+	struct object_id oid;
+	struct commit *commit;
+
+	if (get_oid(item->string, &oid)) {
+		error(_("malformed object name %s"), item->string);
+		exit(1);
+	}
+	commit = lookup_commit_reference(&oid);
+	if (!commit) {
+		error(_("no such commit %s"), item->string);
+		exit(1);
+	}
+	commit_list_insert(commit, commit_list);
+
+	return 0;
+}
+
 /*
  * API for filtering a set of refs. Based on the type of refs the user
  * has requested, we iterate through those refs and apply filters
@@ -2012,6 +2031,10 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int
 	int ret = 0;
 	unsigned int broken = 0;
 
+	/* Convert string representation and add to commit list. */
+	for_each_string_list(&filter->with_commit_strs, add_str_to_commit_list, &filter->with_commit);
+	for_each_string_list(&filter->no_commit_strs, add_str_to_commit_list, &filter->no_commit);
+
 	ref_cbdata.array = array;
 	ref_cbdata.filter = filter;
 
diff --git a/ref-filter.h b/ref-filter.h
index 0d98342b3..62f37760f 100644
--- a/ref-filter.h
+++ b/ref-filter.h
@@ -55,6 +55,9 @@ struct ref_filter {
 	struct commit_list *with_commit;
 	struct commit_list *no_commit;
 
+	struct string_list with_commit_strs;
+	struct string_list no_commit_strs;
+
 	enum {
 		REF_FILTER_MERGED_NONE = 0,
 		REF_FILTER_MERGED_INCLUDE,
diff --git a/t/tcontains.sh b/t/tcontains.sh
new file mode 100755
index 000000000..4856111ff
--- /dev/null
+++ b/t/tcontains.sh
@@ -0,0 +1,92 @@
+#!/bin/sh
+
+test_description='Test "contains" argument behavior'
+
+. ./test-lib.sh
+
+test_expect_success 'setup ' '
+	git init . &&
+	echo "this is a test" >file &&
+	git add -A &&
+	git commit -am "tag test" &&
+	git tag "v1.0" &&
+	git tag "v1.1"
+'
+
+test_expect_success 'tag --contains <existent_tag>' '
+	git tag --contains "v1.0" >actual &&
+	grep "v1.0" actual &&
+	grep "v1.1" actual
+'
+
+test_expect_success 'tag --contains <inexistent_tag>' '
+	test_must_fail git tag --contains "notag" 2>actual &&
+	test_i18ngrep "error" actual
+'
+
+test_expect_success 'tag --no-contains <existent_tag>' '
+	git tag --no-contains "v1.1" >actual &&
+	test_line_count = 0 actual
+'
+
+test_expect_success 'tag --no-contains <inexistent_tag>' '
+	test_must_fail git tag --no-contains "notag" 2>actual &&
+	test_i18ngrep "error" actual
+'
+
+test_expect_success 'tag usage error' '
+	test_must_fail git tag --noopt 2>actual &&
+	test_i18ngrep "usage" actual
+'
+
+test_expect_success 'branch --contains <existent_commit>' '
+	git branch --contains "master" >actual &&
+	test_i18ngrep "master" actual
+'
+
+test_expect_success 'branch --contains <inexistent_commit>' '
+	test_must_fail git branch --no-contains "nocommit" 2>actual &&
+	test_i18ngrep "error" actual
+'
+
+test_expect_success 'branch --no-contains <existent_commit>' '
+	git branch --no-contains "master" >actual &&
+	test_line_count = 0 actual
+'
+
+test_expect_success 'branch --no-contains <inexistent_commit>' '
+	test_must_fail git branch --no-contains "nocommit" 2>actual &&
+	test_i18ngrep "error" actual
+'
+
+test_expect_success 'branch usage error' '
+	test_must_fail git branch --noopt 2>actual &&
+	test_i18ngrep "usage" actual
+'
+
+test_expect_success 'for-each-ref --contains <existent_object>' '
+	git for-each-ref --contains "master" >actual &&
+	test_line_count = 3 actual
+'
+
+test_expect_success 'for-each-ref --contains <inexistent_object>' '
+	test_must_fail git for-each-ref --no-contains "noobject" 2>actual &&
+	test_i18ngrep "error" actual
+'
+
+test_expect_success 'for-each-ref --no-contains <existent_object>' '
+	git for-each-ref --no-contains "master" >actual &&
+	test_line_count = 0 actual
+'
+
+test_expect_success 'for-each-ref --no-contains <inexistent_object>' '
+	test_must_fail git for-each-ref --no-contains "noobject" 2>actual &&
+	test_i18ngrep "error" actual
+'
+
+test_expect_success 'for-each-ref usage error' '
+	test_must_fail git for-each-ref --noopt 2>actual &&
+	test_i18ngrep "usage" actual
+'
+
+test_done
-- 
2.16.1.195.g8f471d4ad.dirty


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [GSoC][PATCH v2] ref-filter: Make "--contains <id>" less chatty if <id> is invalid
  2018-02-23 16:25 [GSoC][PATCH v2] ref-filter: Make "--contains <id>" less chatty if <id> is invalid Paul-Sebastian Ungureanu
@ 2018-02-23 20:58 ` Junio C Hamano
  2018-02-23 23:44   ` Junio C Hamano
  0 siblings, 1 reply; 4+ messages in thread
From: Junio C Hamano @ 2018-02-23 20:58 UTC (permalink / raw)
  To: Paul-Sebastian Ungureanu; +Cc: git

Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com> writes:

> Hello,
> I have made the changes after review. This is the updated patch
> based on what was discussed last time [1].
>
> In this patch, I have fixed the same issue that was also seen
> in "git branch" and "git for-reach-ref". I have also removed the
> dead code that was left and updated the patches accordingly.
>
> [1] https://public-inbox.org/git/20180219212130.4217-1-ungureanupaulsebastian@gmail.com/
>
> Best regards,
> Paul Ungureanu
>
> https://public-inbox.org/git/20180219212130.4217-1-ungureanupaulsebastian@gmail.com/

You do not want all of the above, upto and including the "---" below,
to appear in the log message of the resulting commit.  One way to
tell the reading end that you have such preamble in your message is
to write "-- >8 --" instead of "---" there.

> ---
>
> Some git commands which use --contains <id> print the whole
> help text if <id> is invalid. It should only show the error
> message instead.
>
> This patch applies to "git tag", "git branch", "git for-each-ref".
>
> This bug was a side effect of looking up the commit in option
> parser callback. When a error occurs in the option parser, the
> full usage is shown. To fix this bug, the part related to
> looking up the commit was moved outside of the option parser
> to the ref-filter module.
>
> Basically, the option parser only parses strings that represent
> commits and the ref-filter performs the commit look-up. If an
> error occurs during the option parsing, then it must be an invalid
> argument and the user should be informed of usage, but if a error
> occurs during ref-filtering, then it is a problem with the
> argument.

The same problem appears for "git branch --points-at <commit>",
doesn't it?

> diff --git a/ref-filter.c b/ref-filter.c
> index f9e25aea7..aa282a27f 100644
> --- a/ref-filter.c
> +++ b/ref-filter.c
> @@ -2000,6 +2000,25 @@ static void do_merge_filter(struct ref_filter_cbdata *ref_cbdata)
>  	free(to_clear);
>  }
>  
> +int add_str_to_commit_list(struct string_list_item *item, void *commit_list)

If this function can be static to this file (and I suspect it is),
please make it so.

> +{
> +	struct object_id oid;
> +	struct commit *commit;
> +
> +	if (get_oid(item->string, &oid)) {
> +		error(_("malformed object name %s"), item->string);
> +		exit(1);
> +	}
> +	commit = lookup_commit_reference(&oid);
> +	if (!commit) {
> +		error(_("no such commit %s"), item->string);
> +		exit(1);
> +	}
> +	commit_list_insert(commit, commit_list);

The original (i.e. before this patch) does commit_list_insert() in
the order the commits are given on the command line.  This version
collects the command line arguments with string_list_append() that
preserves the order, and feeds them to commit_list_insert() here, so
the resulting commit_list will have the commits in the same order
before or after this patch.

Which is good.

> +	return 0;
> +}

The code after this patch is a strict improvement (the current code
do not do so either), so this is outside the scope of this patch,
but we may want to give this function another "const char *" that is
used to report which option we got a malformed object name for.

> @@ -2012,6 +2031,10 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int
>  	int ret = 0;
>  	unsigned int broken = 0;
>  
> +	/* Convert string representation and add to commit list. */
> +	for_each_string_list(&filter->with_commit_strs, add_str_to_commit_list, &filter->with_commit);
> +	for_each_string_list(&filter->no_commit_strs, add_str_to_commit_list, &filter->no_commit);
> +

As it does not use item->util in the callback helper, this should
use for_each_string_list_item() instead; then you can do

	for_each_string_list_item(item, &filter_no_commit_strs)
		collect_commit(&filter->no_commit, item->string);

which allows the other helper take a simple string, instead of
requiring a string_list_item.

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [GSoC][PATCH v2] ref-filter: Make "--contains <id>" less chatty if <id> is invalid
  2018-02-23 20:58 ` Junio C Hamano
@ 2018-02-23 23:44   ` Junio C Hamano
  2018-02-24 14:12     ` Paul-Sebastian Ungureanu
  0 siblings, 1 reply; 4+ messages in thread
From: Junio C Hamano @ 2018-02-23 23:44 UTC (permalink / raw)
  To: Paul-Sebastian Ungureanu; +Cc: git

Junio C Hamano <gitster@pobox.com> writes:

> Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com> writes:
>
>> Basically, the option parser only parses strings that represent
>> commits and the ref-filter performs the commit look-up. If an
>> error occurs during the option parsing, then it must be an invalid
>> argument and the user should be informed of usage, but if a error
>> occurs during ref-filtering, then it is a problem with the
>> argument.

After staring the code a bit longer, I started to dislike the
approach taken by this patch quite a lot.  Isn't the problem that we
let parse-options machinery to show the usage help, which is
designed to be shown when the user does not know what options the
command supports, even when we recognised the option perfectly fine?

That is, if we added a mechanism for a callback function given to
OPT_CALLBACK to tell the calling parse-options machinery "I
recognise the option; but the value given to the option is wrong and
that is why I am returning an error", and made the caller in the
parse-options machinery to refrain from showing the usage help,
would it solve the issue with minimum fuss and stop the execution at
the very first error we detect?

Stepping back even further, I wonder if any error detected in a
custom callback handler given to OPT_CALLBACK even wants to show the
usage help.  I offhand do not think of any situation--- the callback
was called only because OPT_CALLBACK item in the options[] list
matched what the user gave us, so at that point we know the user
gave us one of the valid options.  The error message from the
callback may say "Hey I only take commit object name", or it could
(theoretically) even be "Sorry I do not take any values", but in any
case, I do not think there is a reason for a failure detected in the
callback should lead to the usage help.

So perhaps "if we added a machanism...to tell..." part in the
previous paragraph is not even needed, and the only thing we need to
do is to make the caller in parse-options that calls a custom
callback function given to OPT_CALLBACK to stop giving the usage
help.  Wouldn't that automatically fix the "branch --points-at
garbage" issue that is not addressed by this patch, too?

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [GSoC][PATCH v2] ref-filter: Make "--contains <id>" less chatty if <id> is invalid
  2018-02-23 23:44   ` Junio C Hamano
@ 2018-02-24 14:12     ` Paul-Sebastian Ungureanu
  0 siblings, 0 replies; 4+ messages in thread
From: Paul-Sebastian Ungureanu @ 2018-02-24 14:12 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Hello,

Your proposed solution makes a lot more sense. I have actually
considered a solution similar to this (the third solution from [1]),
but found it more complicated. I did not account for the fact that
once a callback is called, the user is already aware of the available
options and the user only supplied an invalid argument value.

I also have to make sure that all parsers (all callbacks and standard
ones, for integer, filename, etc.) are already printing errors
appropriately. Otherwise, some commands may fail and the user will not
be aware of it because nothing will be shown (no usage is shown and no
errors either).

I will be implementing this solution and come back with another patch.

Thank you for your review. I really appreciate it!

[1] https://public-inbox.org/git/20160118215433.GB24136@sigill.intra.pe
ff.net/

Best regards,
Paul Ungureanu

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2018-02-24 14:12 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-02-23 16:25 [GSoC][PATCH v2] ref-filter: Make "--contains <id>" less chatty if <id> is invalid Paul-Sebastian Ungureanu
2018-02-23 20:58 ` Junio C Hamano
2018-02-23 23:44   ` Junio C Hamano
2018-02-24 14:12     ` Paul-Sebastian Ungureanu

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).