Stefan Beller <sbel...@google.com> 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 <p...@peff.net>
> Helped-by: Junio C Hamano <gits...@pobox.com>
> Signed-off-by: Stefan Beller <sbel...@google.com>
> ---

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

Reply via email to