From: Junio C Hamano Date: Wed, 9 Feb 2022 22:20:58 +0000 (-0800) Subject: Merge branch 'jt/conditional-config-on-remote-url' X-Git-Tag: v2.36.0-rc0~181 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=13ce8f9f148637a885003c63e11a020d0aeca3f7;hp=-c;p=thirdparty%2Fgit.git Merge branch 'jt/conditional-config-on-remote-url' The conditional inclusion mechanism of configuration files using "[includeIf ]" learns to base its decision on the URL of the remote repository the repository interacts with. * jt/conditional-config-on-remote-url: config: include file if remote URL matches a glob config: make git_config_include() static --- 13ce8f9f148637a885003c63e11a020d0aeca3f7 diff --combined Documentation/config.txt index b168f02dc3,0b8929baa4..bf3e512921 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@@ -159,6 -159,33 +159,33 @@@ all branches that begin with `foo/`. Th organized hierarchically and you would like to apply a configuration to all the branches in that hierarchy. + `hasconfig:remote.*.url:`:: + The data that follows this keyword is taken to + be a pattern with standard globbing wildcards and two + additional ones, `**/` and `/**`, that can match multiple + components. The first time this keyword is seen, the rest of + the config files will be scanned for remote URLs (without + applying any values). If there exists at least one remote URL + that matches this pattern, the include condition is met. + + + Files included by this option (directly or indirectly) are not allowed + to contain remote URLs. + + + Note that unlike other includeIf conditions, resolving this condition + relies on information that is not yet known at the point of reading the + condition. A typical use case is this option being present as a + system-level or global-level config, and the remote URL being in a + local-level config; hence the need to scan ahead when resolving this + condition. In order to avoid the chicken-and-egg problem in which + potentially-included files can affect whether such files are potentially + included, Git breaks the cycle by prohibiting these files from affecting + the resolution of these conditions (thus, prohibiting them from + declaring remote URLs). + + + As for the naming of this keyword, it is for forwards compatibiliy with + a naming scheme that supports more variable-based include conditions, + but currently Git only supports the exact keyword described above. + A few more notes on matching via `gitdir` and `gitdir/i`: * Symlinks in `$GIT_DIR` are not resolved before matching. @@@ -226,6 -253,14 +253,14 @@@ Exampl ; currently checked out [includeIf "onbranch:foo-branch"] path = foo.inc + + ; include only if a remote with the given URL exists (note + ; that such a URL may be provided later in a file or in a + ; file read after this file is read, as seen in this example) + [includeIf "hasconfig:remote.*.url:https://example.com/**"] + path = foo.inc + [remote "origin"] + url = https://example.com/git ---- Values @@@ -262,19 -297,11 +297,19 @@@ color: colors (at most two, one for foreground and one for background) and attributes (as many as you want), separated by spaces. + -The basic colors accepted are `normal`, `black`, `red`, `green`, `yellow`, -`blue`, `magenta`, `cyan` and `white`. The first color given is the -foreground; the second is the background. All the basic colors except -`normal` have a bright variant that can be specified by prefixing the -color with `bright`, like `brightred`. +The basic colors accepted are `normal`, `black`, `red`, `green`, +`yellow`, `blue`, `magenta`, `cyan`, `white` and `default`. The first +color given is the foreground; the second is the background. All the +basic colors except `normal` and `default` have a bright variant that can +be specified by prefixing the color with `bright`, like `brightred`. ++ +The color `normal` makes no change to the color. It is the same as an +empty string, but can be used as the foreground color when specifying a +background color alone (for example, "normal red"). ++ +The color `default` explicitly resets the color to the terminal default, +for example to specify a cleared background. Although it varies between +terminals, this is usually not the same as setting to "white black". + Colors may also be given as numbers between 0 and 255; these use ANSI 256-color mode (but note that not all terminals may support this). If @@@ -288,11 -315,6 +323,11 @@@ The position of any attributes with res be turned off by prefixing them with `no` or `no-` (e.g., `noreverse`, `no-ul`, etc). + +The pseudo-attribute `reset` resets all colors and attributes before +applying the specified coloring. For example, `reset green` will result +in a green foreground and default background without any active +attributes. ++ An empty color string produces no color effect at all. This can be used to avoid coloring specific elements without disabling color entirely. + diff --combined config.c index 2bffa8d4a0,d98e6c9dc0..e0c03d154c --- a/config.c +++ b/config.c @@@ -120,6 -120,22 +120,22 @@@ static long config_buf_ftell(struct con return conf->u.buf.pos; } + struct config_include_data { + int depth; + config_fn_t fn; + void *data; + const struct config_options *opts; + struct git_config_source *config_source; + + /* + * All remote URLs discovered when reading all config files. + */ + struct string_list *remote_urls; + }; + #define CONFIG_INCLUDE_INIT { 0 } + + static int git_config_include(const char *var, const char *value, void *data); + #define MAX_INCLUDE_DEPTH 10 static const char include_depth_advice[] = N_( "exceeded maximum include depth (%d) while including\n" @@@ -294,9 -310,92 +310,92 @@@ static int include_by_branch(const cha return ret; } - static int include_condition_is_true(const struct config_options *opts, + static int add_remote_url(const char *var, const char *value, void *data) + { + struct string_list *remote_urls = data; + const char *remote_name; + size_t remote_name_len; + const char *key; + + if (!parse_config_key(var, "remote", &remote_name, &remote_name_len, + &key) && + remote_name && + !strcmp(key, "url")) + string_list_append(remote_urls, value); + return 0; + } + + static void populate_remote_urls(struct config_include_data *inc) + { + struct config_options opts; + + struct config_source *store_cf = cf; + struct key_value_info *store_kvi = current_config_kvi; + enum config_scope store_scope = current_parsing_scope; + + opts = *inc->opts; + opts.unconditional_remote_url = 1; + + cf = NULL; + current_config_kvi = NULL; + current_parsing_scope = 0; + + inc->remote_urls = xmalloc(sizeof(*inc->remote_urls)); + string_list_init_dup(inc->remote_urls); + config_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts); + + cf = store_cf; + current_config_kvi = store_kvi; + current_parsing_scope = store_scope; + } + + static int forbid_remote_url(const char *var, const char *value, void *data) + { + const char *remote_name; + size_t remote_name_len; + const char *key; + + if (!parse_config_key(var, "remote", &remote_name, &remote_name_len, + &key) && + remote_name && + !strcmp(key, "url")) + die(_("remote URLs cannot be configured in file directly or indirectly included by includeIf.hasconfig:remote.*.url")); + return 0; + } + + static int at_least_one_url_matches_glob(const char *glob, int glob_len, + struct string_list *remote_urls) + { + struct strbuf pattern = STRBUF_INIT; + struct string_list_item *url_item; + int found = 0; + + strbuf_add(&pattern, glob, glob_len); + for_each_string_list_item(url_item, remote_urls) { + if (!wildmatch(pattern.buf, url_item->string, WM_PATHNAME)) { + found = 1; + break; + } + } + strbuf_release(&pattern); + return found; + } + + static int include_by_remote_url(struct config_include_data *inc, + const char *cond, size_t cond_len) + { + if (inc->opts->unconditional_remote_url) + return 1; + if (!inc->remote_urls) + populate_remote_urls(inc); + return at_least_one_url_matches_glob(cond, cond_len, + inc->remote_urls); + } + + static int include_condition_is_true(struct config_include_data *inc, const char *cond, size_t cond_len) { + const struct config_options *opts = inc->opts; if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len)) return include_by_gitdir(opts, cond, cond_len, 0); @@@ -304,12 -403,15 +403,15 @@@ return include_by_gitdir(opts, cond, cond_len, 1); else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len)) return include_by_branch(cond, cond_len); + else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.*.url:", &cond, + &cond_len)) + return include_by_remote_url(inc, cond, cond_len); /* unknown conditionals are always false */ return 0; } - int git_config_include(const char *var, const char *value, void *data) + static int git_config_include(const char *var, const char *value, void *data) { struct config_include_data *inc = data; const char *cond, *key; @@@ -328,9 -430,15 +430,15 @@@ ret = handle_path_include(value, inc); if (!parse_config_key(var, "includeif", &cond, &cond_len, &key) && - (cond && include_condition_is_true(inc->opts, cond, cond_len)) && - !strcmp(key, "path")) + cond && include_condition_is_true(inc, cond, cond_len) && + !strcmp(key, "path")) { + config_fn_t old_fn = inc->fn; + + if (inc->opts->unconditional_remote_url) + inc->fn = forbid_remote_url; ret = handle_path_include(value, inc); + inc->fn = old_fn; + } return ret; } @@@ -1559,12 -1667,9 +1667,12 @@@ static int git_default_i18n_config(cons static int git_default_branch_config(const char *var, const char *value) { if (!strcmp(var, "branch.autosetupmerge")) { - if (value && !strcasecmp(value, "always")) { + if (value && !strcmp(value, "always")) { git_branch_track = BRANCH_TRACK_ALWAYS; return 0; + } else if (value && !strcmp(value, "inherit")) { + git_branch_track = BRANCH_TRACK_INHERIT; + return 0; } git_branch_track = git_config_bool(var, value); return 0; @@@ -1929,11 -2034,13 +2037,13 @@@ int config_with_options(config_fn_t fn const struct config_options *opts) { struct config_include_data inc = CONFIG_INCLUDE_INIT; + int ret; if (opts->respect_includes) { inc.fn = fn; inc.data = data; inc.opts = opts; + inc.config_source = config_source; fn = git_config_include; data = &inc; } @@@ -1946,17 -2053,23 +2056,23 @@@ * regular lookup sequence. */ if (config_source && config_source->use_stdin) { - return git_config_from_stdin(fn, data); + ret = git_config_from_stdin(fn, data); } else if (config_source && config_source->file) { - return git_config_from_file(fn, config_source->file, data); + ret = git_config_from_file(fn, config_source->file, data); } else if (config_source && config_source->blob) { struct repository *repo = config_source->repo ? config_source->repo : the_repository; - return git_config_from_blob_ref(fn, repo, config_source->blob, + ret = git_config_from_blob_ref(fn, repo, config_source->blob, data); + } else { + ret = do_git_config_sequence(opts, fn, data); } - return do_git_config_sequence(opts, fn, data); + if (inc.remote_urls) { + string_list_clear(inc.remote_urls, 0); + FREE_AND_NULL(inc.remote_urls); + } + return ret; } static void configset_iter(struct config_set *cs, config_fn_t fn, void *data) @@@ -2558,12 -2671,11 +2674,12 @@@ void git_die_config(const char *key, co { const struct string_list *values; struct key_value_info *kv_info; + report_fn error_fn = get_error_routine(); if (err) { va_list params; va_start(params, err); - vreportf("error: ", err, params); + error_fn(err, params); va_end(params); } values = git_config_get_value_multi(key); diff --combined t/t1300-config.sh index 78359f1f4a,c6b5911c4d..7dd9b325d9 --- a/t/t1300-config.sh +++ b/t/t1300-config.sh @@@ -8,7 -8,6 +8,7 @@@ test_description='Test git config in di GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'clear default config' ' @@@ -718,8 -717,8 +718,8 @@@ test_expect_success bool rm -f result && for i in 1 2 3 4 do - git config --bool --get bool.true$i >>result - git config --bool --get bool.false$i >>result + git config --bool --get bool.true$i >>result && + git config --bool --get bool.false$i >>result || return 1 done && test_cmp expect result' @@@ -902,7 -901,7 +902,7 @@@ test_expect_success 'get --expiry-date EOF : "work around heredoc parsing bug fixed in dash 0.5.7 (in ec2c84d)" && { - echo "$rel_out $(git config --expiry-date date.valid1)" + echo "$rel_out $(git config --expiry-date date.valid1)" && git config --expiry-date date.valid2 && git config --expiry-date date.valid3 && git config --expiry-date date.valid4 && @@@ -2388,4 -2387,122 +2388,122 @@@ test_expect_success '--get and --get-al test_must_fail git config --file=config --get-regexp --fixed-value fixed+ non-existent ' + test_expect_success 'includeIf.hasconfig:remote.*.url' ' + git init hasremoteurlTest && + test_when_finished "rm -rf hasremoteurlTest" && + + cat >include-this <<-\EOF && + [user] + this = this-is-included + EOF + cat >dont-include-that <<-\EOF && + [user] + that = that-is-not-included + EOF + cat >>hasremoteurlTest/.git/config <<-EOF && + [includeIf "hasconfig:remote.*.url:foourl"] + path = "$(pwd)/include-this" + [includeIf "hasconfig:remote.*.url:barurl"] + path = "$(pwd)/dont-include-that" + [remote "foo"] + url = foourl + EOF + + echo this-is-included >expect-this && + git -C hasremoteurlTest config --get user.this >actual-this && + test_cmp expect-this actual-this && + + test_must_fail git -C hasremoteurlTest config --get user.that + ' + + test_expect_success 'includeIf.hasconfig:remote.*.url respects last-config-wins' ' + git init hasremoteurlTest && + test_when_finished "rm -rf hasremoteurlTest" && + + cat >include-two-three <<-\EOF && + [user] + two = included-config + three = included-config + EOF + cat >>hasremoteurlTest/.git/config <<-EOF && + [remote "foo"] + url = foourl + [user] + one = main-config + two = main-config + [includeIf "hasconfig:remote.*.url:foourl"] + path = "$(pwd)/include-two-three" + [user] + three = main-config + EOF + + echo main-config >expect-main-config && + echo included-config >expect-included-config && + + git -C hasremoteurlTest config --get user.one >actual && + test_cmp expect-main-config actual && + + git -C hasremoteurlTest config --get user.two >actual && + test_cmp expect-included-config actual && + + git -C hasremoteurlTest config --get user.three >actual && + test_cmp expect-main-config actual + ' + + test_expect_success 'includeIf.hasconfig:remote.*.url globs' ' + git init hasremoteurlTest && + test_when_finished "rm -rf hasremoteurlTest" && + + printf "[user]\ndss = yes\n" >double-star-start && + printf "[user]\ndse = yes\n" >double-star-end && + printf "[user]\ndsm = yes\n" >double-star-middle && + printf "[user]\nssm = yes\n" >single-star-middle && + printf "[user]\nno = no\n" >no && + + cat >>hasremoteurlTest/.git/config <<-EOF && + [remote "foo"] + url = https://foo/bar/baz + [includeIf "hasconfig:remote.*.url:**/baz"] + path = "$(pwd)/double-star-start" + [includeIf "hasconfig:remote.*.url:**/nomatch"] + path = "$(pwd)/no" + [includeIf "hasconfig:remote.*.url:https:/**"] + path = "$(pwd)/double-star-end" + [includeIf "hasconfig:remote.*.url:nomatch:/**"] + path = "$(pwd)/no" + [includeIf "hasconfig:remote.*.url:https:/**/baz"] + path = "$(pwd)/double-star-middle" + [includeIf "hasconfig:remote.*.url:https:/**/nomatch"] + path = "$(pwd)/no" + [includeIf "hasconfig:remote.*.url:https://*/bar/baz"] + path = "$(pwd)/single-star-middle" + [includeIf "hasconfig:remote.*.url:https://*/baz"] + path = "$(pwd)/no" + EOF + + git -C hasremoteurlTest config --get user.dss && + git -C hasremoteurlTest config --get user.dse && + git -C hasremoteurlTest config --get user.dsm && + git -C hasremoteurlTest config --get user.ssm && + test_must_fail git -C hasremoteurlTest config --get user.no + ' + + test_expect_success 'includeIf.hasconfig:remote.*.url forbids remote url in such included files' ' + git init hasremoteurlTest && + test_when_finished "rm -rf hasremoteurlTest" && + + cat >include-with-url <<-\EOF && + [remote "bar"] + url = barurl + EOF + cat >>hasremoteurlTest/.git/config <<-EOF && + [includeIf "hasconfig:remote.*.url:foourl"] + path = "$(pwd)/include-with-url" + EOF + + # test with any Git command + test_must_fail git -C hasremoteurlTest status 2>err && + grep "fatal: remote URLs cannot be configured in file directly or indirectly included by includeIf.hasconfig:remote.*.url" err + ' + test_done