Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ set(GIT2CPP_SRC
${GIT2CPP_SOURCE_DIR}/utils/progress.hpp
${GIT2CPP_SOURCE_DIR}/utils/terminal_pager.cpp
${GIT2CPP_SOURCE_DIR}/utils/terminal_pager.hpp
${GIT2CPP_SOURCE_DIR}/wasm/constants.hpp
${GIT2CPP_SOURCE_DIR}/wasm/libgit2_internals.cpp
${GIT2CPP_SOURCE_DIR}/wasm/libgit2_internals.hpp
${GIT2CPP_SOURCE_DIR}/wasm/response.cpp
Expand All @@ -112,6 +113,8 @@ set(GIT2CPP_SRC
${GIT2CPP_SOURCE_DIR}/wasm/subtransport.hpp
${GIT2CPP_SOURCE_DIR}/wasm/transport.cpp
${GIT2CPP_SOURCE_DIR}/wasm/transport.hpp
${GIT2CPP_SOURCE_DIR}/wasm/utils.cpp
${GIT2CPP_SOURCE_DIR}/wasm/utils.hpp
${GIT2CPP_SOURCE_DIR}/wrapper/annotated_commit_wrapper.cpp
${GIT2CPP_SOURCE_DIR}/wrapper/annotated_commit_wrapper.hpp
${GIT2CPP_SOURCE_DIR}/wrapper/branch_wrapper.cpp
Expand Down
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@
"show_navbar_depth": 2,
}
html_title = "git2cpp documentation"
myst_enable_extensions = ["deflist"]
11 changes: 11 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,14 @@ please create an issue in the [git2cpp github repository](https://github.com/Qua
:hidden:
created/git2cpp
```

## Environment variables

`GIT_HTTP_TIMEOUT`
: In the WebAssembly build, all http(s) requests are limited by a timeout which has a default of 10
seconds. To use a different timeout set the `GIT_HTTP_TIMEOUT` environment variable. For example,
to set a timeout of 20 seconds use:

```bash
export GIT_HTTP_TIMEOUT=20
```
11 changes: 11 additions & 0 deletions src/wasm/constants.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#pragma once

#include <string_view>

// Constants used in wasm transport layer.
// Exposed to non-emscripten builds so that they can be used in help.

// Environment variable for http transport timeout.
// Must be a positive number as a timeout of 0 will block forever.
inline constexpr std::string_view WASM_HTTP_TRANSPORT_TIMEOUT_NAME = "GIT_HTTP_TIMEOUT";
inline constexpr unsigned int WASM_HTTP_TRANSPORT_TIMEOUT_DEFAULT_S = 10;
20 changes: 17 additions & 3 deletions src/wasm/stream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
# include <emscripten.h>

# include "../utils/common.hpp"
# include "constants.hpp"
# include "response.hpp"

// Buffer size used in transport_smart, hardcoded in libgit2.
Expand Down Expand Up @@ -51,6 +52,7 @@ EM_JS(
const char* method,
const char* content_type_header,
const char* authorization_header,
unsigned long request_timeout_ms,
size_t buffer_size),
{
const url_js = UTF8ToString(url);
Expand All @@ -76,6 +78,7 @@ EM_JS(
{
xhr.setRequestHeader("x-runtime-token", "{#{RUNTIME_TOKEN}#}");
}
xhr.timeout = request_timeout_ms;

// Cache request info on JavaScript side so that it is available in subsequent calls
// without having to pass it back and forth to/from C++.
Expand Down Expand Up @@ -104,7 +107,7 @@ EM_JS(
// clang-format off
Module["git2cpp_js_error"] = { name: err.name ?? "", message : err.message ?? "" };
// clang-format on
console.error(err);
(err.name == "TimeoutError" ? console.warn : console.error)(err);
return -1;
}
}
Expand Down Expand Up @@ -206,7 +209,7 @@ EM_JS(
// clang-format off
Module["git2cpp_js_error"] = { name: err.name ?? "", message : err.message ?? "" };
// clang-format on
console.error(err);
(err.name == "TimeoutError" ? console.warn : console.error)(err);
return -1;
}
}
Expand Down Expand Up @@ -243,7 +246,7 @@ EM_JS(size_t, js_write, (int request_index, const char* buffer, size_t buffer_si
// clang-format off
Module["git2cpp_js_error"] = { name: err.name ?? "", message : err.message ?? "" };
// clang-format on
console.error(err);
(err.name == "TimeoutError" ? console.warn : console.error)(err);
return -1;
}
});
Expand Down Expand Up @@ -271,6 +274,16 @@ static void convert_js_to_git_error(wasm_http_stream* stream)
stream->m_unconverted_url.c_str()
);
}
else if (std::string_view(error_str).starts_with("TimeoutError:"))
{
git_error_set(
GIT_ERROR_HTTP,
"network request timed out connecting to %s. You can set a longer timeout in seconds using the environment variable %s, the default value is %u seconds.",
stream->m_unconverted_url.c_str(),
WASM_HTTP_TRANSPORT_TIMEOUT_NAME.data(),
WASM_HTTP_TRANSPORT_TIMEOUT_DEFAULT_S
);
}
else
{
git_error_set(GIT_ERROR_HTTP, "%s", error_str);
Expand All @@ -285,6 +298,7 @@ static int create_request(wasm_http_stream* stream, std::string_view content_hea
name_for_method(stream->m_service.m_method).c_str(),
content_header.data(),
stream->m_subtransport->m_authorization_header.c_str(),
stream->m_subtransport->m_request_timeout_ms,
EMFORGE_BUFSIZE
);
return stream->m_request_index;
Expand Down
4 changes: 3 additions & 1 deletion src/wasm/subtransport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@

# include "subtransport.hpp"

# include <iostream>
# include <regex>
# include <sstream>

# include <emscripten.h>
# include <git2/sys/credential.h>
# include <git2/sys/remote.h>

# include "libgit2_internals.hpp"
# include "stream.hpp"
# include "utils.hpp"

// C functions.

Expand Down Expand Up @@ -93,6 +94,7 @@ int create_wasm_http_subtransport(git_smart_subtransport** out, git_transport* o
subtransport->m_owner = owner;
subtransport->m_base_url = "";
subtransport->m_credential = nullptr;
subtransport->m_request_timeout_ms = get_request_timeout_ms();

*out = &subtransport->m_parent;
return 0;
Expand Down
3 changes: 2 additions & 1 deletion src/wasm/subtransport.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ struct wasm_http_subtransport
// Data stored for reuse on other streams of this transport:
std::string m_base_url;
std::string m_authorization_header;
git_credential* m_credential; // libgit2 creates this, we are responsible for deleting it.
git_credential* m_credential; // libgit2 creates this, we are responsible for deleting it.
unsigned long m_request_timeout_ms; // Timeout for http(s) requests in milliseconds.
};

// git_smart_subtransport_cb
Expand Down
38 changes: 38 additions & 0 deletions src/wasm/utils.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#ifdef EMSCRIPTEN

# include "utils.hpp"

# include <termcolor/termcolor.hpp>

# include "constants.hpp"

unsigned long get_request_timeout_ms()
{
double timeout_seconds = WASM_HTTP_TRANSPORT_TIMEOUT_DEFAULT_S;
auto env_var = std::getenv(WASM_HTTP_TRANSPORT_TIMEOUT_NAME.data());
if (env_var != nullptr)
{
try
{
auto value = std::stod(env_var);
if (value < 1e-3) // Must be at least 1 ms.
{
throw std::runtime_error(""); // Caught below.
}
timeout_seconds = value;
}
catch (std::exception& e)
{
// Catch failures from (1) stod and (2) timeout <= 0.
// Print warning and use default value.
std::cout << termcolor::yellow << "Warning: environment variable "
<< WASM_HTTP_TRANSPORT_TIMEOUT_NAME
<< " must be a positive number of seconds, using default value of "
<< WASM_HTTP_TRANSPORT_TIMEOUT_DEFAULT_S << " seconds instead." << termcolor::reset
<< std::endl;
}
}
return 1000 * timeout_seconds;
}

#endif // EMSCRIPTEN
8 changes: 8 additions & 0 deletions src/wasm/utils.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#pragma once

#ifdef EMSCRIPTEN

// Get wasm http request timeout in milliseconds from environment variable or default value.
unsigned long get_request_timeout_ms();

#endif // EMSCRIPTEN
61 changes: 61 additions & 0 deletions test/test_clone.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import pytest
import subprocess
from .conftest import GIT2CPP_TEST_WASM

xtl_url = "https://github.com/xtensor-stack/xtl.git"

Expand Down Expand Up @@ -144,3 +145,63 @@ def test_clone_gitlab(git2cpp_path, tmp_path, run_in_tmp_path, protocol):
assert p_status.returncode == 0
assert "On branch main" in p_status.stdout
assert "Your branch is up to date with 'origin/main'" in p_status.stdout


@pytest.mark.skipif(not GIT2CPP_TEST_WASM, reason="Only test in WebAssembly")
def test_clone_timeout(git2cpp_path, tmp_path, run_in_tmp_path):
# Set very short timeout.
subprocess.run(["export", "GIT_HTTP_TIMEOUT=0.001"], check=True)

# Check timeout is set.
check_env_cmd = ["env"]
p_check_env = subprocess.run(check_env_cmd, capture_output=True, cwd=tmp_path, text=True)
assert "GIT_HTTP_TIMEOUT=0.001" in p_check_env.stdout

# Clone fails with timeout.
clone_cmd = [git2cpp_path, "clone", xtl_url]
p_clone = subprocess.run(clone_cmd, capture_output=True, cwd=tmp_path, text=True)
assert p_clone.returncode != 0
assert "network request timed out connecting to" in p_clone.stderr
assert (
"set a longer timeout in seconds using the environment variable GIT_HTTP_TIMEOUT"
in p_clone.stderr
)

# Set more reasonable timeout.
subprocess.run(["export", "GIT_HTTP_TIMEOUT=10"], check=True)

# Check timeout is set.
p_check_env = subprocess.run(check_env_cmd, capture_output=True, cwd=tmp_path, text=True)
assert "GIT_HTTP_TIMEOUT=10" in p_check_env.stdout

# Clone succeeds.
clone_cmd = [git2cpp_path, "clone", xtl_url]
p_clone = subprocess.run(clone_cmd, capture_output=True, cwd=tmp_path, text=True)
assert p_clone.returncode == 0

assert (tmp_path / "xtl").exists()
assert (tmp_path / "xtl/include").exists()


@pytest.mark.skipif(not GIT2CPP_TEST_WASM, reason="Only test in WebAssembly")
def test_clone_negative_timeout_ignored(git2cpp_path, tmp_path, run_in_tmp_path):
# Set negative timeout.
subprocess.run(["export", "GIT_HTTP_TIMEOUT=-1"], check=True)

# Check timeout is set.
check_env_cmd = ["env"]
p_check_env = subprocess.run(check_env_cmd, capture_output=True, cwd=tmp_path, text=True)
assert "GIT_HTTP_TIMEOUT=-1" in p_check_env.stdout

# Clone succeeds, ignoring invalid timeout.
clone_cmd = [git2cpp_path, "clone", xtl_url]
p_clone = subprocess.run(clone_cmd, capture_output=True, cwd=tmp_path, text=True)
assert p_clone.returncode == 0

assert (tmp_path / "xtl").exists()
assert (tmp_path / "xtl/include").exists()

assert (
"environment variable GIT_HTTP_TIMEOUT must be a positive number of seconds"
in p_clone.stdout
)