Re: [PATCH v4] config: add conditional include

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Ping..

On Thu, Jul 14, 2016 at 10:33 PM, Nguyễn Thái Ngọc Duy
<pclouds@xxxxxxxxx> wrote:
> Helped-by: Jeff King <peff@xxxxxxxx>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@xxxxxxxxx>
> ---
>  The diff from v3 is mostly clarification in code and document.
>
>     diff --git a/Documentation/config.txt b/Documentation/config.txt
>     index 18623ee..d971334 100644
>     --- a/Documentation/config.txt
>     +++ b/Documentation/config.txt
>     @@ -99,10 +99,9 @@ Supported keywords are:
>
>      `gitdir`::
>         The environment variable `GIT_DIR` must match the following
>     -   pattern for files to be included. The pattern can contain
>     -   standard globbing wildcards and two additional ones, `**/` and
>     -   `/**`, that can match multiple path components. Please refer
>     -   to linkgit:gitignore[5] for details. For convenience:
>     +   pattern for files to be included. The pattern shares the same
>     +   syntax as patterns in link:gitignore[5] with a few exceptions
>     +   below:
>
>       * If the pattern starts with `~/`, `~` will be substituted with the
>         content of the environment variable `HOME`.
>     diff --git a/config.c b/config.c
>     index ff44e00..690f3d5 100644
>     --- a/config.c
>     +++ b/config.c
>     @@ -183,6 +183,11 @@ static int prepare_include_condition_pattern(struct strbuf *pat)
>
>                 strbuf_add_absolute_path(&path, home);
>                 strbuf_splice(pat, 0, 1, path.buf, path.len);
>     +           /*
>     +            * This part, path.buf[0..len], should be considered
>     +            * a literal string even if it has wildcards in it,
>     +            * because those wildcards are not wanted by the user.
>     +            */
>                 prefix = path.len + 1 /*slash*/;
>                 strbuf_release(&path);
>         } else if (pat->buf[0] == '.' && is_dir_sep(pat->buf[1])) {
>     @@ -198,6 +203,11 @@ static int prepare_include_condition_pattern(struct strbuf *pat)
>                 if (!slash)
>                         die("BUG: how is this possible?");
>                 strbuf_splice(pat, 0, 1, path.buf, slash - path.buf);
>     +           /*
>     +            * This part, path.buf[0..slash], should be consider
>     +            * a literal string even if it has wildcards in it,
>     +            * because those wildcards are not wanted by the user.
>     +            */
>                 prefix = slash - path.buf + 1 /* slash */;
>                 strbuf_release(&path);
>         } else if (!is_absolute_path(pat->buf))
>     @@ -224,8 +234,9 @@ static int include_by_gitdir(const char *cond, size_t cond_len, int icase)
>
>         if (prefix > 0) {
>                 /*
>     -            * perform literal matching on the prefix part so that
>     -            * any wildcard character in it can't create side effects.
>     +            * perform literal matching on the expanded prefix
>     +            * part so that any wildcard character in it (e.g in
>     +            * the expansion of ~) can't create side effects.
>                  */
>                 if (text.len < prefix)
>                         goto done;
>  Documentation/config.txt  |  47 +++++++++++++++++++
>  config.c                  | 113 +++++++++++++++++++++++++++++++++++++++++++++-
>  t/t1305-config-include.sh |  56 +++++++++++++++++++++++
>  3 files changed, 214 insertions(+), 2 deletions(-)
>
> diff --git a/Documentation/config.txt b/Documentation/config.txt
> index db05dec..d971334 100644
> --- a/Documentation/config.txt
> +++ b/Documentation/config.txt
> @@ -91,6 +91,42 @@ found at the location of the include directive. If the value of the
>  relative to the configuration file in which the include directive was
>  found.  See below for examples.
>
> +Included files can be grouped into subsections where the subsection
> +name is the condition that must be met for the files to be included.
> +The condition starts with a keyword, followed by a colon and a
> +pattern. The interpretation of the pattern depends on the keyword.
> +Supported keywords are:
> +
> +`gitdir`::
> +       The environment variable `GIT_DIR` must match the following
> +       pattern for files to be included. The pattern shares the same
> +       syntax as patterns in link:gitignore[5] with a few exceptions
> +       below:
> +
> + * If the pattern starts with `~/`, `~` will be substituted with the
> +   content of the environment variable `HOME`.
> +
> + * If the pattern starts with `./`, it is replaced with the directory
> +   containing the current config file.
> +
> + * If the pattern does not start with either `~/`, `./` or `/`, `**/`
> +   will be automatically prepended. For example, the pattern `foo/bar`
> +   becomes `**/foo/bar` and would match `/any/path/to/foo/bar`.
> +
> + * If the pattern ends with `/`, `**` will be automatically added. For
> +   example, the pattern `foo/` becomes `foo/**`. In other words, it
> +   matches "foo" and everything inside, recursively.
> +
> +`gitdir/i`::
> +       This is the same as `gitdir` except that matching is done
> +       case-insensitively (e.g. on case-insensitive file sytems)
> +
> +A few more notes on matching:
> +
> + * Symlinks in `$GIT_DIR` are not resolved before matching.
> +
> + * Note that "../" is not special and will match literally, which is
> +   unlikely what you want.
>
>  Example
>  ~~~~~~~
> @@ -119,6 +155,17 @@ Example
>                 path = foo ; expand "foo" relative to the current file
>                 path = ~/foo ; expand "foo" in your `$HOME` directory
>
> +       ; include if $GIT_DIR is /path/to/foo/.git
> +       [include "gitdir:/path/to/foo/.git"]
> +               path = /path/to/foo.inc
> +
> +       ; include for all repositories inside /path/to/group
> +       [include "gitdir:/path/to/group/"]
> +               path = /path/to/foo.inc
> +
> +       ; include for all repositories inside $HOME/to/group
> +       [include "gitdir:~/to/group/"]
> +               path = /path/to/foo.inc
>
>  Values
>  ~~~~~~
> diff --git a/config.c b/config.c
> index bea937e..690f3d5 100644
> --- a/config.c
> +++ b/config.c
> @@ -13,6 +13,7 @@
>  #include "hashmap.h"
>  #include "string-list.h"
>  #include "utf8.h"
> +#include "dir.h"
>
>  struct config_source {
>         struct config_source *prev;
> @@ -168,9 +169,113 @@ static int handle_path_include(const char *path, struct config_include_data *inc
>         return ret;
>  }
>
> +static int prepare_include_condition_pattern(struct strbuf *pat)
> +{
> +       int prefix = 0;
> +
> +       /* TODO: maybe support ~user/ too */
> +       if (pat->buf[0] == '~' && is_dir_sep(pat->buf[1])) {
> +               struct strbuf path = STRBUF_INIT;
> +               const char *home = getenv("HOME");
> +
> +               if (!home)
> +                       return error(_("$HOME is not defined"));
> +
> +               strbuf_add_absolute_path(&path, home);
> +               strbuf_splice(pat, 0, 1, path.buf, path.len);
> +               /*
> +                * This part, path.buf[0..len], should be considered
> +                * a literal string even if it has wildcards in it,
> +                * because those wildcards are not wanted by the user.
> +                */
> +               prefix = path.len + 1 /*slash*/;
> +               strbuf_release(&path);
> +       } else if (pat->buf[0] == '.' && is_dir_sep(pat->buf[1])) {
> +               struct strbuf path = STRBUF_INIT;
> +               const char *slash;
> +
> +               if (!cf || !cf->path)
> +                       return error(_("relative config include "
> +                                      "conditionals must come from files"));
> +
> +               strbuf_add_absolute_path(&path, cf->path);
> +               slash = find_last_dir_sep(path.buf);
> +               if (!slash)
> +                       die("BUG: how is this possible?");
> +               strbuf_splice(pat, 0, 1, path.buf, slash - path.buf);
> +               /*
> +                * This part, path.buf[0..slash], should be consider
> +                * a literal string even if it has wildcards in it,
> +                * because those wildcards are not wanted by the user.
> +                */
> +               prefix = slash - path.buf + 1 /* slash */;
> +               strbuf_release(&path);
> +       } else if (!is_absolute_path(pat->buf))
> +               strbuf_insert(pat, 0, "**/", 3);
> +
> +       if (pat->len && is_dir_sep(pat->buf[pat->len - 1]))
> +               strbuf_addstr(pat, "**");
> +
> +       return prefix;
> +}
> +
> +static int include_by_gitdir(const char *cond, size_t cond_len, int icase)
> +{
> +       struct strbuf text = STRBUF_INIT;
> +       struct strbuf pattern = STRBUF_INIT;
> +       int ret = 0, prefix;
> +
> +       strbuf_add_absolute_path(&text, get_git_dir());
> +       strbuf_add(&pattern, cond, cond_len);
> +       prefix = prepare_include_condition_pattern(&pattern);
> +
> +       if (prefix < 0)
> +               goto done;
> +
> +       if (prefix > 0) {
> +               /*
> +                * perform literal matching on the expanded prefix
> +                * part so that any wildcard character in it (e.g in
> +                * the expansion of ~) can't create side effects.
> +                */
> +               if (text.len < prefix)
> +                       goto done;
> +               if (!icase && strncmp(pattern.buf, text.buf, prefix))
> +                       goto done;
> +               if (icase && strncasecmp(pattern.buf, text.buf, prefix))
> +                       goto done;
> +       }
> +
> +       ret = !wildmatch(pattern.buf + prefix, text.buf + prefix,
> +                        icase ? WM_CASEFOLD : 0, NULL);
> +
> +done:
> +       strbuf_release(&pattern);
> +       strbuf_release(&text);
> +       return ret;
> +}
> +
> +static int include_condition_is_true(const char *cond, size_t cond_len)
> +{
> +       /* no condition (i.e., "include.path") is always true */
> +       if (!cond)
> +               return 1;
> +
> +       if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len))
> +               return include_by_gitdir(cond, cond_len, 0);
> +       else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len))
> +               return include_by_gitdir(cond, cond_len, 1);
> +
> +       error(_("unrecognized include condition: %.*s"), (int)cond_len, cond);
> +       /* unknown conditionals are always false */
> +       return 0;
> +}
> +
>  int git_config_include(const char *var, const char *value, void *data)
>  {
>         struct config_include_data *inc = data;
> +       const char *cond, *key;
> +       int cond_len;
>         int ret;
>
>         /*
> @@ -181,8 +286,12 @@ int git_config_include(const char *var, const char *value, void *data)
>         if (ret < 0)
>                 return ret;
>
> -       if (!strcmp(var, "include.path"))
> -               ret = handle_path_include(value, inc);
> +       if (!parse_config_key(var, "include", &cond, &cond_len, &key) &&
> +           include_condition_is_true(cond, cond_len)) {
> +               if (!strcmp(key, "path"))
> +                       ret = handle_path_include(value, inc);
> +               /* else we do not know about this type of include; ignore */
> +       }
>         return ret;
>  }
>
> diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh
> index 9ba2ba1..83501ec 100755
> --- a/t/t1305-config-include.sh
> +++ b/t/t1305-config-include.sh
> @@ -152,6 +152,62 @@ test_expect_success 'relative includes from stdin line fail' '
>         test_must_fail git config --file - test.one
>  '
>
> +test_expect_success 'conditional include, both unanchored' '
> +       git init foo &&
> +       (
> +               cd foo &&
> +               echo "[include \"gitdir:foo/\"]path=bar" >>.git/config &&
> +               echo "[test]one=1" >.git/bar &&
> +               echo 1 >expect &&
> +               git config test.one >actual &&
> +               test_cmp expect actual
> +       )
> +'
> +
> +test_expect_success 'conditional include, $HOME expansion' '
> +       (
> +               cd foo &&
> +               echo "[include \"gitdir:~/foo/\"]path=bar2" >>.git/config &&
> +               echo "[test]two=2" >.git/bar2 &&
> +               echo 2 >expect &&
> +               git config test.two >actual &&
> +               test_cmp expect actual
> +       )
> +'
> +
> +test_expect_success 'conditional include, full pattern' '
> +       (
> +               cd foo &&
> +               echo "[include \"gitdir:**/foo/**\"]path=bar3" >>.git/config &&
> +               echo "[test]three=3" >.git/bar3 &&
> +               echo 3 >expect &&
> +               git config test.three >actual &&
> +               test_cmp expect actual
> +       )
> +'
> +
> +test_expect_success 'conditional include, relative path' '
> +       echo "[include \"gitdir:./foo/.git\"]path=bar4" >>.gitconfig &&
> +       echo "[test]four=4" >bar4 &&
> +       (
> +               cd foo &&
> +               echo 4 >expect &&
> +               git config test.four >actual &&
> +               test_cmp expect actual
> +       )
> +'
> +
> +test_expect_success 'conditional include, both unanchored, icase' '
> +       (
> +               cd foo &&
> +               echo "[include \"gitdir/i:FOO/\"]path=bar5" >>.git/config &&
> +               echo "[test]five=5" >.git/bar5 &&
> +               echo 5 >expect &&
> +               git config test.five >actual &&
> +               test_cmp expect actual
> +       )
> +'
> +
>  test_expect_success 'include cycles are detected' '
>         cat >.gitconfig <<-\EOF &&
>         [test]value = gitconfig
> --
> 2.9.1.566.gbd532d4
>



-- 
Duy
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]