Skip to content

Commit 931fbf4

Browse files
committed
config-batch: implement get v1
The 'get' command for the 'git config-batch' builtin is the first command and is currently at version 1. It returns at most one value, the same as 'git config --get <key>' with optional value-based filtering. The documentation and tests detail the specifics of how to format requests of this format and how to parse the results. Future versions could consider multi-valued responses or regex-based key matching. For the sake of incremental exploration of the potential in the 'git config-batch' command, this is the only implementation being presented in the first patch series. Future extensions could include a '-z' parameter that uses NUL bytes in the command and output format to allow for spaces or newlines in the input or newlines in the output. Signed-off-by: Derrick Stolee <stolee@gmail.com>
1 parent 31393f9 commit 931fbf4

File tree

4 files changed

+348
-2
lines changed

4 files changed

+348
-2
lines changed

Documentation/git-config-batch.adoc

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,56 @@ set. Thus, if the Git version includes the `git config-batch` builtin
3232
but doesn't understand an input command, it will return a single line
3333
response:
3434

35-
```
35+
------------
3636
unknown_command LF
37-
```
37+
------------
38+
39+
These are the commands that are currently understood:
40+
41+
`get` version 1::
42+
The `get` command searches the config key-value pairs within a
43+
given `<scope>` for values that match the fixed `<key>` and
44+
filters the resulting value based on an optional `<value-filter>`.
45+
This can either be a regex or a fixed value. The command format
46+
is one of the following formats:
47+
+
48+
------------
49+
get 1 <scope> <key>
50+
get 1 <scope> <key> regex <value-pattern>
51+
get 1 <scope> <key> fixed-value <value>
52+
------------
53+
+
54+
The `<scope>` value can be one of `inherited`, `system`, `global`,
55+
`local`, `worktree`, `submodule`, or `command`. If `inherited`, then all
56+
config key-value pairs will be considered regardless of scope. Otherwise,
57+
only the given scope will be considered.
58+
+
59+
If no optional arguments are given, then the value will not be filtered
60+
by any pattern matching. If `regex` is specified, then `<value-pattern>`
61+
is interpreted as a regular expression for matching against stored
62+
values, similar to specifying a value to `get config --get <key> <value>`.
63+
If `fixed-value` is specified, then `<value>` is checked for an exact
64+
match against the key-value pairs, simmilar to `git config --get <key>
65+
--fixed-value <value>`.
66+
+
67+
At mmost one key-value pair is returned, that being the last key-value
68+
pair in the standard config order by scope and sequence within each scope.
69+
+
70+
If a key-value pair is found, then the following output is given:
71+
+
72+
------------
73+
get found <key> <scope> <value>
74+
------------
75+
+
76+
If no matching key-value pair is found, then the following output is
77+
given:
78+
+
79+
------------
80+
get missing <key> [<value-pattern>|<value>]
81+
------------
82+
+
83+
where `<value-pattern>` or `<value>` is only supplied if provided in
84+
the command.
3885

3986
SEE ALSO
4087
--------

builtin/config-batch.c

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ static const char *const builtin_config_batch_usage[] = {
1212
};
1313

1414
#define UNKNOWN_COMMAND "unknown_command"
15+
#define GET_COMMAND "get"
16+
#define COMMAND_PARSE_ERROR "command_parse_error"
1517

1618
static int emit_response(const char *response, ...)
1719
{
@@ -30,6 +32,19 @@ static int emit_response(const char *response, ...)
3032
return 0;
3133
}
3234

35+
static int unknown_version(const char *command, int version)
36+
{
37+
char *vstr = xstrfmt("%d", version);
38+
int res = emit_response(UNKNOWN_COMMAND, command, vstr, NULL);
39+
free(vstr);
40+
return res;
41+
}
42+
43+
static int command_parse_error(const char *command)
44+
{
45+
return emit_response(COMMAND_PARSE_ERROR, command, NULL);
46+
}
47+
3348
/**
3449
* A function pointer type for defining a command. The function is
3550
* responsible for handling different versions of the command name.
@@ -48,12 +63,202 @@ static int unknown_commmand(struct repository *repo UNUSED, int version UNUSED,
4863
return emit_response(UNKNOWN_COMMAND, NULL);
4964
}
5065

66+
enum value_match_mode {
67+
MATCH_ALL,
68+
MATCH_EXACT,
69+
MATCH_REGEX,
70+
};
71+
72+
struct get_command_1_data {
73+
/* parameters */
74+
const char *key;
75+
enum config_scope scope;
76+
enum value_match_mode mode;
77+
const char *value;
78+
regex_t *value_pattern;
79+
80+
/* data along the way, for single values. */
81+
char *found;
82+
enum config_scope found_scope;
83+
};
84+
85+
static int get_command_1_cb(const char *key, const char *value,
86+
const struct config_context *context,
87+
void *data)
88+
{
89+
struct get_command_1_data *d = data;
90+
91+
if (strcasecmp(key, d->key))
92+
return 0;
93+
94+
if (d->scope != CONFIG_SCOPE_UNKNOWN &&
95+
d->scope != context->kvi->scope)
96+
return 0;
97+
98+
switch (d->mode) {
99+
case MATCH_EXACT:
100+
if (strcasecmp(value, d->value))
101+
return 0;
102+
break;
103+
104+
case MATCH_REGEX:
105+
if (regexec(d->value_pattern, value, 0, NULL, 0))
106+
return 0;
107+
break;
108+
109+
default:
110+
break;
111+
}
112+
113+
free(d->found);
114+
d->found = xstrdup(value);
115+
d->found_scope = context->kvi->scope;
116+
return 0;
117+
}
118+
119+
static const char *scope_str(enum config_scope scope)
120+
{
121+
switch (scope) {
122+
case CONFIG_SCOPE_UNKNOWN:
123+
return "unknown";
124+
125+
case CONFIG_SCOPE_SYSTEM:
126+
return "system";
127+
128+
case CONFIG_SCOPE_GLOBAL:
129+
return "global";
130+
131+
case CONFIG_SCOPE_LOCAL:
132+
return "local";
133+
134+
case CONFIG_SCOPE_WORKTREE:
135+
return "worktree";
136+
137+
case CONFIG_SCOPE_SUBMODULE:
138+
return "submodule";
139+
140+
case CONFIG_SCOPE_COMMAND:
141+
return "command";
142+
143+
default:
144+
BUG("invalid config scope");
145+
}
146+
}
147+
148+
static int parse_scope(const char *str, enum config_scope *scope)
149+
{
150+
if (!strcmp(str, "inherited")) {
151+
*scope = CONFIG_SCOPE_UNKNOWN;
152+
return 0;
153+
}
154+
155+
for (enum config_scope s = 0; s < CONFIG_SCOPE__NR; s++) {
156+
if (!strcmp(str, scope_str(s))) {
157+
*scope = s;
158+
return 0;
159+
}
160+
}
161+
162+
return -1;
163+
}
164+
165+
/**
166+
* 'get' command, version 1.
167+
*
168+
* Positional arguments should be of the form:
169+
*
170+
* [0] scope ("system", "global", "local", "worktree", "command", "submodule", or "inherited")
171+
* [1] config key
172+
* [2*] multi-mode ("all", "regex", "fixed-value")
173+
* [3*] value regex OR value string
174+
*
175+
* [N*] indicates optional parameters that are not needed.
176+
*/
177+
static int get_command_1(struct repository *repo, int argc, const char **argv)
178+
{
179+
struct get_command_1_data data = {
180+
.found = NULL,
181+
.mode = MATCH_ALL,
182+
};
183+
int res = 0;
184+
185+
if (argc < 2 || argc >= 5)
186+
goto parse_error;
187+
188+
if (parse_scope(argv[0], &data.scope))
189+
goto parse_error;
190+
191+
data.key = argv[1];
192+
193+
if (argc >= 3) {
194+
if (!strcmp(argv[2], "regex"))
195+
data.mode = MATCH_REGEX;
196+
else if (!strcmp(argv[2], "fixed-value"))
197+
data.mode = MATCH_EXACT;
198+
else
199+
goto parse_error;
200+
}
201+
202+
if (data.mode == MATCH_REGEX) {
203+
if (argc < 4)
204+
goto parse_error;
205+
206+
data.value = argv[3];
207+
208+
CALLOC_ARRAY(data.value_pattern, 1);
209+
if (regcomp(data.value_pattern, argv[3], REG_EXTENDED)) {
210+
FREE_AND_NULL(data.value_pattern);
211+
goto parse_error;
212+
}
213+
} else if (data.mode == MATCH_EXACT) {
214+
if (argc < 4)
215+
goto parse_error;
216+
217+
data.value = argv[3];
218+
}
219+
220+
repo_config(repo, get_command_1_cb, &data);
221+
222+
if (data.found)
223+
res = emit_response(GET_COMMAND, "found", data.key,
224+
scope_str(data.found_scope), data.found,
225+
NULL);
226+
else
227+
res = emit_response(GET_COMMAND, "missing", data.key, data.value, NULL);
228+
229+
goto cleanup;
230+
231+
232+
parse_error:
233+
res = command_parse_error(GET_COMMAND);
234+
235+
cleanup:
236+
if (data.value_pattern) {
237+
regfree(data.value_pattern);
238+
free(data.value_pattern);
239+
}
240+
free(data.found);
241+
return res;
242+
}
243+
244+
static int get_command(struct repository *repo, int version,
245+
int argc, const char **argv)
246+
{
247+
if (version == 1)
248+
return get_command_1(repo, argc, argv);
249+
return unknown_version(GET_COMMAND, version);
250+
}
251+
51252
struct command {
52253
const char *name;
53254
command_fn fn;
54255
};
55256

56257
static struct command commands[] = {
258+
{
259+
.name = GET_COMMAND,
260+
.fn = get_command,
261+
},
57262
/* unknown_command must be last. */
58263
{
59264
.name = "",

config.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ enum config_scope {
4444
CONFIG_SCOPE_WORKTREE,
4545
CONFIG_SCOPE_COMMAND,
4646
CONFIG_SCOPE_SUBMODULE,
47+
48+
/* Must be last */
49+
CONFIG_SCOPE__NR
4750
};
4851
const char *config_scope_name(enum config_scope scope);
4952

t/t1312-config-batch.sh

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,95 @@ test_expect_success 'failed to parse version' '
2222
test_grep BAD_VERSION err
2323
'
2424

25+
test_expect_success 'get inherited config' '
26+
test_when_finished git config --unset test.key &&
27+
28+
git config test.key "test value with spaces" &&
29+
30+
echo "get 1 inherited test.key" >in &&
31+
echo "get found test.key local test value with spaces" >expect &&
32+
git config-batch >out <in &&
33+
test_cmp expect out &&
34+
35+
echo "get 1 global test.key" >in &&
36+
echo "get missing test.key" >expect &&
37+
git config-batch >out <in &&
38+
test_cmp expect out
39+
'
40+
41+
test_expect_success 'set up worktree' '
42+
test_commit A &&
43+
git config extensions.worktreeconfig true &&
44+
git worktree add --detach worktree
45+
'
46+
47+
test_expect_success 'get config with regex' '
48+
test_when_finished git config --unset-all test.key &&
49+
GIT_CONFIG_SYSTEM=system-config-file &&
50+
GIT_CONFIG_NOSYSTEM=0 &&
51+
GIT_CONFIG_GLOBAL=global-config-file &&
52+
export GIT_CONFIG_SYSTEM &&
53+
export GIT_CONFIG_NOSYSTEM &&
54+
export GIT_CONFIG_GLOBAL &&
55+
56+
git config --system test.key on1e &&
57+
git config --global test.key t2wo &&
58+
git config test.key thre3e &&
59+
git config --worktree test.key 4four &&
60+
61+
cat >in <<-\EOF &&
62+
get 1 inherited test.key regex .*1.*
63+
get 1 inherited test.key regex [a-z]2.*
64+
get 1 inherited test.key regex .*3e
65+
get 1 inherited test.key regex 4.*
66+
get 1 inherited test.key regex .*5.*
67+
get 1 inherited test.key regex .*6.*
68+
EOF
69+
70+
cat >expect <<-\EOF &&
71+
get found test.key system on1e
72+
get found test.key global t2wo
73+
get found test.key local thre3e
74+
get found test.key worktree 4four
75+
get found test.key command five5
76+
get missing test.key .*6.*
77+
EOF
78+
79+
git -c test.key=five5 config-batch >out <in &&
80+
test_cmp expect out
81+
'
82+
83+
test_expect_success 'get config with fixed-value' '
84+
test_when_finished git config --unset-all test.key &&
85+
export GIT_CONFIG_SYSTEM=system-config-file &&
86+
export GIT_CONFIG_NOSYSTEM=0 &&
87+
export GIT_CONFIG_GLOBAL=global-config-file &&
88+
89+
git config --system test.key one &&
90+
git config --global test.key two &&
91+
git config test.key three &&
92+
git config --worktree test.key four &&
93+
94+
cat >in <<-\EOF &&
95+
get 1 inherited test.key fixed-value one
96+
get 1 inherited test.key fixed-value two
97+
get 1 inherited test.key fixed-value three
98+
get 1 inherited test.key fixed-value four
99+
get 1 inherited test.key fixed-value five
100+
get 1 inherited test.key fixed-value six
101+
EOF
102+
103+
cat >expect <<-\EOF &&
104+
get found test.key system one
105+
get found test.key global two
106+
get found test.key local three
107+
get found test.key worktree four
108+
get found test.key command five
109+
get missing test.key six
110+
EOF
111+
112+
git -c test.key=five config-batch >out <in &&
113+
test_cmp expect out
114+
'
115+
25116
test_done

0 commit comments

Comments
 (0)