Merge pull request #7163 from libgit2/ethomson/ssh_exec_updates

SSH exec updates
This commit is contained in:
Edward Thomson
2025-12-06 16:41:10 +00:00
committed by GitHub
16 changed files with 510 additions and 80 deletions

View File

@@ -120,7 +120,8 @@ GIT_INLINE(int) ensure_transport_state(
}
static int get_ssh_cmdline(
git_str *out,
git_vector *args,
bool *use_shell,
ssh_exec_subtransport *transport,
git_net_url *url,
const char *command)
@@ -128,7 +129,8 @@ static int get_ssh_cmdline(
git_remote *remote = ((transport_smart *)transport->owner)->owner;
git_repository *repo = remote->repo;
git_config *cfg;
git_str ssh_cmd = GIT_STR_INIT;
git_str ssh_cmd = GIT_STR_INIT, url_and_host = GIT_STR_INIT,
remote_cmd = GIT_STR_INIT;
const char *default_ssh_cmd = "ssh";
int error;
@@ -151,25 +153,57 @@ static int get_ssh_cmdline(
if ((error = git_repository_config_snapshot(&cfg, repo)) < 0)
return error;
if ((error = git__getenv(&ssh_cmd, "GIT_SSH")) == 0)
;
if ((error = git__getenv(&ssh_cmd, "GIT_SSH_COMMAND")) == 0)
*use_shell = true;
else if (error != GIT_ENOTFOUND)
goto done;
else if ((error = git__getenv(&ssh_cmd, "GIT_SSH")) == 0)
*use_shell = false;
else if (error != GIT_ENOTFOUND)
goto done;
else if ((error = git_config__get_string_buf(&ssh_cmd, cfg, "core.sshcommand")) < 0 && error != GIT_ENOTFOUND)
goto done;
error = git_str_printf(out, "%s %s %s \"%s%s%s\" \"%s '%s'\"",
ssh_cmd.size > 0 ? ssh_cmd.ptr : default_ssh_cmd,
url->port_specified ? "-p" : "",
url->port_specified ? url->port : "",
url->username ? url->username : "",
url->username ? "@" : "",
url->host,
command,
url->path);
git_error_clear();
if (!ssh_cmd.size &&
git_str_puts(&ssh_cmd, default_ssh_cmd) < 0)
goto done;
if ((error = git_vector_insert(args, git_str_detach(&ssh_cmd))) < 0)
goto done;
if (url->port_specified) {
char *p = git__strdup("-p");
char *port = git__strdup(url->port);
if (!p || !port ||
(error = git_vector_insert(args, p)) < 0 ||
(error = git_vector_insert(args, port)) < 0)
goto done;
}
if (url->username) {
if ((error = git_str_puts(&url_and_host, url->username)) < 0 ||
(error = git_str_putc(&url_and_host, '@')) < 0)
goto done;
}
if ((error = git_str_puts(&url_and_host, url->host)) < 0 ||
(error = git_vector_insert(args, git_str_detach(&url_and_host))) < 0)
goto done;
if ((error = git_str_puts(&remote_cmd, command)) < 0 ||
(error = git_str_puts(&remote_cmd, " '")) < 0 ||
(error = git_str_puts_escaped(&remote_cmd, url->path, "'!", "'\\", "'")) < 0 ||
(error = git_str_puts(&remote_cmd, "'")) < 0 ||
(error = git_vector_insert(args, git_str_detach(&remote_cmd))) < 0)
goto done;
done:
git_str_dispose(&ssh_cmd);
git_str_dispose(&url_and_host);
git_str_dispose(&remote_cmd);
git_config_free(cfg);
return error;
}
@@ -183,7 +217,8 @@ static int start_ssh(
git_process_options process_opts = GIT_PROCESS_OPTIONS_INIT;
git_net_url url = GIT_NET_URL_INIT;
git_str ssh_cmdline = GIT_STR_INIT;
git_vector args = GIT_VECTOR_INIT;
bool use_shell = false;
const char *command;
int error;
@@ -214,11 +249,15 @@ static int start_ssh(
if (error < 0)
goto done;
if ((error = get_ssh_cmdline(&ssh_cmdline, transport, &url, command)) < 0)
if ((error = get_ssh_cmdline(&args, &use_shell,
transport, &url, command)) < 0)
goto done;
if ((error = git_process_new_from_cmdline(&transport->process,
ssh_cmdline.ptr, env, ARRAY_SIZE(env), &process_opts)) < 0 ||
process_opts.use_shell = use_shell;
if ((error = git_process_new(&transport->process,
(const char **)args.contents, args.length,
env, ARRAY_SIZE(env), &process_opts)) < 0 ||
(error = git_process_start(transport->process)) < 0) {
git_process_free(transport->process);
transport->process = NULL;
@@ -226,7 +265,7 @@ static int start_ssh(
}
done:
git_str_dispose(&ssh_cmdline);
git_vector_dispose_deep(&args);
git_net_url_dispose(&url);
return error;
}

View File

@@ -611,6 +611,37 @@ bool git_fs_path_isfile(const char *path)
return S_ISREG(st.st_mode) != 0;
}
#ifdef GIT_WIN32
bool git_fs_path_isexecutable(const char *path)
{
struct stat st;
GIT_ASSERT_ARG_WITH_RETVAL(path, false);
if (git__suffixcmp_icase(path, ".exe") != 0 &&
git__suffixcmp_icase(path, ".cmd") != 0)
return false;
return (p_stat(path, &st) == 0);
}
#else
bool git_fs_path_isexecutable(const char *path)
{
struct stat st;
GIT_ASSERT_ARG_WITH_RETVAL(path, false);
if (p_stat(path, &st) < 0)
return false;
return S_ISREG(st.st_mode) != 0 &&
((st.st_mode & S_IXUSR) != 0);
}
#endif
bool git_fs_path_islink(const char *path)
{
struct stat st;
@@ -2020,9 +2051,10 @@ int git_fs_path_owner_is_system(bool *out, const char *path)
return git_fs_path_owner_is(out, path, GIT_FS_PATH_OWNER_ADMINISTRATOR);
}
int git_fs_path_find_executable(git_str *fullpath, const char *executable)
{
#ifdef GIT_WIN32
static int find_executable(git_str *fullpath, const char *executable)
{
git_win32_path fullpath_w, executable_w;
int error;
@@ -2035,9 +2067,15 @@ int git_fs_path_find_executable(git_str *fullpath, const char *executable)
error = git_str_put_w(fullpath, fullpath_w, wcslen(fullpath_w));
return error;
}
#else
static int find_executable(git_str *fullpath, const char *executable)
{
git_str path = GIT_STR_INIT;
const char *current_dir, *term;
size_t current_dirlen;
bool found = false;
if (git__getenv(&path, "PATH") < 0)
@@ -2049,20 +2087,28 @@ int git_fs_path_find_executable(git_str *fullpath, const char *executable)
if (! (term = strchr(current_dir, GIT_PATH_LIST_SEPARATOR)))
term = strchr(current_dir, '\0');
current_dirlen = term - current_dir;
git_str_clear(fullpath);
if (git_str_put(fullpath, current_dir, (term - current_dir)) < 0 ||
git_str_putc(fullpath, '/') < 0 ||
/* An empty path segment is treated as '.' */
if (current_dirlen == 0 && git_str_putc(fullpath, '.'))
return -1;
else if (current_dirlen != 0 &&
git_str_put(fullpath, current_dir, current_dirlen) < 0)
return -1;
if (git_str_putc(fullpath, '/') < 0 ||
git_str_puts(fullpath, executable) < 0)
return -1;
if (git_fs_path_isfile(fullpath->ptr)) {
if (git_fs_path_isexecutable(fullpath->ptr)) {
found = true;
break;
}
current_dir = term;
while (*current_dir == GIT_PATH_LIST_SEPARATOR)
if (*current_dir == GIT_PATH_LIST_SEPARATOR)
current_dir++;
}
@@ -2073,5 +2119,19 @@ int git_fs_path_find_executable(git_str *fullpath, const char *executable)
git_str_clear(fullpath);
return GIT_ENOTFOUND;
#endif
}
#endif
int git_fs_path_find_executable(git_str *fullpath, const char *executable)
{
/* For qualified paths we do not look in PATH */
if (strchr(executable, '/') != NULL) {
if (!git_fs_path_isexecutable(executable))
return GIT_ENOTFOUND;
return git_str_puts(fullpath, executable);
}
return find_executable(fullpath, executable);
}

View File

@@ -204,6 +204,12 @@ extern bool git_fs_path_isdir(const char *path);
*/
extern bool git_fs_path_isfile(const char *path);
/**
* Check if the given path points to an executable.
* @return true or false
*/
extern bool git_fs_path_isexecutable(const char *path);
/**
* Check if the given path points to a symbolic link.
* @return true or false

View File

@@ -11,7 +11,8 @@
typedef struct git_process git_process;
typedef struct {
unsigned int capture_in : 1,
unsigned int use_shell : 1,
capture_in : 1,
capture_out : 1,
capture_err : 1,
exclude_env : 1;

View File

@@ -1065,10 +1065,13 @@ int git_str_puts_escaped(
git_str *buf,
const char *string,
const char *esc_chars,
const char *esc_with)
const char *esc_prefix,
const char *esc_suffix)
{
const char *scan;
size_t total = 0, esc_len = strlen(esc_with), count, alloclen;
size_t total = 0, count, alloclen;
size_t esc_prefix_len = esc_prefix ? strlen(esc_prefix) : 0;
size_t esc_suffix_len = esc_suffix ? strlen(esc_suffix) : 0;
if (!string)
return 0;
@@ -1080,7 +1083,7 @@ int git_str_puts_escaped(
scan += count;
/* count run of escaped characters */
count = strspn(scan, esc_chars);
total += count * (esc_len + 1);
total += count * (esc_prefix_len + esc_suffix_len + 1);
scan += count;
}
@@ -1096,13 +1099,22 @@ int git_str_puts_escaped(
buf->size += count;
for (count = strspn(scan, esc_chars); count > 0; --count) {
/* copy escape sequence */
memmove(buf->ptr + buf->size, esc_with, esc_len);
buf->size += esc_len;
/* copy escape prefix sequence */
if (esc_prefix) {
memmove(buf->ptr + buf->size, esc_prefix, esc_prefix_len);
buf->size += esc_prefix_len;
}
/* copy character to be escaped */
buf->ptr[buf->size] = *scan;
buf->size++;
scan++;
/* copy escape suffix sequence */
if (esc_suffix) {
memmove(buf->ptr + buf->size, esc_suffix, esc_suffix_len);
buf->size += esc_suffix_len;
}
}
}

View File

@@ -268,21 +268,23 @@ int git_str_splice(
* @param str String buffer to append data to
* @param string String to escape and append
* @param esc_chars Characters to be escaped
* @param esc_with String to insert in from of each found character
* @param esc_prefix String to insert as prefix of each found character
* @param esc_suffix String to insert as suffix of each found character
* @return 0 on success, <0 on failure (probably allocation problem)
*/
extern int git_str_puts_escaped(
git_str *str,
const char *string,
const char *esc_chars,
const char *esc_with);
const char *esc_prefix,
const char *esc_suffix);
/**
* Append string escaping characters that are regex special
*/
GIT_INLINE(int) git_str_puts_escape_regex(git_str *str, const char *string)
{
return git_str_puts_escaped(str, string, "^.[]$()|*+?{}\\", "\\");
return git_str_puts_escaped(str, string, "^.[]$()|*+?{}\\", "\\", NULL);
}
/**

View File

@@ -12,6 +12,7 @@
#include "git2_util.h"
#include "vector.h"
#include "futils.h"
#include "process.h"
#include "strlist.h"
@@ -28,7 +29,8 @@ struct git_process {
char *cwd;
unsigned int capture_in : 1,
unsigned int use_shell : 1,
capture_in : 1,
capture_out : 1,
capture_err : 1;
@@ -50,6 +52,53 @@ GIT_INLINE(bool) is_delete_env(const char *env)
return *(c+1) == '\0';
}
GIT_INLINE(int) insert_dup(git_vector *v, const char *s)
{
char *dup = git__strdup(s);
GIT_ERROR_CHECK_ALLOC(dup);
return git_vector_insert(v, dup);
}
static int setup_args(
char ***out,
const char **args,
size_t args_len,
bool use_shell)
{
git_vector prefixed = GIT_VECTOR_INIT;
git_str first = GIT_STR_INIT;
size_t cnt;
GIT_ASSERT(args && args_len);
if (use_shell) {
if (git_str_puts(&first, args[0]) < 0 ||
git_str_puts(&first, " \"$@\"") < 0 ||
insert_dup(&prefixed, "/bin/sh") < 0 ||
insert_dup(&prefixed, "-c") < 0 ||
git_vector_insert(&prefixed, git_str_detach(&first)) < 0)
goto on_error;
}
for (cnt = 0; args && cnt < args_len; cnt++) {
if (insert_dup(&prefixed, args[cnt]) < 0)
goto on_error;
}
git_vector_insert(&prefixed, NULL);
*out = (char **)prefixed.contents;
return 0;
on_error:
git_str_dispose(&first);
git_vector_dispose_deep(&prefixed);
return -1;
}
static int merge_env(
char ***out,
const char **env,
@@ -57,7 +106,7 @@ static int merge_env(
bool exclude_env)
{
git_vector merged = GIT_VECTOR_INIT;
char **kv, *dup;
char **kv;
size_t max, cnt;
int error = 0;
@@ -71,10 +120,7 @@ static int merge_env(
if (is_delete_env(env[cnt]))
continue;
dup = git__strdup(env[cnt]);
GIT_ERROR_CHECK_ALLOC(dup);
if ((error = git_vector_insert(&merged, dup)) < 0)
if (insert_dup(&merged, env[cnt]) < 0)
goto on_error;
}
@@ -83,10 +129,7 @@ static int merge_env(
if (env && git_strlist_contains_key(env, env_len, *kv, '='))
continue;
dup = git__strdup(*kv);
GIT_ERROR_CHECK_ALLOC(dup);
if ((error = git_vector_insert(&merged, dup)) < 0)
if (insert_dup(&merged, *kv) < 0)
goto on_error;
}
}
@@ -125,13 +168,14 @@ int git_process_new(
process = git__calloc(sizeof(git_process), 1);
GIT_ERROR_CHECK_ALLOC(process);
if (git_strlist_copy_with_null(&process->args, args, args_len) < 0 ||
if (setup_args(&process->args, args, args_len, opts ? opts->use_shell : false) < 0 ||
merge_env(&process->env, env, env_len, opts ? opts->exclude_env : false) < 0) {
git_process_free(process);
return -1;
}
if (opts) {
process->use_shell = opts->use_shell;
process->capture_in = opts->capture_in;
process->capture_out = opts->capture_out;
process->capture_err = opts->capture_err;
@@ -158,10 +202,12 @@ extern int git_process_new_from_cmdline(
size_t env_len,
git_process_options *opts)
{
const char *args[] = { "/bin/sh", "-c", cmdline };
git_process_options merged_opts = {0};
return git_process_new(out,
args, ARRAY_SIZE(args), env, env_len, opts);
memcpy(&merged_opts, opts, sizeof(git_process_options));
merged_opts.use_shell = 1;
return git_process_new(out, &cmdline, 1, env, env_len, &merged_opts);
}
#define CLOSE_FD(fd) \
@@ -271,6 +317,33 @@ static void write_status(int fd, const char *fn, int error, int os_error)
try_write_status(fd, fn, fn_len);
}
static int resolve_path(git_process *process)
{
git_str full_path = GIT_STR_INIT;
int error = 0;
/* The shell will resolve the path for us */
if (process->use_shell)
goto done;
error = git_fs_path_find_executable(&full_path, process->args[0]);
if (error == GIT_ENOTFOUND) {
git_error_set(GIT_ERROR_SSH, "cannot run %s: No such file or directory", process->args[0]);
error = -1;
}
if (error)
goto done;
git__free(process->args[0]);
process->args[0] = git_str_detach(&full_path);
done:
git_str_dispose(&full_path);
return error;
}
int git_process_start(git_process *process)
{
int in[2] = { -1, -1 }, out[2] = { -1, -1 },
@@ -278,6 +351,10 @@ int git_process_start(git_process *process)
int fdflags, state, error;
pid_t pid;
/* Locate the path (unless we're letting the shell do it for us) */
if ((error = resolve_path(process)) < 0)
goto on_error;
/* Set up the pipes to read from/write to the process */
if ((process->capture_in && pipe(in) < 0) ||
(process->capture_out && pipe(out) < 0) ||

View File

@@ -269,13 +269,26 @@ int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix)
return prefixcmp(str, str_n, prefix, true);
}
int git__suffixcmp(const char *str, const char *suffix)
static int suffixcmp(const char *str, const char *suffix, bool icase)
{
size_t a = strlen(str);
size_t b = strlen(suffix);
if (a < b)
return -1;
return strcmp(str + (a - b), suffix);
return icase ? strcasecmp(str + (a - b), suffix) :
strcmp(str + (a - b), suffix);
}
int git__suffixcmp(const char *str, const char *suffix)
{
return suffixcmp(str, suffix, false);
}
int git__suffixcmp_icase(const char *str, const char *suffix)
{
return suffixcmp(str, suffix, true);
}
char *git__strtok(char **end, const char *sep)

View File

@@ -57,6 +57,7 @@ extern int git__prefixcmp_icase(const char *str, const char *prefix);
extern int git__prefixncmp(const char *str, size_t str_n, const char *prefix);
extern int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix);
extern int git__suffixcmp(const char *str, const char *suffix);
extern int git__suffixcmp_icase(const char *str, const char *suffix);
GIT_INLINE(int) git__signum(int val)
{

View File

@@ -153,6 +153,7 @@ int git_win32_path_canonicalize(git_win32_path path)
static int git_win32_path_join(
git_win32_path dest,
size_t *dest_len,
const wchar_t *one,
size_t one_len,
const wchar_t *two,
@@ -176,6 +177,9 @@ static int git_win32_path_join(
memcpy(dest + one_len + backslash, two, two_len * sizeof(wchar_t));
dest[one_len + backslash + two_len] = L'\0';
if (dest_len)
*dest_len = one_len + backslash + two_len;
return 0;
}
@@ -253,21 +257,92 @@ static void win32_path_iter_dispose(struct win32_path_iter *iter)
iter->current_dir = NULL;
}
struct executable_suffix {
const wchar_t *suffix;
size_t len;
};
static struct executable_suffix suffixes[] = { { NULL, 0 }, { L".exe", 4 }, { L".cmd", 4 } };
static bool has_executable_suffix(wchar_t *exe, size_t exe_len)
{
size_t i;
for (i = 0; i < ARRAY_SIZE(suffixes); i++) {
struct executable_suffix *suffix = &suffixes[i];
if (!suffix->len)
continue;
if (exe_len < suffix->len)
continue;
if (memcmp(&exe[exe_len - suffix->len], suffix->suffix, suffix->len) == 0)
return true;
}
return false;
}
static int is_executable(git_win32_path path, size_t path_len, bool suffixed)
{
size_t i;
/*
* if the given name has an executable suffix, then try looking for it
* directly. in all cases, append executable extensions
* (".exe", ".cmd"...)
*/
for (i = suffixed ? 0 : 1; i < ARRAY_SIZE(suffixes); i++) {
struct executable_suffix *suffix = &suffixes[i];
if (suffix->len) {
if (path_len + suffix->len > MAX_PATH)
continue;
wcscat(path, suffix->suffix);
}
if (_waccess(path, 0) == 0)
return true;
path[path_len] = L'\0';
}
return false;
}
int git_win32_path_find_executable(git_win32_path fullpath, wchar_t *exe)
{
struct win32_path_iter path_iter;
const wchar_t *dir;
size_t dir_len, exe_len = wcslen(exe);
bool found = false;
size_t dir_len, exe_len, fullpath_len;
bool suffixed = false, found = false;
if ((exe_len = wcslen(exe)) > MAX_PATH)
goto done;
/* see if the given executable has an executable suffix; if so we will
* look for the explicit name directly, as well as with added suffixes.
*/
suffixed = has_executable_suffix(exe, exe_len);
/* For fully-qualified paths we do not look in PATH */
if (wcschr(exe, L'\\') != NULL || wcschr(exe, L'/') != NULL) {
if ((found = is_executable(exe, exe_len, suffixed)))
wcscpy(fullpath, exe);
goto done;
}
if (win32_path_iter_init(&path_iter) < 0)
return -1;
while (win32_path_iter_next(&dir, &dir_len, &path_iter) != GIT_ITEROVER) {
if (git_win32_path_join(fullpath, dir, dir_len, exe, exe_len) < 0)
while (win32_path_iter_next(&dir, &dir_len, &path_iter) != GIT_ITEROVER && !found) {
if (git_win32_path_join(fullpath, &fullpath_len, dir, dir_len, exe, exe_len) < 0)
continue;
if (_waccess(fullpath, 0) == 0) {
if (is_executable(fullpath, fullpath_len, suffixed)) {
found = true;
break;
}
@@ -275,6 +350,7 @@ int git_win32_path_find_executable(git_win32_path fullpath, wchar_t *exe)
win32_path_iter_dispose(&path_iter);
done:
if (found)
return 0;

View File

@@ -11,6 +11,7 @@
#include "git2_util.h"
#include "process.h"
#include "strlist.h"
#include "fs_path.h"
#ifndef DWORD_MAX
# define DWORD_MAX INT32_MAX
@@ -19,7 +20,8 @@
#define ENV_MAX 32767
struct git_process {
wchar_t *appname;
char *app_name;
wchar_t *app_path;
wchar_t *cmdline;
wchar_t *env;
@@ -178,8 +180,7 @@ static int process_new(
process = git__calloc(1, sizeof(git_process));
GIT_ERROR_CHECK_ALLOC(process);
if (appname &&
git_utf8_to_16_alloc(&process->appname, appname) < 0) {
if (appname && (process->app_name = git__strdup(appname)) == NULL) {
error = -1;
goto done;
}
@@ -233,7 +234,7 @@ int git_process_new(
size_t env_len,
git_process_options *opts)
{
git_str cmdline = GIT_STR_INIT;
git_str cmd_path = GIT_STR_INIT, cmdline = GIT_STR_INIT;
int error;
GIT_ASSERT_ARG(out && args && args_len > 0);
@@ -244,6 +245,7 @@ int git_process_new(
error = process_new(out, args[0], cmdline.ptr, env, env_len, opts);
done:
git_str_dispose(&cmd_path);
git_str_dispose(&cmdline);
return error;
}
@@ -259,6 +261,19 @@ int git_process_start(git_process *process)
out[2] = { NULL, NULL },
err[2] = { NULL, NULL };
if (process->app_name) {
git_str cmd_path = GIT_STR_INIT;
int error;
if ((error = git_fs_path_find_executable(&cmd_path, process->app_name)) == 0)
error = git_utf8_to_16_alloc(&process->app_path, cmd_path.ptr);
git_str_dispose(&cmd_path);
if (error < 0)
goto on_error;
}
memset(&security_attrs, 0, sizeof(SECURITY_ATTRIBUTES));
security_attrs.bInheritHandle = TRUE;
@@ -303,7 +318,7 @@ int git_process_start(git_process *process)
memset(&process->process_info, 0, sizeof(PROCESS_INFORMATION));
if (!CreateProcessW(process->appname, process->cmdline,
if (!CreateProcessW(process->app_path, process->cmdline,
NULL, NULL, TRUE, flags, process->env,
process->cwd,
&startup_info,
@@ -501,6 +516,7 @@ void git_process_free(git_process *process)
git__free(process->env);
git__free(process->cwd);
git__free(process->cmdline);
git__free(process->appname);
git__free(process->app_path);
git__free(process->app_name);
git__free(process);
}

View File

@@ -105,13 +105,13 @@ void test_online_clone__initialize(void)
_orig_https_proxy = cl_getenv("HTTPS_PROXY");
_orig_no_proxy = cl_getenv("NO_PROXY");
_orig_ssh_cmd = cl_getenv("GIT_SSH");
_orig_ssh_cmd = cl_getenv("GIT_SSH_COMMAND");
_ssh_cmd = cl_getenv("GITTEST_SSH_CMD");
if (_ssh_cmd)
cl_setenv("GIT_SSH", _ssh_cmd);
cl_setenv("GIT_SSH_COMMAND", _ssh_cmd);
else
cl_setenv("GIT_SSH", NULL);
cl_setenv("GIT_SSH_COMMAND", NULL);
if (_remote_expectcontinue)
git_libgit2_opts(GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE, 1);
@@ -174,7 +174,7 @@ void test_online_clone__cleanup(void)
git__free(_orig_https_proxy);
git__free(_orig_no_proxy);
cl_setenv("GIT_SSH", _orig_ssh_cmd);
cl_setenv("GIT_SSH_COMMAND", _orig_ssh_cmd);
git__free(_orig_ssh_cmd);
git__free(_ssh_cmd);

View File

@@ -376,13 +376,13 @@ void test_online_push__initialize(void)
_remote_push_options = cl_getenv("GITTEST_PUSH_OPTIONS");
_remote = NULL;
_orig_ssh_cmd = cl_getenv("GIT_SSH");
_orig_ssh_cmd = cl_getenv("GIT_SSH_COMMAND");
_ssh_cmd = cl_getenv("GITTEST_SSH_CMD");
if (_ssh_cmd)
cl_setenv("GIT_SSH", _ssh_cmd);
cl_setenv("GIT_SSH_COMMAND", _ssh_cmd);
else
cl_setenv("GIT_SSH", NULL);
cl_setenv("GIT_SSH_COMMAND", NULL);
/* Skip the test if we're missing the remote URL */
if (!_remote_url)
@@ -439,6 +439,7 @@ void test_online_push__cleanup(void)
git__free(_remote_expectcontinue);
git__free(_remote_push_options);
cl_setenv("GIT_SSH_COMMAND", _orig_ssh_cmd);
git__free(_orig_ssh_cmd);
git__free(_ssh_cmd);

View File

@@ -691,17 +691,33 @@ void test_gitstr__puts_escaped(void)
git_str a = GIT_STR_INIT;
git_str_clear(&a);
cl_git_pass(git_str_puts_escaped(&a, "this is a test", "", ""));
cl_git_pass(git_str_puts_escaped(&a, "this is a test", "", "", ""));
cl_assert_equal_s("this is a test", a.ptr);
git_str_clear(&a);
cl_git_pass(git_str_puts_escaped(&a, "this is a test", "t", "\\"));
cl_git_pass(git_str_puts_escaped(&a, "this is a test", "", NULL, NULL));
cl_assert_equal_s("this is a test", a.ptr);
git_str_clear(&a);
cl_git_pass(git_str_puts_escaped(&a, "this is a test", "t", "\\", ""));
cl_assert_equal_s("\\this is a \\tes\\t", a.ptr);
git_str_clear(&a);
cl_git_pass(git_str_puts_escaped(&a, "this is a test", "i ", "__"));
cl_git_pass(git_str_puts_escaped(&a, "this is a test", "t", "\\", NULL));
cl_assert_equal_s("\\this is a \\tes\\t", a.ptr);
git_str_clear(&a);
cl_git_pass(git_str_puts_escaped(&a, "this is a test", "i ", "__", NULL));
cl_assert_equal_s("th__is__ __is__ a__ test", a.ptr);
git_str_clear(&a);
cl_git_pass(git_str_puts_escaped(&a, "this is a test", "i ", "__", "!!"));
cl_assert_equal_s("th__i!!s__ !!__i!!s__ !!a__ !!test", a.ptr);
git_str_clear(&a);
cl_git_pass(git_str_puts_escaped(&a, "this' is' an' escape! ", "'!", "'\\", "'"));
cl_assert_equal_s("this'\\'' is'\\'' an'\\'' escape'\\!' ", a.ptr);
git_str_clear(&a);
cl_git_pass(git_str_puts_escape_regex(&a, "^match\\s*[A-Z]+.*"));
cl_assert_equal_s("\\^match\\\\s\\*\\[A-Z\\]\\+\\.\\*", a.ptr);

View File

@@ -698,12 +698,20 @@ static void fix_path(git_str *s)
#endif
}
#ifdef GIT_WIN32
# define PATH_DELIMITER "\\"
#else
# define PATH_DELIMITER "/"
#endif
void test_path_core__find_exe_in_path(void)
{
char *orig_path;
git_str sandbox_path = GIT_STR_INIT;
git_str new_path = GIT_STR_INIT, full_path = GIT_STR_INIT,
dummy_path = GIT_STR_INIT;
dummy_path_one = GIT_STR_INIT,
dummy_path_two = GIT_STR_INIT,
dummy_path_three = GIT_STR_INIT;
#ifdef GIT_WIN32
static const char *bogus_path_1 = "c:\\does\\not\\exist\\";
@@ -716,28 +724,84 @@ void test_path_core__find_exe_in_path(void)
orig_path = cl_getenv("PATH");
git_str_puts(&sandbox_path, clar_sandbox_path());
git_str_joinpath(&dummy_path, sandbox_path.ptr, "dummmmmmmy_libgit2_file");
cl_git_rewritefile(dummy_path.ptr, "this is a dummy file");
git_str_joinpath(&dummy_path_one, sandbox_path.ptr, "dummmmmmmy_libgit2_file_one");
cl_git_rewritefile(dummy_path_one.ptr, "this is a dummy file");
git_str_joinpath(&dummy_path_two, sandbox_path.ptr, "dummmmmmmy_libgit2_file_two.exe");
cl_git_rewritefile(dummy_path_two.ptr, "this is a dummy file");
cl_must_pass(p_chmod("dummmmmmmy_libgit2_file_two.exe", 0777));
cl_must_pass(p_mkdir("sub", 0777));
cl_must_pass(p_mkdir("sub/dir", 0777));
git_str_joinpath(&dummy_path_three, sandbox_path.ptr, "sub/dir/dummmmmmmy_libgit2_file_three.exe");
cl_git_rewritefile(dummy_path_three.ptr, "this is a dummy file");
cl_must_pass(p_chmod("sub/dir/dummmmmmmy_libgit2_file_three.exe", 0777));
fix_path(&sandbox_path);
fix_path(&dummy_path);
fix_path(&dummy_path_one);
fix_path(&dummy_path_two);
fix_path(&dummy_path_three);
cl_git_pass(git_str_printf(&new_path, "%s%c%s%c%s%c%s",
bogus_path_1, GIT_PATH_LIST_SEPARATOR,
orig_path, GIT_PATH_LIST_SEPARATOR,
sandbox_path.ptr, GIT_PATH_LIST_SEPARATOR,
bogus_path_2));
check_setenv("PATH", new_path.ptr);
/* ensure that we cannot find a nonexistent file */
cl_git_fail_with(GIT_ENOTFOUND, git_fs_path_find_executable(&full_path, "this_file_does_not_exist"));
cl_git_pass(git_fs_path_find_executable(&full_path, "dummmmmmmy_libgit2_file"));
cl_assert_equal_s(full_path.ptr, dummy_path.ptr);
/* ensure that non-executable files are ignored */
cl_git_fail_with(GIT_ENOTFOUND, git_fs_path_find_executable(&full_path, "dummmmmmmy_libgit2_file_one"));
/* find an executable in the path */
git_str_clear(&full_path);
cl_git_pass(git_fs_path_find_executable(&full_path, "dummmmmmmy_libgit2_file_two.exe"));
cl_assert_equal_s(full_path.ptr, dummy_path_two.ptr);
/* find an executable in the current directory */
git_str_clear(&new_path);
cl_git_pass(git_str_printf(&new_path, "%s%c%s%c%s%c%s",
bogus_path_1, GIT_PATH_LIST_SEPARATOR,
orig_path, GIT_PATH_LIST_SEPARATOR,
".", GIT_PATH_LIST_SEPARATOR,
bogus_path_2));
check_setenv("PATH", new_path.ptr);
git_str_clear(&full_path);
cl_git_pass(git_fs_path_find_executable(&full_path, "dummmmmmmy_libgit2_file_two.exe"));
cl_assert_equal_s(full_path.ptr, "." PATH_DELIMITER "dummmmmmmy_libgit2_file_two.exe");
/* empty path is the same as current directory on POSIX */
#ifndef GIT_WIN32
git_str_clear(&new_path);
cl_git_pass(git_str_printf(&new_path, "%s%c%s%c%s%c%s",
bogus_path_1, GIT_PATH_LIST_SEPARATOR,
orig_path, GIT_PATH_LIST_SEPARATOR,
"", GIT_PATH_LIST_SEPARATOR,
bogus_path_2));
check_setenv("PATH", new_path.ptr);
git_str_clear(&full_path);
cl_git_pass(git_fs_path_find_executable(&full_path, "dummmmmmmy_libgit2_file_two.exe"));
cl_assert_equal_s(full_path.ptr, "." PATH_DELIMITER "dummmmmmmy_libgit2_file_two.exe");
#endif
/* don't look for qualified paths in PATH */
cl_git_fail_with(GIT_ENOTFOUND, git_fs_path_find_executable(&full_path, "dir/dummmmmmmy_libgit2_file_three.exe"));
/* but do allow users to use paths relative to current dir */
git_str_clear(&full_path);
cl_git_pass(git_fs_path_find_executable(&full_path, "sub/dir/dummmmmmmy_libgit2_file_three.exe"));
cl_assert_equal_s(full_path.ptr, "sub/dir/dummmmmmmy_libgit2_file_three.exe");
git_str_dispose(&full_path);
git_str_dispose(&new_path);
git_str_dispose(&dummy_path);
git_str_dispose(&dummy_path_one);
git_str_dispose(&dummy_path_two);
git_str_dispose(&dummy_path_three);
git_str_dispose(&sandbox_path);
git__free(orig_path);
}

View File

@@ -78,6 +78,52 @@ void test_process_start__not_found(void)
git_process_free(process);
}
void test_process_start__finds_in_path(void)
{
#ifdef GIT_WIN32
const char *args_array[] = { "cmd", "/c", "exit", "0" };
#else
const char *args_array[] = { "true" };
#endif
git_process *process;
git_process_options opts = GIT_PROCESS_OPTIONS_INIT;
git_process_result result = GIT_PROCESS_RESULT_INIT;
cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), NULL, 0, &opts));
cl_git_pass(git_process_start(process));
cl_git_pass(git_process_wait(&result, process));
cl_assert_equal_i(GIT_PROCESS_STATUS_NORMAL, result.status);
cl_assert_equal_i(0, result.exitcode);
cl_assert_equal_i(0, result.signal);
git_process_free(process);
}
void test_process_start__adds_suffix(void)
{
#ifdef GIT_WIN32
const char *args_array[] = { "C:\\Windows\\System32\\cmd", "/c", "exit", "0" };
git_process *process;
git_process_options opts = GIT_PROCESS_OPTIONS_INIT;
git_process_result result = GIT_PROCESS_RESULT_INIT;
cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), NULL, 0, &opts));
cl_git_pass(git_process_start(process));
cl_git_pass(git_process_wait(&result, process));
cl_assert_equal_i(GIT_PROCESS_STATUS_NORMAL, result.status);
cl_assert_equal_i(0, result.exitcode);
cl_assert_equal_i(0, result.signal);
git_process_free(process);
#else
cl_skip();
#endif
}
static void write_all(git_process *process, char *buf)
{
size_t buf_len = strlen(buf);