diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 393ea4d1ccf784ce6247c55abf8a75888b00fb46..135eb5afe922c1c958b211ba163e8984aac8bcd2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -395,9 +395,8 @@ jobs: cc: gcc - jobname: linux-musl-meson image: alpine:latest - # Supported until 2025-04-02. - jobname: linux32 - image: i386/ubuntu:focal + image: i386/debian:latest # A RHEL 8 compatible distro. Supported until 2029-05-31. - jobname: almalinux-8 image: almalinux:8 @@ -458,6 +457,21 @@ jobs: - run: ci/install-dependencies.sh - run: ci/run-static-analysis.sh - run: ci/check-directional-formatting.bash + rust-analysis: + needs: ci-config + if: needs.ci-config.outputs.enabled == 'yes' + env: + jobname: RustAnalysis + CI_JOB_IMAGE: ubuntu:rolling + runs-on: ubuntu-latest + container: ubuntu:rolling + concurrency: + group: rust-analysis-${{ github.ref }} + cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }} + steps: + - uses: actions/checkout@v4 + - run: ci/install-dependencies.sh + - run: ci/run-rust-checks.sh sparse: needs: ci-config if: needs.ci-config.outputs.enabled == 'yes' diff --git a/.gitignore b/.gitignore index 78a45cb5bec99153c74b1978e789535e58115e14..6b565dbddea612e0d537c5162272d06c437c0a53 100644 --- a/.gitignore +++ b/.gitignore @@ -197,6 +197,7 @@ /gitweb/gitweb.cgi /gitweb/static/gitweb.js /gitweb/static/gitweb.min.* +/c-bindings-generated.h /config-list.h /command-list.h /hook-list.h diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 230868b79b4596afef57d750416d5efcc0a9a458..85feebf72d67d5d02373b43e1e1ff23917e74325 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -63,13 +63,15 @@ test:linux: - jobname: linux-musl-meson image: alpine:latest - jobname: linux32 - image: i386/ubuntu:20.04 + image: i386/debian:latest - jobname: linux-meson image: ubuntu:rolling CC: gcc artifacts: paths: - t/failed-test-artifacts + reports: + junit: build/meson-logs/testlog.junit.xml when: on_failure test:osx: @@ -110,8 +112,16 @@ test:osx: artifacts: paths: - t/failed-test-artifacts + reports: + junit: build/meson-logs/testlog.junit.xml when: on_failure +.windows_before_script: &windows_before_script + # Disabling realtime monitoring fails on some of the runners, but it + # significantly speeds up test execution in the case where it works. We thus + # try our luck, but ignore any failures. + - Set-MpPreference -DisableRealtimeMonitoring $true; $true + build:mingw64: stage: build tags: @@ -119,7 +129,7 @@ build:mingw64: variables: NO_PERL: 1 before_script: - - Set-MpPreference -DisableRealtimeMonitoring $true + - *windows_before_script - ./ci/install-sdk.ps1 -directory "git-sdk" script: - git-sdk/usr/bin/bash.exe -l -c 'ci/make-test-artifacts.sh artifacts' @@ -136,7 +146,7 @@ test:mingw64: - job: "build:mingw64" artifacts: true before_script: - - Set-MpPreference -DisableRealtimeMonitoring $true + - *windows_before_script - git-sdk/usr/bin/bash.exe -l -c 'tar xf artifacts/artifacts.tar.gz' - New-Item -Path .git/info -ItemType Directory - New-Item .git/info/exclude -ItemType File -Value "/git-sdk" @@ -150,18 +160,10 @@ test:mingw64: tags: - saas-windows-medium-amd64 before_script: - - Set-MpPreference -DisableRealtimeMonitoring $true - - choco install -y git meson ninja openssl + - *windows_before_script + - choco install -y git meson ninja rust-ms - Import-Module $env:ChocolateyInstall\helpers\chocolateyProfile.psm1 - refreshenv - # The certificate store for Python on Windows is broken and fails to fetch - # certificates, see https://bugs.python.org/issue36011. This seems to - # mostly be an issue with how the GitLab image is set up as it is a - # non-issue on GitHub Actions. Work around the issue by importing - # cetrificates manually. - - Invoke-WebRequest https://curl.haxx.se/ca/cacert.pem -OutFile cacert.pem - - openssl pkcs12 -export -nokeys -in cacert.pem -out certs.pfx -passout "pass:" - - Import-PfxCertificate -CertStoreLocation Cert:\LocalMachine\Root -FilePath certs.pfx build:msvc-meson: extends: .msvc-meson @@ -183,6 +185,9 @@ test:msvc-meson: script: - meson test -C build --no-rebuild --print-errorlogs --slice $Env:CI_NODE_INDEX/$Env:CI_NODE_TOTAL parallel: 10 + artifacts: + reports: + junit: build/meson-logs/testlog.junit.xml test:fuzz-smoke-tests: image: ubuntu:latest @@ -207,6 +212,17 @@ static-analysis: - ./ci/run-static-analysis.sh - ./ci/check-directional-formatting.bash +rust-analysis: + image: ubuntu:rolling + stage: analyze + needs: [ ] + variables: + jobname: RustAnalysis + before_script: + - ./ci/install-dependencies.sh + script: + - ./ci/run-rust-checks.sh + check-whitespace: image: ubuntu:latest stage: analyze diff --git a/Cargo.toml b/Cargo.toml index 45c9b34981abb311866666d3ef495611b28ffa3b..2f51bf5d5ff5f83e023cbc17f4365d99ab837ce0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ name = "gitcore" version = "0.1.0" edition = "2018" +rust-version = "1.49.0" [lib] crate-type = ["staticlib"] diff --git a/Makefile b/Makefile index 7ea149598d8ed870e385e6a67d31338ed20a6f53..c3a5be8ed485992a99bc9cbb70a1e22d094026a9 100644 --- a/Makefile +++ b/Makefile @@ -929,10 +929,17 @@ TEST_SHELL_PATH = $(SHELL_PATH) LIB_FILE = libgit.a XDIFF_LIB = xdiff/lib.a REFTABLE_LIB = reftable/libreftable.a + ifdef DEBUG -RUST_LIB = target/debug/libgitcore.a +RUST_TARGET_DIR = target/debug else -RUST_LIB = target/release/libgitcore.a +RUST_TARGET_DIR = target/release +endif + +ifeq ($(uname_S),Windows) +RUST_LIB = $(RUST_TARGET_DIR)/gitcore.lib +else +RUST_LIB = $(RUST_TARGET_DIR)/libgitcore.a endif # xdiff and reftable libs may in turn depend on what is in libgit.a @@ -1538,6 +1545,19 @@ ALL_LDFLAGS = $(LDFLAGS) $(LDFLAGS_APPEND) ifdef WITH_RUST BASIC_CFLAGS += -DWITH_RUST GITLIBS += $(RUST_LIB) + +# Note: the .depend file emitted by cbindgen uses absolute paths, so we have to +# use those here, as well. +C_BINDINGS = $(shell pwd)/c-bindings-generated.h + +GENERATED_H += $(C_BINDINGS) + +$(C_BINDINGS): cbindgen.toml + $(QUIET_CBINDGEN)cbindgen --output $@ + +ifeq ($(uname_S),Windows) +EXTLIBS += -luserenv +endif endif ifdef SANITIZE @@ -2849,7 +2869,7 @@ missing_compdb_dir = compdb_args = endif -$(OBJECTS): %.o: %.c GIT-CFLAGS $(missing_dep_dirs) $(missing_compdb_dir) +$(OBJECTS): %.o: %.c GIT-CFLAGS $(missing_dep_dirs) $(missing_compdb_dir) $(C_BINDINGS) $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(compdb_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $< %.s: %.c GIT-CFLAGS FORCE @@ -3810,7 +3830,7 @@ clean: profile-clean coverage-clean cocciclean $(RM) $(FUZZ_PROGRAMS) $(RM) $(SP_OBJ) $(RM) $(HCC) - $(RM) -r Cargo.lock target/ + $(RM) -r Cargo.lock target/ $(C_BINDINGS) $(RM) version-def.h $(RM) -r $(dep_dirs) $(compdb_dir) compile_commands.json $(RM) $(test_bindir_programs) diff --git a/c-bindings.h b/c-bindings.h new file mode 100644 index 0000000000000000000000000000000000000000..064fc73e2d5548488507653d56a9342b23c9df2c --- /dev/null +++ b/c-bindings.h @@ -0,0 +1,8 @@ +#ifndef C_BINDINGS_H +#define C_BINDINGS_H + +#ifdef WITH_RUST +# include "c-bindings-generated.h" +#endif + +#endif diff --git a/cbindgen.toml b/cbindgen.toml new file mode 100644 index 0000000000000000000000000000000000000000..ba4b2d6367294c9e20f9263bc23149e3f36885cc --- /dev/null +++ b/cbindgen.toml @@ -0,0 +1,7 @@ +language = "C" + +# Don't include standard C headers. These are managed by "git-compat-util.h". +no_includes = true + +# Use plain structs instead of using typedefs. +style = "tag" diff --git a/ci/install-dependencies.sh b/ci/install-dependencies.sh index 0d3aa496fc3a24afb8e74c131222c9681423cb23..a7b5a38c63fcbe467f4f04f4a88e7813ea9a971a 100755 --- a/ci/install-dependencies.sh +++ b/ci/install-dependencies.sh @@ -10,6 +10,8 @@ begin_group "Install dependencies" P4WHENCE=https://cdist2.perforce.com/perforce/r23.2 LFSWHENCE=https://github.com/github/git-lfs/releases/download/v$LINUX_GIT_LFS_VERSION JGITWHENCE=https://repo1.maven.org/maven2/org/eclipse/jgit/org.eclipse.jgit.pgm/6.8.0.202311291450-r/org.eclipse.jgit.pgm-6.8.0.202311291450-r.sh +CARGO_MSRV_VERSION=0.18.4 +CARGO_MSRV_WHENCE=https://github.com/foresterre/cargo-msrv/releases/download/v$CARGO_MSRV_VERSION/cargo-msrv-x86_64-unknown-linux-musl-v$CARGO_MSRV_VERSION.tgz # Make sudo a no-op and execute the command directly when running as root. # While using sudo would be fine on most platforms when we are root already, @@ -35,9 +37,9 @@ fedora-*|almalinux-*) MESON_DEPS="meson ninja";; esac dnf -yq update >/dev/null && - dnf -yq install shadow-utils sudo make pkg-config gcc findutils diffutils perl python3 gawk gettext zlib-devel expat-devel openssl-devel curl-devel pcre2-devel $MESON_DEPS cargo >/dev/null + dnf -yq install shadow-utils sudo make pkg-config gcc findutils diffutils perl python3 gawk gettext zlib-devel expat-devel openssl-devel curl-devel pcre2-devel $MESON_DEPS cargo cbindgen >/dev/null ;; -ubuntu-*|i386/ubuntu-*|debian-*) +ubuntu-*|i386/debian-*|debian-*) # Required so that apt doesn't wait for user input on certain packages. export DEBIAN_FRONTEND=noninteractive @@ -46,9 +48,9 @@ ubuntu-*|i386/ubuntu-*|debian-*) SVN='libsvn-perl subversion' LANGUAGES='language-pack-is' ;; - i386/ubuntu-*) + i386/debian-*) SVN= - LANGUAGES='language-pack-is' + LANGUAGES='locales-all' ;; *) SVN='libsvn-perl subversion' @@ -62,7 +64,7 @@ ubuntu-*|i386/ubuntu-*|debian-*) make libssl-dev libcurl4-openssl-dev libexpat-dev wget sudo default-jre \ tcl tk gettext zlib1g-dev perl-modules liberror-perl libauthen-sasl-perl \ libemail-valid-perl libio-pty-perl libio-socket-ssl-perl libnet-smtp-ssl-perl libdbd-sqlite3-perl libcgi-pm-perl \ - libsecret-1-dev libpcre2-dev meson ninja-build pkg-config cargo \ + libsecret-1-dev libpcre2-dev meson ninja-build pkg-config cargo cbindgen \ ${CC_PACKAGE:-${CC:-gcc}} $PYTHON_PACKAGE case "$distro" in @@ -120,21 +122,28 @@ esac case "$jobname" in ClangFormat) - sudo apt-get -q update sudo apt-get -q -y install clang-format ;; StaticAnalysis) - sudo apt-get -q update sudo apt-get -q -y install coccinelle libcurl4-openssl-dev libssl-dev \ libexpat-dev gettext make ;; +RustAnalysis) + sudo apt-get -q -y install rustup + rustup default stable + rustup component add clippy rustfmt + + wget -q "$CARGO_MSRV_WHENCE" -O "cargo-msvc.tgz" + sudo mkdir -p "$CUSTOM_PATH" + sudo tar -xf "cargo-msvc.tgz" --strip-components=1 \ + --directory "$CUSTOM_PATH" --wildcards "*/cargo-msrv" + sudo chmod a+x "$CUSTOM_PATH/cargo-msrv" + ;; sparse) - sudo apt-get -q update -q sudo apt-get -q -y install libssl-dev libcurl4-openssl-dev \ libexpat-dev gettext zlib1g-dev sparse ;; Documentation) - sudo apt-get -q update sudo apt-get -q -y install asciidoc xmlto docbook-xsl-ns make test -n "$ALREADY_HAVE_ASCIIDOCTOR" || diff --git a/ci/lib.sh b/ci/lib.sh index f561884d40166cc6bdde07e178170ba33aac1347..865a06753710422e7f4c5c19fc1674ed3ad50b25 100755 --- a/ci/lib.sh +++ b/ci/lib.sh @@ -250,7 +250,7 @@ then CI_OS_NAME=osx JOBS=$(nproc) ;; - *,alpine:*|*,fedora:*|*,ubuntu:*|*,i386/ubuntu:*) + *,alpine:*|*,fedora:*|*,ubuntu:*|*,i386/debian:*) CI_OS_NAME=linux JOBS=$(nproc) ;; diff --git a/ci/run-rust-checks.sh b/ci/run-rust-checks.sh new file mode 100755 index 0000000000000000000000000000000000000000..b5ad9e8dc6f71fbd8a329f23b80bab8935fd6ff0 --- /dev/null +++ b/ci/run-rust-checks.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +. ${0%/*}/lib.sh + +set +x + +if ! group "Check Rust formatting" cargo fmt --all --check +then + RET=1 +fi + +if ! group "Check for common Rust mistakes" cargo clippy --all-targets --all-features -- -Dwarnings +then + RET=1 +fi + +if ! group "Check for minimum required Rust version" cargo msrv verify +then + RET=1 +fi + +exit $RET diff --git a/meson.build b/meson.build index ec55d6a5fdfae299cb5b3bd33b98a3a067307da4..b1cd145ac9063118238b2e52a0cd1626cb9bb15c 100644 --- a/meson.build +++ b/meson.build @@ -1703,10 +1703,50 @@ version_def_h = custom_target( libgit_sources += version_def_h cargo = find_program('cargo', dirs: program_path, native: true, required: get_option('rust')) -rust_option = get_option('rust').disable_auto_if(not cargo.found()) +cbindgen = find_program('cbindgen', dirs: program_path, native: true, required: get_option('rust')) + +rust_option = get_option('rust').disable_auto_if(not cargo.found() or not cbindgen.found()) if rust_option.allowed() subdir('src') libgit_c_args += '-DWITH_RUST' + + if host_machine.system() == 'windows' + libgit_dependencies += compiler.find_library('userenv') + endif + + cbindgen_kwargs = { + 'build_always_stale': true, + } + + cbindgen_args = [ + cbindgen, + '--output', + '@OUTPUT@', + meson.current_source_dir(), + ] + + # cbindgen only learned about `--depfile` in v0.25.0. In earlier versions we + # thus have to execute it unconditionally. This isn't too bad though, as the + # target file is only updated in case the file actually changes. + if meson.version().version_compare('>=0.62.0') + if cbindgen.version().version_compare('>=0.25.0') + cbindgen_kwargs = { + 'depfile': 'c-bindings-generated.d', + } + + cbindgen_args += [ + '--depfile', + meson.current_build_dir() / 'c-bindings-generated.d', + ] + endif + endif + + libgit_sources += custom_target('cbindgen', + input: 'cbindgen.toml', + output: 'c-bindings-generated.h', + command: cbindgen_args, + kwargs: cbindgen_kwargs, + ) else libgit_sources += [ 'varint.c', diff --git a/shared.mak b/shared.mak index 0e7492076ebc4f65990401c745aebc6bb0533830..598e58e069ca3ffd9bd6796df20379f28ca13ebb 100644 --- a/shared.mak +++ b/shared.mak @@ -57,6 +57,7 @@ ifndef V ## Used in "Makefile" QUIET_CARGO = @echo ' ' CARGO $@; + QUIET_CBINDGEN = @echo ' ' CBINDGEN $@; QUIET_CC = @echo ' ' CC $@; QUIET_AR = @echo ' ' AR $@; QUIET_LINK = @echo ' ' LINK $@; diff --git a/src/cargo-meson.sh b/src/cargo-meson.sh index 99400986d935098045b07a94d30f640625cb0d4f..3998db043548640b48ab502a7d848bf87ad627bb 100755 --- a/src/cargo-meson.sh +++ b/src/cargo-meson.sh @@ -26,7 +26,14 @@ then exit $RET fi -if ! cmp "$BUILD_DIR/$BUILD_TYPE/libgitcore.a" "$BUILD_DIR/libgitcore.a" >/dev/null 2>&1 +case "$(cargo -vV | sed -s 's/^host: \(.*\)$/\1/')" in + *-windows-*) + LIBNAME=gitcore.lib;; + *) + LIBNAME=libgitcore.a;; +esac + +if ! cmp "$BUILD_DIR/$BUILD_TYPE/$LIBNAME" "$BUILD_DIR/libgitcore.a" >/dev/null 2>&1 then - cp "$BUILD_DIR/$BUILD_TYPE/libgitcore.a" "$BUILD_DIR/libgitcore.a" + cp "$BUILD_DIR/$BUILD_TYPE/$LIBNAME" "$BUILD_DIR/libgitcore.a" fi diff --git a/src/varint.rs b/src/varint.rs index 6e610bdd8e079443116a02e3875eb2ed35a947d3..43b48debb54e133086ebfcad8c1c9ed46e759ded 100644 --- a/src/varint.rs +++ b/src/varint.rs @@ -1,3 +1,6 @@ +/// # Safety +/// +/// Callers must provide a NUL-terminated array to ensure safety. #[no_mangle] pub unsafe extern "C" fn decode_varint(bufp: *mut *const u8) -> u64 { let mut buf = *bufp; @@ -22,6 +25,11 @@ pub unsafe extern "C" fn decode_varint(bufp: *mut *const u8) -> u64 { val } +/// # Safety +/// +/// The provided buffer must be large enough to store the encoded varint. Callers may either provide +/// a `[u8; 16]` here, which is guaranteed to satisfy all encodable numbers. Or they can call this +/// function with a `NULL` pointer first to figure out array size. #[no_mangle] pub unsafe extern "C" fn encode_varint(value: u64, buf: *mut u8) -> u8 { let mut varint: [u8; 16] = [0; 16]; diff --git a/t/t8020-last-modified.sh b/t/t8020-last-modified.sh index e13aad14398dd9055ff45ab57700329e7aab37c4..61f00bc15c3b2d170d981038f0ebb48fd4bf31c2 100755 --- a/t/t8020-last-modified.sh +++ b/t/t8020-last-modified.sh @@ -33,7 +33,6 @@ check_last_modified() { done && cat >expect && - test_when_finished "rm -f tmp.*" && git ${indir:+-C "$indir"} last-modified "$@" >tmp.1 && git name-rev --annotate-stdin --name-only --tags \ tmp.2 && @@ -128,20 +127,25 @@ test_expect_success 'only last-modified files in the current tree' ' EOF ' -test_expect_success 'last-modified with subdir and criss-cross merge' ' - git checkout -b branch-k1 1 && - mkdir -p a k && - test_commit k1 a/file2 && - git checkout -b branch-k2 && - test_commit k2 k/file2 && - git checkout branch-k1 && - test_merge km2 branch-k2 && - test_merge km3 3 && - check_last_modified <<-\EOF - km3 a - k2 k - 1 file - EOF +test_expect_success 'subdirectory modified via merge' ' + test_when_finished rm -rf repo && + git init repo && + ( + cd repo && + test_commit base && + git switch --create left && + mkdir subdir && + test_commit left subdir/left && + git switch --create right base && + mkdir subdir && + test_commit right subdir/right && + git switch - && + test_merge merge right && + check_last_modified <<-\EOF + merge subdir + base base.t + EOF + ) ' test_expect_success 'cross merge boundaries in blaming' ' diff --git a/varint.h b/varint.h index eb401935bd24c9040694497dc7d5d39b007c3aab..e20c151371238db6c498fcfe64774710764f22c3 100644 --- a/varint.h +++ b/varint.h @@ -1,6 +1,8 @@ #ifndef VARINT_H #define VARINT_H +#include "c-bindings.h" + uint8_t encode_varint(uint64_t, unsigned char *); uint64_t decode_varint(const unsigned char **);