checkpoint

This commit is contained in:
Edward Thomson
2025-02-19 23:28:32 +00:00
parent 31fa5617eb
commit 07d0f7e028
3 changed files with 392 additions and 1 deletions

251
src/libgit2/exec_filter.c Normal file
View File

@@ -0,0 +1,251 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include <stdio.h>
#include <git2.h>
#include <git2/sys/filter.h>
#include "git2_util.h"
#include "process.h"
#define EXEC_FILTER_NAME "exec"
typedef struct {
git_filter parent;
} exec_filter;
typedef struct {
git_writestream parent;
exec_filter *filter;
git_repository *repo;
const char *cmd;
git_writestream *next;
git_filter_mode_t mode;
git_process *process;
} exec_filter_stream;
static int exec_filter_check(
git_filter *f,
void **payload,
const git_filter_source *src,
const char **attr_values)
{
exec_filter *filter = (exec_filter *)f;
git_config *config = NULL;
git_str configkey = GIT_BUF_INIT, filepath = GIT_BUF_INIT,
cmdline = GIT_BUF_INIT;
const char *replacements[][2] = { { "%f", NULL } };
const char *direction;
int error;
GIT_UNUSED(filter);
GIT_UNUSED(payload);
GIT_UNUSED(src);
/* TODO: support `process` */
/* TODO: how does `required` actually work? */
if (git_filter_source_mode(src) == GIT_FILTER_SMUDGE)
direction = "smudge";
else
direction = "clean";
if ((error = git_repository_config_snapshot(&config, git_filter_source_repo(src))) < 0 ||
(error = git_str_printf(&configkey, "filter.%s.%s", attr_values[0], direction)) < 0)
goto done;
/* TODO: don't just cast to a git buf */
if ((error = git_config_get_string_buf((git_buf *)&cmdline, config, configkey.ptr)) == GIT_ENOTFOUND) {
git_error_clear();
error = GIT_PASSTHROUGH;
goto done;
} else if (error < 0) {
goto done;
}
if ((error = git_str_puts(&filepath, git_filter_source_path(src))) < 0 ||
(error = git_str_shellquote(&filepath)) < 0)
goto done;
replacements[0][1] = filepath.ptr;
if ((error = git_str_replace(&cmdline, replacements, 1)) < 0)
goto done;
*payload = git_str_detach(&cmdline);
done:
git_str_dispose(&cmdline);
git_str_dispose(&filepath);
git_str_dispose(&configkey);
git_config_free(config);
return error;
}
static int exec_filter_stream_write(
git_writestream *s,
const char *buffer,
size_t len)
{
exec_filter_stream *stream = (exec_filter_stream *)s;
while (len) {
int chunk_len = len < INT_MAX ? (int)len : INT_MAX;
ssize_t ret = git_process_write(stream->process, buffer, chunk_len);
if (ret < INT_MIN)
return -1;
else if (ret < 0)
return (int)ret;
len -= ret;
}
return 0;
}
static int exec_filter_stream_close(git_writestream *s)
{
exec_filter_stream *stream = (exec_filter_stream *)s;
git_process_result result = GIT_PROCESS_RESULT_INIT;
git_str process_msg = GIT_BUF_INIT;
ssize_t ret = -1;
int error = 0;
char buffer[1024];
size_t buffer_len = 1024;
git_process_close_in(stream->process);
while (ret) {
ret = git_process_read(stream->process, buffer, buffer_len);
if (ret > 0)
ret = stream->next->write(stream->next, buffer, (size_t)ret);
if (ret < INT_MIN) {
error = -1;
goto done;
} else if (ret < 0) {
error = (int)ret;
goto done;
}
}
if ((error = git_process_wait(&result, stream->process)) < 0)
goto done;
if (result.status != GIT_PROCESS_STATUS_NORMAL || result.exitcode) {
if (git_process_result_msg(&process_msg, &result) == 0)
git_error_set(GIT_ERROR_FILTER,
"external filter '%s' failed: %s",
stream->cmd, process_msg.ptr);
error = -1;
goto done;
}
done:
stream->next->close(stream->next);
git_str_dispose(&process_msg);
return error;
}
static void exec_filter_stream_free(git_writestream *s)
{
exec_filter_stream *stream = (exec_filter_stream *)s;
git__free(stream);
}
static int exec_filter_stream_start(exec_filter_stream *stream)
{
git_process_options process_opts = GIT_PROCESS_OPTIONS_INIT;
const char *cmd[3] = { "/bin/sh", "-c", stream->cmd };
int error;
process_opts.cwd = git_repository_workdir(stream->repo);
process_opts.capture_in = 1;
process_opts.capture_out = 1;
if ((error = git_process_new(&stream->process, cmd, 3, NULL, 0, &process_opts)) < 0 ||
(error = git_process_start(stream->process)) < 0) {
git_process_free(stream->process);
stream->process = NULL;
}
return error;
}
static int exec_filter_stream_init(
git_writestream **out,
git_filter *f,
void **payload,
const git_filter_source *src,
git_writestream *next)
{
exec_filter *filter = (exec_filter *)f;
exec_filter_stream *stream;
int error;
stream = git__calloc(1, sizeof(exec_filter_stream));
GIT_ERROR_CHECK_ALLOC(stream);
stream->parent.write = exec_filter_stream_write;
stream->parent.close = exec_filter_stream_close;
stream->parent.free = exec_filter_stream_free;
stream->filter = filter;
stream->repo = git_filter_source_repo(src);
stream->cmd = (const char *)*payload;
stream->mode = git_filter_source_mode(src);
stream->next = next;
if ((error = exec_filter_stream_start(stream)) < 0) {
git__free(stream);
return error;
}
*out = &stream->parent;
return 0;
}
static void exec_filter_cleanup(git_filter *f, void *payload)
{
GIT_UNUSED(f);
git__free(payload);
}
static void exec_filter_free(git_filter *f)
{
exec_filter *filter = (exec_filter *)f;
git__free(filter);
}
int git_exec_filter_register(void)
{
exec_filter *filter = git__calloc(1, sizeof(exec_filter));
int error;
GIT_ERROR_CHECK_ALLOC(filter);
filter->parent.version = GIT_FILTER_VERSION;
filter->parent.attributes = "filter=*";
filter->parent.check = exec_filter_check;
filter->parent.stream = exec_filter_stream_init;
filter->parent.cleanup = exec_filter_cleanup;
filter->parent.shutdown = exec_filter_free;
error = git_filter_register(EXEC_FILTER_NAME,
&filter->parent,
GIT_FILTER_DRIVER_PRIORITY);
if (error < 0)
git__free(filter);
return error;
}

View File

@@ -16,7 +16,7 @@ typedef struct {
capture_err : 1,
exclude_env : 1;
char *cwd;
const char *cwd;
} git_process_options;
typedef enum {

140
tests/libgit2/filter/exec.c Normal file
View File

@@ -0,0 +1,140 @@
#include "clar_libgit2.h"
#include "posix.h"
#include "blob.h"
#include "filter.h"
#include "git2/sys/filter.h"
#include "git2/sys/repository.h"
#include "custom_helpers.h"
#ifdef GIT_WIN32
# define NEWLINE "\r\n"
#else
# define NEWLINE "\n"
#endif
static char workdir_data[] =
"some simple" NEWLINE
"data" NEWLINE
"that represents" NEWLINE
"the working directory" NEWLINE
"(smudged) contents" NEWLINE;
static char repo_data[] =
"elpmis emos" NEWLINE
"atad" NEWLINE
"stneserper taht" NEWLINE
"yrotcerid gnikrow eht" NEWLINE
"stnetnoc )degdums(" NEWLINE;
static git_repository *g_repo = NULL;
extern int git_exec_filter_register(void);
void test_filter_exec__initialize(void)
{
git_str reverse_cmd = GIT_STR_INIT;
g_repo = cl_git_sandbox_init("empty_standard_repo");
git_exec_filter_register();
cl_git_pass(git_str_printf(&reverse_cmd, "%s/reverse %%f", cl_fixture("filters")));
cl_git_mkfile(
"empty_standard_repo/.gitattributes",
"*.txt filter=bitflip -text\n"
"*.bad1 filter=undefined -text\n"
"*.bad2 filter=notfound -text\n");
cl_repo_set_string(g_repo, "filter.bitflip.smudge", reverse_cmd.ptr);
cl_repo_set_string(g_repo, "filter.bitflip.clean", reverse_cmd.ptr);
cl_repo_set_string(g_repo, "filter.notfound.smudge", "/non/existent/path %f");
cl_repo_set_string(g_repo, "filter.notfound.clean", "/non/existent/path %f");
git_str_dispose(&reverse_cmd);
}
void test_filter_exec__cleanup(void)
{
cl_git_sandbox_cleanup();
g_repo = NULL;
}
void test_filter_exec__to_odb(void)
{
git_filter_list *fl;
git_buf out = GIT_BUF_INIT;
const char *in;
size_t in_len;
cl_git_pass(git_filter_list_load(
&fl, g_repo, NULL, "file.txt", GIT_FILTER_TO_ODB, 0));
in = workdir_data;
in_len = strlen(workdir_data);
cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len));
cl_assert_equal_s(repo_data, out.ptr);
git_filter_list_free(fl);
git_buf_dispose(&out);
}
void test_filter_exec__to_workdir(void)
{
git_filter_list *fl;
git_buf out = GIT_BUF_INIT;
const char *in;
size_t in_len;
cl_git_pass(git_filter_list_load(
&fl, g_repo, NULL, "file.txt", GIT_FILTER_TO_WORKTREE, 0));
in = repo_data;
in_len = strlen(repo_data);
cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len));
cl_assert_equal_s(workdir_data, out.ptr);
git_filter_list_free(fl);
git_buf_dispose(&out);
}
void test_filter_exec__undefined(void)
{
git_filter_list *fl;
git_buf out = GIT_BUF_INIT;
const char *in;
size_t in_len;
cl_git_pass(git_filter_list_load(
&fl, g_repo, NULL, "file.bad1", GIT_FILTER_TO_WORKTREE, 0));
in = workdir_data;
in_len = strlen(workdir_data);
cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len));
cl_assert_equal_s(workdir_data, out.ptr);
git_filter_list_free(fl);
git_buf_dispose(&out);
}
void test_filter_exec__notfound(void)
{
git_filter_list *fl;
git_buf out = GIT_BUF_INIT;
const char *in;
size_t in_len;
cl_git_pass(git_filter_list_load(
&fl, g_repo, NULL, "file.bad2", GIT_FILTER_TO_WORKTREE, 0));
in = workdir_data;
in_len = strlen(workdir_data);
cl_git_fail(git_filter_list_apply_to_buffer(&out, fl, in, in_len));
git_filter_list_free(fl);
git_buf_dispose(&out);
}