mirror of
https://github.com/libgit2/libgit2.git
synced 2026-06-22 06:26:26 +00:00
Final final v1.9.3 updates (#7250)
This commit is contained in:
13
ci/test.sh
13
ci/test.sh
@@ -198,7 +198,7 @@ if should_run "PROXY_TESTS"; then
|
||||
fi
|
||||
|
||||
if should_run "NTLM_TESTS" || should_run "ONLINE_TESTS"; then
|
||||
curl --location --silent --show-error https://github.com/ethomson/poxygit/releases/download/v0.6.0/poxygit-0.6.0.jar >poxygit.jar
|
||||
curl --location --silent --show-error https://github.com/ethomson/poxygit/releases/download/v0.8.1/poxygit-0.8.1.jar >poxygit.jar
|
||||
|
||||
echo "Starting HTTP server..."
|
||||
HTTP_DIR=`mktemp -d ${TMPDIR}/http.XXXXXXXX`
|
||||
@@ -216,18 +216,13 @@ if should_run "SSH_TESTS"; then
|
||||
cat >"${SSHD_DIR}/sshd_config" <<-EOF
|
||||
Port 2222
|
||||
ListenAddress 0.0.0.0
|
||||
Protocol 2
|
||||
HostKey ${SSHD_DIR}/id_${GITTEST_SSH_KEYTYPE}
|
||||
PidFile ${SSHD_DIR}/pid
|
||||
AuthorizedKeysFile ${HOME}/.ssh/authorized_keys
|
||||
LogLevel DEBUG
|
||||
RSAAuthentication yes
|
||||
PasswordAuthentication yes
|
||||
PubkeyAuthentication yes
|
||||
ChallengeResponseAuthentication no
|
||||
StrictModes no
|
||||
HostCertificate ${SSHD_DIR}/id_${GITTEST_SSH_KEYTYPE}.pub
|
||||
HostKey ${SSHD_DIR}/id_${GITTEST_SSH_KEYTYPE}
|
||||
# Required here as sshd will simply close connection otherwise
|
||||
UsePAM no
|
||||
EOF
|
||||
@@ -304,10 +299,10 @@ if should_run "ONLINE_TESTS"; then
|
||||
echo "## Running networking (online) tests"
|
||||
echo "##############################################################################"
|
||||
|
||||
export GITTEST_REMOTE_REDIRECT_INITIAL="http://localhost:9000/initial-redirect/libgit2/TestGitRepository"
|
||||
export GITTEST_REMOTE_REDIRECT_INITIAL="http://localhost:9000/initial-redirect:none/libgit2/TestGitRepository"
|
||||
export GITTEST_REMOTE_REDIRECT_SUBSEQUENT="http://localhost:9000/subsequent-redirect/libgit2/TestGitRepository"
|
||||
export GITTEST_REMOTE_SPEED_SLOW="http://localhost:9000/speed-9600/test.git"
|
||||
export GITTEST_REMOTE_SPEED_TIMESOUT="http://localhost:9000/speed-0.5/test.git"
|
||||
export GITTEST_REMOTE_SPEED_SLOW="http://localhost:9000/speed:9600/test.git"
|
||||
export GITTEST_REMOTE_SPEED_TIMESOUT="http://localhost:9000/speed:0.5/test.git"
|
||||
run_test online
|
||||
unset GITTEST_REMOTE_REDIRECT_INITIAL
|
||||
unset GITTEST_REMOTE_REDIRECT_SUBSEQUENT
|
||||
|
||||
@@ -4,6 +4,18 @@ v1.9.3
|
||||
This release includes a number of bugfixes and compatibility
|
||||
improvements, particularly around SHA256 support.
|
||||
|
||||
* cmake: fix linker error when using ninja build generator by
|
||||
@kcsaul in https://github.com/libgit2/libgit2/pull/7249
|
||||
* Handle redirects with Content-Length: 0 correctly by
|
||||
@ethomson in https://github.com/libgit2/libgit2/pull/7246
|
||||
* ci: use poxygit v0.8.1 in the tests by @ethomson in
|
||||
https://github.com/libgit2/libgit2/pull/7248
|
||||
* Zero indexer stats in pack objects by @ethomson in
|
||||
https://github.com/libgit2/libgit2/pull/7243
|
||||
* submodule: git_index_add_bypath does not move conflict entries
|
||||
to REUC by @lrm29 in https://github.com/libgit2/libgit2/pull/7003
|
||||
* fix: prevent SSH timeout infinite loop and enable TCP keepalive
|
||||
by @ambv in https://github.com/libgit2/libgit2/pull/7165
|
||||
* merge_files: avoid UB in xdiff by @ethomson in
|
||||
https://github.com/libgit2/libgit2/pull/7239
|
||||
* git_merge_file_from_index: handle cases when a child (ours or
|
||||
|
||||
@@ -1448,7 +1448,7 @@ out:
|
||||
return error;
|
||||
}
|
||||
|
||||
static int index_conflict_to_reuc(git_index *index, const char *path)
|
||||
int git_index__conflict_to_reuc(git_index *index, const char *path)
|
||||
{
|
||||
const git_index_entry *conflict_entries[3];
|
||||
int ancestor_mode, our_mode, their_mode;
|
||||
@@ -1528,7 +1528,7 @@ int git_index_add_from_buffer(
|
||||
return error;
|
||||
|
||||
/* Adding implies conflict was resolved, move conflict entries to REUC */
|
||||
if ((error = index_conflict_to_reuc(index, entry->path)) < 0 && error != GIT_ENOTFOUND)
|
||||
if ((error = git_index__conflict_to_reuc(index, entry->path)) < 0 && error != GIT_ENOTFOUND)
|
||||
return error;
|
||||
|
||||
git_tree_cache_invalidate_path(index->tree, entry->path);
|
||||
@@ -1624,7 +1624,7 @@ int git_index_add_bypath(git_index *index, const char *path)
|
||||
}
|
||||
|
||||
/* Adding implies conflict was resolved, move conflict entries to REUC */
|
||||
if ((ret = index_conflict_to_reuc(index, path)) < 0 && ret != GIT_ENOTFOUND)
|
||||
if ((ret = git_index__conflict_to_reuc(index, path)) < 0 && ret != GIT_ENOTFOUND)
|
||||
return ret;
|
||||
|
||||
git_tree_cache_invalidate_path(index->tree, entry->path);
|
||||
@@ -1640,7 +1640,7 @@ int git_index_remove_bypath(git_index *index, const char *path)
|
||||
|
||||
if (((ret = git_index_remove(index, path, 0)) < 0 &&
|
||||
ret != GIT_ENOTFOUND) ||
|
||||
((ret = index_conflict_to_reuc(index, path)) < 0 &&
|
||||
((ret = git_index__conflict_to_reuc(index, path)) < 0 &&
|
||||
ret != GIT_ENOTFOUND))
|
||||
return ret;
|
||||
|
||||
|
||||
@@ -154,6 +154,9 @@ extern int git_index__open(
|
||||
const char *index_path,
|
||||
git_oid_t oid_type);
|
||||
|
||||
/* If the path is conflicted, move it from the index to reuc. */
|
||||
int git_index__conflict_to_reuc(git_index *index, const char *path);
|
||||
|
||||
/* Copy the current entries vector *and* increment the index refcount.
|
||||
* Call `git_index__release_snapshot` when done.
|
||||
*/
|
||||
|
||||
@@ -921,12 +921,12 @@ int git_indexer_append(git_indexer *idx, const void *data, size_t size, git_inde
|
||||
if (git_vector_init(&idx->deltas, total_objects / 2, NULL) < 0)
|
||||
return -1;
|
||||
|
||||
stats->total_objects = total_objects;
|
||||
stats->indexed_objects = 0;
|
||||
stats->received_objects = 0;
|
||||
stats->local_objects = 0;
|
||||
stats->total_deltas = 0;
|
||||
stats->indexed_deltas = 0;
|
||||
stats->indexed_objects = 0;
|
||||
stats->total_objects = total_objects;
|
||||
|
||||
if ((error = do_progress_callback(idx, stats)) != 0)
|
||||
return error;
|
||||
|
||||
@@ -1420,7 +1420,7 @@ int git_packbuilder_write(
|
||||
git_str object_path = GIT_STR_INIT;
|
||||
git_indexer_options opts = GIT_INDEXER_OPTIONS_INIT;
|
||||
git_indexer *indexer = NULL;
|
||||
git_indexer_progress stats;
|
||||
git_indexer_progress stats = { 0 };
|
||||
struct pack_write_context ctx;
|
||||
int t;
|
||||
|
||||
|
||||
@@ -20,6 +20,10 @@
|
||||
# include <netdb.h>
|
||||
# include <netinet/in.h>
|
||||
# include <arpa/inet.h>
|
||||
# include <netinet/tcp.h>
|
||||
# ifdef __APPLE__
|
||||
# include <sys/socket.h>
|
||||
# endif
|
||||
#else
|
||||
# include <winsock2.h>
|
||||
# include <ws2tcpip.h>
|
||||
@@ -188,6 +192,32 @@ static int socket_connect(git_stream *stream)
|
||||
for (p = info; p != NULL; p = p->ai_next) {
|
||||
s = socket(p->ai_family, p->ai_socktype | SOCK_CLOEXEC, p->ai_protocol);
|
||||
|
||||
/* Enable TCP keepalive to detect dead connections */
|
||||
if (s != INVALID_SOCKET && p->ai_family == AF_INET) {
|
||||
int keepalive = 1;
|
||||
if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE,
|
||||
(const char *)&keepalive, sizeof(keepalive)) == 0) {
|
||||
#ifdef __APPLE__
|
||||
/* macOS: Set idle time to 60 seconds */
|
||||
int keepidle = 60;
|
||||
setsockopt(s, IPPROTO_TCP, TCP_KEEPALIVE,
|
||||
&keepidle, sizeof(keepidle));
|
||||
/* Note: TCP_CONNECTIONTIMEOUT (0x20) could also be set */
|
||||
#elif defined(__linux__)
|
||||
/* Linux: Set idle, interval, and count */
|
||||
int keepidle = 60; /* Start probes after 60 seconds */
|
||||
int keepintvl = 10; /* 10 seconds between probes */
|
||||
int keepcnt = 3; /* 3 probes before giving up */
|
||||
setsockopt(s, IPPROTO_TCP, TCP_KEEPIDLE,
|
||||
&keepidle, sizeof(keepidle));
|
||||
setsockopt(s, IPPROTO_TCP, TCP_KEEPINTVL,
|
||||
&keepintvl, sizeof(keepintvl));
|
||||
setsockopt(s, IPPROTO_TCP, TCP_KEEPCNT,
|
||||
&keepcnt, sizeof(keepcnt));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
if (s == INVALID_SOCKET)
|
||||
continue;
|
||||
|
||||
|
||||
@@ -1075,16 +1075,23 @@ int git_submodule_add_to_index(git_submodule *sm, int write_index)
|
||||
git_commit_free(head);
|
||||
|
||||
/* add it */
|
||||
error = git_index_add(index, &entry);
|
||||
if ((error = git_index_add(index, &entry)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
/* Adding implies conflict was resolved, move conflict entries to REUC */
|
||||
if ((error = git_index__conflict_to_reuc(index, entry.path)) < 0 && error != GIT_ENOTFOUND)
|
||||
goto cleanup;
|
||||
|
||||
/* write it, if requested */
|
||||
if (!error && write_index) {
|
||||
error = git_index_write(index);
|
||||
if (write_index) {
|
||||
if ((error = git_index_write(index)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if (!error)
|
||||
git_oid_cpy(&sm->index_oid, &sm->wd_oid);
|
||||
git_oid_cpy(&sm->index_oid, &sm->wd_oid);
|
||||
}
|
||||
|
||||
error = 0;
|
||||
|
||||
cleanup:
|
||||
git_repository_free(sm_repo);
|
||||
git_str_dispose(&path);
|
||||
|
||||
@@ -379,7 +379,7 @@ static int on_headers_complete(git_http_parser *parser)
|
||||
ctx->response->resend_credentials = resend_needed(ctx->client,
|
||||
ctx->response);
|
||||
|
||||
if (ctx->response->content_type || ctx->response->chunked)
|
||||
if (ctx->response->content_length || ctx->response->chunked)
|
||||
ctx->client->state = READING_BODY;
|
||||
else
|
||||
ctx->client->state = DONE;
|
||||
@@ -1243,13 +1243,16 @@ GIT_INLINE(int) client_read_and_parse(git_http_client *client)
|
||||
}
|
||||
|
||||
/*
|
||||
* See if we've consumed the entire response body. If the client was
|
||||
* reading the body but did not consume it entirely, it's possible that
|
||||
* they knew that the stream had finished (in a git response, seeing a
|
||||
* final flush) and stopped reading. But if the response was chunked,
|
||||
* we may have not consumed the final chunk marker. Consume it to
|
||||
* ensure that we don't have it waiting in our socket. If there's
|
||||
* more than just a chunk marker, close the connection.
|
||||
* Try to consume any remaining response body. The client may have
|
||||
* decided that it did not need to consume the entire response body.
|
||||
* For example, the client saw a redirect in the header and ignored
|
||||
* the body. Or the client saw a particular sequence (like a final
|
||||
* flush in a git response) and stopped reading (but there were
|
||||
* additional response bytes, perhaps because the response was chunked).
|
||||
* Do one more read to try to clear this out; this takes care of small
|
||||
* remainders, like a chunk response or a small redirect message. If
|
||||
* there is too much data, we'll just leave it and close the
|
||||
* connection.
|
||||
*/
|
||||
static void complete_response_body(git_http_client *client)
|
||||
{
|
||||
|
||||
@@ -366,7 +366,7 @@ static int _git_ssh_authenticate_session(
|
||||
default:
|
||||
rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED;
|
||||
}
|
||||
} while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc);
|
||||
} while (LIBSSH2_ERROR_EAGAIN == rc);
|
||||
|
||||
if (rc == LIBSSH2_ERROR_PASSWORD_EXPIRED ||
|
||||
rc == LIBSSH2_ERROR_AUTHENTICATION_FAILED ||
|
||||
@@ -556,7 +556,7 @@ static int _git_ssh_session_create(
|
||||
if (git_str_len(&prefs) > 0) {
|
||||
do {
|
||||
rc = libssh2_session_method_pref(s, LIBSSH2_METHOD_HOSTKEY, git_str_cstr(&prefs));
|
||||
} while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc);
|
||||
} while (LIBSSH2_ERROR_EAGAIN == rc);
|
||||
if (rc != LIBSSH2_ERROR_NONE) {
|
||||
ssh_error(s, "failed to set hostkey preference");
|
||||
goto on_error;
|
||||
@@ -566,7 +566,7 @@ static int _git_ssh_session_create(
|
||||
|
||||
do {
|
||||
rc = libssh2_session_handshake(s, socket->s);
|
||||
} while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc);
|
||||
} while (LIBSSH2_ERROR_EAGAIN == rc);
|
||||
|
||||
if (rc != LIBSSH2_ERROR_NONE) {
|
||||
ssh_error(s, "failed to start SSH session");
|
||||
|
||||
@@ -53,6 +53,16 @@ void test_merge_workdir_submodules__automerge(void)
|
||||
|
||||
cl_git_pass(git_repository_index(&index, repo));
|
||||
cl_assert(merge_test_index(index, merge_index_entries, 6));
|
||||
cl_assert_equal_i(true, git_index_has_conflicts(index));
|
||||
|
||||
/* Put an actual Git repository into the submodule path on disk.
|
||||
* Add it to the index and assert that the conflict is resolved.
|
||||
*/
|
||||
cl_fixture_sandbox("testrepo");
|
||||
p_rename("testrepo", TEST_REPO_PATH "/submodule");
|
||||
p_rename(TEST_REPO_PATH "/submodule/.gitted", TEST_REPO_PATH "/submodule/.git");
|
||||
cl_git_pass(git_index_add_bypath(index, "submodule"));
|
||||
cl_assert_equal_i(false, git_index_has_conflicts(index));
|
||||
|
||||
git_index_free(index);
|
||||
git_annotated_commit_free(their_head);
|
||||
|
||||
@@ -15,6 +15,17 @@ static git_strarray push_array = {
|
||||
1,
|
||||
};
|
||||
|
||||
static int push_transfer_progress_cb(unsigned int current, unsigned int total, size_t bytes, void* payload)
|
||||
{
|
||||
GIT_UNUSED(current);
|
||||
GIT_UNUSED(total);
|
||||
GIT_UNUSED(payload);
|
||||
|
||||
cl_assert(bytes == 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void test_network_remote_local__initialize(void)
|
||||
{
|
||||
cl_git_pass(git_repository_init(&repo, "remotelocal/", 0));
|
||||
@@ -200,6 +211,7 @@ void test_network_remote_local__push_to_bare_remote(void)
|
||||
|
||||
/* Should be able to push to a bare remote */
|
||||
git_remote *localremote;
|
||||
git_push_options opts = GIT_PUSH_OPTIONS_INIT;
|
||||
|
||||
/* Get some commits */
|
||||
connect_to_local_repository(cl_fixture("testrepo.git"));
|
||||
@@ -217,7 +229,8 @@ void test_network_remote_local__push_to_bare_remote(void)
|
||||
cl_git_pass(git_remote_connect(localremote, GIT_DIRECTION_PUSH, NULL, NULL, NULL));
|
||||
|
||||
/* Try to push */
|
||||
cl_git_pass(git_remote_upload(localremote, &push_array, NULL));
|
||||
opts.callbacks.push_transfer_progress = push_transfer_progress_cb;
|
||||
cl_git_pass(git_remote_upload(localremote, &push_array, &opts));
|
||||
|
||||
/* Clean up */
|
||||
git_remote_free(localremote);
|
||||
|
||||
254
tests/libgit2/online/ssh_timeout.c
Normal file
254
tests/libgit2/online/ssh_timeout.c
Normal file
@@ -0,0 +1,254 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "git2/sys/transport.h"
|
||||
#include "thread.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
# include <sys/socket.h>
|
||||
# include <netinet/in.h>
|
||||
# include <arpa/inet.h>
|
||||
# include <pthread.h>
|
||||
# include <unistd.h>
|
||||
# include <time.h>
|
||||
#else
|
||||
# include <winsock2.h>
|
||||
# include <ws2tcpip.h>
|
||||
#endif
|
||||
|
||||
extern int git_socket_stream__timeout;
|
||||
|
||||
#ifdef GIT_SSH_LIBSSH2
|
||||
|
||||
#ifdef _WIN32
|
||||
static SOCKET server_socket = INVALID_SOCKET;
|
||||
#else
|
||||
static int server_socket = -1;
|
||||
#endif
|
||||
static int server_port = 0;
|
||||
#ifndef _WIN32
|
||||
static pthread_t server_thread;
|
||||
#else
|
||||
static HANDLE server_thread;
|
||||
#endif
|
||||
static git_atomic32 server_running;
|
||||
|
||||
/* Black hole server: accepts connections but never responds */
|
||||
#ifdef _WIN32
|
||||
static DWORD WINAPI blackhole_server(LPVOID param)
|
||||
{
|
||||
SOCKET client_socket;
|
||||
struct sockaddr_in client_addr;
|
||||
int client_len = sizeof(client_addr);
|
||||
|
||||
GIT_UNUSED(param);
|
||||
|
||||
git_atomic32_set(&server_running, 1);
|
||||
|
||||
while (git_atomic32_get(&server_running)) {
|
||||
client_socket = accept(server_socket,
|
||||
(struct sockaddr *)&client_addr, &client_len);
|
||||
if (client_socket == INVALID_SOCKET)
|
||||
break;
|
||||
|
||||
/* Accept the connection but never send data - this will
|
||||
* cause SSH handshake to timeout */
|
||||
Sleep(10000); /* 10 seconds */
|
||||
closesocket(client_socket);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
static void *blackhole_server(void *param)
|
||||
{
|
||||
int client_socket;
|
||||
struct sockaddr_in client_addr;
|
||||
socklen_t client_len = sizeof(client_addr);
|
||||
|
||||
GIT_UNUSED(param);
|
||||
|
||||
git_atomic32_set(&server_running, 1);
|
||||
|
||||
while (git_atomic32_get(&server_running)) {
|
||||
client_socket = accept(server_socket,
|
||||
(struct sockaddr *)&client_addr, &client_len);
|
||||
if (client_socket < 0)
|
||||
break;
|
||||
|
||||
/* Accept the connection but never send data - this will
|
||||
* cause SSH handshake to timeout */
|
||||
sleep(10); /* 10 seconds */
|
||||
close(client_socket);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int start_blackhole_server(void)
|
||||
{
|
||||
struct sockaddr_in addr;
|
||||
#ifdef _WIN32
|
||||
int addr_len = sizeof(addr);
|
||||
#else
|
||||
socklen_t addr_len = sizeof(addr);
|
||||
#endif
|
||||
int opt = 1;
|
||||
|
||||
#ifdef _WIN32
|
||||
WSADATA wsa_data;
|
||||
WSAStartup(MAKEWORD(2, 2), &wsa_data);
|
||||
#endif
|
||||
|
||||
server_socket = socket(AF_INET, SOCK_STREAM, 0);
|
||||
#ifdef _WIN32
|
||||
if (server_socket == INVALID_SOCKET)
|
||||
return -1;
|
||||
#else
|
||||
if (server_socket < 0)
|
||||
return -1;
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR,
|
||||
(const char *)&opt, sizeof(opt));
|
||||
#else
|
||||
setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR,
|
||||
&opt, sizeof(opt));
|
||||
#endif
|
||||
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
|
||||
addr.sin_port = 0; /* Let OS choose port */
|
||||
|
||||
if (bind(server_socket, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
#ifdef _WIN32
|
||||
closesocket(server_socket);
|
||||
#else
|
||||
close(server_socket);
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (listen(server_socket, 5) < 0) {
|
||||
#ifdef _WIN32
|
||||
closesocket(server_socket);
|
||||
#else
|
||||
close(server_socket);
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Get the actual port assigned */
|
||||
if (getsockname(server_socket, (struct sockaddr *)&addr, &addr_len) < 0) {
|
||||
#ifdef _WIN32
|
||||
closesocket(server_socket);
|
||||
#else
|
||||
close(server_socket);
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
server_port = ntohs(addr.sin_port);
|
||||
|
||||
/* Start server thread */
|
||||
#ifdef _WIN32
|
||||
server_thread = CreateThread(NULL, 0, blackhole_server, NULL, 0, NULL);
|
||||
if (server_thread == NULL) {
|
||||
closesocket(server_socket);
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
if (pthread_create(&server_thread, NULL, blackhole_server, NULL) != 0) {
|
||||
close(server_socket);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void stop_blackhole_server(void)
|
||||
{
|
||||
git_atomic32_set(&server_running, 0);
|
||||
|
||||
#ifdef _WIN32
|
||||
if (server_socket != INVALID_SOCKET) {
|
||||
closesocket(server_socket);
|
||||
if (server_thread)
|
||||
WaitForSingleObject(server_thread, INFINITE);
|
||||
server_socket = INVALID_SOCKET;
|
||||
}
|
||||
WSACleanup();
|
||||
#else
|
||||
if (server_socket >= 0) {
|
||||
close(server_socket);
|
||||
pthread_join(server_thread, NULL);
|
||||
server_socket = -1;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif /* GIT_SSH_LIBSSH2 */
|
||||
|
||||
/*
|
||||
* Test that SSH connection timeout doesn't cause infinite retry loop.
|
||||
*
|
||||
* This test creates a TCP server that accepts connections but never
|
||||
* responds to SSH handshake, causing libssh2 to timeout.
|
||||
*
|
||||
* Before the fix: The code would retry indefinitely on LIBSSH2_ERROR_TIMEOUT
|
||||
* After the fix: The code properly returns an error after first timeout
|
||||
*/
|
||||
void test_online_ssh_timeout__no_infinite_loop(void)
|
||||
{
|
||||
#ifndef GIT_SSH_LIBSSH2
|
||||
cl_skip();
|
||||
#else
|
||||
git_remote *remote = NULL;
|
||||
git_repository *repo = NULL;
|
||||
git_transport *transport = NULL;
|
||||
git_remote_connect_options opts = GIT_REMOTE_CONNECT_OPTIONS_INIT;
|
||||
char url[256];
|
||||
int old_timeout;
|
||||
clock_t start, end;
|
||||
double elapsed_ms;
|
||||
|
||||
/* Start black hole server */
|
||||
cl_git_pass(start_blackhole_server());
|
||||
|
||||
/* Create URL to our black hole server */
|
||||
sprintf(url, "ssh://localhost:%d/test.git", server_port);
|
||||
|
||||
/* Set a short timeout (100ms) */
|
||||
old_timeout = git_socket_stream__timeout;
|
||||
git_socket_stream__timeout = 100;
|
||||
|
||||
cl_git_pass(git_repository_init(&repo, "./transport-timeout", 0));
|
||||
cl_git_pass(git_remote_create(&remote, repo, "test", url));
|
||||
|
||||
/* Get transport */
|
||||
cl_git_pass(git_transport_new(&transport, remote, url));
|
||||
|
||||
/* Attempt connection - should fail due to timeout */
|
||||
start = clock();
|
||||
cl_git_fail(transport->connect(transport, url,
|
||||
GIT_SERVICE_UPLOADPACK_LS, &opts));
|
||||
end = clock();
|
||||
|
||||
/* Calculate elapsed time in milliseconds */
|
||||
elapsed_ms = ((double)(end - start) / CLOCKS_PER_SEC) * 1000.0;
|
||||
|
||||
/* With the fix, this should fail relatively quickly (within 2 seconds).
|
||||
* Without the fix, it would loop many times and take much longer.
|
||||
* We use a generous timeout of 5 seconds to avoid flakiness. */
|
||||
cl_assert(elapsed_ms < 5000);
|
||||
|
||||
/* Cleanup */
|
||||
transport->free(transport);
|
||||
git_remote_free(remote);
|
||||
git_repository_free(repo);
|
||||
git_socket_stream__timeout = old_timeout;
|
||||
|
||||
stop_blackhole_server();
|
||||
#endif
|
||||
}
|
||||
Reference in New Issue
Block a user