Unverified Commit f51e70dc by Edward Thomson Committed by GitHub

Merge pull request #6617 from libgit2/ethomson/openssh

Add OpenSSH support
parents da265cdf ac399148
......@@ -25,40 +25,40 @@ jobs:
strategy:
matrix:
platform:
- name: "Linux (Xenial, GCC, OpenSSL)"
- name: "Linux (Xenial, GCC, OpenSSL, libssh2)"
id: xenial-gcc-openssl
container:
name: xenial
env:
CC: gcc
CMAKE_GENERATOR: Ninja
CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL -DREGEX_BACKEND=builtin -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON -DDEBUG_STRICT_ALLOC=ON -DDEBUG_STRICT_OPEN=ON
CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL -DREGEX_BACKEND=builtin -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=libssh2 -DDEBUG_STRICT_ALLOC=ON -DDEBUG_STRICT_OPEN=ON
os: ubuntu-latest
- name: Linux (Xenial, GCC, mbedTLS)
- name: Linux (Xenial, GCC, mbedTLS, OpenSSH)
id: xenial-gcc-mbedtls
container:
name: xenial
env:
CC: gcc
CMAKE_GENERATOR: Ninja
CMAKE_OPTIONS: -DUSE_HTTPS=mbedTLS -DUSE_SHA1=HTTPS -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON
CMAKE_OPTIONS: -DUSE_HTTPS=mbedTLS -DUSE_SHA1=HTTPS -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=exec
os: ubuntu-latest
- name: "Linux (Xenial, Clang, OpenSSL)"
- name: "Linux (Xenial, Clang, OpenSSL, OpenSSH)"
id: xenial-clang-openssl
container:
name: xenial
env:
CC: clang
CMAKE_GENERATOR: Ninja
CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON
CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=exec
os: ubuntu-latest
- name: "Linux (Xenial, Clang, mbedTLS)"
- name: "Linux (Xenial, Clang, mbedTLS, libssh2)"
id: xenial-clang-mbedtls
container:
name: xenial
env:
CC: clang
CMAKE_OPTIONS: -DUSE_HTTPS=mbedTLS -DUSE_SHA1=HTTPS -DREGEX_BACKEND=pcre -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON
CMAKE_OPTIONS: -DUSE_HTTPS=mbedTLS -DUSE_SHA1=HTTPS -DREGEX_BACKEND=pcre -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=libssh2
CMAKE_GENERATOR: Ninja
os: ubuntu-latest
- name: "macOS"
......
......@@ -30,7 +30,7 @@ option(USE_THREADS "Use threads for parallel processing when possibl
option(USE_NSEC "Support nanosecond precision file mtimes and ctimes" ON)
# Backend selection
option(USE_SSH "Link with libssh2 to enable SSH support" OFF)
option(USE_SSH "Enable SSH support. Can be set to a specific backend" OFF)
option(USE_HTTPS "Enable HTTPS support. Can be set to a specific backend" ON)
option(USE_SHA1 "Enable SHA1. Can be set to CollisionDetection(ON)/HTTPS" ON)
option(USE_SHA256 "Enable SHA256. Can be set to HTTPS/Builtin" ON)
......
......@@ -199,6 +199,8 @@ if [ -z "$SKIP_SSH_TESTS" ]; then
PubkeyAuthentication yes
ChallengeResponseAuthentication no
StrictModes no
HostCertificate ${SSHD_DIR}/id_rsa.pub
HostKey ${SSHD_DIR}/id_rsa
# Required here as sshd will simply close connection otherwise
UsePAM no
EOF
......@@ -414,6 +416,8 @@ if [ -z "$SKIP_SSH_TESTS" ]; then
export GITTEST_REMOTE_SSH_PASSPHRASE=""
export GITTEST_REMOTE_SSH_FINGERPRINT="${SSH_FINGERPRINT}"
export GITTEST_SSH_CMD="ssh -i ${HOME}/.ssh/id_rsa -o UserKnownHostsFile=${HOME}/.ssh/known_hosts"
echo ""
echo "Running ssh tests"
echo ""
......@@ -430,6 +434,8 @@ if [ -z "$SKIP_SSH_TESTS" ]; then
run_test ssh
unset GITTEST_REMOTE_URL
unset GITTEST_SSH_CMD
unset GITTEST_REMOTE_USER
unset GITTEST_REMOTE_SSH_KEY
unset GITTEST_REMOTE_SSH_PUBKEY
......
# Optional external dependency: libssh2
if(USE_SSH)
if(USE_SSH STREQUAL "exec")
set(GIT_SSH 1)
set(GIT_SSH_EXEC 1)
add_feature_info(SSH ON "using OpenSSH exec support")
elseif(USE_SSH STREQUAL ON OR USE_SSH STREQUAL "libssh2")
find_pkglibraries(LIBSSH2 libssh2)
if(NOT LIBSSH2_FOUND)
find_package(LibSSH2)
set(LIBSSH2_INCLUDE_DIRS ${LIBSSH2_INCLUDE_DIR})
......@@ -12,30 +17,28 @@ if(USE_SSH)
if(NOT LIBSSH2_FOUND)
message(FATAL_ERROR "LIBSSH2 not found. Set CMAKE_PREFIX_PATH if it is installed outside of the default search path.")
endif()
endif()
if(LIBSSH2_FOUND)
set(GIT_SSH 1)
list(APPEND LIBGIT2_SYSTEM_INCLUDES ${LIBSSH2_INCLUDE_DIRS})
list(APPEND LIBGIT2_SYSTEM_LIBS ${LIBSSH2_LIBRARIES})
list(APPEND LIBGIT2_PC_LIBS ${LIBSSH2_LDFLAGS})
check_library_exists("${LIBSSH2_LIBRARIES}" libssh2_userauth_publickey_frommemory "${LIBSSH2_LIBRARY_DIRS}" HAVE_LIBSSH2_MEMORY_CREDENTIALS)
if(HAVE_LIBSSH2_MEMORY_CREDENTIALS)
set(GIT_SSH_MEMORY_CREDENTIALS 1)
set(GIT_SSH_LIBSSH2_MEMORY_CREDENTIALS 1)
endif()
else()
message(STATUS "LIBSSH2 not found. Set CMAKE_PREFIX_PATH if it is installed outside of the default search path.")
endif()
if(WIN32 AND EMBED_SSH_PATH)
file(GLOB SSH_SRC "${EMBED_SSH_PATH}/src/*.c")
list(SORT SSH_SRC)
list(APPEND LIBGIT2_DEPENDENCY_OBJECTS ${SSH_SRC})
if(WIN32 AND EMBED_SSH_PATH)
file(GLOB SSH_SRC "${EMBED_SSH_PATH}/src/*.c")
list(SORT SSH_SRC)
list(APPEND LIBGIT2_DEPENDENCY_OBJECTS ${SSH_SRC})
list(APPEND LIBGIT2_DEPENDENCY_INCLUDES "${EMBED_SSH_PATH}/include")
file(WRITE "${EMBED_SSH_PATH}/src/libssh2_config.h" "#define HAVE_WINCNG\n#define LIBSSH2_WINCNG\n#include \"../win32/libssh2_config.h\"")
endif()
list(APPEND LIBGIT2_DEPENDENCY_INCLUDES "${EMBED_SSH_PATH}/include")
file(WRITE "${EMBED_SSH_PATH}/src/libssh2_config.h" "#define HAVE_WINCNG\n#define LIBSSH2_WINCNG\n#include \"../win32/libssh2_config.h\"")
set(GIT_SSH 1)
set(GIT_SSH_LIBSSH2 1)
add_feature_info(SSH ON "using libssh2")
else()
add_feature_info(SSH OFF "SSH transport support")
endif()
add_feature_info(SSH GIT_SSH "SSH transport support")
#!/bin/bash
exec valgrind --leak-check=full --show-reachable=yes --error-exitcode=125 --num-callers=50 --suppressions="$(dirname "${BASH_SOURCE[0]}")/valgrind.supp" "$@"
exec valgrind --leak-check=full --show-reachable=yes --child-silent-after-fork=yes --error-exitcode=125 --num-callers=50 --suppressions="$(dirname "${BASH_SOURCE[0]}")/valgrind.supp" "$@"
......@@ -34,7 +34,7 @@
#include "streams/socket.h"
#include "transports/smart.h"
#include "transports/http.h"
#include "transports/ssh.h"
#include "transports/ssh_libssh2.h"
#ifdef GIT_WIN32
# include "win32/w32_leakcheck.h"
......@@ -80,7 +80,7 @@ int git_libgit2_init(void)
git_sysdir_global_init,
git_filter_global_init,
git_merge_driver_global_init,
git_transport_ssh_global_init,
git_transport_ssh_libssh2_global_init,
git_stream_registry_global_init,
git_socket_stream_global_init,
git_openssl_stream_global_init,
......@@ -126,10 +126,10 @@ int git_libgit2_features(void)
#ifdef GIT_HTTPS
| GIT_FEATURE_HTTPS
#endif
#if defined(GIT_SSH)
#ifdef GIT_SSH
| GIT_FEATURE_SSH
#endif
#if defined(GIT_USE_NSEC)
#ifdef GIT_USE_NSEC
| GIT_FEATURE_NSEC
#endif
;
......
......@@ -22,6 +22,7 @@ typedef struct transport_definition {
static git_smart_subtransport_definition http_subtransport_definition = { git_smart_subtransport_http, 1, NULL };
static git_smart_subtransport_definition git_subtransport_definition = { git_smart_subtransport_git, 0, NULL };
#ifdef GIT_SSH
static git_smart_subtransport_definition ssh_subtransport_definition = { git_smart_subtransport_ssh, 0, NULL };
#endif
......@@ -33,11 +34,13 @@ static transport_definition transports[] = {
{ "http://", git_transport_smart, &http_subtransport_definition },
{ "https://", git_transport_smart, &http_subtransport_definition },
{ "file://", git_transport_local, NULL },
#ifdef GIT_SSH
{ "ssh://", git_transport_smart, &ssh_subtransport_definition },
{ "ssh+git://", git_transport_smart, &ssh_subtransport_definition },
{ "git+ssh://", git_transport_smart, &ssh_subtransport_definition },
#endif
{ NULL, 0, 0 }
};
......
......@@ -204,7 +204,7 @@ int git_credential_ssh_key_memory_new(
const char *privatekey,
const char *passphrase)
{
#ifdef GIT_SSH_MEMORY_CREDENTIALS
#ifdef GIT_SSH_LIBSSH2_MEMORY_CREDENTIALS
return git_credential_ssh_key_type_new(
cred,
username,
......
......@@ -370,17 +370,27 @@ static int git_smart__close(git_transport *transport)
git_vector *common = &t->common;
unsigned int i;
git_pkt *p;
git_smart_service_t service;
int ret;
git_smart_subtransport_stream *stream;
const char flush[] = "0000";
if (t->direction == GIT_DIRECTION_FETCH) {
service = GIT_SERVICE_UPLOADPACK;
} else if (t->direction == GIT_DIRECTION_PUSH) {
service = GIT_SERVICE_RECEIVEPACK;
} else {
git_error_set(GIT_ERROR_NET, "invalid direction");
return -1;
}
/*
* If we're still connected at this point and not using RPC,
* we should say goodbye by sending a flush, or git-daemon
* will complain that we disconnected unexpectedly.
*/
if (t->connected && !t->rpc &&
!t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) {
!t->wrapped->action(&stream, t->wrapped, t->url, service)) {
t->current_stream->write(t->current_stream, flush, 4);
}
......@@ -513,7 +523,6 @@ int git_transport_smart(git_transport **out, git_remote *owner, void *param)
definition->callback(&t->wrapped, &t->parent, definition->param) < 0) {
git_vector_free(&t->refs);
git_vector_free(&t->heads);
t->wrapped->free(t->wrapped);
git__free(t);
return -1;
}
......
......@@ -59,7 +59,7 @@ int git_smart__store_refs(transport_smart *t, int flushes)
return recvd;
if (recvd == 0) {
git_error_set(GIT_ERROR_NET, "early EOF");
git_error_set(GIT_ERROR_NET, "could not read refs from remote repository");
return GIT_EEOF;
}
......@@ -285,7 +285,7 @@ static int recv_pkt(
if ((ret = git_smart__recv(t)) < 0) {
return ret;
} else if (ret == 0) {
git_error_set(GIT_ERROR_NET, "early EOF");
git_error_set(GIT_ERROR_NET, "could not read from remote repository");
return GIT_EEOF;
}
} while (error);
......@@ -940,7 +940,7 @@ static int parse_report(transport_smart *transport, git_push *push)
}
if (recvd == 0) {
git_error_set(GIT_ERROR_NET, "early EOF");
git_error_set(GIT_ERROR_NET, "could not read report from remote repository");
error = GIT_EEOF;
goto done;
}
......
/*
* 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 "ssh_exec.h"
#ifdef GIT_SSH_EXEC
#include "common.h"
#include "config.h"
#include "net.h"
#include "path.h"
#include "futils.h"
#include "process.h"
#include "transports/smart.h"
typedef struct {
git_smart_subtransport_stream parent;
} ssh_exec_subtransport_stream;
typedef struct {
git_smart_subtransport parent;
git_transport *owner;
ssh_exec_subtransport_stream *current_stream;
char *cmd_uploadpack;
char *cmd_receivepack;
git_smart_service_t action;
git_process *process;
} ssh_exec_subtransport;
static int ssh_exec_subtransport_stream_read(
git_smart_subtransport_stream *s,
char *buffer,
size_t buf_size,
size_t *bytes_read)
{
ssh_exec_subtransport *transport;
ssh_exec_subtransport_stream *stream = (ssh_exec_subtransport_stream *)s;
ssize_t ret;
GIT_ASSERT_ARG(stream);
GIT_ASSERT(stream->parent.subtransport);
transport = (ssh_exec_subtransport *)stream->parent.subtransport;
if ((ret = git_process_read(transport->process, buffer, buf_size)) < 0) {
return (int)ret;
}
*bytes_read = (size_t)ret;
return 0;
}
static int ssh_exec_subtransport_stream_write(
git_smart_subtransport_stream *s,
const char *buffer,
size_t len)
{
ssh_exec_subtransport *transport;
ssh_exec_subtransport_stream *stream = (ssh_exec_subtransport_stream *)s;
ssize_t ret;
GIT_ASSERT(stream && stream->parent.subtransport);
transport = (ssh_exec_subtransport *)stream->parent.subtransport;
while (len > 0) {
if ((ret = git_process_write(transport->process, buffer, len)) < 0)
return (int)ret;
len -= ret;
}
return 0;
}
static void ssh_exec_subtransport_stream_free(git_smart_subtransport_stream *s)
{
ssh_exec_subtransport_stream *stream = (ssh_exec_subtransport_stream *)s;
git__free(stream);
}
static int ssh_exec_subtransport_stream_init(
ssh_exec_subtransport_stream **out,
ssh_exec_subtransport *transport)
{
GIT_ASSERT_ARG(out);
*out = git__calloc(sizeof(ssh_exec_subtransport_stream), 1);
GIT_ERROR_CHECK_ALLOC(*out);
(*out)->parent.subtransport = &transport->parent;
(*out)->parent.read = ssh_exec_subtransport_stream_read;
(*out)->parent.write = ssh_exec_subtransport_stream_write;
(*out)->parent.free = ssh_exec_subtransport_stream_free;
return 0;
}
GIT_INLINE(int) ensure_transport_state(
ssh_exec_subtransport *transport,
git_smart_service_t expected,
git_smart_service_t next)
{
if (transport->action != expected && transport->action != next) {
git_error_set(GIT_ERROR_NET, "invalid transport state");
return -1;
}
return 0;
}
static int get_ssh_cmdline(
git_str *out,
ssh_exec_subtransport *transport,
git_net_url *url,
const char *command)
{
git_remote *remote = ((transport_smart *)transport->owner)->owner;
git_repository *repo = remote->repo;
git_config *cfg;
git_str ssh_cmd = GIT_STR_INIT;
const char *default_ssh_cmd = "ssh";
int error;
if ((error = git_repository_config_snapshot(&cfg, repo)) < 0)
return error;
if ((error = git__getenv(&ssh_cmd, "GIT_SSH")) == 0)
;
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 -p %s \"%s%s%s\" \"%s\" \"%s\"",
ssh_cmd.size > 0 ? ssh_cmd.ptr : default_ssh_cmd,
url->port,
url->username ? url->username : "",
url->username ? "@" : "",
url->host,
command,
url->path);
done:
git_str_dispose(&ssh_cmd);
git_config_free(cfg);
return error;
}
static int start_ssh(
ssh_exec_subtransport *transport,
git_smart_service_t action,
const char *sshpath)
{
const char *env[] = { "GIT_DIR=" };
git_process_options process_opts = GIT_PROCESS_OPTIONS_INIT;
git_net_url url = GIT_NET_URL_INIT;
git_str ssh_cmdline = GIT_STR_INIT;
const char *command;
int error;
process_opts.capture_in = 1;
process_opts.capture_out = 1;
process_opts.capture_err = 0;
switch (action) {
case GIT_SERVICE_UPLOADPACK_LS:
command = transport->cmd_uploadpack ?
transport->cmd_uploadpack : "git-upload-pack";
break;
case GIT_SERVICE_RECEIVEPACK_LS:
command = transport->cmd_receivepack ?
transport->cmd_receivepack : "git-receive-pack";
break;
default:
git_error_set(GIT_ERROR_NET, "invalid action");
error = -1;
goto done;
}
if (git_net_str_is_url(sshpath))
error = git_net_url_parse(&url, sshpath);
else
error = git_net_url_parse_scp(&url, sshpath);
if (error < 0)
goto done;
if ((error = get_ssh_cmdline(&ssh_cmdline, 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 ||
(error = git_process_start(transport->process)) < 0) {
git_process_free(transport->process);
transport->process = NULL;
goto done;
}
done:
git_str_dispose(&ssh_cmdline);
git_net_url_dispose(&url);
return error;
}
static int ssh_exec_subtransport_action(
git_smart_subtransport_stream **out,
git_smart_subtransport *t,
const char *sshpath,
git_smart_service_t action)
{
ssh_exec_subtransport *transport = (ssh_exec_subtransport *)t;
ssh_exec_subtransport_stream *stream = NULL;
git_smart_service_t expected;
int error;
switch (action) {
case GIT_SERVICE_UPLOADPACK_LS:
case GIT_SERVICE_RECEIVEPACK_LS:
if ((error = ensure_transport_state(transport, 0, 0)) < 0 ||
(error = ssh_exec_subtransport_stream_init(&stream, transport)) < 0 ||
(error = start_ssh(transport, action, sshpath)) < 0)
goto on_error;
transport->current_stream = stream;
break;
case GIT_SERVICE_UPLOADPACK:
case GIT_SERVICE_RECEIVEPACK:
expected = (action == GIT_SERVICE_UPLOADPACK) ?
GIT_SERVICE_UPLOADPACK_LS : GIT_SERVICE_RECEIVEPACK_LS;
if ((error = ensure_transport_state(transport, expected, action)) < 0)
goto on_error;
break;
default:
git_error_set(GIT_ERROR_INVALID, "invalid service request");
goto on_error;
}
transport->action = action;
*out = &transport->current_stream->parent;
return 0;
on_error:
if (stream != NULL)
ssh_exec_subtransport_stream_free(&stream->parent);
return -1;
}
static int ssh_exec_subtransport_close(git_smart_subtransport *t)
{
ssh_exec_subtransport *transport = (ssh_exec_subtransport *)t;
if (transport->process) {
git_process_close(transport->process);
git_process_free(transport->process);
transport->process = NULL;
}
transport->action = 0;
return 0;
}
static void ssh_exec_subtransport_free(git_smart_subtransport *t)
{
ssh_exec_subtransport *transport = (ssh_exec_subtransport *)t;
git__free(transport->cmd_uploadpack);
git__free(transport->cmd_receivepack);
git__free(transport);
}
int git_smart_subtransport_ssh_exec(
git_smart_subtransport **out,
git_transport *owner,
void *payload)
{
ssh_exec_subtransport *transport;
GIT_UNUSED(payload);
transport = git__calloc(sizeof(ssh_exec_subtransport), 1);
GIT_ERROR_CHECK_ALLOC(transport);
transport->owner = owner;
transport->parent.action = ssh_exec_subtransport_action;
transport->parent.close = ssh_exec_subtransport_close;
transport->parent.free = ssh_exec_subtransport_free;
*out = (git_smart_subtransport *) transport;
return 0;
}
int git_smart_subtransport_ssh_exec_set_paths(
git_smart_subtransport *subtransport,
const char *cmd_uploadpack,
const char *cmd_receivepack)
{
ssh_exec_subtransport *t = (ssh_exec_subtransport *)subtransport;
git__free(t->cmd_uploadpack);
git__free(t->cmd_receivepack);
t->cmd_uploadpack = git__strdup(cmd_uploadpack);
GIT_ERROR_CHECK_ALLOC(t->cmd_uploadpack);
t->cmd_receivepack = git__strdup(cmd_receivepack);
GIT_ERROR_CHECK_ALLOC(t->cmd_receivepack);
return 0;
}
#endif
......@@ -4,11 +4,23 @@
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_transports_ssh_h__
#define INCLUDE_transports_ssh_h__
#ifndef INCLUDE_transports_ssh_exec_h__
#define INCLUDE_transports_ssh_exec_h__
#include "common.h"
int git_transport_ssh_global_init(void);
#include "git2.h"
#include "git2/transport.h"
#include "git2/sys/transport.h"
int git_smart_subtransport_ssh_exec(
git_smart_subtransport **out,
git_transport *owner,
void *param);
int git_smart_subtransport_ssh_exec_set_paths(
git_smart_subtransport *subtransport,
const char *cmd_uploadpack,
const char *cmd_receivepack);
#endif
/*
* 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.
*/
#ifndef INCLUDE_transports_libssh2_h__
#define INCLUDE_transports_libssh2_h__
#include "common.h"
#include "git2.h"
#include "git2/transport.h"
#include "git2/sys/transport.h"
int git_transport_ssh_libssh2_global_init(void);
int git_smart_subtransport_ssh_libssh2(
git_smart_subtransport **out,
git_transport *owner,
void *param);
int git_smart_subtransport_ssh_libssh2_set_paths(
git_smart_subtransport *subtransport,
const char *cmd_uploadpack,
const char *cmd_receivepack);
#endif
......@@ -30,7 +30,9 @@
#cmakedefine GIT_QSORT_MSC
#cmakedefine GIT_SSH 1
#cmakedefine GIT_SSH_MEMORY_CREDENTIALS 1
#cmakedefine GIT_SSH_EXEC 1
#cmakedefine GIT_SSH_LIBSSH2 1
#cmakedefine GIT_SSH_LIBSSH2_MEMORY_CREDENTIALS 1
#cmakedefine GIT_NTLM 1
#cmakedefine GIT_GSSAPI 1
......
/*
* 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.
*/
#ifndef INCLUDE_process_h__
#define INCLUDE_process_h__
typedef struct git_process git_process;
typedef struct {
int capture_in : 1,
capture_out : 1,
capture_err : 1,
exclude_env : 1;
char *cwd;
} git_process_options;
typedef enum {
GIT_PROCESS_STATUS_NONE,
GIT_PROCESS_STATUS_NORMAL,
GIT_PROCESS_STATUS_ERROR
} git_process_result_status;
#define GIT_PROCESS_RESULT_INIT { GIT_PROCESS_STATUS_NONE }
typedef struct {
git_process_result_status status;
int exitcode;
int signal;
} git_process_result;
#define GIT_PROCESS_OPTIONS_INIT { 0 }
#ifdef GIT_WIN32
# define p_pid_t DWORD
#else
# define p_pid_t pid_t
#endif
/**
* Create a new process. The command to run should be specified as the
* element of the `arg` array, execv-style. This should be the full path
* to the command to run, the PATH is not obeyed.
*
* This function will add the given environment variables (in `env`)
* to the current environment. Operations on environment variables
* are not thread safe, so you may not modify the environment during
* this call. You can avoid this by setting `exclude_env` in the
* options and providing the entire environment yourself.
*
* @param out location to store the process
* @param args the command (with arguments) to run
* @param args_len the length of the args array
* @param env environment variables to add (or NULL)
* @param env_len the length of the env len
* @param opts the options for creating the process
* @return 0 or an error code
*/
extern int git_process_new(
git_process **out,
const char **args,
size_t args_len,
const char **env,
size_t env_len,
git_process_options *opts);
/**
* Create a new process. The command to run should be specified as the
* `cmdline` option - which is the full text of the command line as it
* would be specified or run by a user. The command to run will be
* looked up in the PATH.
*
* On Unix, this will be executed by the system's shell (`/bin/sh`)
* and may contain _Bourne-style_ shell quoting rules. On Windows,
* this will be passed to `CreateProcess`, and similarly, may
* contain _Windows-style_ shell quoting rules.
*
* This function will add the given environment variables (in `env`)
* to the current environment. Operations on environment variables
* are not thread safe, so you may not modify the environment during
* this call. You can avoid this by setting `exclude_env` in the
* options and providing the entire environment yourself.
*/
extern int git_process_new_from_cmdline(
git_process **out,
const char *cmdline,
const char **env,
size_t env_len,
git_process_options *opts);
#ifdef GIT_WIN32
extern int git_process__appname(
git_str *out,
const char *cmdline);
/* Windows path parsing is tricky; this helper function is for testing. */
extern int git_process__cmdline(
git_str *out,
const char **in,
size_t in_len);
#endif
/**
* Start the process.
*
* @param process the process to start
* @return 0 or an error code
*/
extern int git_process_start(git_process *process);
/**
* Returns the process id of the process.
*
* @param out pointer to a pid_t to store the process id
* @param process the process to query
* @return 0 or an error code
*/
extern int git_process_id(p_pid_t *out, git_process *process);
/**
* Read from the process's stdout. The process must have been created with
* `capture_out` set to true.
*
* @param process the process to read from
* @param buf the buf to read into
* @param count maximum number of bytes to read
* @return number of bytes read or an error code
*/
extern ssize_t git_process_read(git_process *process, void *buf, size_t count);
/**
* Read from the process's stderr. The process must have been created with
* `capture_err` set to true.
*
* @param process the process to read from
* @param buf the buf to read into
* @param count maximum number of bytes to read
* @return number of bytes read or an error code
*/
extern ssize_t git_process_read_err(git_process *process, void *buf, size_t count);
/**
* Write to the process's stdin. The process must have been created with
* `capture_in` set to true.
*
* @param process the process to write to
* @param buf the buf to write
* @param count maximum number of bytes to write
* @return number of bytes written or an error code
*/
extern ssize_t git_process_write(git_process *process, const void *buf, size_t count);
/**
* Wait for the process to finish.
*
* @param result the result of the process or NULL
* @param process the process to wait on
*/
extern int git_process_wait(git_process_result *result, git_process *process);
/**
* Close the input pipe from the child.
*
* @param process the process to close the pipe on
*/
extern int git_process_close_in(git_process *process);
/**
* Close the output pipe from the child.
*
* @param process the process to close the pipe on
*/
extern int git_process_close_out(git_process *process);
/**
* Close the error pipe from the child.
*
* @param process the process to close the pipe on
*/
extern int git_process_close_err(git_process *process);
/**
* Close all resources that are used by the process. This does not
* wait for the process to complete.
*
* @parma process the process to close
*/
extern int git_process_close(git_process *process);
/**
* Place a human-readable error message in the given git buffer.
*
* @param msg the buffer to store the message
* @param result the process result that produced an error
*/
extern int git_process_result_msg(git_str *msg, git_process_result *result);
/**
* Free a process structure
*
* @param process the process to free
*/
extern void git_process_free(git_process *process);
#endif
......@@ -28,6 +28,59 @@ int git_strlist_copy(char ***out, const char **in, size_t len)
return 0;
}
int git_strlist_copy_with_null(char ***out, const char **in, size_t len)
{
char **dup;
size_t new_len, i;
GIT_ERROR_CHECK_ALLOC_ADD(&new_len, len, 1);
dup = git__calloc(new_len, sizeof(char *));
GIT_ERROR_CHECK_ALLOC(dup);
for (i = 0; i < len; i++) {
dup[i] = git__strdup(in[i]);
GIT_ERROR_CHECK_ALLOC(dup[i]);
}
*out = dup;
return 0;
}
bool git_strlist_contains_prefix(
const char **strings,
size_t len,
const char *str,
size_t n)
{
size_t i;
for (i = 0; i < len; i++) {
if (strncmp(strings[i], str, n) == 0)
return true;
}
return false;
}
bool git_strlist_contains_key(
const char **strings,
size_t len,
const char *key,
char delimiter)
{
const char *c;
for (c = key; *c; c++) {
if (*c == delimiter)
break;
}
return *c ?
git_strlist_contains_prefix(strings, len, key, (c - key)) :
false;
}
void git_strlist_free(char **strings, size_t len)
{
size_t i;
......@@ -40,3 +93,16 @@ void git_strlist_free(char **strings, size_t len)
git__free(strings);
}
void git_strlist_free_with_null(char **strings)
{
char **s;
if (!strings)
return;
for (s = strings; *s; s++)
git__free(*s);
git__free(strings);
}
......@@ -11,6 +11,26 @@
#include "git2_util.h"
extern int git_strlist_copy(char ***out, const char **in, size_t len);
extern int git_strlist_copy_with_null(
char ***out,
const char **in,
size_t len);
extern bool git_strlist_contains_prefix(
const char **strings,
size_t len,
const char *str,
size_t n);
extern bool git_strlist_contains_key(
const char **strings,
size_t len,
const char *key,
char delimiter);
extern void git_strlist_free(char **strings, size_t len);
extern void git_strlist_free_with_null(char **strings);
#endif
......@@ -47,6 +47,9 @@ static char *_orig_http_proxy = NULL;
static char *_orig_https_proxy = NULL;
static char *_orig_no_proxy = NULL;
static char *_ssh_cmd = NULL;
static char *_orig_ssh_cmd = NULL;
static int ssl_cert(git_cert *cert, int valid, const char *host, void *payload)
{
GIT_UNUSED(cert);
......@@ -102,6 +105,14 @@ 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");
_ssh_cmd = cl_getenv("GITTEST_SSH_CMD");
if (_ssh_cmd)
cl_setenv("GIT_SSH", _ssh_cmd);
else
cl_setenv("GIT_SSH", NULL);
if (_remote_expectcontinue)
git_libgit2_opts(GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE, 1);
}
......@@ -149,6 +160,11 @@ void test_online_clone__cleanup(void)
git__free(_orig_https_proxy);
git__free(_orig_no_proxy);
cl_setenv("GIT_SSH", _orig_ssh_cmd);
git__free(_orig_ssh_cmd);
git__free(_ssh_cmd);
git_libgit2_opts(GIT_OPT_SET_SSL_CERT_LOCATIONS, NULL, NULL);
git_libgit2_opts(GIT_OPT_SET_SERVER_TIMEOUT, 0);
git_libgit2_opts(GIT_OPT_SET_SERVER_CONNECT_TIMEOUT, 0);
......@@ -653,7 +669,7 @@ void test_online_clone__ssh_auth_methods(void)
{
int with_user;
#ifndef GIT_SSH
#ifndef GIT_SSH_LIBSSH2
clar__skip();
#endif
g_options.fetch_opts.callbacks.credentials = check_ssh_auth_methods;
......@@ -675,7 +691,7 @@ void test_online_clone__ssh_auth_methods(void)
*/
void test_online_clone__ssh_certcheck_accepts_unknown(void)
{
#if !defined(GIT_SSH) || !defined(GIT_SSH_MEMORY_CREDENTIALS)
#if !defined(GIT_SSH_LIBSSH2) || !defined(GIT_SSH_MEMORY_CREDENTIALS)
clar__skip();
#endif
......@@ -793,9 +809,10 @@ static int cred_foo_bar(git_credential **cred, const char *url, const char *user
void test_online_clone__ssh_cannot_change_username(void)
{
#ifndef GIT_SSH
#ifndef GIT_SSH_LIBSSH2
clar__skip();
#endif
g_options.fetch_opts.callbacks.credentials = cred_foo_bar;
cl_git_fail(git_clone(&g_repo, "ssh://git@github.com/libgit2/TestGitRepository", "./foo", &g_options));
......@@ -837,6 +854,10 @@ static int ssh_certificate_check(git_cert *cert, int valid, const char *host, vo
void test_online_clone__ssh_cert(void)
{
#ifndef GIT_SSH_LIBSSH2
cl_skip();
#endif
g_options.fetch_opts.callbacks.certificate_check = ssh_certificate_check;
if (!_remote_ssh_fingerprint)
......@@ -908,12 +929,12 @@ void test_online_clone__certificate_invalid(void)
{
g_options.fetch_opts.callbacks.certificate_check = fail_certificate_check;
cl_git_fail_with(git_clone(&g_repo, "https://github.com/libgit2/TestGitRepository", "./foo", &g_options),
GIT_ECERTIFICATE);
cl_git_fail_with(GIT_ECERTIFICATE,
git_clone(&g_repo, "https://github.com/libgit2/TestGitRepository", "./foo", &g_options));
#ifdef GIT_SSH
cl_git_fail_with(git_clone(&g_repo, "ssh://github.com/libgit2/TestGitRepository", "./foo", &g_options),
GIT_ECERTIFICATE);
#ifdef GIT_SSH_LIBSSH2
cl_git_fail_with(GIT_ECERTIFICATE,
git_clone(&g_repo, "ssh://github.com/libgit2/TestGitRepository", "./foo", &g_options));
#endif
}
......
......@@ -20,6 +20,9 @@ static char *_remote_ssh_passphrase = NULL;
static char *_remote_default = NULL;
static char *_remote_expectcontinue = NULL;
static char *_orig_ssh_cmd = NULL;
static char *_ssh_cmd = NULL;
static int cred_acquire_cb(git_credential **, const char *, const char *, unsigned int, void *);
static git_remote *_remote;
......@@ -369,6 +372,14 @@ void test_online_push__initialize(void)
_remote_expectcontinue = cl_getenv("GITTEST_REMOTE_EXPECTCONTINUE");
_remote = NULL;
_orig_ssh_cmd = cl_getenv("GIT_SSH");
_ssh_cmd = cl_getenv("GITTEST_SSH_CMD");
if (_ssh_cmd)
cl_setenv("GIT_SSH", _ssh_cmd);
else
cl_setenv("GIT_SSH", NULL);
/* Skip the test if we're missing the remote URL */
if (!_remote_url)
cl_skip();
......@@ -423,6 +434,9 @@ void test_online_push__cleanup(void)
git__free(_remote_default);
git__free(_remote_expectcontinue);
git__free(_orig_ssh_cmd);
git__free(_ssh_cmd);
/* Freed by cl_git_sandbox_cleanup */
_repo = NULL;
......
@ECHO OFF
FOR /F "tokens=*" %%a IN ('more') DO ECHO %%a
#!/bin/sh
echo "Hello, world."
#include "clar_libgit2.h"
#include "process.h"
#include "vector.h"
static git_str env_cmd = GIT_STR_INIT;
static git_str accumulator = GIT_STR_INIT;
static git_vector env_result = GIT_VECTOR_INIT;
void test_process_env__initialize(void)
{
#ifdef GIT_WIN32
git_str_printf(&env_cmd, "%s/env.cmd", cl_fixture("process"));
#else
git_str_puts(&env_cmd, "/usr/bin/env");
#endif
cl_git_pass(git_vector_init(&env_result, 32, git__strcmp_cb));
}
void test_process_env__cleanup(void)
{
git_vector_free(&env_result);
git_str_dispose(&accumulator);
git_str_dispose(&env_cmd);
}
static void run_env(const char **env_array, size_t env_len, bool exclude_env)
{
const char *args_array[] = { env_cmd.ptr };
git_process *process;
git_process_options opts = GIT_PROCESS_OPTIONS_INIT;
git_process_result result = GIT_PROCESS_RESULT_INIT;
char buf[1024], *tok;
ssize_t ret;
opts.capture_out = 1;
opts.exclude_env = exclude_env;
cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), env_array, env_len, &opts));
cl_git_pass(git_process_start(process));
while ((ret = git_process_read(process, buf, 1024)) > 0)
cl_git_pass(git_str_put(&accumulator, buf, (size_t)ret));
cl_assert_equal_i(0, ret);
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);
for (tok = strtok(accumulator.ptr, "\n"); tok; tok = strtok(NULL, "\n")) {
#ifdef GIT_WIN32
if (strlen(tok) && tok[strlen(tok) - 1] == '\r')
tok[strlen(tok) - 1] = '\0';
#endif
cl_git_pass(git_vector_insert(&env_result, tok));
}
git_process_close(process);
git_process_free(process);
}
void test_process_env__can_add_env(void)
{
const char *env_array[] = { "TEST_NEW_ENV=added", "TEST_OTHER_ENV=also_added" };
run_env(env_array, 2, false);
cl_git_pass(git_vector_search(NULL, &env_result, "TEST_NEW_ENV=added"));
cl_git_pass(git_vector_search(NULL, &env_result, "TEST_OTHER_ENV=also_added"));
}
void test_process_env__can_propagate_env(void)
{
cl_setenv("TEST_NEW_ENV", "propagated");
run_env(NULL, 0, false);
cl_git_pass(git_vector_search(NULL, &env_result, "TEST_NEW_ENV=propagated"));
}
void test_process_env__can_remove_env(void)
{
const char *env_array[] = { "TEST_NEW_ENV=" };
char *str;
size_t i;
cl_setenv("TEST_NEW_ENV", "propagated");
run_env(env_array, 1, false);
git_vector_foreach(&env_result, i, str)
cl_assert(git__prefixcmp(str, "TEST_NEW_ENV=") != 0);
}
void test_process_env__can_clear_env(void)
{
const char *env_array[] = { "TEST_NEW_ENV=added", "TEST_OTHER_ENV=also_added" };
cl_setenv("SOME_EXISTING_ENV", "propagated");
run_env(env_array, 2, true);
/*
* We can't simply test that the environment is precisely what we
* provided. Some systems (eg win32) will add environment variables
* to all processes.
*/
cl_assert_equal_i(GIT_ENOTFOUND, git_vector_search(NULL, &env_result, "SOME_EXISTING_ENV=propagated"));
}
#include "clar_libgit2.h"
#include "process.h"
#include "vector.h"
#ifndef GIT_WIN32
# include <signal.h>
#endif
#ifndef SIGTERM
# define SIGTERM 42
#endif
#ifndef SIGPIPE
# define SIGPIPE 42
#endif
static git_str helloworld_cmd = GIT_STR_INIT;
static git_str cat_cmd = GIT_STR_INIT;
static git_str pwd_cmd = GIT_STR_INIT;
void test_process_start__initialize(void)
{
#ifdef GIT_WIN32
git_str_printf(&helloworld_cmd, "%s/helloworld.bat", cl_fixture("process"));
git_str_printf(&cat_cmd, "%s/cat.bat", cl_fixture("process"));
git_str_printf(&pwd_cmd, "%s/pwd.bat", cl_fixture("process"));
#else
git_str_printf(&helloworld_cmd, "%s/helloworld.sh", cl_fixture("process"));
#endif
}
void test_process_start__cleanup(void)
{
git_str_dispose(&pwd_cmd);
git_str_dispose(&cat_cmd);
git_str_dispose(&helloworld_cmd);
}
void test_process_start__returncode(void)
{
#ifdef GIT_WIN32
const char *args_array[] = { "C:\\Windows\\System32\\cmd.exe", "/c", "exit", "1" };
#elif __APPLE__
const char *args_array[] = { "/usr/bin/false" };
#else
const char *args_array[] = { "/bin/false" };
#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(1, result.exitcode);
cl_assert_equal_i(0, result.signal);
git_process_free(process);
}
void test_process_start__not_found(void)
{
#ifdef GIT_WIN32
const char *args_array[] = { "C:\\a\\b\\z\\y\\not_found" };
#else
const char *args_array[] = { "/a/b/z/y/not_found" };
#endif
git_process *process;
git_process_options opts = GIT_PROCESS_OPTIONS_INIT;
cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), NULL, 0, &opts));
cl_git_fail(git_process_start(process));
git_process_free(process);
}
static void write_all(git_process *process, char *buf)
{
size_t buf_len = strlen(buf);
ssize_t ret;
while (buf_len) {
ret = git_process_write(process, buf, buf_len);
cl_git_pass(ret < 0 ? (int)ret : 0);
buf += ret;
buf_len -= ret;
}
}
static void read_all(git_str *out, git_process *process)
{
char buf[32];
size_t buf_len = 32;
ssize_t ret;
while ((ret = git_process_read(process, buf, buf_len)) > 0)
cl_git_pass(git_str_put(out, buf, ret));
cl_git_pass(ret < 0 ? (int)ret : 0);
}
void test_process_start__redirect_stdio(void)
{
#ifdef GIT_WIN32
const char *args_array[] = { "C:\\Windows\\System32\\cmd.exe", "/c", cat_cmd.ptr };
#else
const char *args_array[] = { "/bin/cat" };
#endif
git_process *process;
git_process_options opts = GIT_PROCESS_OPTIONS_INIT;
git_process_result result = GIT_PROCESS_RESULT_INIT;
git_str buf = GIT_STR_INIT;
opts.capture_in = 1;
opts.capture_out = 1;
cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), NULL, 0, &opts));
cl_git_pass(git_process_start(process));
write_all(process, "Hello, world.\r\nHello!\r\n");
cl_git_pass(git_process_close_in(process));
read_all(&buf, process);
cl_assert_equal_s("Hello, world.\r\nHello!\r\n", buf.ptr);
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_str_dispose(&buf);
git_process_free(process);
}
/*
void test_process_start__catch_sigterm(void)
{
const char *args_array[] = { "/bin/cat" };
git_process *process;
git_process_options opts = GIT_PROCESS_OPTIONS_INIT;
git_process_result result = GIT_PROCESS_RESULT_INIT;
p_pid_t pid;
opts.capture_out = 1;
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_id(&pid, process));
cl_must_pass(kill(pid, SIGTERM));
cl_git_pass(git_process_wait(&result, process));
cl_assert_equal_i(GIT_PROCESS_STATUS_ERROR, result.status);
cl_assert_equal_i(0, result.exitcode);
cl_assert_equal_i(SIGTERM, result.signal);
git_process_free(process);
}
void test_process_start__catch_sigpipe(void)
{
const char *args_array[] = { helloworld_cmd.ptr };
git_process *process;
git_process_options opts = GIT_PROCESS_OPTIONS_INIT;
git_process_result result = GIT_PROCESS_RESULT_INIT;
opts.capture_out = 1;
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_close(process));
cl_git_pass(git_process_wait(&result, process));
cl_assert_equal_i(GIT_PROCESS_STATUS_ERROR, result.status);
cl_assert_equal_i(0, result.exitcode);
cl_assert_equal_i(SIGPIPE, result.signal);
git_process_free(process);
}
*/
void test_process_start__can_chdir(void)
{
#ifdef GIT_WIN32
const char *args_array[] = { "C:\\Windows\\System32\\cmd.exe", "/c", pwd_cmd.ptr };
char *startwd = "C:\\";
#else
const char *args_array[] = { "/bin/pwd" };
char *startwd = "/";
#endif
git_process *process;
git_process_options opts = GIT_PROCESS_OPTIONS_INIT;
git_process_result result = GIT_PROCESS_RESULT_INIT;
git_str buf = GIT_STR_INIT;
opts.cwd = startwd;
opts.capture_out = 1;
cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), NULL, 0, &opts));
cl_git_pass(git_process_start(process));
read_all(&buf, process);
git_str_rtrim(&buf);
cl_assert_equal_s(startwd, buf.ptr);
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_str_dispose(&buf);
git_process_free(process);
}
void test_process_start__cannot_chdir_to_nonexistent_dir(void)
{
#ifdef GIT_WIN32
const char *args_array[] = { "C:\\Windows\\System32\\cmd.exe", "/c", pwd_cmd.ptr };
char *startwd = "C:\\a\\b\\z\\y\\not_found";
#else
const char *args_array[] = { "/bin/pwd" };
char *startwd = "/a/b/z/y/not_found";
#endif
git_process *process;
git_process_options opts = GIT_PROCESS_OPTIONS_INIT;
opts.cwd = startwd;
opts.capture_out = 1;
cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), NULL, 0, &opts));
cl_git_fail(git_process_start(process));
git_process_free(process);
}
#include "clar_libgit2.h"
#include "process.h"
#include "vector.h"
#ifdef GIT_WIN32
static git_str result;
# define assert_cmdline(expected, given) do { \
cl_git_pass(git_process__cmdline(&result, given, ARRAY_SIZE(given))); \
cl_assert_equal_s(expected, result.ptr); \
git_str_dispose(&result); \
} while(0)
#endif
void test_process_win32__cmdline_is_whitespace_delimited(void)
{
#ifdef GIT_WIN32
const char *one[] = { "one" };
const char *two[] = { "one", "two" };
const char *three[] = { "one", "two", "three" };
const char *four[] = { "one", "two", "three", "four" };
assert_cmdline("one", one);
assert_cmdline("one two", two);
assert_cmdline("one two three", three);
assert_cmdline("one two three four", four);
#endif
}
void test_process_win32__cmdline_escapes_whitespace(void)
{
#ifdef GIT_WIN32
const char *spaces[] = { "one with spaces" };
const char *tabs[] = { "one\twith\ttabs" };
const char *multiple[] = { "one with many spaces" };
assert_cmdline("one\" \"with\" \"spaces", spaces);
assert_cmdline("one\"\t\"with\"\t\"tabs", tabs);
assert_cmdline("one\" \"with\" \"many\" \"spaces", multiple);
#endif
}
void test_process_win32__cmdline_escapes_quotes(void)
{
#ifdef GIT_WIN32
const char *one[] = { "echo", "\"hello world\"" };
assert_cmdline("echo \\\"hello\" \"world\\\"", one);
#endif
}
void test_process_win32__cmdline_escapes_backslash(void)
{
#ifdef GIT_WIN32
const char *one[] = { "foo\\bar", "foo\\baz" };
const char *two[] = { "c:\\program files\\foo bar\\foo bar.exe", "c:\\path\\to\\other\\", "/a", "/b" };
assert_cmdline("foo\\\\bar foo\\\\baz", one);
assert_cmdline("c:\\\\program\" \"files\\\\foo\" \"bar\\\\foo\" \"bar.exe c:\\\\path\\\\to\\\\other\\\\ /a /b", two);
#endif
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment