Stefan Beller <[email protected]> writes:
> Every once in a while someone complains to the mailing list to have
> run into this weird assertion[1]. The usual response from the mailing
> list is link to old discussions[2], and acknowledging the problem
> stating it is known.
>
> This patch accomplishes two things:
>
> 1. Switch assert() to die("BUG") to give a more readable message.
>
> 2. Take one of the cases where we hit a BUG and turn it into a normal
> "there was something wrong with the input" message.
>
> This assertion triggered for cases where there wasn't a programming
> bug, but just bogus input. In particular, if the user asks for a
> pathspec that is inside a submodule, we shouldn't assert() or
> die("BUG"); we should tell the user their request is bogus.
>
> The only reason we did not check for it, is the expensive nature
> of such a check, so callers avoid setting the flag
> PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE. However when we die due
> to bogus input, the expense of CPU cycles spent outweighs the user
> wondering what went wrong, so run that check unconditionally before
> dying with a more generic error message.
>
> Note: There is a case (e.g. "git -C submodule add .") in which we call
> strip_submodule_slash_expensive, as git-add requests it via the flag
> PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE, but the assert used to
> trigger nevertheless, because the flag PATHSPEC_LITERAL was not set,
> such that we executed
>
> if (item->nowildcard_len < prefixlen)
> item->nowildcard_len = prefixlen;
>
> and prefixlen was not adapted (e.g. it was computed from "submodule/")
> So in the die_inside_submodule_path function we also need handle paths,
> that were stripped before, i.e. are the exact submodule path. This
> is why the conditions in die_inside_submodule_path are slightly
> different than in strip_submodule_slash_expensive.
>
> [1] https://www.google.com/search?q=item-%3Enowildcard_len
> [2]
> http://git.661346.n2.nabble.com/assert-failed-in-submodule-edge-case-td7628687.html
> https://www.spinics.net/lists/git/msg249473.html
>
> Helped-by: Jeff King <[email protected]>
> Helped-by: Junio C Hamano <[email protected]>
> Signed-off-by: Stefan Beller <[email protected]>
> ---
For future reference, do not bury a useful fix behind unproven new
things. The main purpose of this two-patch series is this change,
and it does not have to wait for the change to allow test_commit to
notice "-C" you have in another series.
Just write it in longhand, and when both topics graduate, send in
another patch to update "(cd <dir> && test_commit <others>)" to use
the new "test_commit -C <dir> <others>".
Thanks.
> pathspec.c | 35 +++++++++++++++++++++++++++++++++--
> t/t6134-pathspec-in-submodule.sh | 33 +++++++++++++++++++++++++++++++++
> 2 files changed, 66 insertions(+), 2 deletions(-)
> create mode 100755 t/t6134-pathspec-in-submodule.sh
>
> diff --git a/pathspec.c b/pathspec.c
> index d4efcf6662..42cd83c235 100644
> --- a/pathspec.c
> +++ b/pathspec.c
> @@ -296,6 +296,27 @@ static void strip_submodule_slash_expensive(struct
> pathspec_item *item)
> }
> }
>
> +static void die_inside_submodule_path(struct pathspec_item *item)
> +{
> + int i;
> +
> + for (i = 0; i < active_nr; i++) {
> + struct cache_entry *ce = active_cache[i];
> + int ce_len = ce_namelen(ce);
> +
> + if (!S_ISGITLINK(ce->ce_mode))
> + continue;
> +
> + if (item->len < ce_len ||
> + !(item->match[ce_len] == '/' || item->match[ce_len] ==
> '\0') ||
> + memcmp(ce->name, item->match, ce_len))
> + continue;
> +
> + die(_("Pathspec '%s' is in submodule '%.*s'"),
> + item->original, ce_len, ce->name);
> + }
> +}
> +
> /*
> * Perform the initialization of a pathspec_item based on a pathspec element.
> */
> @@ -391,8 +412,18 @@ static void init_pathspec_item(struct pathspec_item
> *item, unsigned flags,
> }
>
> /* sanity checks, pathspec matchers assume these are sane */
> - assert(item->nowildcard_len <= item->len &&
> - item->prefix <= item->len);
> + if (item->nowildcard_len > item->len ||
> + item->prefix > item->len) {
> + /*
> + * This case can be triggered by the user pointing us to a
> + * pathspec inside a submodule, which is an input error.
> + * Detect that here and complain, but fallback in the
> + * non-submodule case to a BUG, as we have no idea what
> + * would trigger that.
> + */
> + die_inside_submodule_path(item);
> + die ("BUG: item->nowildcard_len > item->len || item->prefix >
> item->len)");
> + }
> }
>
> static int pathspec_item_cmp(const void *a_, const void *b_)
> diff --git a/t/t6134-pathspec-in-submodule.sh
> b/t/t6134-pathspec-in-submodule.sh
> new file mode 100755
> index 0000000000..2900d8d06e
> --- /dev/null
> +++ b/t/t6134-pathspec-in-submodule.sh
> @@ -0,0 +1,33 @@
> +#!/bin/sh
> +
> +test_description='test case exclude pathspec'
> +
> +TEST_CREATE_SUBMODULE=yes
> +. ./test-lib.sh
> +
> +test_expect_success 'setup a submodule' '
> + git submodule add ./pretzel.bare sub &&
> + git commit -a -m "add submodule" &&
> + git submodule deinit --all
> +'
> +
> +cat <<EOF >expect
> +fatal: Pathspec 'sub/a' is in submodule 'sub'
> +EOF
> +
> +test_expect_success 'error message for path inside submodule' '
> + echo a >sub/a &&
> + test_must_fail git add sub/a 2>actual &&
> + test_cmp expect actual
> +'
> +
> +cat <<EOF >expect
> +fatal: Pathspec '.' is in submodule 'sub'
> +EOF
> +
> +test_expect_success 'error message for path inside submodule from within
> submodule' '
> + test_must_fail git -C sub add . 2>actual &&
> + test_cmp expect actual
> +'
> +
> +test_done