Merge branch 'mt/rot13-in-c'

Test portability improvements.

* mt/rot13-in-c:
  tests: use the new C rot13-filter helper to avoid PERL prereq
  t0021: implementation the rot13-filter.pl script in C
  t0021: avoid grepping for a Perl-specific string at filter output
This commit is contained in:
Junio C Hamano 2022-08-29 14:55:11 -07:00
commit 64cb4c34d1
10 changed files with 434 additions and 296 deletions

View File

@ -772,6 +772,7 @@ TEST_BUILTINS_OBJS += test-read-midx.o
TEST_BUILTINS_OBJS += test-ref-store.o
TEST_BUILTINS_OBJS += test-reftable.o
TEST_BUILTINS_OBJS += test-regex.o
TEST_BUILTINS_OBJS += test-rot13-filter.o
TEST_BUILTINS_OBJS += test-repository.o
TEST_BUILTINS_OBJS += test-revision-walking.o
TEST_BUILTINS_OBJS += test-run-command.o

View File

@ -309,7 +309,8 @@ int write_packetized_from_fd_no_flush(int fd_in, int fd_out)
return err;
}
int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_out)
int write_packetized_from_buf_no_flush_count(const char *src_in, size_t len,
int fd_out, int *packet_counter)
{
int err = 0;
size_t bytes_written = 0;
@ -324,6 +325,8 @@ int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_ou
break;
err = packet_write_gently(fd_out, src_in + bytes_written, bytes_to_write);
bytes_written += bytes_to_write;
if (packet_counter)
(*packet_counter)++;
}
return err;
}

View File

@ -32,7 +32,13 @@ void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((f
int packet_flush_gently(int fd);
int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
int write_packetized_from_fd_no_flush(int fd_in, int fd_out);
int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_out);
int write_packetized_from_buf_no_flush_count(const char *src_in, size_t len,
int fd_out, int *packet_counter);
static inline int write_packetized_from_buf_no_flush(const char *src_in,
size_t len, int fd_out)
{
return write_packetized_from_buf_no_flush_count(src_in, len, fd_out, NULL);
}
/*
* Stdio versions of packet_write functions. When mixing these with fd

View File

@ -0,0 +1,382 @@
/*
* Example implementation for the Git filter protocol version 2
* See Documentation/gitattributes.txt, section "Filter Protocol"
*
* Usage: test-tool rot13-filter [--always-delay] --log=<path> <capabilities>
*
* Log path defines a debug log file that the script writes to. The
* subsequent arguments define a list of supported protocol capabilities
* ("clean", "smudge", etc).
*
* When --always-delay is given all pathnames with the "can-delay" flag
* that don't appear on the list bellow are delayed with a count of 1
* (see more below).
*
* This implementation supports special test cases:
* (1) If data with the pathname "clean-write-fail.r" is processed with
* a "clean" operation then the write operation will die.
* (2) If data with the pathname "smudge-write-fail.r" is processed with
* a "smudge" operation then the write operation will die.
* (3) If data with the pathname "error.r" is processed with any
* operation then the filter signals that it cannot or does not want
* to process the file.
* (4) If data with the pathname "abort.r" is processed with any
* operation then the filter signals that it cannot or does not want
* to process the file and any file after that is processed with the
* same command.
* (5) If data with a pathname that is a key in the delay hash is
* requested (e.g. "test-delay10.a") then the filter responds with
* a "delay" status and sets the "requested" field in the delay hash.
* The filter will signal the availability of this object after
* "count" (field in delay hash) "list_available_blobs" commands.
* (6) If data with the pathname "missing-delay.a" is processed that the
* filter will drop the path from the "list_available_blobs" response.
* (7) If data with the pathname "invalid-delay.a" is processed that the
* filter will add the path "unfiltered" which was not delayed before
* to the "list_available_blobs" response.
*/
#include "test-tool.h"
#include "pkt-line.h"
#include "string-list.h"
#include "strmap.h"
#include "parse-options.h"
static FILE *logfile;
static int always_delay, has_clean_cap, has_smudge_cap;
static struct strmap delay = STRMAP_INIT;
static inline const char *str_or_null(const char *str)
{
return str ? str : "(null)";
}
static char *rot13(char *str)
{
char *c;
for (c = str; *c; c++)
if (isalpha(*c))
*c += tolower(*c) < 'n' ? 13 : -13;
return str;
}
static char *get_value(char *buf, const char *key)
{
const char *orig_buf = buf;
if (!buf ||
!skip_prefix((const char *)buf, key, (const char **)&buf) ||
!skip_prefix((const char *)buf, "=", (const char **)&buf) ||
!*buf)
die("expected key '%s', got '%s'", key, str_or_null(orig_buf));
return buf;
}
/*
* Read a text packet, expecting that it is in the form "key=value" for
* the given key. An EOF does not trigger any error and is reported
* back to the caller with NULL. Die if the "key" part of "key=value" does
* not match the given key, or the value part is empty.
*/
static char *packet_key_val_read(const char *key)
{
char *buf;
if (packet_read_line_gently(0, NULL, &buf) < 0)
return NULL;
return xstrdup(get_value(buf, key));
}
static inline void assert_remote_capability(struct strset *caps, const char *cap)
{
if (!strset_contains(caps, cap))
die("required '%s' capability not available from remote", cap);
}
static void read_capabilities(struct strset *remote_caps)
{
for (;;) {
char *buf = packet_read_line(0, NULL);
if (!buf)
break;
strset_add(remote_caps, get_value(buf, "capability"));
}
assert_remote_capability(remote_caps, "clean");
assert_remote_capability(remote_caps, "smudge");
assert_remote_capability(remote_caps, "delay");
}
static void check_and_write_capabilities(struct strset *remote_caps,
const char **caps, int nr_caps)
{
int i;
for (i = 0; i < nr_caps; i++) {
if (!strset_contains(remote_caps, caps[i]))
die("our capability '%s' is not available from remote",
caps[i]);
packet_write_fmt(1, "capability=%s\n", caps[i]);
}
packet_flush(1);
}
struct delay_entry {
int requested, count;
char *output;
};
static void free_delay_entries(void)
{
struct hashmap_iter iter;
struct strmap_entry *ent;
strmap_for_each_entry(&delay, &iter, ent) {
struct delay_entry *delay_entry = ent->value;
free(delay_entry->output);
free(delay_entry);
}
strmap_clear(&delay, 0);
}
static void add_delay_entry(char *pathname, int count, int requested)
{
struct delay_entry *entry = xcalloc(1, sizeof(*entry));
entry->count = count;
entry->requested = requested;
if (strmap_put(&delay, pathname, entry))
BUG("adding the same path twice to delay hash?");
}
static void reply_list_available_blobs_cmd(void)
{
struct hashmap_iter iter;
struct strmap_entry *ent;
struct string_list_item *str_item;
struct string_list paths = STRING_LIST_INIT_NODUP;
/* flush */
if (packet_read_line(0, NULL))
die("bad list_available_blobs end");
strmap_for_each_entry(&delay, &iter, ent) {
struct delay_entry *delay_entry = ent->value;
if (!delay_entry->requested)
continue;
delay_entry->count--;
if (!strcmp(ent->key, "invalid-delay.a")) {
/* Send Git a pathname that was not delayed earlier */
packet_write_fmt(1, "pathname=unfiltered");
}
if (!strcmp(ent->key, "missing-delay.a")) {
/* Do not signal Git that this file is available */
} else if (!delay_entry->count) {
string_list_append(&paths, ent->key);
packet_write_fmt(1, "pathname=%s", ent->key);
}
}
/* Print paths in sorted order. */
string_list_sort(&paths);
for_each_string_list_item(str_item, &paths)
fprintf(logfile, " %s", str_item->string);
string_list_clear(&paths, 0);
packet_flush(1);
fprintf(logfile, " [OK]\n");
packet_write_fmt(1, "status=success");
packet_flush(1);
}
static void command_loop(void)
{
for (;;) {
char *buf, *output;
char *pathname;
struct delay_entry *entry;
struct strbuf input = STRBUF_INIT;
char *command = packet_key_val_read("command");
if (!command) {
fprintf(logfile, "STOP\n");
break;
}
fprintf(logfile, "IN: %s", command);
if (!strcmp(command, "list_available_blobs")) {
reply_list_available_blobs_cmd();
free(command);
continue;
}
pathname = packet_key_val_read("pathname");
if (!pathname)
die("unexpected EOF while expecting pathname");
fprintf(logfile, " %s", pathname);
/* Read until flush */
while ((buf = packet_read_line(0, NULL))) {
if (!strcmp(buf, "can-delay=1")) {
entry = strmap_get(&delay, pathname);
if (entry && !entry->requested)
entry->requested = 1;
else if (!entry && always_delay)
add_delay_entry(pathname, 1, 1);
} else if (starts_with(buf, "ref=") ||
starts_with(buf, "treeish=") ||
starts_with(buf, "blob=")) {
fprintf(logfile, " %s", buf);
} else {
/*
* In general, filters need to be graceful about
* new metadata, since it's documented that we
* can pass any key-value pairs, but for tests,
* let's be a little stricter.
*/
die("Unknown message '%s'", buf);
}
}
read_packetized_to_strbuf(0, &input, 0);
fprintf(logfile, " %"PRIuMAX" [OK] -- ", (uintmax_t)input.len);
entry = strmap_get(&delay, pathname);
if (entry && entry->output) {
output = entry->output;
} else if (!strcmp(pathname, "error.r") || !strcmp(pathname, "abort.r")) {
output = "";
} else if (!strcmp(command, "clean") && has_clean_cap) {
output = rot13(input.buf);
} else if (!strcmp(command, "smudge") && has_smudge_cap) {
output = rot13(input.buf);
} else {
die("bad command '%s'", command);
}
if (!strcmp(pathname, "error.r")) {
fprintf(logfile, "[ERROR]\n");
packet_write_fmt(1, "status=error");
packet_flush(1);
} else if (!strcmp(pathname, "abort.r")) {
fprintf(logfile, "[ABORT]\n");
packet_write_fmt(1, "status=abort");
packet_flush(1);
} else if (!strcmp(command, "smudge") &&
(entry = strmap_get(&delay, pathname)) &&
entry->requested == 1) {
fprintf(logfile, "[DELAYED]\n");
packet_write_fmt(1, "status=delayed");
packet_flush(1);
entry->requested = 2;
if (entry->output != output) {
free(entry->output);
entry->output = xstrdup(output);
}
} else {
int i, nr_packets = 0;
size_t output_len;
const char *p;
packet_write_fmt(1, "status=success");
packet_flush(1);
if (skip_prefix(pathname, command, &p) &&
!strcmp(p, "-write-fail.r")) {
fprintf(logfile, "[WRITE FAIL]\n");
die("%s write error", command);
}
output_len = strlen(output);
fprintf(logfile, "OUT: %"PRIuMAX" ", (uintmax_t)output_len);
if (write_packetized_from_buf_no_flush_count(output,
output_len, 1, &nr_packets))
die("failed to write buffer to stdout");
packet_flush(1);
for (i = 0; i < nr_packets; i++)
fprintf(logfile, ".");
fprintf(logfile, " [OK]\n");
packet_flush(1);
}
free(pathname);
strbuf_release(&input);
free(command);
}
}
static void packet_initialize(void)
{
char *pkt_buf = packet_read_line(0, NULL);
if (!pkt_buf || strcmp(pkt_buf, "git-filter-client"))
die("bad initialize: '%s'", str_or_null(pkt_buf));
pkt_buf = packet_read_line(0, NULL);
if (!pkt_buf || strcmp(pkt_buf, "version=2"))
die("bad version: '%s'", str_or_null(pkt_buf));
pkt_buf = packet_read_line(0, NULL);
if (pkt_buf)
die("bad version end: '%s'", pkt_buf);
packet_write_fmt(1, "git-filter-server");
packet_write_fmt(1, "version=2");
packet_flush(1);
}
static const char *rot13_usage[] = {
"test-tool rot13-filter [--always-delay] --log=<path> <capabilities>",
NULL
};
int cmd__rot13_filter(int argc, const char **argv)
{
int i, nr_caps;
struct strset remote_caps = STRSET_INIT;
const char *log_path = NULL;
struct option options[] = {
OPT_BOOL(0, "always-delay", &always_delay,
"delay all paths with the can-delay flag"),
OPT_STRING(0, "log", &log_path, "path",
"path to the debug log file"),
OPT_END()
};
nr_caps = parse_options(argc, argv, NULL, options, rot13_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
if (!log_path || !nr_caps)
usage_with_options(rot13_usage, options);
logfile = fopen(log_path, "a");
if (!logfile)
die_errno("failed to open log file");
for (i = 0; i < nr_caps; i++) {
if (!strcmp(argv[i], "smudge"))
has_smudge_cap = 1;
if (!strcmp(argv[i], "clean"))
has_clean_cap = 1;
}
add_delay_entry("test-delay10.a", 1, 0);
add_delay_entry("test-delay11.a", 1, 0);
add_delay_entry("test-delay20.a", 2, 0);
add_delay_entry("test-delay10.b", 1, 0);
add_delay_entry("missing-delay.a", 1, 0);
add_delay_entry("invalid-delay.a", 1, 0);
fprintf(logfile, "START\n");
packet_initialize();
read_capabilities(&remote_caps);
check_and_write_capabilities(&remote_caps, argv, nr_caps);
fprintf(logfile, "init handshake complete\n");
strset_clear(&remote_caps);
command_loop();
if (fclose(logfile))
die_errno("error closing logfile");
free_delay_entries();
return 0;
}

View File

@ -65,6 +65,7 @@ static struct test_cmd cmds[] = {
{ "read-midx", cmd__read_midx },
{ "ref-store", cmd__ref_store },
{ "reftable", cmd__reftable },
{ "rot13-filter", cmd__rot13_filter },
{ "dump-reftable", cmd__dump_reftable },
{ "regex", cmd__regex },
{ "repository", cmd__repository },

View File

@ -54,6 +54,7 @@ int cmd__read_cache(int argc, const char **argv);
int cmd__read_graph(int argc, const char **argv);
int cmd__read_midx(int argc, const char **argv);
int cmd__ref_store(int argc, const char **argv);
int cmd__rot13_filter(int argc, const char **argv);
int cmd__reftable(int argc, const char **argv);
int cmd__regex(int argc, const char **argv);
int cmd__repository(int argc, const char **argv);

View File

@ -17,9 +17,6 @@ tr \
'nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM'
EOF
write_script rot13-filter.pl "$PERL_PATH" \
<"$TEST_DIRECTORY"/t0021/rot13-filter.pl
generate_random_characters () {
LEN=$1
NAME=$2
@ -365,8 +362,8 @@ test_expect_success 'diff does not reuse worktree files that need cleaning' '
test_line_count = 0 count
'
test_expect_success PERL 'required process filter should filter data' '
test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" &&
test_expect_success 'required process filter should filter data' '
test_config_global filter.protocol.process "test-tool rot13-filter --log=debug.log clean smudge" &&
test_config_global filter.protocol.required true &&
rm -rf repo &&
mkdir repo &&
@ -450,8 +447,8 @@ test_expect_success PERL 'required process filter should filter data' '
)
'
test_expect_success PERL 'required process filter should filter data for various subcommands' '
test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" &&
test_expect_success 'required process filter should filter data for various subcommands' '
test_config_global filter.protocol.process "test-tool rot13-filter --log=debug.log clean smudge" &&
test_config_global filter.protocol.required true &&
(
cd repo &&
@ -561,9 +558,9 @@ test_expect_success PERL 'required process filter should filter data for various
)
'
test_expect_success PERL 'required process filter takes precedence' '
test_expect_success 'required process filter takes precedence' '
test_config_global filter.protocol.clean false &&
test_config_global filter.protocol.process "rot13-filter.pl debug.log clean" &&
test_config_global filter.protocol.process "test-tool rot13-filter --log=debug.log clean" &&
test_config_global filter.protocol.required true &&
rm -rf repo &&
mkdir repo &&
@ -587,8 +584,8 @@ test_expect_success PERL 'required process filter takes precedence' '
)
'
test_expect_success PERL 'required process filter should be used only for "clean" operation only' '
test_config_global filter.protocol.process "rot13-filter.pl debug.log clean" &&
test_expect_success 'required process filter should be used only for "clean" operation only' '
test_config_global filter.protocol.process "test-tool rot13-filter --log=debug.log clean" &&
rm -rf repo &&
mkdir repo &&
(
@ -622,8 +619,8 @@ test_expect_success PERL 'required process filter should be used only for "clean
)
'
test_expect_success PERL 'required process filter should process multiple packets' '
test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" &&
test_expect_success 'required process filter should process multiple packets' '
test_config_global filter.protocol.process "test-tool rot13-filter --log=debug.log clean smudge" &&
test_config_global filter.protocol.required true &&
rm -rf repo &&
@ -687,8 +684,8 @@ test_expect_success PERL 'required process filter should process multiple packet
)
'
test_expect_success PERL 'required process filter with clean error should fail' '
test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" &&
test_expect_success 'required process filter with clean error should fail' '
test_config_global filter.protocol.process "test-tool rot13-filter --log=debug.log clean smudge" &&
test_config_global filter.protocol.required true &&
rm -rf repo &&
mkdir repo &&
@ -706,8 +703,8 @@ test_expect_success PERL 'required process filter with clean error should fail'
)
'
test_expect_success PERL 'process filter should restart after unexpected write failure' '
test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" &&
test_expect_success 'process filter should restart after unexpected write failure' '
test_config_global filter.protocol.process "test-tool rot13-filter --log=debug.log clean smudge" &&
rm -rf repo &&
mkdir repo &&
(
@ -735,7 +732,7 @@ test_expect_success PERL 'process filter should restart after unexpected write f
rm -f debug.log &&
git checkout --quiet --no-progress . 2>git-stderr.log &&
grep "smudge write error at" git-stderr.log &&
grep "smudge write error" git-stderr.log &&
test_i18ngrep "error: external filter" git-stderr.log &&
cat >expected.log <<-EOF &&
@ -761,8 +758,8 @@ test_expect_success PERL 'process filter should restart after unexpected write f
)
'
test_expect_success PERL 'process filter should not be restarted if it signals an error' '
test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" &&
test_expect_success 'process filter should not be restarted if it signals an error' '
test_config_global filter.protocol.process "test-tool rot13-filter --log=debug.log clean smudge" &&
rm -rf repo &&
mkdir repo &&
(
@ -804,8 +801,8 @@ test_expect_success PERL 'process filter should not be restarted if it signals a
)
'
test_expect_success PERL 'process filter abort stops processing of all further files' '
test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" &&
test_expect_success 'process filter abort stops processing of all further files' '
test_config_global filter.protocol.process "test-tool rot13-filter --log=debug.log clean smudge" &&
rm -rf repo &&
mkdir repo &&
(
@ -861,10 +858,10 @@ test_expect_success PERL 'invalid process filter must fail (and not hang!)' '
)
'
test_expect_success PERL 'delayed checkout in process filter' '
test_config_global filter.a.process "rot13-filter.pl a.log clean smudge delay" &&
test_expect_success 'delayed checkout in process filter' '
test_config_global filter.a.process "test-tool rot13-filter --log=a.log clean smudge delay" &&
test_config_global filter.a.required true &&
test_config_global filter.b.process "rot13-filter.pl b.log clean smudge delay" &&
test_config_global filter.b.process "test-tool rot13-filter --log=b.log clean smudge delay" &&
test_config_global filter.b.required true &&
rm -rf repo &&
@ -940,8 +937,8 @@ test_expect_success PERL 'delayed checkout in process filter' '
)
'
test_expect_success PERL 'missing file in delayed checkout' '
test_config_global filter.bug.process "rot13-filter.pl bug.log clean smudge delay" &&
test_expect_success 'missing file in delayed checkout' '
test_config_global filter.bug.process "test-tool rot13-filter --log=bug.log clean smudge delay" &&
test_config_global filter.bug.required true &&
rm -rf repo &&
@ -960,8 +957,8 @@ test_expect_success PERL 'missing file in delayed checkout' '
grep "error: .missing-delay\.a. was not filtered properly" git-stderr.log
'
test_expect_success PERL 'invalid file in delayed checkout' '
test_config_global filter.bug.process "rot13-filter.pl bug.log clean smudge delay" &&
test_expect_success 'invalid file in delayed checkout' '
test_config_global filter.bug.process "test-tool rot13-filter --log=bug.log clean smudge delay" &&
test_config_global filter.bug.required true &&
rm -rf repo &&
@ -990,10 +987,10 @@ do
mode_prereq='UTF8_NFD_TO_NFC' ;;
esac
test_expect_success PERL,SYMLINKS,$mode_prereq \
test_expect_success SYMLINKS,$mode_prereq \
"delayed checkout with $mode-collision don't write to the wrong place" '
test_config_global filter.delay.process \
"\"$TEST_ROOT/rot13-filter.pl\" --always-delay delayed.log clean smudge delay" &&
"test-tool rot13-filter --always-delay --log=delayed.log clean smudge delay" &&
test_config_global filter.delay.required true &&
git init $mode-collision &&
@ -1026,12 +1023,12 @@ do
'
done
test_expect_success PERL,SYMLINKS,CASE_INSENSITIVE_FS \
test_expect_success SYMLINKS,CASE_INSENSITIVE_FS \
"delayed checkout with submodule collision don't write to the wrong place" '
git init collision-with-submodule &&
(
cd collision-with-submodule &&
git config filter.delay.process "\"$TEST_ROOT/rot13-filter.pl\" --always-delay delayed.log clean smudge delay" &&
git config filter.delay.process "test-tool rot13-filter --always-delay --log=delayed.log clean smudge delay" &&
git config filter.delay.required true &&
# We need Git to treat the submodule "a" and the
@ -1062,11 +1059,11 @@ test_expect_success PERL,SYMLINKS,CASE_INSENSITIVE_FS \
)
'
test_expect_success PERL 'setup for progress tests' '
test_expect_success 'setup for progress tests' '
git init progress &&
(
cd progress &&
git config filter.delay.process "rot13-filter.pl delay-progress.log clean smudge delay" &&
git config filter.delay.process "test-tool rot13-filter --log=delay-progress.log clean smudge delay" &&
git config filter.delay.required true &&
echo "*.a filter=delay" >.gitattributes &&
@ -1132,12 +1129,12 @@ do
'
done
test_expect_success PERL 'delayed checkout correctly reports the number of updated entries' '
test_expect_success 'delayed checkout correctly reports the number of updated entries' '
rm -rf repo &&
git init repo &&
(
cd repo &&
git config filter.delay.process "../rot13-filter.pl delayed.log clean smudge delay" &&
git config filter.delay.process "test-tool rot13-filter --log=delayed.log clean smudge delay" &&
git config filter.delay.required true &&
echo "*.a filter=delay" >.gitattributes &&

View File

@ -1,247 +0,0 @@
#
# Example implementation for the Git filter protocol version 2
# See Documentation/gitattributes.txt, section "Filter Protocol"
#
# Usage: rot13-filter.pl [--always-delay] <log path> <capabilities>
#
# Log path defines a debug log file that the script writes to. The
# subsequent arguments define a list of supported protocol capabilities
# ("clean", "smudge", etc).
#
# When --always-delay is given all pathnames with the "can-delay" flag
# that don't appear on the list bellow are delayed with a count of 1
# (see more below).
#
# This implementation supports special test cases:
# (1) If data with the pathname "clean-write-fail.r" is processed with
# a "clean" operation then the write operation will die.
# (2) If data with the pathname "smudge-write-fail.r" is processed with
# a "smudge" operation then the write operation will die.
# (3) If data with the pathname "error.r" is processed with any
# operation then the filter signals that it cannot or does not want
# to process the file.
# (4) If data with the pathname "abort.r" is processed with any
# operation then the filter signals that it cannot or does not want
# to process the file and any file after that is processed with the
# same command.
# (5) If data with a pathname that is a key in the DELAY hash is
# requested (e.g. "test-delay10.a") then the filter responds with
# a "delay" status and sets the "requested" field in the DELAY hash.
# The filter will signal the availability of this object after
# "count" (field in DELAY hash) "list_available_blobs" commands.
# (6) If data with the pathname "missing-delay.a" is processed that the
# filter will drop the path from the "list_available_blobs" response.
# (7) If data with the pathname "invalid-delay.a" is processed that the
# filter will add the path "unfiltered" which was not delayed before
# to the "list_available_blobs" response.
#
use 5.008;
sub gitperllib {
# Git assumes that all path lists are Unix-y colon-separated ones. But
# when the Git for Windows executes the test suite, its MSYS2 Bash
# calls git.exe, and colon-separated path lists are converted into
# Windows-y semicolon-separated lists of *Windows* paths (which
# naturally contain a colon after the drive letter, so splitting by
# colons simply does not cut it).
#
# Detect semicolon-separated path list and handle them appropriately.
if ($ENV{GITPERLLIB} =~ /;/) {
return split(/;/, $ENV{GITPERLLIB});
}
return split(/:/, $ENV{GITPERLLIB});
}
use lib (gitperllib());
use strict;
use warnings;
use IO::File;
use Git::Packet;
my $MAX_PACKET_CONTENT_SIZE = 65516;
my $always_delay = 0;
if ( $ARGV[0] eq '--always-delay' ) {
$always_delay = 1;
shift @ARGV;
}
my $log_file = shift @ARGV;
my @capabilities = @ARGV;
open my $debug, ">>", $log_file or die "cannot open log file: $!";
my %DELAY = (
'test-delay10.a' => { "requested" => 0, "count" => 1 },
'test-delay11.a' => { "requested" => 0, "count" => 1 },
'test-delay20.a' => { "requested" => 0, "count" => 2 },
'test-delay10.b' => { "requested" => 0, "count" => 1 },
'missing-delay.a' => { "requested" => 0, "count" => 1 },
'invalid-delay.a' => { "requested" => 0, "count" => 1 },
);
sub rot13 {
my $str = shift;
$str =~ y/A-Za-z/N-ZA-Mn-za-m/;
return $str;
}
print $debug "START\n";
$debug->flush();
packet_initialize("git-filter", 2);
my %remote_caps = packet_read_and_check_capabilities("clean", "smudge", "delay");
packet_check_and_write_capabilities(\%remote_caps, @capabilities);
print $debug "init handshake complete\n";
$debug->flush();
while (1) {
my ( $res, $command ) = packet_key_val_read("command");
if ( $res == -1 ) {
print $debug "STOP\n";
exit();
}
print $debug "IN: $command";
$debug->flush();
if ( $command eq "list_available_blobs" ) {
# Flush
packet_compare_lists([1, ""], packet_bin_read()) ||
die "bad list_available_blobs end";
foreach my $pathname ( sort keys %DELAY ) {
if ( $DELAY{$pathname}{"requested"} >= 1 ) {
$DELAY{$pathname}{"count"} = $DELAY{$pathname}{"count"} - 1;
if ( $pathname eq "invalid-delay.a" ) {
# Send Git a pathname that was not delayed earlier
packet_txt_write("pathname=unfiltered");
}
if ( $pathname eq "missing-delay.a" ) {
# Do not signal Git that this file is available
} elsif ( $DELAY{$pathname}{"count"} == 0 ) {
print $debug " $pathname";
packet_txt_write("pathname=$pathname");
}
}
}
packet_flush();
print $debug " [OK]\n";
$debug->flush();
packet_txt_write("status=success");
packet_flush();
} else {
my ( $res, $pathname ) = packet_key_val_read("pathname");
if ( $res == -1 ) {
die "unexpected EOF while expecting pathname";
}
print $debug " $pathname";
$debug->flush();
# Read until flush
my ( $done, $buffer ) = packet_txt_read();
while ( $buffer ne '' ) {
if ( $buffer eq "can-delay=1" ) {
if ( exists $DELAY{$pathname} and $DELAY{$pathname}{"requested"} == 0 ) {
$DELAY{$pathname}{"requested"} = 1;
} elsif ( !exists $DELAY{$pathname} and $always_delay ) {
$DELAY{$pathname} = { "requested" => 1, "count" => 1 };
}
} elsif ($buffer =~ /^(ref|treeish|blob)=/) {
print $debug " $buffer";
} else {
# In general, filters need to be graceful about
# new metadata, since it's documented that we
# can pass any key-value pairs, but for tests,
# let's be a little stricter.
die "Unknown message '$buffer'";
}
( $done, $buffer ) = packet_txt_read();
}
if ( $done == -1 ) {
die "unexpected EOF after pathname '$pathname'";
}
my $input = "";
{
binmode(STDIN);
my $buffer;
my $done = 0;
while ( !$done ) {
( $done, $buffer ) = packet_bin_read();
$input .= $buffer;
}
if ( $done == -1 ) {
die "unexpected EOF while reading input for '$pathname'";
}
print $debug " " . length($input) . " [OK] -- ";
$debug->flush();
}
my $output;
if ( exists $DELAY{$pathname} and exists $DELAY{$pathname}{"output"} ) {
$output = $DELAY{$pathname}{"output"}
} elsif ( $pathname eq "error.r" or $pathname eq "abort.r" ) {
$output = "";
} elsif ( $command eq "clean" and grep( /^clean$/, @capabilities ) ) {
$output = rot13($input);
} elsif ( $command eq "smudge" and grep( /^smudge$/, @capabilities ) ) {
$output = rot13($input);
} else {
die "bad command '$command'";
}
if ( $pathname eq "error.r" ) {
print $debug "[ERROR]\n";
$debug->flush();
packet_txt_write("status=error");
packet_flush();
} elsif ( $pathname eq "abort.r" ) {
print $debug "[ABORT]\n";
$debug->flush();
packet_txt_write("status=abort");
packet_flush();
} elsif ( $command eq "smudge" and
exists $DELAY{$pathname} and
$DELAY{$pathname}{"requested"} == 1 ) {
print $debug "[DELAYED]\n";
$debug->flush();
packet_txt_write("status=delayed");
packet_flush();
$DELAY{$pathname}{"requested"} = 2;
$DELAY{$pathname}{"output"} = $output;
} else {
packet_txt_write("status=success");
packet_flush();
if ( $pathname eq "${command}-write-fail.r" ) {
print $debug "[WRITE FAIL]\n";
$debug->flush();
die "${command} write error";
}
print $debug "OUT: " . length($output) . " ";
$debug->flush();
while ( length($output) > 0 ) {
my $packet = substr( $output, 0, $MAX_PACKET_CONTENT_SIZE );
packet_bin_write($packet);
# dots represent the number of packets
print $debug ".";
if ( length($output) > $MAX_PACKET_CONTENT_SIZE ) {
$output = substr( $output, $MAX_PACKET_CONTENT_SIZE );
} else {
$output = "";
}
}
packet_flush();
print $debug " [OK]\n";
$debug->flush();
packet_flush();
}
}
}

View File

@ -230,12 +230,9 @@ test_expect_success SYMLINKS 'parallel checkout checks for symlinks in leading d
# check the final report including sequential, parallel, and delayed entries
# all at the same time. So we must have finer control of the parallel checkout
# variables.
test_expect_success PERL '"git checkout ." report should not include failed entries' '
write_script rot13-filter.pl "$PERL_PATH" \
<"$TEST_DIRECTORY"/t0021/rot13-filter.pl &&
test_expect_success '"git checkout ." report should not include failed entries' '
test_config_global filter.delay.process \
"\"$(pwd)/rot13-filter.pl\" --always-delay delayed.log clean smudge delay" &&
"test-tool rot13-filter --always-delay --log=delayed.log clean smudge delay" &&
test_config_global filter.delay.required true &&
test_config_global filter.cat.clean cat &&
test_config_global filter.cat.smudge cat &&

View File

@ -138,12 +138,9 @@ test_expect_success 'parallel-checkout and external filter' '
# The delayed queue is independent from the parallel queue, and they should be
# able to work together in the same checkout process.
#
test_expect_success PERL 'parallel-checkout and delayed checkout' '
write_script rot13-filter.pl "$PERL_PATH" \
<"$TEST_DIRECTORY"/t0021/rot13-filter.pl &&
test_expect_success 'parallel-checkout and delayed checkout' '
test_config_global filter.delay.process \
"\"$(pwd)/rot13-filter.pl\" --always-delay \"$(pwd)/delayed.log\" clean smudge delay" &&
"test-tool rot13-filter --always-delay --log=\"$(pwd)/delayed.log\" clean smudge delay" &&
test_config_global filter.delay.required true &&
echo "abcd" >original &&