Skip to content

Commit a0a08d4

Browse files
committed
Merge branch 'jc/url-match'
Allow section.<urlpattern>.var configuration variables to be treated as a "virtual" section.var given a URL, and use the mechanism to enhance http.* configuration variables. This is a reroll of Kyle J. McKay's work. * jc/url-match: builtin/config.c: compilation fix config: "git config --get-urlmatch" parses section.<url>.key builtin/config: refactor collect_config() config: parse http.<url>.<variable> using urlmatch config: add generic callback wrapper to parse section.<url>.key config: add helper to normalize and match URLs http.c: fix parsing of http.sslCertPasswordProtected variable
2 parents b02f5ae + 6667a6a commit a0a08d4

24 files changed

+1072
-24
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@
202202
/test-string-list
203203
/test-subprocess
204204
/test-svn-fe
205+
/test-urlmatch-normalization
205206
/test-wildmatch
206207
/common-cmds.h
207208
*.tar.gz

Documentation/config.txt

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1533,6 +1533,51 @@ http.useragent::
15331533
of common USER_AGENT strings (but not including those like git/1.7.1).
15341534
Can be overridden by the 'GIT_HTTP_USER_AGENT' environment variable.
15351535

1536+
http.<url>.*::
1537+
Any of the http.* options above can be applied selectively to some urls.
1538+
For a config key to match a URL, each element of the config key is
1539+
compared to that of the URL, in the following order:
1540+
+
1541+
--
1542+
. Scheme (e.g., `https` in `https://example.com/`). This field
1543+
must match exactly between the config key and the URL.
1544+
1545+
. Host/domain name (e.g., `example.com` in `https://example.com/`).
1546+
This field must match exactly between the config key and the URL.
1547+
1548+
. Port number (e.g., `8080` in `http://example.com:8080/`).
1549+
This field must match exactly between the config key and the URL.
1550+
Omitted port numbers are automatically converted to the correct
1551+
default for the scheme before matching.
1552+
1553+
. Path (e.g., `repo.git` in `https://example.com/repo.git`). The
1554+
path field of the config key must match the path field of the URL
1555+
either exactly or as a prefix of slash-delimited path elements. This means
1556+
a config key with path `foo/` matches URL path `foo/bar`. A prefix can only
1557+
match on a slash (`/`) boundary. Longer matches take precedence (so a config
1558+
key with path `foo/bar` is a better match to URL path `foo/bar` than a config
1559+
key with just path `foo/`).
1560+
1561+
. User name (e.g., `user` in `https://[email protected]/repo.git`). If
1562+
the config key has a user name it must match the user name in the
1563+
URL exactly. If the config key does not have a user name, that
1564+
config key will match a URL with any user name (including none),
1565+
but at a lower precedence than a config key with a user name.
1566+
--
1567+
+
1568+
The list above is ordered by decreasing precedence; a URL that matches
1569+
a config key's path is preferred to one that matches its user name. For example,
1570+
if the URL is `https://[email protected]/foo/bar` a config key match of
1571+
`https://example.com/foo` will be preferred over a config key match of
1572+
1573+
+
1574+
All URLs are normalized before attempting any matching (the password part,
1575+
if embedded in the URL, is always ignored for matching purposes) so that
1576+
equivalent urls that are simply spelled differently will match properly.
1577+
Environment variable settings always override any matches. The urls that are
1578+
matched against are those given directly to Git commands. This means any URLs
1579+
visited as a result of a redirection do not participate in matching.
1580+
15361581
i18n.commitEncoding::
15371582
Character encoding the commit messages are stored in; Git itself
15381583
does not care per se, but this information is necessary e.g. when

Documentation/git-config.txt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ SYNOPSIS
1515
'git config' [<file-option>] [type] [-z|--null] --get name [value_regex]
1616
'git config' [<file-option>] [type] [-z|--null] --get-all name [value_regex]
1717
'git config' [<file-option>] [type] [-z|--null] --get-regexp name_regex [value_regex]
18+
'git config' [<file-option>] [type] [-z|--null] --get-urlmatch name URL
1819
'git config' [<file-option>] --unset name [value_regex]
1920
'git config' [<file-option>] --unset-all name [value_regex]
2021
'git config' [<file-option>] --rename-section old_name new_name
@@ -95,6 +96,14 @@ OPTIONS
9596
in which section and variable names are lowercased, but subsection
9697
names are not.
9798

99+
--get-urlmatch name URL::
100+
When given a two-part name section.key, the value for
101+
section.<url>.key whose <url> part matches the best to the
102+
given URL is returned (if no such key exists, the value for
103+
section.key is used as a fallback). When given just the
104+
section as name, do so for all the keys in the section and
105+
list them.
106+
98107
--global::
99108
For writing options: write to global `~/.gitconfig` file
100109
rather than the repository `.git/config`, write to
@@ -295,6 +304,13 @@ Given a .git/config like this:
295304
gitproxy=proxy-command for kernel.org
296305
gitproxy=default-proxy ; for all the rest
297306

307+
; HTTP
308+
[http]
309+
sslVerify
310+
[http "https://weak.example.com"]
311+
sslVerify = false
312+
cookieFile = /tmp/cookie.txt
313+
298314
you can set the filemode to true with
299315

300316
------------
@@ -380,6 +396,19 @@ RESET=$(git config --get-color "" "reset")
380396
echo "${WS}your whitespace color or blue reverse${RESET}"
381397
------------
382398

399+
For URLs in `https://weak.example.com`, `http.sslVerify` is set to
400+
false, while it is set to `true` for all others:
401+
402+
------------
403+
% git config --bool --get-urlmatch http.sslverify https://good.example.com
404+
true
405+
% git config --bool --get-urlmatch http.sslverify https://weak.example.com
406+
false
407+
% git config --get-urlmatch http https://weak.example.com
408+
http.cookiefile /tmp/cookie.txt
409+
http.sslverify false
410+
------------
411+
383412
include::config.txt[]
384413

385414
GIT

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,7 @@ TEST_PROGRAMS_NEED_X += test-sigchain
580580
TEST_PROGRAMS_NEED_X += test-string-list
581581
TEST_PROGRAMS_NEED_X += test-subprocess
582582
TEST_PROGRAMS_NEED_X += test-svn-fe
583+
TEST_PROGRAMS_NEED_X += test-urlmatch-normalization
583584
TEST_PROGRAMS_NEED_X += test-wildmatch
584585

585586
TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X))
@@ -736,6 +737,7 @@ LIB_H += tree-walk.h
736737
LIB_H += tree.h
737738
LIB_H += unpack-trees.h
738739
LIB_H += url.h
740+
LIB_H += urlmatch.h
739741
LIB_H += userdiff.h
740742
LIB_H += utf8.h
741743
LIB_H += varint.h
@@ -886,6 +888,7 @@ LIB_OBJS += tree.o
886888
LIB_OBJS += tree-walk.o
887889
LIB_OBJS += unpack-trees.o
888890
LIB_OBJS += url.o
891+
LIB_OBJS += urlmatch.o
889892
LIB_OBJS += usage.o
890893
LIB_OBJS += userdiff.o
891894
LIB_OBJS += utf8.o

builtin/config.c

Lines changed: 119 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include "cache.h"
33
#include "color.h"
44
#include "parse-options.h"
5+
#include "urlmatch.h"
56

67
static const char *const builtin_config_usage[] = {
78
N_("git config [options]"),
@@ -42,6 +43,7 @@ static int respect_includes = -1;
4243
#define ACTION_SET_ALL (1<<12)
4344
#define ACTION_GET_COLOR (1<<13)
4445
#define ACTION_GET_COLORBOOL (1<<14)
46+
#define ACTION_GET_URLMATCH (1<<15)
4547

4648
#define TYPE_BOOL (1<<0)
4749
#define TYPE_INT (1<<1)
@@ -59,6 +61,7 @@ static struct option builtin_config_options[] = {
5961
OPT_BIT(0, "get", &actions, N_("get value: name [value-regex]"), ACTION_GET),
6062
OPT_BIT(0, "get-all", &actions, N_("get all values: key [value-regex]"), ACTION_GET_ALL),
6163
OPT_BIT(0, "get-regexp", &actions, N_("get values for regexp: name-regex [value-regex]"), ACTION_GET_REGEXP),
64+
OPT_BIT(0, "get-urlmatch", &actions, N_("get value specific for the URL: section[.var] URL"), ACTION_GET_URLMATCH),
6265
OPT_BIT(0, "replace-all", &actions, N_("replace all matching variables: name value [value_regex]"), ACTION_REPLACE_ALL),
6366
OPT_BIT(0, "add", &actions, N_("add a new variable: name value"), ACTION_ADD),
6467
OPT_BIT(0, "unset", &actions, N_("remove a variable: name [value-regex]"), ACTION_UNSET),
@@ -102,33 +105,21 @@ struct strbuf_list {
102105
int alloc;
103106
};
104107

105-
static int collect_config(const char *key_, const char *value_, void *cb)
108+
static int format_config(struct strbuf *buf, const char *key_, const char *value_)
106109
{
107-
struct strbuf_list *values = cb;
108-
struct strbuf *buf;
109-
char value[256];
110-
const char *vptr = value;
111110
int must_free_vptr = 0;
112111
int must_print_delim = 0;
112+
char value[256];
113+
const char *vptr = value;
113114

114-
if (!use_key_regexp && strcmp(key_, key))
115-
return 0;
116-
if (use_key_regexp && regexec(key_regexp, key_, 0, NULL, 0))
117-
return 0;
118-
if (regexp != NULL &&
119-
(do_not_match ^ !!regexec(regexp, (value_?value_:""), 0, NULL, 0)))
120-
return 0;
121-
122-
ALLOC_GROW(values->items, values->nr + 1, values->alloc);
123-
buf = &values->items[values->nr++];
124115
strbuf_init(buf, 0);
125116

126117
if (show_keys) {
127118
strbuf_addstr(buf, key_);
128119
must_print_delim = 1;
129120
}
130121
if (types == TYPE_INT)
131-
sprintf(value, "%d", git_config_int(key_, value_?value_:""));
122+
sprintf(value, "%d", git_config_int(key_, value_ ? value_ : ""));
132123
else if (types == TYPE_BOOL)
133124
vptr = git_config_bool(key_, value_) ? "true" : "false";
134125
else if (types == TYPE_BOOL_OR_INT) {
@@ -156,15 +147,27 @@ static int collect_config(const char *key_, const char *value_, void *cb)
156147
strbuf_addch(buf, term);
157148

158149
if (must_free_vptr)
159-
/* If vptr must be freed, it's a pointer to a
160-
* dynamically allocated buffer, it's safe to cast to
161-
* const.
162-
*/
163150
free((char *)vptr);
164-
165151
return 0;
166152
}
167153

154+
static int collect_config(const char *key_, const char *value_, void *cb)
155+
{
156+
struct strbuf_list *values = cb;
157+
158+
if (!use_key_regexp && strcmp(key_, key))
159+
return 0;
160+
if (use_key_regexp && regexec(key_regexp, key_, 0, NULL, 0))
161+
return 0;
162+
if (regexp != NULL &&
163+
(do_not_match ^ !!regexec(regexp, (value_?value_:""), 0, NULL, 0)))
164+
return 0;
165+
166+
ALLOC_GROW(values->items, values->nr + 1, values->alloc);
167+
168+
return format_config(&values->items[values->nr++], key_, value_);
169+
}
170+
168171
static int get_value(const char *key_, const char *regex_)
169172
{
170173
int ret = CONFIG_GENERIC_ERROR;
@@ -364,6 +367,97 @@ static void check_blob_write(void)
364367
die("writing config blobs is not supported");
365368
}
366369

370+
struct urlmatch_current_candidate_value {
371+
char value_is_null;
372+
struct strbuf value;
373+
};
374+
375+
static int urlmatch_collect_fn(const char *var, const char *value, void *cb)
376+
{
377+
struct string_list *values = cb;
378+
struct string_list_item *item = string_list_insert(values, var);
379+
struct urlmatch_current_candidate_value *matched = item->util;
380+
381+
if (!matched) {
382+
matched = xmalloc(sizeof(*matched));
383+
strbuf_init(&matched->value, 0);
384+
item->util = matched;
385+
} else {
386+
strbuf_reset(&matched->value);
387+
}
388+
389+
if (value) {
390+
strbuf_addstr(&matched->value, value);
391+
matched->value_is_null = 0;
392+
} else {
393+
matched->value_is_null = 1;
394+
}
395+
return 0;
396+
}
397+
398+
static char *dup_downcase(const char *string)
399+
{
400+
char *result;
401+
size_t len, i;
402+
403+
len = strlen(string);
404+
result = xmalloc(len + 1);
405+
for (i = 0; i < len; i++)
406+
result[i] = tolower(string[i]);
407+
result[i] = '\0';
408+
return result;
409+
}
410+
411+
static int get_urlmatch(const char *var, const char *url)
412+
{
413+
char *section_tail;
414+
struct string_list_item *item;
415+
struct urlmatch_config config = { STRING_LIST_INIT_DUP };
416+
struct string_list values = STRING_LIST_INIT_DUP;
417+
418+
config.collect_fn = urlmatch_collect_fn;
419+
config.cascade_fn = NULL;
420+
config.cb = &values;
421+
422+
if (!url_normalize(url, &config.url))
423+
die("%s", config.url.err);
424+
425+
config.section = dup_downcase(var);
426+
section_tail = strchr(config.section, '.');
427+
if (section_tail) {
428+
*section_tail = '\0';
429+
config.key = section_tail + 1;
430+
show_keys = 0;
431+
} else {
432+
config.key = NULL;
433+
show_keys = 1;
434+
}
435+
436+
git_config_with_options(urlmatch_config_entry, &config,
437+
given_config_file, NULL, respect_includes);
438+
439+
for_each_string_list_item(item, &values) {
440+
struct urlmatch_current_candidate_value *matched = item->util;
441+
struct strbuf key = STRBUF_INIT;
442+
struct strbuf buf = STRBUF_INIT;
443+
444+
strbuf_addstr(&key, item->string);
445+
format_config(&buf, key.buf,
446+
matched->value_is_null ? NULL : matched->value.buf);
447+
fwrite(buf.buf, 1, buf.len, stdout);
448+
strbuf_release(&key);
449+
strbuf_release(&buf);
450+
451+
strbuf_release(&matched->value);
452+
}
453+
string_list_clear(&config.vars, 1);
454+
string_list_clear(&values, 1);
455+
free(config.url.url);
456+
457+
free((void *)config.section);
458+
return 0;
459+
}
460+
367461
int cmd_config(int argc, const char **argv, const char *prefix)
368462
{
369463
int nongit = !startup_info->have_repository;
@@ -523,6 +617,10 @@ int cmd_config(int argc, const char **argv, const char *prefix)
523617
check_argc(argc, 1, 2);
524618
return get_value(argv[0], argv[1]);
525619
}
620+
else if (actions == ACTION_GET_URLMATCH) {
621+
check_argc(argc, 2, 2);
622+
return get_urlmatch(argv[0], argv[1]);
623+
}
526624
else if (actions == ACTION_UNSET) {
527625
check_blob_write();
528626
check_argc(argc, 1, 2);

http.c

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "sideband.h"
44
#include "run-command.h"
55
#include "url.h"
6+
#include "urlmatch.h"
67
#include "credential.h"
78
#include "version.h"
89
#include "pkt-line.h"
@@ -161,8 +162,7 @@ static int http_options(const char *var, const char *value, void *cb)
161162
if (!strcmp("http.sslcainfo", var))
162163
return git_config_string(&ssl_cainfo, var, value);
163164
if (!strcmp("http.sslcertpasswordprotected", var)) {
164-
if (git_config_bool(var, value))
165-
ssl_cert_password_required = 1;
165+
ssl_cert_password_required = git_config_bool(var, value);
166166
return 0;
167167
}
168168
if (!strcmp("http.ssltry", var)) {
@@ -346,10 +346,20 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
346346
{
347347
char *low_speed_limit;
348348
char *low_speed_time;
349+
char *normalized_url;
350+
struct urlmatch_config config = { STRING_LIST_INIT_DUP };
351+
352+
config.section = "http";
353+
config.key = NULL;
354+
config.collect_fn = http_options;
355+
config.cascade_fn = git_default_config;
356+
config.cb = NULL;
349357

350358
http_is_verbose = 0;
359+
normalized_url = url_normalize(url, &config.url);
351360

352-
git_config(http_options, NULL);
361+
git_config(urlmatch_config_entry, &config);
362+
free(normalized_url);
353363

354364
curl_global_init(CURL_GLOBAL_ALL);
355365

t/.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
t[0-9][0-9][0-9][0-9]/* -whitespace
2+
t0110/url-* binary

0 commit comments

Comments
 (0)