mirror of
https://github.com/libgit2/libgit2.git
synced 2026-01-25 02:56:17 +00:00
checkpoint
This commit is contained in:
251
src/libgit2/exec_filter.c
Normal file
251
src/libgit2/exec_filter.c
Normal 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;
|
||||
}
|
||||
@@ -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
140
tests/libgit2/filter/exec.c
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user