Visitar URL original
Override `core.filemode=false` in wasm build by ianthomas23 · Pull Request #189 · QuantStack/git2cpp · GitHub
Skip to content
Merged
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
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ set(GIT2CPP_SRC
${GIT2CPP_SOURCE_DIR}/utils/ansi_code.hpp
${GIT2CPP_SOURCE_DIR}/utils/common.cpp
${GIT2CPP_SOURCE_DIR}/utils/common.hpp
${GIT2CPP_SOURCE_DIR}/utils/config_overrides.cpp
${GIT2CPP_SOURCE_DIR}/utils/config_overrides.hpp
${GIT2CPP_SOURCE_DIR}/utils/credentials.cpp
${GIT2CPP_SOURCE_DIR}/utils/credentials.hpp
${GIT2CPP_SOURCE_DIR}/utils/git_exception.cpp
Expand Down
51 changes: 51 additions & 0 deletions src/utils/config_overrides.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#include "config_overrides.hpp"

#ifdef EMSCRIPTEN
# include <iterator>

# include <git2/sys/config.h>

# include "git_exception.hpp"
#endif

void apply_config_overrides([[maybe_unused]] git_repository* repo)
{
#ifdef EMSCRIPTEN
// Config values to override, each in "name=value" form. core.filemode is disabled because
// the emscripten filesystem does support executable file permissions but some JupyterLite
// drive implementations do not.
const char* values[] = {"core.filemode=false"};

// Create an in-memory config backend holding these values. This backend is read-only, so
// any config writes (e.g. `git config` or values set by clone/init) skip it and go to the
// highest-priority writable backend instead, which is the repository's own config file.
// The overrides are therefore never persisted to disk. backend_type is a label that
// identifies where these entries came from when inspecting a config entry's origin.
git_config_backend* backend = nullptr;
git_config_backend_memory_options opts = GIT_CONFIG_BACKEND_MEMORY_OPTIONS_INIT;
opts.backend_type = "git2cpp-overrides";
throw_if_error(git_config_backend_from_values(&backend, values, std::size(values), &opts));

// git_repository_config returns the repository's cached, shared config object rather than a
// copy, so adding a backend to it affects all later config lookups through this repository.
// GIT_CONFIG_LEVEL_APP is the highest priority level, above the local, global and system
// config files, so these values win over any existing setting. The repo is passed so that
// conditional includes can be evaluated, and force=0 means fail rather than replace if a
// backend already exists at this level.
git_config* cfg = nullptr;
int error = git_repository_config(&cfg, repo);
if (error == 0)
{
error = git_config_add_backend(cfg, backend, GIT_CONFIG_LEVEL_APP, repo, 0);
// Only releases our reference; the repository still holds the config and its backends.
git_config_free(cfg);
}
if (error < 0)
{
// On success ownership of the backend passes to the config, but on failure it is still
// ours to free.
backend->free(backend);
throw_if_error(error);
}
#endif
}
8 changes: 8 additions & 0 deletions src/utils/config_overrides.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#pragma once

#include <git2.h>

// Apply in-memory config overrides to a repository's config. These have a higher priority than
// the repository's config file but are never written to it. Only used in WebAssembly, where
// core.filemode=false is set as the filesystem does not always support executable file permissions.
void apply_config_overrides(git_repository* repo);
5 changes: 5 additions & 0 deletions src/wrapper/repository_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <fstream>
#include <iostream>

#include "../utils/config_overrides.hpp"
#include "../utils/git_exception.hpp"
#include "../wrapper/commit_wrapper.hpp"
#include "../wrapper/config_wrapper.hpp"
Expand All @@ -22,20 +23,23 @@ repository_wrapper repository_wrapper::open(std::string_view directory)
{
repository_wrapper rw;
throw_if_error(git_repository_open(&(rw.p_resource), directory.data()));
apply_config_overrides(rw.p_resource);
return rw;
}

repository_wrapper repository_wrapper::init(std::string_view directory, bool bare)
{
repository_wrapper rw;
throw_if_error(git_repository_init(&(rw.p_resource), directory.data(), bare));
apply_config_overrides(rw.p_resource);
return rw;
}

repository_wrapper repository_wrapper::init_ext(std::string_view directory, git_repository_init_options* opts)
{
repository_wrapper rw;
throw_if_error(git_repository_init_ext(&(rw.p_resource), directory.data(), opts));
apply_config_overrides(rw.p_resource);
return rw;
}

Expand All @@ -44,6 +48,7 @@ repository_wrapper::clone(std::string_view url, std::string_view path, const git
{
repository_wrapper rw;
throw_if_error(git_clone(&(rw.p_resource), url.data(), path.data(), &opts));
apply_config_overrides(rw.p_resource);
return rw;
}

Expand Down
15 changes: 15 additions & 0 deletions test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,21 @@ def private_test_repo():
# Functions not fixtures below here.


def get_filemode_lines(git2cpp_path, cwd):
# Return all core.filemode lines from config list.
cmd_list = [git2cpp_path, "config", "list"]
p_list = subprocess.run(cmd_list, capture_output=True, cwd=cwd, text=True)
assert p_list.returncode == 0
return [line for line in p_list.stdout.splitlines() if line.startswith("core.filemode")]


def set_filemode(git2cpp_path, cwd, value):
# Set core.filemode in the repository's config file.
cmd_set = [git2cpp_path, "config", "set", "core.filemode", value]
p_set = subprocess.run(cmd_set, capture_output=True, cwd=cwd, text=True)
assert p_set.returncode == 0


def strip_ansi_colours(text):
# Strip ansi colour code sequences from a string.
return re.sub(r"\x1b\[[^m]*m", "", text)
6 changes: 3 additions & 3 deletions test/conftest_wasm.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,11 @@ def subprocess_run(
raise RuntimeError(f"Error setting cwd to {cwd}")

def maybe_wrap_arg(s: str | MockPath) -> str:
# An argument containing spaces needs to be wrapped in quotes if it is not already, due
# to how the command is passed to cockle as a single string.
# An argument containing whitespace (including newlines) needs to be wrapped in quotes if
# it is not already, due to how the command is passed to cockle as a single string.
# Could do better here.
s = str(s)
if " " in s and not s.endswith("'"):
if re.search(r"\s", s) and not s.endswith("'"):
return "'" + s + "'"
return s

Expand Down
33 changes: 32 additions & 1 deletion test/test_clone.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import pytest

from .conftest import GIT2CPP_TEST_WASM
from .conftest import GIT2CPP_TEST_WASM, get_filemode_lines, set_filemode

xsimd_url = "https://github.com/xtensor-stack/xsimd.git"
xtl_url = "https://github.com/xtensor-stack/xtl.git"
Expand Down Expand Up @@ -229,3 +229,34 @@ def test_clone_large_repo(git2cpp_path, tmp_path, run_in_tmp_path):
assert (tmp_path / "xsimd").exists()
assert (tmp_path / "xsimd/include").exists()
assert (tmp_path / "xsimd/xsimdConfig.cmake.in").exists()


def test_clone_filemode(git2cpp_path, tmp_path, run_in_tmp_path):
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

xtl_path = tmp_path / "xtl"

filemode_lines = get_filemode_lines(git2cpp_path, xtl_path)
if GIT2CPP_TEST_WASM:
# In WebAssembly core.filemode=false is set by an in-memory config override that has higher
# priority than the repository's config file, so is listed after it.
assert filemode_lines == ["core.filemode=true", "core.filemode=false"]
else:
assert filemode_lines == ["core.filemode=true"]

# Setting writes to the repository's config file, not the in-memory override.
set_filemode(git2cpp_path, xtl_path, "true")
filemode_lines = get_filemode_lines(git2cpp_path, xtl_path)
if GIT2CPP_TEST_WASM:
assert filemode_lines == ["core.filemode=true", "core.filemode=false"]
else:
assert filemode_lines == ["core.filemode=true"]

set_filemode(git2cpp_path, xtl_path, "false")
filemode_lines = get_filemode_lines(git2cpp_path, xtl_path)
if GIT2CPP_TEST_WASM:
assert filemode_lines == ["core.filemode=false", "core.filemode=false"]
else:
assert filemode_lines == ["core.filemode=false"]
31 changes: 31 additions & 0 deletions test/test_init.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import subprocess
from pathlib import Path

from .conftest import GIT2CPP_TEST_WASM, get_filemode_lines, set_filemode


def test_init_in_directory(git2cpp_path, tmp_path):
# tmp_path exists and is empty.
Expand Down Expand Up @@ -155,3 +157,32 @@ def test_init_initial_branch_bare(git2cpp_path, tmp_path):

head = (tmp_path / "HEAD").read_text()
assert "refs/heads/main" in head


def test_init_filemode(git2cpp_path, tmp_path):
cmd_init = [git2cpp_path, "init", "."]
p_init = subprocess.run(cmd_init, capture_output=True, cwd=tmp_path, text=True)
assert p_init.returncode == 0

filemode_lines = get_filemode_lines(git2cpp_path, tmp_path)
if GIT2CPP_TEST_WASM:
# In WebAssembly core.filemode=false is set by an in-memory config override that has higher
# priority than the repository's config file, so is listed after it.
assert filemode_lines == ["core.filemode=true", "core.filemode=false"]
else:
assert filemode_lines == ["core.filemode=true"]

# Setting writes to the repository's config file, not the in-memory override.
set_filemode(git2cpp_path, tmp_path, "true")
filemode_lines = get_filemode_lines(git2cpp_path, tmp_path)
if GIT2CPP_TEST_WASM:
assert filemode_lines == ["core.filemode=true", "core.filemode=false"]
else:
assert filemode_lines == ["core.filemode=true"]

set_filemode(git2cpp_path, tmp_path, "false")
filemode_lines = get_filemode_lines(git2cpp_path, tmp_path)
if GIT2CPP_TEST_WASM:
assert filemode_lines == ["core.filemode=false", "core.filemode=false"]
else:
assert filemode_lines == ["core.filemode=false"]
Loading