Skip to content

Commit

Permalink
Merge branch 'jc/url-match'
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Junio C Hamano committed Sep 9, 2013
2 parents b02f5ae + 6667a6a commit a0a08d4
Show file tree
Hide file tree
Showing 24 changed files with 1,072 additions and 24 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@
/test-string-list
/test-subprocess
/test-svn-fe
/test-urlmatch-normalization
/test-wildmatch
/common-cmds.h
*.tar.gz
Expand Down
45 changes: 45 additions & 0 deletions Documentation/config.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1533,6 +1533,51 @@ http.useragent::
of common USER_AGENT strings (but not including those like git/1.7.1).
Can be overridden by the 'GIT_HTTP_USER_AGENT' environment variable.

http.<url>.*::
Any of the http.* options above can be applied selectively to some urls.
For a config key to match a URL, each element of the config key is
compared to that of the URL, in the following order:
+
--
. Scheme (e.g., `https` in `https://example.com/`). This field
must match exactly between the config key and the URL.

. Host/domain name (e.g., `example.com` in `https://example.com/`).
This field must match exactly between the config key and the URL.

. Port number (e.g., `8080` in `http://example.com:8080/`).
This field must match exactly between the config key and the URL.
Omitted port numbers are automatically converted to the correct
default for the scheme before matching.

. Path (e.g., `repo.git` in `https://example.com/repo.git`). The
path field of the config key must match the path field of the URL
either exactly or as a prefix of slash-delimited path elements. This means
a config key with path `foo/` matches URL path `foo/bar`. A prefix can only
match on a slash (`/`) boundary. Longer matches take precedence (so a config
key with path `foo/bar` is a better match to URL path `foo/bar` than a config
key with just path `foo/`).

. User name (e.g., `user` in `https://user@example.com/repo.git`). If
the config key has a user name it must match the user name in the
URL exactly. If the config key does not have a user name, that
config key will match a URL with any user name (including none),
but at a lower precedence than a config key with a user name.
--
+
The list above is ordered by decreasing precedence; a URL that matches
a config key's path is preferred to one that matches its user name. For example,
if the URL is `https://user@example.com/foo/bar` a config key match of
`https://example.com/foo` will be preferred over a config key match of
`https://user@example.com`.
+
All URLs are normalized before attempting any matching (the password part,
if embedded in the URL, is always ignored for matching purposes) so that
equivalent urls that are simply spelled differently will match properly.
Environment variable settings always override any matches. The urls that are
matched against are those given directly to Git commands. This means any URLs
visited as a result of a redirection do not participate in matching.

i18n.commitEncoding::
Character encoding the commit messages are stored in; Git itself
does not care per se, but this information is necessary e.g. when
Expand Down
29 changes: 29 additions & 0 deletions Documentation/git-config.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ SYNOPSIS
'git config' [<file-option>] [type] [-z|--null] --get name [value_regex]
'git config' [<file-option>] [type] [-z|--null] --get-all name [value_regex]
'git config' [<file-option>] [type] [-z|--null] --get-regexp name_regex [value_regex]
'git config' [<file-option>] [type] [-z|--null] --get-urlmatch name URL
'git config' [<file-option>] --unset name [value_regex]
'git config' [<file-option>] --unset-all name [value_regex]
'git config' [<file-option>] --rename-section old_name new_name
Expand Down Expand Up @@ -95,6 +96,14 @@ OPTIONS
in which section and variable names are lowercased, but subsection
names are not.

--get-urlmatch name URL::
When given a two-part name section.key, the value for
section.<url>.key whose <url> part matches the best to the
given URL is returned (if no such key exists, the value for
section.key is used as a fallback). When given just the
section as name, do so for all the keys in the section and
list them.

--global::
For writing options: write to global `~/.gitconfig` file
rather than the repository `.git/config`, write to
Expand Down Expand Up @@ -295,6 +304,13 @@ Given a .git/config like this:
gitproxy=proxy-command for kernel.org
gitproxy=default-proxy ; for all the rest

; HTTP
[http]
sslVerify
[http "https://weak.example.com"]
sslVerify = false
cookieFile = /tmp/cookie.txt

you can set the filemode to true with

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

For URLs in `https://weak.example.com`, `http.sslVerify` is set to
false, while it is set to `true` for all others:

------------
% git config --bool --get-urlmatch http.sslverify https://good.example.com
true
% git config --bool --get-urlmatch http.sslverify https://weak.example.com
false
% git config --get-urlmatch http https://weak.example.com
http.cookiefile /tmp/cookie.txt
http.sslverify false
------------

include::config.txt[]

GIT
Expand Down
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,7 @@ TEST_PROGRAMS_NEED_X += test-sigchain
TEST_PROGRAMS_NEED_X += test-string-list
TEST_PROGRAMS_NEED_X += test-subprocess
TEST_PROGRAMS_NEED_X += test-svn-fe
TEST_PROGRAMS_NEED_X += test-urlmatch-normalization
TEST_PROGRAMS_NEED_X += test-wildmatch

TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X))
Expand Down Expand Up @@ -736,6 +737,7 @@ LIB_H += tree-walk.h
LIB_H += tree.h
LIB_H += unpack-trees.h
LIB_H += url.h
LIB_H += urlmatch.h
LIB_H += userdiff.h
LIB_H += utf8.h
LIB_H += varint.h
Expand Down Expand Up @@ -886,6 +888,7 @@ LIB_OBJS += tree.o
LIB_OBJS += tree-walk.o
LIB_OBJS += unpack-trees.o
LIB_OBJS += url.o
LIB_OBJS += urlmatch.o
LIB_OBJS += usage.o
LIB_OBJS += userdiff.o
LIB_OBJS += utf8.o
Expand Down
140 changes: 119 additions & 21 deletions builtin/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "cache.h"
#include "color.h"
#include "parse-options.h"
#include "urlmatch.h"

static const char *const builtin_config_usage[] = {
N_("git config [options]"),
Expand Down Expand Up @@ -42,6 +43,7 @@ static int respect_includes = -1;
#define ACTION_SET_ALL (1<<12)
#define ACTION_GET_COLOR (1<<13)
#define ACTION_GET_COLORBOOL (1<<14)
#define ACTION_GET_URLMATCH (1<<15)

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

static int collect_config(const char *key_, const char *value_, void *cb)
static int format_config(struct strbuf *buf, const char *key_, const char *value_)
{
struct strbuf_list *values = cb;
struct strbuf *buf;
char value[256];
const char *vptr = value;
int must_free_vptr = 0;
int must_print_delim = 0;
char value[256];
const char *vptr = value;

if (!use_key_regexp && strcmp(key_, key))
return 0;
if (use_key_regexp && regexec(key_regexp, key_, 0, NULL, 0))
return 0;
if (regexp != NULL &&
(do_not_match ^ !!regexec(regexp, (value_?value_:""), 0, NULL, 0)))
return 0;

ALLOC_GROW(values->items, values->nr + 1, values->alloc);
buf = &values->items[values->nr++];
strbuf_init(buf, 0);

if (show_keys) {
strbuf_addstr(buf, key_);
must_print_delim = 1;
}
if (types == TYPE_INT)
sprintf(value, "%d", git_config_int(key_, value_?value_:""));
sprintf(value, "%d", git_config_int(key_, value_ ? value_ : ""));
else if (types == TYPE_BOOL)
vptr = git_config_bool(key_, value_) ? "true" : "false";
else if (types == TYPE_BOOL_OR_INT) {
Expand Down Expand Up @@ -156,15 +147,27 @@ static int collect_config(const char *key_, const char *value_, void *cb)
strbuf_addch(buf, term);

if (must_free_vptr)
/* If vptr must be freed, it's a pointer to a
* dynamically allocated buffer, it's safe to cast to
* const.
*/
free((char *)vptr);

return 0;
}

static int collect_config(const char *key_, const char *value_, void *cb)
{
struct strbuf_list *values = cb;

if (!use_key_regexp && strcmp(key_, key))
return 0;
if (use_key_regexp && regexec(key_regexp, key_, 0, NULL, 0))
return 0;
if (regexp != NULL &&
(do_not_match ^ !!regexec(regexp, (value_?value_:""), 0, NULL, 0)))
return 0;

ALLOC_GROW(values->items, values->nr + 1, values->alloc);

return format_config(&values->items[values->nr++], key_, value_);
}

static int get_value(const char *key_, const char *regex_)
{
int ret = CONFIG_GENERIC_ERROR;
Expand Down Expand Up @@ -364,6 +367,97 @@ static void check_blob_write(void)
die("writing config blobs is not supported");
}

struct urlmatch_current_candidate_value {
char value_is_null;
struct strbuf value;
};

static int urlmatch_collect_fn(const char *var, const char *value, void *cb)
{
struct string_list *values = cb;
struct string_list_item *item = string_list_insert(values, var);
struct urlmatch_current_candidate_value *matched = item->util;

if (!matched) {
matched = xmalloc(sizeof(*matched));
strbuf_init(&matched->value, 0);
item->util = matched;
} else {
strbuf_reset(&matched->value);
}

if (value) {
strbuf_addstr(&matched->value, value);
matched->value_is_null = 0;
} else {
matched->value_is_null = 1;
}
return 0;
}

static char *dup_downcase(const char *string)
{
char *result;
size_t len, i;

len = strlen(string);
result = xmalloc(len + 1);
for (i = 0; i < len; i++)
result[i] = tolower(string[i]);
result[i] = '\0';
return result;
}

static int get_urlmatch(const char *var, const char *url)
{
char *section_tail;
struct string_list_item *item;
struct urlmatch_config config = { STRING_LIST_INIT_DUP };
struct string_list values = STRING_LIST_INIT_DUP;

config.collect_fn = urlmatch_collect_fn;
config.cascade_fn = NULL;
config.cb = &values;

if (!url_normalize(url, &config.url))
die("%s", config.url.err);

config.section = dup_downcase(var);
section_tail = strchr(config.section, '.');
if (section_tail) {
*section_tail = '\0';
config.key = section_tail + 1;
show_keys = 0;
} else {
config.key = NULL;
show_keys = 1;
}

git_config_with_options(urlmatch_config_entry, &config,
given_config_file, NULL, respect_includes);

for_each_string_list_item(item, &values) {
struct urlmatch_current_candidate_value *matched = item->util;
struct strbuf key = STRBUF_INIT;
struct strbuf buf = STRBUF_INIT;

strbuf_addstr(&key, item->string);
format_config(&buf, key.buf,
matched->value_is_null ? NULL : matched->value.buf);
fwrite(buf.buf, 1, buf.len, stdout);
strbuf_release(&key);
strbuf_release(&buf);

strbuf_release(&matched->value);
}
string_list_clear(&config.vars, 1);
string_list_clear(&values, 1);
free(config.url.url);

free((void *)config.section);
return 0;
}

int cmd_config(int argc, const char **argv, const char *prefix)
{
int nongit = !startup_info->have_repository;
Expand Down Expand Up @@ -523,6 +617,10 @@ int cmd_config(int argc, const char **argv, const char *prefix)
check_argc(argc, 1, 2);
return get_value(argv[0], argv[1]);
}
else if (actions == ACTION_GET_URLMATCH) {
check_argc(argc, 2, 2);
return get_urlmatch(argv[0], argv[1]);
}
else if (actions == ACTION_UNSET) {
check_blob_write();
check_argc(argc, 1, 2);
Expand Down
16 changes: 13 additions & 3 deletions http.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "sideband.h"
#include "run-command.h"
#include "url.h"
#include "urlmatch.h"
#include "credential.h"
#include "version.h"
#include "pkt-line.h"
Expand Down Expand Up @@ -161,8 +162,7 @@ static int http_options(const char *var, const char *value, void *cb)
if (!strcmp("http.sslcainfo", var))
return git_config_string(&ssl_cainfo, var, value);
if (!strcmp("http.sslcertpasswordprotected", var)) {
if (git_config_bool(var, value))
ssl_cert_password_required = 1;
ssl_cert_password_required = git_config_bool(var, value);
return 0;
}
if (!strcmp("http.ssltry", var)) {
Expand Down Expand Up @@ -346,10 +346,20 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
{
char *low_speed_limit;
char *low_speed_time;
char *normalized_url;
struct urlmatch_config config = { STRING_LIST_INIT_DUP };

config.section = "http";
config.key = NULL;
config.collect_fn = http_options;
config.cascade_fn = git_default_config;
config.cb = NULL;

http_is_verbose = 0;
normalized_url = url_normalize(url, &config.url);

git_config(http_options, NULL);
git_config(urlmatch_config_entry, &config);
free(normalized_url);

curl_global_init(CURL_GLOBAL_ALL);

Expand Down
1 change: 1 addition & 0 deletions t/.gitattributes
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
t[0-9][0-9][0-9][0-9]/* -whitespace
t0110/url-* binary
Loading

0 comments on commit a0a08d4

Please sign in to comment.