Final final v1.9.3 updates (#7250)

This commit is contained in:
Edward Thomson
2026-05-04 22:25:59 +01:00
committed by GitHub
parent 13458671f3
commit 1affb8b193
13 changed files with 360 additions and 33 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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.
*/

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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)
{

View File

@@ -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");

View File

@@ -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);

View File

@@ -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);

View 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
}