Merge branch 'ps/ci-rust' into dk/ci-rust-fix

* ps/ci-rust:
  rust: support for Windows
  ci: verify minimum supported Rust version
  ci: check for common Rust mistakes via Clippy
  rust/varint: add safety comments
  ci: check formatting of our Rust code
  ci: deduplicate calls to `apt-get update`
  t8020: fix test failure due to indeterministic tag sorting
  gitlab-ci: upload Meson test logs as JUnit reports
  gitlab-ci: drop workaround for Python certificate store on Windows
  gitlab-ci: ignore failures to disable realtime monitoring
  gitlab-ci: dedup instructions to disable realtime monitoring
  ci: enable Rust for breaking-changes jobs
  ci: convert "pedantic" job into full build with breaking changes
  BreakingChanges: announce Rust becoming mandatory
  varint: reimplement as test balloon for Rust
  varint: use explicit width for integers
  help: report on whether or not Rust is enabled
  Makefile: introduce infrastructure to build internal Rust library
  Makefile: reorder sources after includes
  meson: add infrastructure to build internal Rust library
This commit is contained in:
Junio C Hamano 2025-12-19 17:57:16 +09:00
commit beb1789f08
22 changed files with 544 additions and 162 deletions

View File

@ -379,6 +379,8 @@ jobs:
- jobname: linux-breaking-changes
cc: gcc
image: ubuntu:rolling
- jobname: fedora-breaking-changes-meson
image: fedora:latest
- jobname: linux-leaks
image: ubuntu:rolling
cc: gcc
@ -396,8 +398,6 @@ jobs:
# Supported until 2025-04-02.
- jobname: linux32
image: i386/ubuntu:focal
- jobname: pedantic
image: fedora:latest
# A RHEL 8 compatible distro. Supported until 2029-05-31.
- jobname: almalinux-8
image: almalinux:8
@ -458,6 +458,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'

2
.gitignore vendored
View File

@ -1,4 +1,6 @@
/fuzz_corpora
/target/
/Cargo.lock
/GIT-BUILD-DIR
/GIT-BUILD-OPTIONS
/GIT-CFLAGS

View File

@ -45,6 +45,8 @@ test:linux:
- jobname: linux-breaking-changes
image: ubuntu:20.04
CC: gcc
- jobname: fedora-breaking-changes-meson
image: fedora:latest
- jobname: linux-TEST-vars
image: ubuntu:20.04
CC: gcc
@ -58,8 +60,6 @@ test:linux:
- jobname: linux-asan-ubsan
image: ubuntu:rolling
CC: clang
- jobname: pedantic
image: fedora:latest
- jobname: linux-musl-meson
image: alpine:latest
- jobname: linux32
@ -70,6 +70,8 @@ test:linux:
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

10
Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "gitcore"
version = "0.1.0"
edition = "2018"
rust-version = "1.49.0"
[lib]
crate-type = ["staticlib"]
[dependencies]

View File

@ -171,6 +171,51 @@ JGit, libgit2 and Gitoxide need to support it.
matches the default branch name used in new repositories by many of the
big Git forges.
* Git will require Rust as a mandatory part of the build process. While Git
already started to adopt Rust in Git 2.49, all parts written in Rust are
optional for the time being. This includes:
+
** The Rust wrapper around libgit.a that is part of "contrib/" and which has
been introduced in Git 2.49.
** Subsystems that have an alternative implementation in Rust to test
interoperability between our C and Rust codebase.
** Newly written features that are not mission critical for a fully functional
Git client.
+
These changes are meant as test balloons to allow distributors of Git to prepare
for Rust becoming a mandatory part of the build process. There will be multiple
milestones for the introduction of Rust:
+
--
1. Initially, with Git 2.52, support for Rust will be auto-detected by Meson and
disabled in our Makefile so that the project can sort out the initial
infrastructure.
2. In Git 2.53, both build systems will default-enable support for Rust.
Consequently, builds will break by default if Rust is not available on the
build host. The use of Rust can still be explicitly disabled via build
flags.
3. In Git 3.0, the build options will be removed and support for Rust is
mandatory.
--
+
You can explicitly ask both Meson and our Makefile-based system to enable Rust
by saying `meson configure -Drust=enabled` and `make WITH_RUST=YesPlease`,
respectively.
+
The Git project will declare the last version before Git 3.0 to be a long-term
support release. This long-term release will receive important bug fixes for at
least four release cycles and security fixes for six release cycles. The Git
project will hand over maintainership of the long-term release to distributors
in case they need to extend the life of that long-term release even further.
Details of how this long-term release will be handed over to the community will
be discussed once the Git project decides to stop officially supporting it.
+
We will evaluate the impact on downstream distributions before making Rust
mandatory in Git 3.0. If we see that the impact on downstream distributions
would be significant, we may decide to defer this change to a subsequent minor
release. This evaluation will also take into account our own experience with
how painful it is to keep Rust an optional component.
=== Removals
* Support for grafting commits has long been superseded by git-replace(1).

224
Makefile
View File

@ -483,6 +483,14 @@ include shared.mak
# Define LIBPCREDIR=/foo/bar if your PCRE header and library files are
# in /foo/bar/include and /foo/bar/lib directories.
#
# == Optional Rust support ==
#
# Define WITH_RUST if you want to include features and subsystems written in
# Rust into Git. For now, Rust is still an optional feature of the build
# process. With Git 3.0 though, Rust will always be enabled.
#
# Building Rust code requires Cargo.
#
# == SHA-1 and SHA-256 defines ==
#
# === SHA-1 backend ===
@ -683,6 +691,7 @@ OBJECTS =
OTHER_PROGRAMS =
PROGRAM_OBJS =
PROGRAMS =
RUST_SOURCES =
EXCLUDED_PROGRAMS =
SCRIPT_PERL =
SCRIPT_PYTHON =
@ -921,6 +930,115 @@ LIB_FILE = libgit.a
XDIFF_LIB = xdiff/lib.a
REFTABLE_LIB = reftable/libreftable.a
ifdef DEBUG
RUST_TARGET_DIR = target/debug
else
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
GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(LIB_FILE)
EXTLIBS =
GIT_USER_AGENT = git/$(GIT_VERSION)
ifeq ($(wildcard sha1collisiondetection/lib/sha1.h),sha1collisiondetection/lib/sha1.h)
DC_SHA1_SUBMODULE = auto
endif
# Set CFLAGS, LDFLAGS and other *FLAGS variables. These might be
# tweaked by config.* below as well as the command-line, both of
# which'll override these defaults.
# Older versions of GCC may require adding "-std=gnu99" at the end.
CFLAGS = -g -O2 -Wall
LDFLAGS =
CC_LD_DYNPATH = -Wl,-rpath,
BASIC_CFLAGS = -I.
BASIC_LDFLAGS =
# library flags
ARFLAGS = rcs
PTHREAD_CFLAGS =
# Rust flags
CARGO_ARGS =
ifndef V
CARGO_ARGS += --quiet
endif
ifndef DEBUG
CARGO_ARGS += --release
endif
# For the 'sparse' target
SPARSE_FLAGS ?= -std=gnu99 -D__STDC_NO_VLA__
SP_EXTRA_FLAGS =
# For informing GIT-BUILD-OPTIONS of the SANITIZE=leak,address targets
SANITIZE_LEAK =
SANITIZE_ADDRESS =
# For the 'coccicheck' target
SPATCH_INCLUDE_FLAGS = --all-includes
SPATCH_FLAGS =
SPATCH_TEST_FLAGS =
# If *.o files are present, have "coccicheck" depend on them, with
# COMPUTE_HEADER_DEPENDENCIES this will speed up the common-case of
# only needing to re-generate coccicheck results for the users of a
# given API if it's changed, and not all files in the project. If
# COMPUTE_HEADER_DEPENDENCIES=no this will be unset too.
SPATCH_USE_O_DEPENDENCIES = YesPlease
# Set SPATCH_CONCAT_COCCI to concatenate the contrib/cocci/*.cocci
# files into a single contrib/cocci/ALL.cocci before running
# "coccicheck".
#
# Pros:
#
# - Speeds up a one-shot run of "make coccicheck", as we won't have to
# parse *.[ch] files N times for the N *.cocci rules
#
# Cons:
#
# - Will make incremental development of *.cocci slower, as
# e.g. changing strbuf.cocci will re-run all *.cocci.
#
# - Makes error and performance analysis harder, as rules will be
# applied from a monolithic ALL.cocci, rather than
# e.g. strbuf.cocci. To work around this either undefine this, or
# generate a specific patch, e.g. this will always use strbuf.cocci,
# not ALL.cocci:
#
# make contrib/coccinelle/strbuf.cocci.patch
SPATCH_CONCAT_COCCI = YesPlease
# Rebuild 'coccicheck' if $(SPATCH), its flags etc. change
TRACK_SPATCH_DEFINES =
TRACK_SPATCH_DEFINES += $(SPATCH)
TRACK_SPATCH_DEFINES += $(SPATCH_INCLUDE_FLAGS)
TRACK_SPATCH_DEFINES += $(SPATCH_FLAGS)
TRACK_SPATCH_DEFINES += $(SPATCH_TEST_FLAGS)
GIT-SPATCH-DEFINES: FORCE
@FLAGS='$(TRACK_SPATCH_DEFINES)'; \
if test x"$$FLAGS" != x"`cat GIT-SPATCH-DEFINES 2>/dev/null`" ; then \
echo >&2 " * new spatch flags"; \
echo "$$FLAGS" >GIT-SPATCH-DEFINES; \
fi
include config.mak.uname
-include config.mak.autogen
-include config.mak
ifdef DEVELOPER
include config.mak.dev
endif
GENERATED_H += command-list.h
GENERATED_H += config-list.h
GENERATED_H += hook-list.h
@ -1198,7 +1316,9 @@ LIB_OBJS += urlmatch.o
LIB_OBJS += usage.o
LIB_OBJS += userdiff.o
LIB_OBJS += utf8.o
ifndef WITH_RUST
LIB_OBJS += varint.o
endif
LIB_OBJS += version.o
LIB_OBJS += versioncmp.o
LIB_OBJS += walker.o
@ -1390,93 +1510,8 @@ CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o
UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o
# xdiff and reftable libs may in turn depend on what is in libgit.a
GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(LIB_FILE)
EXTLIBS =
GIT_USER_AGENT = git/$(GIT_VERSION)
ifeq ($(wildcard sha1collisiondetection/lib/sha1.h),sha1collisiondetection/lib/sha1.h)
DC_SHA1_SUBMODULE = auto
endif
# Set CFLAGS, LDFLAGS and other *FLAGS variables. These might be
# tweaked by config.* below as well as the command-line, both of
# which'll override these defaults.
# Older versions of GCC may require adding "-std=gnu99" at the end.
CFLAGS = -g -O2 -Wall
LDFLAGS =
CC_LD_DYNPATH = -Wl,-rpath,
BASIC_CFLAGS = -I.
BASIC_LDFLAGS =
# library flags
ARFLAGS = rcs
PTHREAD_CFLAGS =
# For the 'sparse' target
SPARSE_FLAGS ?= -std=gnu99 -D__STDC_NO_VLA__
SP_EXTRA_FLAGS =
# For informing GIT-BUILD-OPTIONS of the SANITIZE=leak,address targets
SANITIZE_LEAK =
SANITIZE_ADDRESS =
# For the 'coccicheck' target
SPATCH_INCLUDE_FLAGS = --all-includes
SPATCH_FLAGS =
SPATCH_TEST_FLAGS =
# If *.o files are present, have "coccicheck" depend on them, with
# COMPUTE_HEADER_DEPENDENCIES this will speed up the common-case of
# only needing to re-generate coccicheck results for the users of a
# given API if it's changed, and not all files in the project. If
# COMPUTE_HEADER_DEPENDENCIES=no this will be unset too.
SPATCH_USE_O_DEPENDENCIES = YesPlease
# Set SPATCH_CONCAT_COCCI to concatenate the contrib/cocci/*.cocci
# files into a single contrib/cocci/ALL.cocci before running
# "coccicheck".
#
# Pros:
#
# - Speeds up a one-shot run of "make coccicheck", as we won't have to
# parse *.[ch] files N times for the N *.cocci rules
#
# Cons:
#
# - Will make incremental development of *.cocci slower, as
# e.g. changing strbuf.cocci will re-run all *.cocci.
#
# - Makes error and performance analysis harder, as rules will be
# applied from a monolithic ALL.cocci, rather than
# e.g. strbuf.cocci. To work around this either undefine this, or
# generate a specific patch, e.g. this will always use strbuf.cocci,
# not ALL.cocci:
#
# make contrib/coccinelle/strbuf.cocci.patch
SPATCH_CONCAT_COCCI = YesPlease
# Rebuild 'coccicheck' if $(SPATCH), its flags etc. change
TRACK_SPATCH_DEFINES =
TRACK_SPATCH_DEFINES += $(SPATCH)
TRACK_SPATCH_DEFINES += $(SPATCH_INCLUDE_FLAGS)
TRACK_SPATCH_DEFINES += $(SPATCH_FLAGS)
TRACK_SPATCH_DEFINES += $(SPATCH_TEST_FLAGS)
GIT-SPATCH-DEFINES: FORCE
@FLAGS='$(TRACK_SPATCH_DEFINES)'; \
if test x"$$FLAGS" != x"`cat GIT-SPATCH-DEFINES 2>/dev/null`" ; then \
echo >&2 " * new spatch flags"; \
echo "$$FLAGS" >GIT-SPATCH-DEFINES; \
fi
include config.mak.uname
-include config.mak.autogen
-include config.mak
ifdef DEVELOPER
include config.mak.dev
endif
RUST_SOURCES += src/lib.rs
RUST_SOURCES += src/varint.rs
GIT-VERSION-FILE: FORCE
@OLD=$$(cat $@ 2>/dev/null || :) && \
@ -1507,6 +1542,14 @@ endif
ALL_CFLAGS = $(DEVELOPER_CFLAGS) $(CPPFLAGS) $(CFLAGS) $(CFLAGS_APPEND)
ALL_LDFLAGS = $(LDFLAGS) $(LDFLAGS_APPEND)
ifdef WITH_RUST
BASIC_CFLAGS += -DWITH_RUST
GITLIBS += $(RUST_LIB)
ifeq ($(uname_S),Windows)
EXTLIBS += -luserenv
endif
endif
ifdef SANITIZE
SANITIZERS := $(foreach flag,$(subst $(comma),$(space),$(SANITIZE)),$(flag))
BASIC_CFLAGS += -fsanitize=$(SANITIZE) -fno-sanitize-recover=$(SANITIZE)
@ -2921,6 +2964,12 @@ scalar$X: scalar.o GIT-LDFLAGS $(GITLIBS)
$(LIB_FILE): $(LIB_OBJS)
$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
$(RUST_LIB): Cargo.toml $(RUST_SOURCES)
$(QUIET_CARGO)cargo build $(CARGO_ARGS)
.PHONY: rust
rust: $(RUST_LIB)
$(XDIFF_LIB): $(XDIFF_OBJS)
$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
@ -3771,6 +3820,7 @@ clean: profile-clean coverage-clean cocciclean
$(RM) $(FUZZ_PROGRAMS)
$(RM) $(SP_OBJ)
$(RM) $(HCC)
$(RM) -r Cargo.lock target/
$(RM) version-def.h
$(RM) -r $(dep_dirs) $(compdb_dir) compile_commands.json
$(RM) $(test_bindir_programs)

View File

@ -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,
@ -30,8 +32,12 @@ alpine-*)
bash cvs gnupg perl-cgi perl-dbd-sqlite perl-io-tty >/dev/null
;;
fedora-*|almalinux-*)
case "$jobname" in
*-meson)
MESON_DEPS="meson ninja";;
esac
dnf -yq update >/dev/null &&
dnf -yq install shadow-utils sudo make gcc findutils diffutils perl python3 gawk gettext zlib-devel expat-devel openssl-devel curl-devel pcre2-devel >/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
;;
ubuntu-*|i386/ubuntu-*|debian-*)
# Required so that apt doesn't wait for user input on certain packages.
@ -58,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 \
libsecret-1-dev libpcre2-dev meson ninja-build pkg-config cargo \
${CC_PACKAGE:-${CC:-gcc}} $PYTHON_PACKAGE
case "$distro" in
@ -116,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" ||

View File

@ -5,11 +5,12 @@
. ${0%/*}/lib.sh
run_tests=t
case "$jobname" in
linux-breaking-changes)
fedora-breaking-changes-musl|linux-breaking-changes)
export WITH_BREAKING_CHANGES=YesPlease
export WITH_RUST=YesPlease
MESONFLAGS="$MESONFLAGS -Dbreaking_changes=true"
MESONFLAGS="$MESONFLAGS -Drust=enabled"
;;
linux-TEST-vars)
export OPENSSL_SHA1_UNSAFE=YesPlease
@ -35,12 +36,6 @@ linux-sha256)
linux-reftable|linux-reftable-leaks|osx-reftable)
export GIT_TEST_DEFAULT_REF_FORMAT=reftable
;;
pedantic)
# Don't run the tests; we only care about whether Git can be
# built.
export DEVOPTS=pedantic
run_tests=
;;
esac
case "$jobname" in
@ -53,21 +48,15 @@ case "$jobname" in
-Dtest_output_directory="${TEST_OUTPUT_DIRECTORY:-$(pwd)/t}" \
$MESONFLAGS
group "Build" meson compile -C build --
if test -n "$run_tests"
then
group "Run tests" meson test -C build --print-errorlogs --test-args="$GIT_TEST_OPTS" || (
./t/aggregate-results.sh "${TEST_OUTPUT_DIRECTORY:-t}/test-results"
handle_failed_tests
)
fi
group "Run tests" meson test -C build --print-errorlogs --test-args="$GIT_TEST_OPTS" || (
./t/aggregate-results.sh "${TEST_OUTPUT_DIRECTORY:-t}/test-results"
handle_failed_tests
)
;;
*)
group Build make
if test -n "$run_tests"
then
group "Run tests" make test ||
handle_failed_tests
fi
group "Run tests" make test ||
handle_failed_tests
;;
esac

22
ci/run-rust-checks.sh Executable file
View File

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

18
dir.c
View File

@ -3579,7 +3579,8 @@ static void write_one_dir(struct untracked_cache_dir *untracked,
struct stat_data stat_data;
struct strbuf *out = &wd->out;
unsigned char intbuf[16];
unsigned int intlen, value;
unsigned int value;
uint8_t intlen;
int i = wd->index++;
/*
@ -3632,7 +3633,7 @@ void write_untracked_extension(struct strbuf *out, struct untracked_cache *untra
struct ondisk_untracked_cache *ouc;
struct write_data wd;
unsigned char varbuf[16];
int varint_len;
uint8_t varint_len;
const unsigned hashsz = the_hash_algo->rawsz;
CALLOC_ARRAY(ouc, 1);
@ -3738,7 +3739,7 @@ static int read_one_dir(struct untracked_cache_dir **untracked_,
struct untracked_cache_dir ud, *untracked;
const unsigned char *data = rd->data, *end = rd->end;
const unsigned char *eos;
unsigned int value;
uint64_t value;
int i;
memset(&ud, 0, sizeof(ud));
@ -3830,7 +3831,8 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long
struct read_data rd;
const unsigned char *next = data, *end = (const unsigned char *)data + sz;
const char *ident;
int ident_len;
uint64_t ident_len;
uint64_t varint_len;
ssize_t len;
const char *exclude_per_dir;
const unsigned hashsz = the_hash_algo->rawsz;
@ -3867,8 +3869,8 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long
if (next >= end)
goto done2;
len = decode_varint(&next);
if (next > end || len == 0)
varint_len = decode_varint(&next);
if (next > end || varint_len == 0)
goto done2;
rd.valid = ewah_new();
@ -3877,9 +3879,9 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long
rd.data = next;
rd.end = end;
rd.index = 0;
ALLOC_ARRAY(rd.ucd, len);
ALLOC_ARRAY(rd.ucd, varint_len);
if (read_one_dir(&uc->root, &rd) || rd.index != len)
if (read_one_dir(&uc->root, &rd) || rd.index != varint_len)
goto done;
next = rd.data;

6
help.c
View File

@ -791,6 +791,12 @@ void get_version_info(struct strbuf *buf, int show_build_options)
strbuf_addf(buf, "shell-path: %s\n", SHELL_PATH);
/* NEEDSWORK: also save and output GIT-BUILD_OPTIONS? */
#if defined WITH_RUST
strbuf_addstr(buf, "rust: enabled\n");
#else
strbuf_addstr(buf, "rust: disabled\n");
#endif
if (fsmonitor_ipc__is_supported())
strbuf_addstr(buf, "feature: fsmonitor--daemon\n");
#if defined LIBCURL_VERSION

View File

@ -220,7 +220,7 @@ project('git', 'c',
# learned to define __STDC_VERSION__ with C11 and later. We thus require
# GNU C99 and fall back to C11. Meson only learned to handle the fallback
# with version 1.3.0, so on older versions we use GNU C99 unconditionally.
default_options: meson.version().version_compare('>=1.3.0') ? ['c_std=gnu99,c11'] : ['c_std=gnu99'],
default_options: meson.version().version_compare('>=1.3.0') ? ['rust_std=2018', 'c_std=gnu99,c11'] : ['rust_std=2018', 'c_std=gnu99'],
)
fs = import('fs')
@ -522,7 +522,6 @@ libgit_sources = [
'usage.c',
'userdiff.c',
'utf8.c',
'varint.c',
'version.c',
'versioncmp.c',
'walker.c',
@ -1703,6 +1702,21 @@ 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())
if rust_option.allowed()
subdir('src')
libgit_c_args += '-DWITH_RUST'
if host_machine.system() == 'windows'
libgit_dependencies += compiler.find_library('userenv')
endif
else
libgit_sources += [
'varint.c',
]
endif
libgit = declare_dependency(
link_with: static_library('git',
sources: libgit_sources,
@ -2249,6 +2263,7 @@ summary({
'pcre2': pcre2,
'perl': perl_features_enabled,
'python': target_python.found(),
'rust': rust_option.allowed(),
}, section: 'Auto-detected features', bool_yn: true)
summary({

View File

@ -71,6 +71,8 @@ option('zlib_backend', type: 'combo', choices: ['auto', 'zlib', 'zlib-ng'], valu
# Build tweaks.
option('breaking_changes', type: 'boolean', value: false,
description: 'Enable upcoming breaking changes.')
option('rust', type: 'feature', value: 'auto',
description: 'Enable building with Rust.')
option('macos_use_homebrew_gettext', type: 'boolean', value: true,
description: 'Use gettext from Homebrew instead of the slightly-broken system-provided one.')

View File

@ -1806,7 +1806,7 @@ static struct cache_entry *create_from_disk(struct mem_pool *ce_mem_pool,
if (expand_name_field) {
const unsigned char *cp = (const unsigned char *)name;
size_t strip_len, previous_len;
uint64_t strip_len, previous_len;
/* If we're at the beginning of a block, ignore the previous name */
strip_len = decode_varint(&cp);
@ -2654,8 +2654,10 @@ static int ce_write_entry(struct hashfile *f, struct cache_entry *ce,
hashwrite(f, ce->name, len);
hashwrite(f, padding, align_padding_size(size, len));
} else {
int common, to_remove, prefix_size;
int common, to_remove;
uint8_t prefix_size;
unsigned char to_remove_vi[16];
for (common = 0;
(common < previous_name->len &&
ce->name[common] &&

View File

@ -56,6 +56,7 @@ ifndef V
QUIET_MKDIR_P_PARENT = @echo ' ' MKDIR -p $(@D);
## Used in "Makefile"
QUIET_CARGO = @echo ' ' CARGO $@;
QUIET_CC = @echo ' ' CC $@;
QUIET_AR = @echo ' ' AR $@;
QUIET_LINK = @echo ' ' LINK $@;

39
src/cargo-meson.sh Executable file
View File

@ -0,0 +1,39 @@
#!/bin/sh
if test "$#" -lt 2
then
exit 1
fi
SOURCE_DIR="$1"
BUILD_DIR="$2"
BUILD_TYPE=debug
shift 2
for arg
do
case "$arg" in
--release)
BUILD_TYPE=release;;
esac
done
cargo build --lib --quiet --manifest-path="$SOURCE_DIR/Cargo.toml" --target-dir="$BUILD_DIR" "$@"
RET=$?
if test $RET -ne 0
then
exit $RET
fi
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/$LIBNAME" "$BUILD_DIR/libgitcore.a"
fi

1
src/lib.rs Normal file
View File

@ -0,0 +1 @@
pub mod varint;

41
src/meson.build Normal file
View File

@ -0,0 +1,41 @@
libgit_rs_sources = [
'lib.rs',
'varint.rs',
]
# Unfortunately we must use a wrapper command to move the output file into the
# current build directory. This can fixed once `cargo build --artifact-dir`
# stabilizes. See https://github.com/rust-lang/cargo/issues/6790 for that
# effort.
cargo_command = [
shell,
meson.current_source_dir() / 'cargo-meson.sh',
meson.project_source_root(),
meson.current_build_dir(),
]
if get_option('buildtype') == 'release'
cargo_command += '--release'
endif
libgit_rs = custom_target('git_rs',
input: libgit_rs_sources + [
meson.project_source_root() / 'Cargo.toml',
],
output: 'libgitcore.a',
command: cargo_command,
)
libgit_dependencies += declare_dependency(link_with: libgit_rs)
if get_option('tests')
test('rust', cargo,
args: [
'test',
'--manifest-path',
meson.project_source_root() / 'Cargo.toml',
'--target-dir',
meson.current_build_dir() / 'target',
],
timeout: 0,
protocol: 'rust',
)
endif

107
src/varint.rs Normal file
View File

@ -0,0 +1,107 @@
/// Decode the variable-length integer stored in `bufp` and return the decoded value.
///
/// Returns 0 in case the decoded integer would overflow u64::MAX.
///
/// # Safety
///
/// The buffer must be NUL-terminated to ensure safety.
#[no_mangle]
pub unsafe extern "C" fn decode_varint(bufp: *mut *const u8) -> u64 {
let mut buf = *bufp;
let mut c = *buf;
let mut val = u64::from(c & 127);
buf = buf.add(1);
while (c & 128) != 0 {
val = val.wrapping_add(1);
if val == 0 || val.leading_zeros() < 7 {
return 0; // overflow
}
c = *buf;
buf = buf.add(1);
val = (val << 7) + u64::from(c & 127);
}
*bufp = buf;
val
}
/// Encode `value` into `buf` as a variable-length integer unless `buf` is null.
///
/// Returns the number of bytes written, or, if `buf` is null, the number of bytes that would be
/// written to encode the integer.
///
/// # Safety
///
/// `buf` must either be null or point to at least 16 bytes of memory.
#[no_mangle]
pub unsafe extern "C" fn encode_varint(value: u64, buf: *mut u8) -> u8 {
let mut varint: [u8; 16] = [0; 16];
let mut pos = varint.len() - 1;
varint[pos] = (value & 127) as u8;
let mut value = value >> 7;
while value != 0 {
pos -= 1;
value -= 1;
varint[pos] = 128 | (value & 127) as u8;
value >>= 7;
}
if !buf.is_null() {
std::ptr::copy_nonoverlapping(varint.as_ptr().add(pos), buf, varint.len() - pos);
}
(varint.len() - pos) as u8
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_decode_varint() {
unsafe {
assert_eq!(decode_varint(&mut [0x00].as_slice().as_ptr()), 0);
assert_eq!(decode_varint(&mut [0x01].as_slice().as_ptr()), 1);
assert_eq!(decode_varint(&mut [0x7f].as_slice().as_ptr()), 127);
assert_eq!(decode_varint(&mut [0x80, 0x00].as_slice().as_ptr()), 128);
assert_eq!(decode_varint(&mut [0x80, 0x01].as_slice().as_ptr()), 129);
assert_eq!(decode_varint(&mut [0x80, 0x7f].as_slice().as_ptr()), 255);
// Overflows are expected to return 0.
assert_eq!(decode_varint(&mut [0x88; 16].as_slice().as_ptr()), 0);
}
}
#[test]
fn test_encode_varint() {
unsafe {
let mut varint: [u8; 16] = [0; 16];
assert_eq!(encode_varint(0, std::ptr::null_mut()), 1);
assert_eq!(encode_varint(0, varint.as_mut_slice().as_mut_ptr()), 1);
assert_eq!(varint, [0; 16]);
assert_eq!(encode_varint(10, varint.as_mut_slice().as_mut_ptr()), 1);
assert_eq!(varint, [10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(encode_varint(127, varint.as_mut_slice().as_mut_ptr()), 1);
assert_eq!(varint, [127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(encode_varint(128, varint.as_mut_slice().as_mut_ptr()), 2);
assert_eq!(varint, [128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(encode_varint(129, varint.as_mut_slice().as_mut_ptr()), 2);
assert_eq!(varint, [128, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(encode_varint(255, varint.as_mut_slice().as_mut_ptr()), 2);
assert_eq!(varint, [128, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
}
}
}

View File

@ -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.1 >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' '

View File

@ -1,11 +1,11 @@
#include "git-compat-util.h"
#include "varint.h"
uintmax_t decode_varint(const unsigned char **bufp)
uint64_t decode_varint(const unsigned char **bufp)
{
const unsigned char *buf = *bufp;
unsigned char c = *buf++;
uintmax_t val = c & 127;
uint64_t val = c & 127;
while (c & 128) {
val += 1;
if (!val || MSB(val, 7))
@ -17,7 +17,7 @@ uintmax_t decode_varint(const unsigned char **bufp)
return val;
}
int encode_varint(uintmax_t value, unsigned char *buf)
uint8_t encode_varint(uint64_t value, unsigned char *buf)
{
unsigned char varint[16];
unsigned pos = sizeof(varint) - 1;

View File

@ -1,7 +1,7 @@
#ifndef VARINT_H
#define VARINT_H
int encode_varint(uintmax_t, unsigned char *);
uintmax_t decode_varint(const unsigned char **);
uint8_t encode_varint(uint64_t, unsigned char *);
uint64_t decode_varint(const unsigned char **);
#endif /* VARINT_H */