Skip to content

Commit 9a8ff89

Browse files
committed
Merge branch 'jt/subprocess-handshake'
Code cleanup. * jt/subprocess-handshake: sub-process: refactor handshake to common function Documentation: migrate sub-process docs to header
2 parents a449130 + fa64a2f commit 9a8ff89

File tree

7 files changed

+161
-151
lines changed

7 files changed

+161
-151
lines changed

Documentation/technical/api-sub-process.txt

Lines changed: 0 additions & 59 deletions
This file was deleted.

convert.c

Lines changed: 7 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -513,78 +513,17 @@ static struct hashmap subprocess_map;
513513

514514
static int start_multi_file_filter_fn(struct subprocess_entry *subprocess)
515515
{
516-
int err, i;
517-
struct cmd2process *entry = (struct cmd2process *)subprocess;
518-
struct string_list cap_list = STRING_LIST_INIT_NODUP;
519-
char *cap_buf;
520-
const char *cap_name;
521-
struct child_process *process = &subprocess->process;
522-
const char *cmd = subprocess->cmd;
523-
524-
static const struct {
525-
const char *name;
526-
unsigned int cap;
527-
} known_caps[] = {
516+
static int versions[] = {2, 0};
517+
static struct subprocess_capability capabilities[] = {
528518
{ "clean", CAP_CLEAN },
529519
{ "smudge", CAP_SMUDGE },
530520
{ "delay", CAP_DELAY },
521+
{ NULL, 0 }
531522
};
532-
533-
sigchain_push(SIGPIPE, SIG_IGN);
534-
535-
err = packet_writel(process->in, "git-filter-client", "version=2", NULL);
536-
if (err)
537-
goto done;
538-
539-
err = strcmp(packet_read_line(process->out, NULL), "git-filter-server");
540-
if (err) {
541-
error("external filter '%s' does not support filter protocol version 2", cmd);
542-
goto done;
543-
}
544-
err = strcmp(packet_read_line(process->out, NULL), "version=2");
545-
if (err)
546-
goto done;
547-
err = packet_read_line(process->out, NULL) != NULL;
548-
if (err)
549-
goto done;
550-
551-
for (i = 0; i < ARRAY_SIZE(known_caps); ++i) {
552-
err = packet_write_fmt_gently(
553-
process->in, "capability=%s\n", known_caps[i].name);
554-
if (err)
555-
goto done;
556-
}
557-
err = packet_flush_gently(process->in);
558-
if (err)
559-
goto done;
560-
561-
for (;;) {
562-
cap_buf = packet_read_line(process->out, NULL);
563-
if (!cap_buf)
564-
break;
565-
string_list_split_in_place(&cap_list, cap_buf, '=', 1);
566-
567-
if (cap_list.nr != 2 || strcmp(cap_list.items[0].string, "capability"))
568-
continue;
569-
570-
cap_name = cap_list.items[1].string;
571-
i = ARRAY_SIZE(known_caps) - 1;
572-
while (i >= 0 && strcmp(cap_name, known_caps[i].name))
573-
i--;
574-
575-
if (i >= 0)
576-
entry->supported_capabilities |= known_caps[i].cap;
577-
else
578-
warning("external filter '%s' requested unsupported filter capability '%s'",
579-
cmd, cap_name);
580-
581-
string_list_clear(&cap_list, 0);
582-
}
583-
584-
done:
585-
sigchain_pop(SIGPIPE);
586-
587-
return err;
523+
struct cmd2process *entry = (struct cmd2process *)subprocess;
524+
return subprocess_handshake(subprocess, "git-filter", versions, NULL,
525+
capabilities,
526+
&entry->supported_capabilities);
588527
}
589528

590529
static void handle_filter_error(const struct strbuf *filter_status,

pkt-line.c

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -171,25 +171,6 @@ int packet_write_fmt_gently(int fd, const char *fmt, ...)
171171
return status;
172172
}
173173

174-
int packet_writel(int fd, const char *line, ...)
175-
{
176-
va_list args;
177-
int err;
178-
va_start(args, line);
179-
for (;;) {
180-
if (!line)
181-
break;
182-
if (strlen(line) > LARGE_PACKET_DATA_MAX)
183-
return -1;
184-
err = packet_write_fmt_gently(fd, "%s\n", line);
185-
if (err)
186-
return err;
187-
line = va_arg(args, const char*);
188-
}
189-
va_end(args);
190-
return packet_flush_gently(fd);
191-
}
192-
193174
static int packet_write_gently(const int fd_out, const char *buf, size_t size)
194175
{
195176
static char packet_write_buffer[LARGE_PACKET_MAX];

pkt-line.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ void packet_buf_flush(struct strbuf *buf);
2525
void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
2626
int packet_flush_gently(int fd);
2727
int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
28-
LAST_ARG_MUST_BE_NULL
29-
int packet_writel(int fd, const char *line, ...);
3028
int write_packetized_from_fd(int fd_in, int fd_out);
3129
int write_packetized_from_buf(const char *src_in, size_t len, int fd_out);
3230

sub-process.c

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,107 @@ int subprocess_start(struct hashmap *hashmap, struct subprocess_entry *entry, co
108108
hashmap_add(hashmap, entry);
109109
return 0;
110110
}
111+
112+
static int handshake_version(struct child_process *process,
113+
const char *welcome_prefix, int *versions,
114+
int *chosen_version)
115+
{
116+
int version_scratch;
117+
int i;
118+
char *line;
119+
const char *p;
120+
121+
if (!chosen_version)
122+
chosen_version = &version_scratch;
123+
124+
if (packet_write_fmt_gently(process->in, "%s-client\n",
125+
welcome_prefix))
126+
return error("Could not write client identification");
127+
for (i = 0; versions[i]; i++) {
128+
if (packet_write_fmt_gently(process->in, "version=%d\n",
129+
versions[i]))
130+
return error("Could not write requested version");
131+
}
132+
if (packet_flush_gently(process->in))
133+
return error("Could not write flush packet");
134+
135+
if (!(line = packet_read_line(process->out, NULL)) ||
136+
!skip_prefix(line, welcome_prefix, &p) ||
137+
strcmp(p, "-server"))
138+
return error("Unexpected line '%s', expected %s-server",
139+
line ? line : "<flush packet>", welcome_prefix);
140+
if (!(line = packet_read_line(process->out, NULL)) ||
141+
!skip_prefix(line, "version=", &p) ||
142+
strtol_i(p, 10, chosen_version))
143+
return error("Unexpected line '%s', expected version",
144+
line ? line : "<flush packet>");
145+
if ((line = packet_read_line(process->out, NULL)))
146+
return error("Unexpected line '%s', expected flush", line);
147+
148+
/* Check to make sure that the version received is supported */
149+
for (i = 0; versions[i]; i++) {
150+
if (versions[i] == *chosen_version)
151+
break;
152+
}
153+
if (!versions[i])
154+
return error("Version %d not supported", *chosen_version);
155+
156+
return 0;
157+
}
158+
159+
static int handshake_capabilities(struct child_process *process,
160+
struct subprocess_capability *capabilities,
161+
unsigned int *supported_capabilities)
162+
{
163+
int i;
164+
char *line;
165+
166+
for (i = 0; capabilities[i].name; i++) {
167+
if (packet_write_fmt_gently(process->in, "capability=%s\n",
168+
capabilities[i].name))
169+
return error("Could not write requested capability");
170+
}
171+
if (packet_flush_gently(process->in))
172+
return error("Could not write flush packet");
173+
174+
while ((line = packet_read_line(process->out, NULL))) {
175+
const char *p;
176+
if (!skip_prefix(line, "capability=", &p))
177+
continue;
178+
179+
for (i = 0;
180+
capabilities[i].name && strcmp(p, capabilities[i].name);
181+
i++)
182+
;
183+
if (capabilities[i].name) {
184+
if (supported_capabilities)
185+
*supported_capabilities |= capabilities[i].flag;
186+
} else {
187+
warning("external filter requested unsupported filter capability '%s'",
188+
p);
189+
}
190+
}
191+
192+
return 0;
193+
}
194+
195+
int subprocess_handshake(struct subprocess_entry *entry,
196+
const char *welcome_prefix,
197+
int *versions,
198+
int *chosen_version,
199+
struct subprocess_capability *capabilities,
200+
unsigned int *supported_capabilities)
201+
{
202+
int retval;
203+
struct child_process *process = &entry->process;
204+
205+
sigchain_push(SIGPIPE, SIG_IGN);
206+
207+
retval = handshake_version(process, welcome_prefix, versions,
208+
chosen_version) ||
209+
handshake_capabilities(process, capabilities,
210+
supported_capabilities);
211+
212+
sigchain_pop(SIGPIPE);
213+
return retval;
214+
}

sub-process.h

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,41 +6,88 @@
66
#include "run-command.h"
77

88
/*
9-
* Generic implementation of background process infrastructure.
10-
* See: Documentation/technical/api-sub-process.txt
9+
* The sub-process API makes it possible to run background sub-processes
10+
* for the entire lifetime of a Git invocation. If Git needs to communicate
11+
* with an external process multiple times, then this can reduces the process
12+
* invocation overhead. Git and the sub-process communicate through stdin and
13+
* stdout.
14+
*
15+
* The sub-processes are kept in a hashmap by command name and looked up
16+
* via the subprocess_find_entry function. If an existing instance can not
17+
* be found then a new process should be created and started. When the
18+
* parent git command terminates, all sub-processes are also terminated.
19+
*
20+
* This API is based on the run-command API.
1121
*/
1222

1323
/* data structures */
1424

25+
/* Members should not be accessed directly. */
1526
struct subprocess_entry {
1627
struct hashmap_entry ent; /* must be the first member! */
1728
const char *cmd;
1829
struct child_process process;
1930
};
2031

32+
struct subprocess_capability {
33+
const char *name;
34+
35+
/*
36+
* subprocess_handshake will "|=" this value to supported_capabilities
37+
* if the server reports that it supports this capability.
38+
*/
39+
unsigned int flag;
40+
};
41+
2142
/* subprocess functions */
2243

44+
/* Function to test two subprocess hashmap entries for equality. */
2345
extern int cmd2process_cmp(const void *unused_cmp_data,
2446
const void *e1,
2547
const void *e2,
2648
const void *unused_keydata);
2749

50+
/*
51+
* User-supplied function to initialize the sub-process. This is
52+
* typically used to negotiate the interface version and capabilities.
53+
*/
2854
typedef int(*subprocess_start_fn)(struct subprocess_entry *entry);
55+
56+
/* Start a subprocess and add it to the subprocess hashmap. */
2957
int subprocess_start(struct hashmap *hashmap, struct subprocess_entry *entry, const char *cmd,
3058
subprocess_start_fn startfn);
3159

60+
/* Kill a subprocess and remove it from the subprocess hashmap. */
3261
void subprocess_stop(struct hashmap *hashmap, struct subprocess_entry *entry);
3362

63+
/* Find a subprocess in the subprocess hashmap. */
3464
struct subprocess_entry *subprocess_find_entry(struct hashmap *hashmap, const char *cmd);
3565

3666
/* subprocess helper functions */
3767

68+
/* Get the underlying `struct child_process` from a subprocess. */
3869
static inline struct child_process *subprocess_get_child_process(
3970
struct subprocess_entry *entry)
4071
{
4172
return &entry->process;
4273
}
4374

75+
/*
76+
* Perform the version and capability negotiation as described in the "Long
77+
* Running Filter Process" section of the gitattributes documentation using the
78+
* given requested versions and capabilities. The "versions" and "capabilities"
79+
* parameters are arrays terminated by a 0 or blank struct.
80+
*
81+
* This function is typically called when a subprocess is started (as part of
82+
* the "startfn" passed to subprocess_start).
83+
*/
84+
int subprocess_handshake(struct subprocess_entry *entry,
85+
const char *welcome_prefix,
86+
int *versions,
87+
int *chosen_version,
88+
struct subprocess_capability *capabilities,
89+
unsigned int *supported_capabilities);
90+
4491
/*
4592
* Helper function that will read packets looking for "status=<foo>"
4693
* key/value pairs and return the value from the last "status" packet

t/t0021-conversion.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -697,7 +697,7 @@ test_expect_success PERL 'invalid process filter must fail (and not hang!)' '
697697
698698
cp "$TEST_ROOT/test.o" test.r &&
699699
test_must_fail git add . 2>git-stderr.log &&
700-
grep "does not support filter protocol version" git-stderr.log
700+
grep "expected git-filter-server" git-stderr.log
701701
)
702702
'
703703

0 commit comments

Comments
 (0)