[PATCH/RFC 0/5] add "unset.variable" for unsetting previously set variables

DORMANTno replies

6 messages, 1 author, 2016-06-15 · open the first message on its own page

[PATCH/RFC 0/5] add "unset.variable" for unsetting previously set variables

From: Tanay Abhra <hidden>
Date: 2016-06-15 23:02:38

Hi,

This series aims to add a method to filter previously set variables.
The patch series can be best described by the 3/5 log message
which I have pasted below verbatim.

"
Add a new config variable "unset.variable" which unsets previously set
variables. It affects `git_config()` and `git_config_get_*()` family
of functions. It removes the matching variables from the `configset`
which were added previously. Those matching variables which come after
the "unset.variable" in parsing order will not be deleted and will
be left untouched.

It affects the result of "git config -l" and similar calls.
It may be used in cases where the user can not access the config files,
for example, the system wide config files may be only accessible to
the system administrator. We can unset an unwanted variable declared in
the system config file by using "unset.variable" in a local config file.

for example, /etc/gitconfig may look like this,
	[foo]
		bar = baz

in the repo config file, we will write,
	[unset]
		variable  = foo.bar
to unset foo.bar previously declared in system wide config file.
"

Now, I have some points of
contention which I like to clarify,

1> The name of the variable, I could not decide between "unset.variable"
and "config.unset", or may be some other name would be more appropriate.

2> It affects both the C git_config() calls and, git config shell
invocations. Due to this some variables may be absent from the git config -l
result which might confuse the user.

3> I also have an another implementation for this series which just marks the config
variables instead of deleting them from the configset. This can be used to
provide two versions of git_config(), one with filtered variables other without
it.

4> While hacking on this series, I saw that git_config_int() does not print
the file name of the invalid variable when values are fed by the configset.
I will correct this regression in another patch.

Cheers,
Tanay


 Documentation/config.txt | 12 +++++++
 config.c                 | 93 +++++++++++++++++++++++++++++++++++++-----------
 t/t1300-repo-config.sh   | 56 ++++++++++++++++++++++++++++-
 3 files changed, 139 insertions(+), 22 deletions(-)

-- 
1.9.0.GIT

[PATCH/RFC 2/5] make git_config_with_options() to use a configset

From: Tanay Abhra <hidden>
Date: 2016-06-15 23:02:38

Make git_config_with_options() to use a configset to feed values
in the callback function. This change gives us the power to filter
variables we feed to the callback using custom constraints.

A slight behaviour change, git_config_int() loses the ability to
print the file name of the invalid variable while dying.

Helped-by: Matthieu Moy [off-list ref]
Signed-off-by: Tanay Abhra <redacted>
---
 config.c               | 21 +++++++++++++++++++--
 t/t1300-repo-config.sh |  2 +-
 2 files changed, 20 insertions(+), 3 deletions(-)
diff --git a/config.c b/config.c
index cb474b2..09cf009 100644
--- a/config.c
+++ b/config.c
@@ -1214,7 +1214,7 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config)
 	return ret == 0 ? found : ret;
 }
 
-int git_config_with_options(config_fn_t fn, void *data,
+static int git_config_with_options_raw(config_fn_t fn, void *data,
 			    struct git_config_source *config_source,
 			    int respect_includes)
 {
@@ -1247,9 +1247,26 @@ int git_config_with_options(config_fn_t fn, void *data,
 	return ret;
 }
 
+static int config_set_callback(const char *key, const char *value, void *cb);
+
+int git_config_with_options(config_fn_t fn, void *data,
+			    struct git_config_source *config_source,
+			    int respect_includes)
+{
+	int ret;
+	struct config_set options_config;
+	git_configset_init(&options_config);
+	ret = git_config_with_options_raw(config_set_callback, &options_config,
+					  config_source, respect_includes);
+	if (ret >= 0)
+		configset_iter(&options_config, fn, data);
+	git_configset_clear(&options_config);
+	return ret;
+}
+
 static void git_config_raw(config_fn_t fn, void *data)
 {
-	if (git_config_with_options(fn, data, NULL, 1) < 0)
+	if (git_config_with_options_raw(fn, data, NULL, 1) < 0)
 		/*
 		 * git_config_with_options() normally returns only
 		 * positive values, as most errors are fatal, and
diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh
index 938fc8b..ce5ea01 100755
--- a/t/t1300-repo-config.sh
+++ b/t/t1300-repo-config.sh
@@ -678,7 +678,7 @@ test_expect_success 'invalid unit' '
 	git config aninvalid.unit >actual &&
 	test_cmp expect actual &&
 	cat >expect <<-\EOF
-	fatal: bad numeric config value '\''1auto'\'' for '\''aninvalid.unit'\'' in .git/config: invalid unit
+	fatal: bad numeric config value '\''1auto'\'' for '\''aninvalid.unit'\'': invalid unit
 	EOF
 	test_must_fail git config --int --get aninvalid.unit 2>actual &&
 	test_i18ncmp expect actual
-- 
1.9.0.GIT

[PATCH/RFC 1/5] config.c : move configset_iter() to an appropriate position

From: Tanay Abhra <hidden>
Date: 2016-06-15 23:02:38

Move configset_iter() to an appropriate position where it
can be called by git_config_*() family without putting
a forward declaration for it. 

Helped-by: Matthieu Moy [off-list ref]
Signed-off-by: Tanay Abhra <redacted>
---
 config.c | 38 +++++++++++++++++++-------------------
 1 file changed, 19 insertions(+), 19 deletions(-)
diff --git a/config.c b/config.c
index a677eb6..cb474b2 100644
--- a/config.c
+++ b/config.c
@@ -1150,6 +1150,25 @@ int git_config_system(void)
 	return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0);
 }
 
+static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)
+{
+	int i, value_index;
+	struct string_list *values;
+	struct config_set_element *entry;
+	struct configset_list *list = &cs->list;
+	struct key_value_info *kv_info;
+
+	for (i = 0; i < list->nr; i++) {
+		entry = list->items[i].e;
+		value_index = list->items[i].value_index;
+		values = &entry->value_list;
+		if (fn(entry->key, values->items[value_index].string, data) < 0) {
+			kv_info = values->items[value_index].util;
+			git_die_config_linenr(entry->key, kv_info->filename, kv_info->linenr);
+		}
+	}
+}
+
 int git_config_early(config_fn_t fn, void *data, const char *repo_config)
 {
 	int ret = 0, found = 0;
@@ -1245,25 +1264,6 @@ static void git_config_raw(config_fn_t fn, void *data)
 		die(_("unknown error occured while reading the configuration files"));
 }
 
-static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)
-{
-	int i, value_index;
-	struct string_list *values;
-	struct config_set_element *entry;
-	struct configset_list *list = &cs->list;
-	struct key_value_info *kv_info;
-
-	for (i = 0; i < list->nr; i++) {
-		entry = list->items[i].e;
-		value_index = list->items[i].value_index;
-		values = &entry->value_list;
-		if (fn(entry->key, values->items[value_index].string, data) < 0) {
-			kv_info = values->items[value_index].util;
-			git_die_config_linenr(entry->key, kv_info->filename, kv_info->linenr);
-		}
-	}
-}
-
 static void git_config_check_init(void);
 
 void git_config(config_fn_t fn, void *data)
-- 
1.9.0.GIT

[PATCH/RFC 3/5] add "unset.variable" for unsetting previously set variables

From: Tanay Abhra <hidden>
Date: 2016-06-15 23:02:38

Add a new config variable "unset.variable" which unsets previously set
variables. It affects `git_config()` and `git_config_get_*()` family
of functions. It removes the matching variables from the `configset`
which were added previously. Those matching variables which come after
the "unset.variable" in parsing order will not be deleted and will
be left untouched.

It affects the result of "git config -l" and similar calls.
It may be used in cases where the user can not access the config files,
for example, the system wide config files may be only accessible to
the system administrator. We can unset an unwanted variable declared in
the system config file by using "unset.variable" in a local config file.

for example, /etc/gitconfig may look like this,
	[foo]
		bar = baz

in the repo config file, we will write,
	[unset]
		variable  = foo.bar
to unset foo.bar previously declared in system wide config file.

Helped-by: Matthieu Moy [off-list ref]
Signed-off-by: Tanay Abhra <redacted>
---
 config.c | 34 ++++++++++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)
diff --git a/config.c b/config.c
index 09cf009..a80832d 100644
--- a/config.c
+++ b/config.c
@@ -1311,6 +1311,38 @@ static struct config_set_element *configset_find_element(struct config_set *cs,
 	return found_entry;
 }
 
+static void delete_config_variable(struct config_set *cs, const char *key, const char *value)
+{
+	char *normalized_value;
+	struct config_set_element *e = NULL;
+	int ret, current = 0, updated = 0;
+	struct configset_list *list = &cs->list;
+	/*
+	 * if we find a key value pair with key as "unset.variable", unset all variables
+	 * in the configset with keys equivalent to the value in "unset.variable".
+	 * unsetting a variable means that the variable is permanently deleted from the
+	 * configset.
+	 */
+	ret = git_config_parse_key(value, &normalized_value, NULL);
+	if (!ret) {
+		/* first remove matching variables from the configset_list */
+		while (current < list->nr) {
+			if (!strcmp(list->items[current].e->key, normalized_value))
+				current++;
+			else
+				list->items[updated++] = list->items[current++];
+		}
+		list->nr = updated;
+		/* then delete the matching entry from the configset hashmap */
+		e = configset_find_element(cs, normalized_value);
+		if (e) {
+			free(e->key);
+			string_list_clear(&e->value_list, 1);
+			hashmap_remove(&cs->config_hash, e, NULL);
+		}
+	}
+}
+
 static int configset_add_value(struct config_set *cs, const char *key, const char *value)
 {
 	struct config_set_element *e;
@@ -1331,6 +1363,8 @@ static int configset_add_value(struct config_set *cs, const char *key, const cha
 		hashmap_add(&cs->config_hash, e);
 	}
 	si = string_list_append_nodup(&e->value_list, value ? xstrdup(value) : NULL);
+	if (!strcmp(key, "unset.variable"))
+		delete_config_variable(cs, key, value);
 
 	ALLOC_GROW(cs->list.items, cs->list.nr + 1, cs->list.alloc);
 	l_item = &cs->list.items[cs->list.nr++];
-- 
1.9.0.GIT

[PATCH/RFC 4/5] document the new "unset.variable" variable

From: Tanay Abhra <hidden>
Date: 2016-06-15 23:02:38

Helped-by: Matthieu Moy [off-list ref]
Signed-off-by: Tanay Abhra <redacted>
---
 Documentation/config.txt | 12 ++++++++++++
 1 file changed, 12 insertions(+)
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 3b5b24a..7f36d35 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -2382,6 +2382,18 @@ transfer.unpackLimit::
 	not set, the value of this variable is used instead.
 	The default value is 100.
 
+unset.variable::
+	This variable can be used to unset previously set variables
+	which had been already declared in files of lower priority
+	or declared before in the same file. It does not unset
+	matching variables declared after its position in the file
+	or in files of higher priority. It can be used to unset
+	pesky variables declared in files which the user might not
+	be able to open due to not having the required security
+	privileges, for example, system wide configuration file
+	`/etc/gitconfig` which may be accessible to the system
+	administrator only.
+
 uploadarchive.allowUnreachable::
 	If true, allow clients to use `git archive --remote` to request
 	any tree, whether reachable from the ref tips or not. See the
-- 
1.9.0.GIT

[PATCH/RFC 5/5] add tests for checking the behaviour of "unset.variable"

From: Tanay Abhra <hidden>
Date: 2016-06-15 23:02:38

Helped-by: Matthieu Moy [off-list ref]
Signed-off-by: Tanay Abhra <redacted>
---
 t/t1300-repo-config.sh | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 54 insertions(+)
diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh
index ce5ea01..f75c001 100755
--- a/t/t1300-repo-config.sh
+++ b/t/t1300-repo-config.sh
@@ -1179,4 +1179,58 @@ test_expect_success POSIXPERM,PERL 'preserves existing permissions' '
 	  "die q(badrename) if ((stat(q(.git/config)))[2] & 07777) != 0600"
 '
 
+test_expect_success 'unset.variable unsets all previous matching keys' '
+	cat >.git/config <<-\EOF &&
+	[alias]
+		checkconfig = -c foo.check=baz config foo.check
+		checkconfig = -c foo.check=bar config foo.check
+	[unset]
+		variable = alias.checkconfig
+	EOF
+
+	test_expect_code 1 git checkconfig
+'
+
+test_expect_success 'unset.variable does not touch all matching keys after it' '
+	cat >.git/config <<-\EOF &&
+	[alias]
+		checkconfig = -c foo.check=foo config foo.check
+	[unset]
+		variable = alias.checkconfig
+	[alias]
+		checkconfig = -c foo.check=baz config foo.check
+		checkconfig = -c foo.check=bar config foo.check
+	EOF
+
+	cat >expect <<-\EOF &&
+	bar
+	EOF
+
+	test_expect_code 0 git checkconfig >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'document how unset.variable will behave in shell scripts' '
+	rm -f .git/config &&
+	cat >expect <<-\EOF &&
+	EOF
+	git config foo.bar boz1 &&
+	git config --add foo.bar boz2 &&
+	git config unset.variable foo.bar &&
+	git config --add foo.bar boz3 &&
+	test_must_fail git config --get-all foo.bar >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'unset.variable declared after in shell scripts' '
+	rm -f .git/config &&
+	cat >expect <<-\EOF &&
+	EOF
+	git config foo.bar boz1 &&
+	git config --add foo.bar boz2 &&
+	git config unset.variable foo.bar &&
+	test_must_fail git config --get-all foo.bar >actual &&
+	test_cmp expect actual
+'
+
 test_done
-- 
1.9.0.GIT
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help