Skip to content

Commit bb7584c

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 <[email protected]>
1 parent 0510637 commit bb7584c

File tree

4 files changed

+333
-2
lines changed

4 files changed

+333
-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: 190 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,11 @@ static int emit_response(const char *response, ...)
3032
return 0;
3133
}
3234

35+
static int command_parse_error(const char *command)
36+
{
37+
return emit_response(COMMAND_PARSE_ERROR, command, NULL);
38+
}
39+
3340
/**
3441
* A function pointer type for defining a command. The function is
3542
* responsible for handling different versions of the command name.
@@ -48,13 +55,196 @@ static int unknown_command(struct repository *repo UNUSED,
4855
return emit_response(UNKNOWN_COMMAND, NULL);
4956
}
5057

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

57242
static struct command commands[] = {
243+
{
244+
.name = GET_COMMAND,
245+
.fn = get_command_1,
246+
.version = 1,
247+
},
58248
/* unknown_command must be last. */
59249
{
60250
.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)