Merge branch 'ps/rust-balloon' into jch

Dip our toes a bit to (optionally) use Rust implemented helper
called from our C code.

* ps/rust-balloon:
  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
seen
Junio C Hamano 2025-10-06 10:25:21 -07:00
commit b6d0473df7
20 changed files with 410 additions and 131 deletions

View File

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

2
.gitignore vendored
View File

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

View File

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

9
Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "gitcore"
version = "0.1.0"
edition = "2018"

[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 matches the default branch name used in new repositories by many of the
big Git forges. 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 === Removals


* Support for grafting commits has long been superseded by git-replace(1). * Support for grafting commits has long been superseded by git-replace(1).

214
Makefile
View File

@ -483,6 +483,14 @@ include shared.mak
# Define LIBPCREDIR=/foo/bar if your PCRE header and library files are # Define LIBPCREDIR=/foo/bar if your PCRE header and library files are
# in /foo/bar/include and /foo/bar/lib directories. # 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 and SHA-256 defines ==
# #
# === SHA-1 backend === # === SHA-1 backend ===
@ -683,6 +691,7 @@ OBJECTS =
OTHER_PROGRAMS = OTHER_PROGRAMS =
PROGRAM_OBJS = PROGRAM_OBJS =
PROGRAMS = PROGRAMS =
RUST_SOURCES =
EXCLUDED_PROGRAMS = EXCLUDED_PROGRAMS =
SCRIPT_PERL = SCRIPT_PERL =
SCRIPT_PYTHON = SCRIPT_PYTHON =
@ -920,6 +929,108 @@ TEST_SHELL_PATH = $(SHELL_PATH)
LIB_FILE = libgit.a LIB_FILE = libgit.a
XDIFF_LIB = xdiff/lib.a XDIFF_LIB = xdiff/lib.a
REFTABLE_LIB = reftable/libreftable.a REFTABLE_LIB = reftable/libreftable.a
ifdef DEBUG
RUST_LIB = target/debug/libgitcore.a
else
RUST_LIB = target/release/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 += command-list.h
GENERATED_H += config-list.h GENERATED_H += config-list.h
@ -1198,7 +1309,9 @@ LIB_OBJS += urlmatch.o
LIB_OBJS += usage.o LIB_OBJS += usage.o
LIB_OBJS += userdiff.o LIB_OBJS += userdiff.o
LIB_OBJS += utf8.o LIB_OBJS += utf8.o
ifndef WITH_RUST
LIB_OBJS += varint.o LIB_OBJS += varint.o
endif
LIB_OBJS += version.o LIB_OBJS += version.o
LIB_OBJS += versioncmp.o LIB_OBJS += versioncmp.o
LIB_OBJS += walker.o LIB_OBJS += walker.o
@ -1390,93 +1503,8 @@ CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o


UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o


# xdiff and reftable libs may in turn depend on what is in libgit.a RUST_SOURCES += src/lib.rs
GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(LIB_FILE) RUST_SOURCES += src/varint.rs
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


GIT-VERSION-FILE: FORCE GIT-VERSION-FILE: FORCE
@OLD=$$(cat $@ 2>/dev/null || :) && \ @OLD=$$(cat $@ 2>/dev/null || :) && \
@ -1507,6 +1535,11 @@ endif
ALL_CFLAGS = $(DEVELOPER_CFLAGS) $(CPPFLAGS) $(CFLAGS) $(CFLAGS_APPEND) ALL_CFLAGS = $(DEVELOPER_CFLAGS) $(CPPFLAGS) $(CFLAGS) $(CFLAGS_APPEND)
ALL_LDFLAGS = $(LDFLAGS) $(LDFLAGS_APPEND) ALL_LDFLAGS = $(LDFLAGS) $(LDFLAGS_APPEND)


ifdef WITH_RUST
BASIC_CFLAGS += -DWITH_RUST
GITLIBS += $(RUST_LIB)
endif

ifdef SANITIZE ifdef SANITIZE
SANITIZERS := $(foreach flag,$(subst $(comma),$(space),$(SANITIZE)),$(flag)) SANITIZERS := $(foreach flag,$(subst $(comma),$(space),$(SANITIZE)),$(flag))
BASIC_CFLAGS += -fsanitize=$(SANITIZE) -fno-sanitize-recover=$(SANITIZE) BASIC_CFLAGS += -fsanitize=$(SANITIZE) -fno-sanitize-recover=$(SANITIZE)
@ -2921,6 +2954,12 @@ scalar$X: scalar.o GIT-LDFLAGS $(GITLIBS)
$(LIB_FILE): $(LIB_OBJS) $(LIB_FILE): $(LIB_OBJS)
$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^ $(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) $(XDIFF_LIB): $(XDIFF_OBJS)
$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^ $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^


@ -3771,6 +3810,7 @@ clean: profile-clean coverage-clean cocciclean
$(RM) $(FUZZ_PROGRAMS) $(RM) $(FUZZ_PROGRAMS)
$(RM) $(SP_OBJ) $(RM) $(SP_OBJ)
$(RM) $(HCC) $(RM) $(HCC)
$(RM) -r Cargo.lock target/
$(RM) version-def.h $(RM) version-def.h
$(RM) -r $(dep_dirs) $(compdb_dir) compile_commands.json $(RM) -r $(dep_dirs) $(compdb_dir) compile_commands.json
$(RM) $(test_bindir_programs) $(RM) $(test_bindir_programs)

View File

@ -30,8 +30,12 @@ alpine-*)
bash cvs gnupg perl-cgi perl-dbd-sqlite perl-io-tty >/dev/null bash cvs gnupg perl-cgi perl-dbd-sqlite perl-io-tty >/dev/null
;; ;;
fedora-*|almalinux-*) fedora-*|almalinux-*)
case "$jobname" in
*-meson)
MESON_DEPS="meson ninja";;
esac
dnf -yq update >/dev/null && 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-*) ubuntu-*|i386/ubuntu-*|debian-*)
# Required so that apt doesn't wait for user input on certain packages. # Required so that apt doesn't wait for user input on certain packages.
@ -58,7 +62,7 @@ ubuntu-*|i386/ubuntu-*|debian-*)
make libssl-dev libcurl4-openssl-dev libexpat-dev wget sudo default-jre \ make libssl-dev libcurl4-openssl-dev libexpat-dev wget sudo default-jre \
tcl tk gettext zlib1g-dev perl-modules liberror-perl libauthen-sasl-perl \ 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 \ 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 ${CC_PACKAGE:-${CC:-gcc}} $PYTHON_PACKAGE


case "$distro" in case "$distro" in

View File

@ -5,11 +5,12 @@


. ${0%/*}/lib.sh . ${0%/*}/lib.sh


run_tests=t

case "$jobname" in case "$jobname" in
linux-breaking-changes) fedora-breaking-changes-musl|linux-breaking-changes)
export WITH_BREAKING_CHANGES=YesPlease export WITH_BREAKING_CHANGES=YesPlease
export WITH_RUST=YesPlease
MESONFLAGS="$MESONFLAGS -Dbreaking_changes=true"
MESONFLAGS="$MESONFLAGS -Drust=enabled"
;; ;;
linux-TEST-vars) linux-TEST-vars)
export OPENSSL_SHA1_UNSAFE=YesPlease export OPENSSL_SHA1_UNSAFE=YesPlease
@ -35,12 +36,6 @@ linux-sha256)
linux-reftable|linux-reftable-leaks|osx-reftable) linux-reftable|linux-reftable-leaks|osx-reftable)
export GIT_TEST_DEFAULT_REF_FORMAT=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 esac


case "$jobname" in case "$jobname" in
@ -53,21 +48,15 @@ case "$jobname" in
-Dtest_output_directory="${TEST_OUTPUT_DIRECTORY:-$(pwd)/t}" \ -Dtest_output_directory="${TEST_OUTPUT_DIRECTORY:-$(pwd)/t}" \
$MESONFLAGS $MESONFLAGS
group "Build" meson compile -C build -- 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" || ( group "Run tests" meson test -C build --print-errorlogs --test-args="$GIT_TEST_OPTS" || (
./t/aggregate-results.sh "${TEST_OUTPUT_DIRECTORY:-t}/test-results" ./t/aggregate-results.sh "${TEST_OUTPUT_DIRECTORY:-t}/test-results"
handle_failed_tests handle_failed_tests
) )
fi
;; ;;
*) *)
group Build make group Build make
if test -n "$run_tests"
then
group "Run tests" make test || group "Run tests" make test ||
handle_failed_tests handle_failed_tests
fi
;; ;;
esac esac



18
dir.c
View File

@ -3607,7 +3607,8 @@ static void write_one_dir(struct untracked_cache_dir *untracked,
struct stat_data stat_data; struct stat_data stat_data;
struct strbuf *out = &wd->out; struct strbuf *out = &wd->out;
unsigned char intbuf[16]; unsigned char intbuf[16];
unsigned int intlen, value; unsigned int value;
uint8_t intlen;
int i = wd->index++; int i = wd->index++;


/* /*
@ -3660,7 +3661,7 @@ void write_untracked_extension(struct strbuf *out, struct untracked_cache *untra
struct ondisk_untracked_cache *ouc; struct ondisk_untracked_cache *ouc;
struct write_data wd; struct write_data wd;
unsigned char varbuf[16]; unsigned char varbuf[16];
int varint_len; uint8_t varint_len;
const unsigned hashsz = the_hash_algo->rawsz; const unsigned hashsz = the_hash_algo->rawsz;


CALLOC_ARRAY(ouc, 1); CALLOC_ARRAY(ouc, 1);
@ -3766,7 +3767,7 @@ static int read_one_dir(struct untracked_cache_dir **untracked_,
struct untracked_cache_dir ud, *untracked; struct untracked_cache_dir ud, *untracked;
const unsigned char *data = rd->data, *end = rd->end; const unsigned char *data = rd->data, *end = rd->end;
const unsigned char *eos; const unsigned char *eos;
unsigned int value; uint64_t value;
int i; int i;


memset(&ud, 0, sizeof(ud)); memset(&ud, 0, sizeof(ud));
@ -3858,7 +3859,8 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long
struct read_data rd; struct read_data rd;
const unsigned char *next = data, *end = (const unsigned char *)data + sz; const unsigned char *next = data, *end = (const unsigned char *)data + sz;
const char *ident; const char *ident;
int ident_len; uint64_t ident_len;
uint64_t varint_len;
ssize_t len; ssize_t len;
const char *exclude_per_dir; const char *exclude_per_dir;
const unsigned hashsz = the_hash_algo->rawsz; const unsigned hashsz = the_hash_algo->rawsz;
@ -3895,8 +3897,8 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long
if (next >= end) if (next >= end)
goto done2; goto done2;


len = decode_varint(&next); varint_len = decode_varint(&next);
if (next > end || len == 0) if (next > end || varint_len == 0)
goto done2; goto done2;


rd.valid = ewah_new(); rd.valid = ewah_new();
@ -3905,9 +3907,9 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long
rd.data = next; rd.data = next;
rd.end = end; rd.end = end;
rd.index = 0; 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; goto done;


next = rd.data; 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); strbuf_addf(buf, "shell-path: %s\n", SHELL_PATH);
/* NEEDSWORK: also save and output GIT-BUILD_OPTIONS? */ /* 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()) if (fsmonitor_ipc__is_supported())
strbuf_addstr(buf, "feature: fsmonitor--daemon\n"); strbuf_addstr(buf, "feature: fsmonitor--daemon\n");
#if defined LIBCURL_VERSION #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 # 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 # 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. # 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') fs = import('fs')
@ -522,7 +522,6 @@ libgit_sources = [
'usage.c', 'usage.c',
'userdiff.c', 'userdiff.c',
'utf8.c', 'utf8.c',
'varint.c',
'version.c', 'version.c',
'versioncmp.c', 'versioncmp.c',
'walker.c', 'walker.c',
@ -1703,6 +1702,17 @@ version_def_h = custom_target(
) )
libgit_sources += version_def_h 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'
else
libgit_sources += [
'varint.c',
]
endif

libgit = declare_dependency( libgit = declare_dependency(
link_with: static_library('git', link_with: static_library('git',
sources: libgit_sources, sources: libgit_sources,
@ -2249,6 +2259,7 @@ summary({
'pcre2': pcre2, 'pcre2': pcre2,
'perl': perl_features_enabled, 'perl': perl_features_enabled,
'python': target_python.found(), 'python': target_python.found(),
'rust': rust_option.allowed(),
}, section: 'Auto-detected features', bool_yn: true) }, section: 'Auto-detected features', bool_yn: true)


summary({ summary({

View File

@ -71,6 +71,8 @@ option('zlib_backend', type: 'combo', choices: ['auto', 'zlib', 'zlib-ng'], valu
# Build tweaks. # Build tweaks.
option('breaking_changes', type: 'boolean', value: false, option('breaking_changes', type: 'boolean', value: false,
description: 'Enable upcoming breaking changes.') 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, option('macos_use_homebrew_gettext', type: 'boolean', value: true,
description: 'Use gettext from Homebrew instead of the slightly-broken system-provided one.') 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) { if (expand_name_field) {
const unsigned char *cp = (const unsigned char *)name; 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 */ /* If we're at the beginning of a block, ignore the previous name */
strip_len = decode_varint(&cp); 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, ce->name, len);
hashwrite(f, padding, align_padding_size(size, len)); hashwrite(f, padding, align_padding_size(size, len));
} else { } else {
int common, to_remove, prefix_size; int common, to_remove;
uint8_t prefix_size;
unsigned char to_remove_vi[16]; unsigned char to_remove_vi[16];

for (common = 0; for (common = 0;
(common < previous_name->len && (common < previous_name->len &&
ce->name[common] && ce->name[common] &&

View File

@ -56,6 +56,7 @@ ifndef V
QUIET_MKDIR_P_PARENT = @echo ' ' MKDIR -p $(@D); QUIET_MKDIR_P_PARENT = @echo ' ' MKDIR -p $(@D);


## Used in "Makefile" ## Used in "Makefile"
QUIET_CARGO = @echo ' ' CARGO $@;
QUIET_CC = @echo ' ' CC $@; QUIET_CC = @echo ' ' CC $@;
QUIET_AR = @echo ' ' AR $@; QUIET_AR = @echo ' ' AR $@;
QUIET_LINK = @echo ' ' LINK $@; QUIET_LINK = @echo ' ' LINK $@;

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

@ -0,0 +1,32 @@
#!/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

if ! cmp "$BUILD_DIR/$BUILD_TYPE/libgitcore.a" "$BUILD_DIR/libgitcore.a" >/dev/null 2>&1
then
cp "$BUILD_DIR/$BUILD_TYPE/libgitcore.a" "$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

92
src/varint.rs Normal file
View File

@ -0,0 +1,92 @@
#[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
}

#[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

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

View File

@ -1,7 +1,7 @@
#ifndef VARINT_H #ifndef VARINT_H
#define VARINT_H #define VARINT_H


int encode_varint(uintmax_t, unsigned char *); uint8_t encode_varint(uint64_t, unsigned char *);
uintmax_t decode_varint(const unsigned char **); uint64_t decode_varint(const unsigned char **);


#endif /* VARINT_H */ #endif /* VARINT_H */