Skip to content

Commit cfa8489

Browse files
committed
config-batch: create parse loop and unknown command
As we build new features in the config-batch command, we define the plaintext protocol with line-by-line output and responses. To think to the future, we make sure that the protocol has a clear way to respond to an unknown command or an unknown version of that command. As some commands will allow the final argument to contain spaces or even be able to parse "\ " as a non-split token, we only provide the remaining line as data. Signed-off-by: Derrick Stolee <stolee@gmail.com>
1 parent 38db639 commit cfa8489

File tree

3 files changed

+170
-5
lines changed

3 files changed

+170
-5
lines changed

Documentation/git-config-batch.adoc

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,28 @@ SYNOPSIS
1313

1414
DESCRIPTION
1515
-----------
16-
TODO
16+
Tools frequently need to change their behavior based on values stored in
17+
Git's configuration files. These files may have complicated conditions
18+
for including extra files, so it is difficult to produce an independent
19+
parser. To avoid executing multiple processes to discover or modify
20+
multiple configuration values, the `git config-batch` command allows a
21+
single process to handle multiple requests using a machine-parseable
22+
interface across `stdin` and `stdout`.
23+
24+
PROTOCOL
25+
--------
26+
By default, the protocol uses line feeds (`LF`) to signal the end of a
27+
command over `stdin` or a response over `stdout`.
28+
29+
The protocol will be extended in the future, and consumers should be
30+
resilient to older Git versions not understanding the latest command
31+
set. Thus, if the Git version includes the `git config-batch` builtin
32+
but doesn't understand an input command, it will return a single line
33+
response:
34+
35+
```
36+
unknown_command LF
37+
```
1738

1839
SEE ALSO
1940
--------

builtin/config-batch.c

Lines changed: 132 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,144 @@
33
#include "config.h"
44
#include "environment.h"
55
#include "parse-options.h"
6+
#include "strbuf.h"
7+
#include "string-list.h"
68

79
static const char *const builtin_config_batch_usage[] = {
810
N_("git config-batch <options>"),
911
NULL
1012
};
1113

14+
#define UNKNOWN_COMMAND "unknown_command"
15+
16+
static int emit_response(const char *response, ...)
17+
{
18+
va_list params;
19+
const char *token;
20+
21+
printf("%s", response);
22+
23+
va_start(params, response);
24+
while ((token = va_arg(params, const char *)))
25+
printf(" %s", token);
26+
va_end(params);
27+
28+
printf("\n");
29+
fflush(stdout);
30+
return 0;
31+
}
32+
33+
/**
34+
* A function pointer type for defining a command. The function is
35+
* responsible for handling different versions of the command name.
36+
*
37+
* Provides the remaining 'data' for the command, to be parsed by
38+
* the function as needed according to its parsing rules.
39+
*
40+
* These functions should only return a negative value if they result
41+
* in such a catastrophic failure that the process should end.
42+
*
43+
* Return 0 on success.
44+
*/
45+
typedef int (*command_fn)(struct repository *repo,
46+
char *data, size_t data_len);
47+
48+
static int unknown_command(struct repository *repo UNUSED,
49+
char *data UNUSED, size_t data_len UNUSED)
50+
{
51+
return emit_response(UNKNOWN_COMMAND, NULL);
52+
}
53+
54+
struct command {
55+
const char *name;
56+
command_fn fn;
57+
int version;
58+
};
59+
60+
static struct command commands[] = {
61+
/* unknown_command must be last. */
62+
{
63+
.name = "",
64+
.fn = unknown_command,
65+
},
66+
};
67+
68+
#define COMMAND_COUNT ((size_t)(sizeof(commands) / sizeof(*commands)))
69+
70+
/**
71+
* Process a single line from stdin and process the command.
72+
*
73+
* Returns 0 on successful processing of command, including the
74+
* unknown_command output.
75+
*
76+
* Returns 1 on natural exit due to exist signal of empty line.
77+
*
78+
* Returns negative value on other catastrophic error.
79+
*/
80+
static int process_command(struct repository *repo)
81+
{
82+
static struct strbuf line = STRBUF_INIT;
83+
struct string_list tokens = STRING_LIST_INIT_NODUP;
84+
const char *command;
85+
int version;
86+
char *data = NULL;
87+
size_t data_len = 0;
88+
int res = 0;
89+
90+
strbuf_getline(&line, stdin);
91+
92+
if (!line.len)
93+
return 1;
94+
95+
/* Parse out the first two tokens, command and version. */
96+
string_list_split_in_place(&tokens, line.buf, " ", 2);
97+
98+
if (tokens.nr < 2) {
99+
res = error(_("expected at least 2 tokens, got %"PRIu32),
100+
(uint32_t)tokens.nr);
101+
goto cleanup;
102+
}
103+
104+
command = tokens.items[0].string;
105+
106+
if (!git_parse_int(tokens.items[1].string, &version)) {
107+
res = error(_("unable to parse '%s' to integer"),
108+
tokens.items[1].string);
109+
goto cleanup;
110+
}
111+
112+
if (tokens.nr >= 3) {
113+
data = tokens.items[2].string;
114+
data_len = strlen(tokens.items[2].string);
115+
}
116+
117+
for (size_t i = 0; i < COMMAND_COUNT; i++) {
118+
/*
119+
* Run the ith command if we have hit the unknown
120+
* command or if the name and version match.
121+
*/
122+
if (!commands[i].name[0] ||
123+
(!strcmp(command, commands[i].name) &&
124+
commands[i].version == version)) {
125+
res = commands[i].fn(repo, data, data_len);
126+
goto cleanup;
127+
}
128+
}
129+
130+
BUG(_("scanned to end of command list, including 'unknown_command'"));
131+
132+
cleanup:
133+
strbuf_reset(&line);
134+
string_list_clear(&tokens, 0);
135+
return res;
136+
}
137+
12138
int cmd_config_batch(int argc,
13139
const char **argv,
14140
const char *prefix,
15141
struct repository *repo)
16142
{
143+
int res = 0;
17144
struct option options[] = {
18145
OPT_END(),
19146
};
@@ -26,5 +153,9 @@ int cmd_config_batch(int argc,
26153

27154
repo_config(repo, git_default_config, NULL);
28155

29-
return 0;
156+
while (!(res = process_command(repo)));
157+
158+
if (res == 1)
159+
return 0;
160+
die(_("an unrecoverable error occurred during command execution"));
30161
}

t/t1312-config-batch.sh

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,22 @@ test_description='Test git config-batch'
44

55
. ./test-lib.sh
66

7-
test_expect_success 'help text' '
8-
test_must_fail git config-batch -h >out &&
9-
grep usage out
7+
test_expect_success 'no commands' '
8+
echo | git config-batch >out &&
9+
test_must_be_empty out
10+
'
11+
12+
test_expect_success 'unknown_command' '
13+
echo unknown_command >expect &&
14+
echo "bogus 1 line of tokens" >in &&
15+
git config-batch >out <in &&
16+
test_cmp expect out
17+
'
18+
19+
test_expect_success 'failed to parse version' '
20+
echo "bogus BAD_VERSION line of tokens" >in &&
21+
test_must_fail git config-batch 2>err <in &&
22+
test_grep BAD_VERSION err
1023
'
1124

1225
test_done

0 commit comments

Comments
 (0)