Skip to contentSkip to conversation

RFC: Accelerate xdiff and begin its rustification

PATCH Superseded Ezekiel Newren via GitGitGadget 24 participants 204 messages Jul 17, 2025 – Nov 17, 2025 Page 1 of 2

Subsystems (4)
lore
Share & tools
lore
#0 lore raw

cover RFC: Accelerate xdiff and begin its rustification

This series accelerates xdiff by 5-19%.

It also introduces Rust as a hard dependency.

…and it doesn’t yet pass a couple of the github workflows; hints from Windows experts, and opinions on ambiguous primitives would be appreciated (see below).

This is just the beginning of many patches that I have to convert portions of, maybe eventually all of, xdiff to Rust. While working on that conversion, I found several ways to clarify the code, along with some optimizations.

So...

This obviously raises the question of whether we are ready to accept a hard dependency on Rust. Previous discussions on the mailing list and at Git Merge 2024 have not answered that question. If not now, will we be willing to accept such a hard dependency later? And what route do we want to take to get there?

About the optimizations in this series:

1. xdiff currently uses DJB2a for hashing (even though it is not explicitly named as such). This is an older hashing algorithm, and modern alternatives are superior. I chose xxhash because it’s faster, more collision resistant, and designed to be a standard. Other hash algorithms like aHash, MurMurHash, SipHash, and Fnv1a were considered, but my local testing made me feel like xxhash was the best choice for usage in xdiff.

2. In support of switching to xxhash, parsing and hashing were split into separate steps. And it turns out that memchr() is faster for parsing than character-by-character iteration.

About the workflow builds/tests that aren’t working with this series:

1. Windows fails to build. I don’t know which rust toolchain is even correct for this or if multiple are needed. Example failed build: https://github.com/git/git/actions/runs/16353209191

2. I386/ubuntu:focal will build, but fails the tests. The kernel reports the bitness as 64 despite the container being 32. I believe the issue is that C uses ambiguous primitives (which differ in size between platforms). The new code should use unambiguous primitives from Rust (u32, u64, etc.) rather than perpetuating ambiguous primitive types. Since the current xdiff API hardcodes the ambiguous types, though, those places will need to be migrated to unambiguous primitives. Much of the C code needs a slight refactor to be compatible with the Rust FFI and usually requires converting ambiguous to unambiguous types. What does this community think of this approach?

My brother (Elijah, cc’ed) has been guiding and reviewing my work here.

Ezekiel Newren (7):
xdiff: introduce rust
xdiff/xprepare: remove superfluous forward declarations
xdiff: delete unnecessary fields from xrecord_t and xdfile_t
xdiff: make fields of xrecord_t Rust friendly
xdiff: separate parsing lines from hashing them
xdiff: conditionally use Rust's implementation of xxhash
github_workflows: install rust

 .github/workflows/main.yml |   1 +
 .gitignore                 |   1 +
 Makefile                   |  60 +++++++---
 build_rust.sh              |  59 ++++++++++
 ci/install-dependencies.sh |  14 +--
 ci/install-rust.sh         |  33 ++++++
 ci/lib.sh                  |   8 ++
 ci/make-test-artifacts.sh  |   7 ++
 ci/run-build-and-tests.sh  |  10 ++
 git-compat-util.h          |  17 +++
 meson.build                |  40 +++++--
 rust/Cargo.lock            |  21 ++++
 rust/Cargo.toml            |   6 +
 rust/interop/Cargo.toml    |  14 +++
 rust/interop/src/lib.rs    |   0
 rust/xdiff/Cargo.toml      |  16 +++
 rust/xdiff/src/lib.rs      |   7 ++
 xdiff/xdiffi.c             |   8 +-
 xdiff/xemit.c              |   2 +-
 xdiff/xmerge.c             |  14 +--
 xdiff/xpatience.c          |   2 +-
 xdiff/xprepare.c           | 226 ++++++++++++++++++-------------------
 xdiff/xtypes.h             |   9 +-
 xdiff/xutils.c             |   4 +-
 24 files changed, 414 insertions(+), 165 deletions(-)
 create mode 100755 build_rust.sh
 create mode 100644 ci/install-rust.sh
 create mode 100644 rust/Cargo.lock
 create mode 100644 rust/Cargo.toml
 create mode 100644 rust/interop/Cargo.toml
 create mode 100644 rust/interop/src/lib.rs
 create mode 100644 rust/xdiff/Cargo.toml
 create mode 100644 rust/xdiff/src/lib.rs

base-commit: 16bd9f20a403117f2e0d9bcda6c6e621d3763e77
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1980%2Fezekielnewren%2Fxdiff_rust_speedup-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1980/ezekielnewren/xdiff_rust_speedup-v1
Pull-Request: https://github.com/git/git/pull/1980

Signature · 1 line
gitgitgadget
#1 lore raw

PATCH 1/7 xdiff: introduce rust

From: Ezekiel Newren <[email protected]>

Upcoming patches will accelerate and simplify xdiff, while also porting parts of it to Rust. In preparation, add some stubs and setup the Rust build. For now, it is easier to let cargo build rust and have make or meson merely link against the static library that cargo builds. In line with ongoing libification efforts, use multiple crates to allow more modularity on the Rust side. xdiff is the crate that this series will focus on, but we also introduce the interop crate for future patch series.

In order to facilitate interoperability between C and Rust, introduce C definitions for Rust primitive types in git-compat-util.h.

---
 Makefile                | 20 +++++++++++++++++++-
 git-compat-util.h       | 17 +++++++++++++++++
 meson.build             | 32 ++++++++++++++++++++++++++++++++
 rust/Cargo.lock         | 14 ++++++++++++++
 rust/Cargo.toml         |  6 ++++++
 rust/interop/Cargo.toml | 14 ++++++++++++++
 rust/interop/src/lib.rs |  0
 rust/xdiff/Cargo.toml   | 15 +++++++++++++++
 rust/xdiff/src/lib.rs   |  0
 9 files changed, 117 insertions(+), 1 deletion(-)
 create mode 100644 rust/Cargo.lock
 create mode 100644 rust/Cargo.toml
 create mode 100644 rust/interop/Cargo.toml
 create mode 100644 rust/interop/src/lib.rs
 create mode 100644 rust/xdiff/Cargo.toml
 create mode 100644 rust/xdiff/src/lib.rs
Makefile +19 -1
index 70d1543b6b86..db39e6e1c28e 100644
@@ -919,6 +919,11 @@ TEST_SHELL_PATH = $(SHELL_PATH)
 
 LIB_FILE = libgit.a
 XDIFF_LIB = xdiff/lib.a
+ifeq ($(DEBUG), 1)
+RUST_LIB = rust/target/debug/libxdiff.a
+else
+RUST_LIB = rust/target/release/libxdiff.a
+endif
 REFTABLE_LIB = reftable/libreftable.a
 
 GENERATED_H += command-list.h
@@ -1392,6 +1397,8 @@ UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o
 GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(LIB_FILE)
 EXTLIBS =
 
+GITLIBS += $(RUST_LIB)
+
 GIT_USER_AGENT = git/$(GIT_VERSION)
 
 ifeq ($(wildcard sha1collisiondetection/lib/sha1.h),sha1collisiondetection/lib/sha1.h)
@@ -2925,6 +2932,14 @@ $(LIB_FILE): $(LIB_OBJS)
 $(XDIFF_LIB): $(XDIFF_OBJS)
 	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
 
+.PHONY: $(RUST_LIB)
+$(RUST_LIB):
+ifeq ($(DEBUG), 1)
+	cd rust && RUSTFLAGS="-Aunused_imports -Adead_code" cargo build --verbose
+else
+	cd rust && RUSTFLAGS="-Aunused_imports -Adead_code" cargo build --verbose --release
+endif
+
 $(REFTABLE_LIB): $(REFTABLE_OBJS)
 	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
 
@@ -3756,7 +3771,10 @@ cocciclean:
 	$(RM) -r .build/contrib/coccinelle
 	$(RM) contrib/coccinelle/*.cocci.patch
 
-clean: profile-clean coverage-clean cocciclean
+rustclean:
+	cd rust && cargo clean
+
+clean: profile-clean coverage-clean cocciclean rustclean
 	$(RM) -r .build $(UNIT_TEST_BIN)
 	$(RM) GIT-TEST-SUITES
 	$(RM) po/git.pot po/git-core.pot
git-compat-util.h +17 -0
index 4678e21c4cb8..82dc99764ac0 100644
@@ -196,6 +196,23 @@ static inline int is_xplatform_dir_sep(int c)
 #include "compat/msvc.h"
 #endif
 
+/* rust types */
+typedef uint8_t   u8;
+typedef uint16_t  u16;
+typedef uint32_t  u32;
+typedef uint64_t  u64;
+
+typedef int8_t    i8;
+typedef int16_t   i16;
+typedef int32_t   i32;
+typedef int64_t   i64;
+
+typedef float     f32;
+typedef double    f64;
+
+typedef size_t    usize;
+typedef ptrdiff_t isize;
+
 /* used on Mac OS X */
 #ifdef PRECOMPOSE_UNICODE
 #include "compat/precompose_utf8.h"
meson.build +32 -0
index 596f5ac7110e..2d8da17f6515 100644
@@ -267,6 +267,36 @@ version_gen_environment.set('GIT_DATE', get_option('build_date'))
 version_gen_environment.set('GIT_USER_AGENT', get_option('user_agent'))
 version_gen_environment.set('GIT_VERSION', get_option('version'))
 
+if get_option('optimization') in ['2', '3', 's', 'z']
+  rust_target = 'release'
+  rust_args = ['--release']
+  rustflags = '-Aunused_imports -Adead_code'
+else
+  rust_target = 'debug'
+  rust_args = []
+  rustflags = '-Aunused_imports -Adead_code -C debuginfo=2 -C opt-level=1 -C force-frame-pointers=yes'
+endif
+
+
+rust_leaf = custom_target('rust_leaf',
+  output: 'libxdiff.a',
+  build_by_default: true,
+  build_always_stale: true,
+  command: ['cargo', 'build',
+            '--manifest-path', meson.project_source_root() / 'rust/Cargo.toml'
+  ] + rust_args,
+  env: {
+    'RUSTFLAGS': rustflags,
+  },
+  install: false,
+)
+
+rust_xdiff_dep = declare_dependency(
+  link_args: ['-L' + meson.project_source_root() / 'rust/target' / rust_target, '-lxdiff'],
+#  include_directories: include_directories('xdiff/include'),  # Adjust if you expose headers
+)
+
+
 compiler = meson.get_compiler('c')
 
 libgit_sources = [
@@ -1677,6 +1707,8 @@ version_def_h = custom_target(
 )
 libgit_sources += version_def_h
 
+libgit_dependencies += rust_xdiff_dep
+
 libgit = declare_dependency(
   link_with: static_library('git',
     sources: libgit_sources,
rust/Cargo.lock new file +14 -0
new file mode 100644
index 000000000000..fb1eac690b39
@@ -0,0 +1,14 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "interop"
+version = "0.1.0"
+
+[[package]]
+name = "xdiff"
+version = "0.1.0"
+dependencies = [
+ "interop",
+]
rust/Cargo.toml new file +6 -0
new file mode 100644
index 000000000000..ed3d79d7f827
@@ -0,0 +1,6 @@
+[workspace]
+members = [
+    "xdiff",
+    "interop",
+]
+resolver = "2"
rust/interop/Cargo.toml new file +14 -0
new file mode 100644
index 000000000000..045e3b01cfad
@@ -0,0 +1,14 @@
+[package]
+name = "interop"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+name = "interop"
+path = "src/lib.rs"
+## staticlib to generate xdiff.a for use by gcc
+## cdylib (optional) to generate xdiff.so for use by gcc
+## rlib is required by the rust unit tests
+crate-type = ["staticlib", "rlib"]
+
+[dependencies]
rust/interop/src/lib.rs new file +0 -0
new file mode 100644
index 000000000000..e69de29bb2d1
rust/xdiff/Cargo.toml new file +15 -0
new file mode 100644
index 000000000000..eb7966aada64
@@ -0,0 +1,15 @@
+[package]
+name = "xdiff"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+name = "xdiff"
+path = "src/lib.rs"
+## staticlib to generate xdiff.a for use by gcc
+## cdylib (optional) to generate xdiff.so for use by gcc
+## rlib is required by the rust unit tests
+crate-type = ["staticlib", "rlib"]
+
+[dependencies]
+interop = { path = "../interop" }
rust/xdiff/src/lib.rs new file +0 -0
new file mode 100644
index 000000000000..e69de29bb2d1
Signature · 1 line
gitgitgadget
#13 lore raw

PATCH 2/7 xdiff/xprepare: remove superfluous forward declarations

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

Move xdl_prepare_env() later in the file to avoid the need for forward declarations.

---
 xdiff/xprepare.c | 116 ++++++++++++++++++++---------------------------
 1 file changed, 50 insertions(+), 66 deletions(-)
xdiff/xprepare.c +50 -66
index e1d4017b2dde..a45c5ee208c8 100644
@@ -53,21 +53,6 @@ typedef struct s_xdlclassifier {
 
 
 
-static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags);
-static void xdl_free_classifier(xdlclassifier_t *cf);
-static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash,
-			       unsigned int hbits, xrecord_t *rec);
-static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
-			   xdlclassifier_t *cf, xdfile_t *xdf);
-static void xdl_free_ctx(xdfile_t *xdf);
-static int xdl_clean_mmatch(char const *dis, long i, long s, long e);
-static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2);
-static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2);
-static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2);
-
-
-
-
 static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags) {
 	cf->flags = flags;
 
@@ -242,57 +227,6 @@ static void xdl_free_ctx(xdfile_t *xdf) {
 }
 
 
-int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
-		    xdfenv_t *xe) {
-	long enl1, enl2, sample;
-	xdlclassifier_t cf;
-
-	memset(&cf, 0, sizeof(cf));
-
-	/*
-	 * For histogram diff, we can afford a smaller sample size and
-	 * thus a poorer estimate of the number of lines, as the hash
-	 * table (rhash) won't be filled up/grown. The number of lines
-	 * (nrecs) will be updated correctly anyway by
-	 * xdl_prepare_ctx().
-	 */
-	sample = (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF
-		  ? XDL_GUESS_NLINES2 : XDL_GUESS_NLINES1);
-
-	enl1 = xdl_guess_lines(mf1, sample) + 1;
-	enl2 = xdl_guess_lines(mf2, sample) + 1;
-
-	if (xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0)
-		return -1;
-
-	if (xdl_prepare_ctx(1, mf1, enl1, xpp, &cf, &xe->xdf1) < 0) {
-
-		xdl_free_classifier(&cf);
-		return -1;
-	}
-	if (xdl_prepare_ctx(2, mf2, enl2, xpp, &cf, &xe->xdf2) < 0) {
-
-		xdl_free_ctx(&xe->xdf1);
-		xdl_free_classifier(&cf);
-		return -1;
-	}
-
-	if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) &&
-	    (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF) &&
-	    xdl_optimize_ctxs(&cf, &xe->xdf1, &xe->xdf2) < 0) {
-
-		xdl_free_ctx(&xe->xdf2);
-		xdl_free_ctx(&xe->xdf1);
-		xdl_free_classifier(&cf);
-		return -1;
-	}
-
-	xdl_free_classifier(&cf);
-
-	return 0;
-}
-
-
 void xdl_free_env(xdfenv_t *xe) {
 
 	xdl_free_ctx(&xe->xdf2);
@@ -460,3 +394,53 @@ static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2
 
 	return 0;
 }
+
+int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+		    xdfenv_t *xe) {
+	long enl1, enl2, sample;
+	xdlclassifier_t cf;
+
+	memset(&cf, 0, sizeof(cf));
+
+	/*
+	 * For histogram diff, we can afford a smaller sample size and
+	 * thus a poorer estimate of the number of lines, as the hash
+	 * table (rhash) won't be filled up/grown. The number of lines
+	 * (nrecs) will be updated correctly anyway by
+	 * xdl_prepare_ctx().
+	 */
+	sample = (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF
+		  ? XDL_GUESS_NLINES2 : XDL_GUESS_NLINES1);
+
+	enl1 = xdl_guess_lines(mf1, sample) + 1;
+	enl2 = xdl_guess_lines(mf2, sample) + 1;
+
+	if (xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0)
+		return -1;
+
+	if (xdl_prepare_ctx(1, mf1, enl1, xpp, &cf, &xe->xdf1) < 0) {
+
+		xdl_free_classifier(&cf);
+		return -1;
+	}
+	if (xdl_prepare_ctx(2, mf2, enl2, xpp, &cf, &xe->xdf2) < 0) {
+
+		xdl_free_ctx(&xe->xdf1);
+		xdl_free_classifier(&cf);
+		return -1;
+	}
+
+	if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) &&
+	    (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF) &&
+	    xdl_optimize_ctxs(&cf, &xe->xdf1, &xe->xdf2) < 0) {
+
+		xdl_free_ctx(&xe->xdf2);
+		xdl_free_ctx(&xe->xdf1);
+		xdl_free_classifier(&cf);
+		return -1;
+	    }
+
+	xdl_free_classifier(&cf);
+
+	return 0;
+}
Signature · 1 line
gitgitgadget
#15 lore raw

PATCH 3/7 xdiff: delete unnecessary fields from xrecord_t and xdfile_t

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

xrecord_t.next, xdfile_t.hbits, xdfile_t.rhash are initialized, but never used for anything by the code. Remove them.

---
 xdiff/xprepare.c | 24 +++---------------------
 xdiff/xtypes.h   |  3 ---
 2 files changed, 3 insertions(+), 24 deletions(-)
xdiff/xprepare.c +3 -21
index a45c5ee208c8..ad356281f939 100644
@@ -91,8 +91,7 @@ static void xdl_free_classifier(xdlclassifier_t *cf) {
 }
 
 
-static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash,
-			       unsigned int hbits, xrecord_t *rec) {
+static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t *rec) {
 	long hi;
 	char const *line;
 	xdlclass_t *rcrec;
@@ -126,23 +125,17 @@ static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t
 
 	rec->ha = (unsigned long) rcrec->idx;
 
-	hi = (long) XDL_HASHLONG(rec->ha, hbits);
-	rec->next = rhash[hi];
-	rhash[hi] = rec;
-
 	return 0;
 }
 
 
 static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
 			   xdlclassifier_t *cf, xdfile_t *xdf) {
-	unsigned int hbits;
-	long nrec, hsize, bsize;
+	long nrec, bsize;
 	unsigned long hav;
 	char const *blk, *cur, *top, *prev;
 	xrecord_t *crec;
 	xrecord_t **recs;
-	xrecord_t **rhash;
 	unsigned long *ha;
 	char *rchg;
 	long *rindex;
@@ -150,7 +143,6 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
 	ha = NULL;
 	rindex = NULL;
 	rchg = NULL;
-	rhash = NULL;
 	recs = NULL;
 
 	if (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0)
@@ -158,11 +150,6 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
 	if (!XDL_ALLOC_ARRAY(recs, narec))
 		goto abort;
 
-	hbits = xdl_hashbits((unsigned int) narec);
-	hsize = 1 << hbits;
-	if (!XDL_CALLOC_ARRAY(rhash, hsize))
-		goto abort;
-
 	nrec = 0;
 	if ((cur = blk = xdl_mmfile_first(mf, &bsize))) {
 		for (top = blk + bsize; cur < top; ) {
@@ -176,7 +163,7 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
 			crec->size = (long) (cur - prev);
 			crec->ha = hav;
 			recs[nrec++] = crec;
-			if (xdl_classify_record(pass, cf, rhash, hbits, crec) < 0)
+			if (xdl_classify_record(pass, cf, crec) < 0)
 				goto abort;
 		}
 	}
@@ -194,8 +181,6 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
 
 	xdf->nrec = nrec;
 	xdf->recs = recs;
-	xdf->hbits = hbits;
-	xdf->rhash = rhash;
 	xdf->rchg = rchg + 1;
 	xdf->rindex = rindex;
 	xdf->nreff = 0;
@@ -209,7 +194,6 @@ abort:
 	xdl_free(ha);
 	xdl_free(rindex);
 	xdl_free(rchg);
-	xdl_free(rhash);
 	xdl_free(recs);
 	xdl_cha_free(&xdf->rcha);
 	return -1;
@@ -217,8 +201,6 @@ abort:
 
 
 static void xdl_free_ctx(xdfile_t *xdf) {
-
-	xdl_free(xdf->rhash);
 	xdl_free(xdf->rindex);
 	xdl_free(xdf->rchg - 1);
 	xdl_free(xdf->ha);
xdiff/xtypes.h +0 -3
index 8442bd436efe..8b8467360ecf 100644
@@ -39,7 +39,6 @@ typedef struct s_chastore {
 } chastore_t;
 
 typedef struct s_xrecord {
-	struct s_xrecord *next;
 	char const *ptr;
 	long size;
 	unsigned long ha;
@@ -48,8 +47,6 @@ typedef struct s_xrecord {
 typedef struct s_xdfile {
 	chastore_t rcha;
 	long nrec;
-	unsigned int hbits;
-	xrecord_t **rhash;
 	long dstart, dend;
 	xrecord_t **recs;
 	char *rchg;
Signature · 1 line
gitgitgadget
#16 lore raw

PATCH 4/7 xdiff: make fields of xrecord_t Rust friendly

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

A few commits ago, we added definitions for Rust primitive types, to facilitate interoperability between C and Rust. Switch a few variables to use these types. Which, for now, will require adding some casts.

---
 xdiff/xdiffi.c    |  8 ++++----
 xdiff/xemit.c     |  2 +-
 xdiff/xmerge.c    | 14 +++++++-------
 xdiff/xpatience.c |  2 +-
 xdiff/xprepare.c  |  6 +++---
 xdiff/xtypes.h    |  6 +++---
 xdiff/xutils.c    |  4 ++--
 7 files changed, 21 insertions(+), 21 deletions(-)
xdiff/xdiffi.c +4 -4
index 5a96e36dfbea..3b364c61f671 100644
@@ -418,7 +418,7 @@ static int get_indent(xrecord_t *rec)
 	long i;
 	int ret = 0;
 
-	for (i = 0; i < rec->size; i++) {
+	for (i = 0; i < (long) rec->size; i++) {
 		char c = rec->ptr[i];
 
 		if (!XDL_ISSPACE(c))
@@ -1005,11 +1005,11 @@ static void xdl_mark_ignorable_lines(xdchange_t *xscr, xdfenv_t *xe, long flags)
 
 		rec = &xe->xdf1.recs[xch->i1];
 		for (i = 0; i < xch->chg1 && ignore; i++)
-			ignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags);
+			ignore = xdl_blankline((const char*) rec[i]->ptr, rec[i]->size, flags);
 
 		rec = &xe->xdf2.recs[xch->i2];
 		for (i = 0; i < xch->chg2 && ignore; i++)
-			ignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags);
+			ignore = xdl_blankline((const char*)rec[i]->ptr, rec[i]->size, flags);
 
 		xch->ignore = ignore;
 	}
@@ -1020,7 +1020,7 @@ static int record_matches_regex(xrecord_t *rec, xpparam_t const *xpp) {
 	size_t i;
 
 	for (i = 0; i < xpp->ignore_regex_nr; i++)
-		if (!regexec_buf(xpp->ignore_regex[i], rec->ptr, rec->size, 1,
+		if (!regexec_buf(xpp->ignore_regex[i], (const char*) rec->ptr, rec->size, 1,
 				 &regmatch, 0))
 			return 1;
 
xdiff/xemit.c +1 -1
index 1d40c9cb4076..bbf7b7f8c862 100644
@@ -24,7 +24,7 @@
 
 static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec) {
 
-	*rec = xdf->recs[ri]->ptr;
+	*rec = (char const*) xdf->recs[ri]->ptr;
 
 	return xdf->recs[ri]->size;
 }
xdiff/xmerge.c +7 -7
index af40c88a5b36..6fa6ea61a208 100644
@@ -101,8 +101,8 @@ static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2,
 	xrecord_t **rec2 = xe2->xdf2.recs + i2;
 
 	for (i = 0; i < line_count; i++) {
-		int result = xdl_recmatch(rec1[i]->ptr, rec1[i]->size,
-			rec2[i]->ptr, rec2[i]->size, flags);
+		int result = xdl_recmatch((const char*) rec1[i]->ptr, rec1[i]->size,
+			(const char*) rec2[i]->ptr, rec2[i]->size, flags);
 		if (!result)
 			return -1;
 	}
@@ -324,8 +324,8 @@ static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1,
 
 static int recmatch(xrecord_t *rec1, xrecord_t *rec2, unsigned long flags)
 {
-	return xdl_recmatch(rec1->ptr, rec1->size,
-			    rec2->ptr, rec2->size, flags);
+	return xdl_recmatch((char const*) rec1->ptr, rec1->size,
+			    (char const*) rec2->ptr, rec2->size, flags);
 }
 
 /*
@@ -383,10 +383,10 @@ static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m,
 		 */
 		t1.ptr = (char *)xe1->xdf2.recs[m->i1]->ptr;
 		t1.size = xe1->xdf2.recs[m->i1 + m->chg1 - 1]->ptr
-			+ xe1->xdf2.recs[m->i1 + m->chg1 - 1]->size - t1.ptr;
+			+ xe1->xdf2.recs[m->i1 + m->chg1 - 1]->size - (u8 const*) t1.ptr;
 		t2.ptr = (char *)xe2->xdf2.recs[m->i2]->ptr;
 		t2.size = xe2->xdf2.recs[m->i2 + m->chg2 - 1]->ptr
-			+ xe2->xdf2.recs[m->i2 + m->chg2 - 1]->size - t2.ptr;
+			+ xe2->xdf2.recs[m->i2 + m->chg2 - 1]->size - (u8 const*) t2.ptr;
 		if (xdl_do_diff(&t1, &t2, xpp, &xe) < 0)
 			return -1;
 		if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 ||
@@ -440,7 +440,7 @@ static int line_contains_alnum(const char *ptr, long size)
 static int lines_contain_alnum(xdfenv_t *xe, int i, int chg)
 {
 	for (; chg; chg--, i++)
-		if (line_contains_alnum(xe->xdf2.recs[i]->ptr,
+		if (line_contains_alnum((char const*) xe->xdf2.recs[i]->ptr,
 				xe->xdf2.recs[i]->size))
 			return 1;
 	return 0;
xdiff/xpatience.c +1 -1
index 77dc411d1937..986a3a3f749a 100644
@@ -121,7 +121,7 @@ static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map,
 		return;
 	map->entries[index].line1 = line;
 	map->entries[index].hash = record->ha;
-	map->entries[index].anchor = is_anchor(xpp, map->env->xdf1.recs[line - 1]->ptr);
+	map->entries[index].anchor = is_anchor(xpp, (const char*) map->env->xdf1.recs[line - 1]->ptr);
 	if (!map->first)
 		map->first = map->entries + index;
 	if (map->last) {
xdiff/xprepare.c +3 -3
index ad356281f939..747268e4fdf7 100644
@@ -96,12 +96,12 @@ static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t
 	char const *line;
 	xdlclass_t *rcrec;
 
-	line = rec->ptr;
+	line = (char const*) rec->ptr;
 	hi = (long) XDL_HASHLONG(rec->ha, cf->hbits);
 	for (rcrec = cf->rchash[hi]; rcrec; rcrec = rcrec->next)
 		if (rcrec->ha == rec->ha &&
 				xdl_recmatch(rcrec->line, rcrec->size,
-					rec->ptr, rec->size, cf->flags))
+					(const char*) rec->ptr, rec->size, cf->flags))
 			break;
 
 	if (!rcrec) {
@@ -159,7 +159,7 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
 				goto abort;
 			if (!(crec = xdl_cha_alloc(&xdf->rcha)))
 				goto abort;
-			crec->ptr = prev;
+			crec->ptr = (u8 const*) prev;
 			crec->size = (long) (cur - prev);
 			crec->ha = hav;
 			recs[nrec++] = crec;
xdiff/xtypes.h +3 -3
index 8b8467360ecf..6e5f67ebf380 100644
@@ -39,9 +39,9 @@ typedef struct s_chastore {
 } chastore_t;
 
 typedef struct s_xrecord {
-	char const *ptr;
-	long size;
-	unsigned long ha;
+	u8 const* ptr;
+	usize size;
+	u64 ha;
 } xrecord_t;
 
 typedef struct s_xdfile {
xdiff/xutils.c +2 -2
index 444a108f87c0..10e4f20b7c31 100644
@@ -418,10 +418,10 @@ int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp,
 
 	subfile1.ptr = (char *)diff_env->xdf1.recs[line1 - 1]->ptr;
 	subfile1.size = diff_env->xdf1.recs[line1 + count1 - 2]->ptr +
-		diff_env->xdf1.recs[line1 + count1 - 2]->size - subfile1.ptr;
+		diff_env->xdf1.recs[line1 + count1 - 2]->size - (u8 const*) subfile1.ptr;
 	subfile2.ptr = (char *)diff_env->xdf2.recs[line2 - 1]->ptr;
 	subfile2.size = diff_env->xdf2.recs[line2 + count2 - 2]->ptr +
-		diff_env->xdf2.recs[line2 + count2 - 2]->size - subfile2.ptr;
+		diff_env->xdf2.recs[line2 + count2 - 2]->size - (u8 const*) subfile2.ptr;
 	if (xdl_do_diff(&subfile1, &subfile2, xpp, &env) < 0)
 		return -1;
 
Signature · 1 line
gitgitgadget
#32 lore raw

PATCH 5/7 xdiff: separate parsing lines from hashing them

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

We want to use xxhash for faster hashing. To facilitate that and to simplify the code. Separate the concerns of parsing and hashing into discrete steps. This makes swapping the hash function much easier. Since xdl_hash_record() both parses and hashses lines, this requires some slight code restructuring.

---
 xdiff/xprepare.c | 75 ++++++++++++++++++++++++++++--------------------
 1 file changed, 44 insertions(+), 31 deletions(-)
xdiff/xprepare.c +44 -31
index 747268e4fdf7..c44005e9bbb8 100644
@@ -129,13 +129,39 @@ static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t
 }
 
 
+static void xdl_parse_lines(mmfile_t *mf, long narec, xdfile_t *xdf) {
+	u8 const* ptr = (u8 const*) mf->ptr;
+	usize len = (usize) mf->size;
+
+	xdf->recs = NULL;
+	xdf->nrec = 0;
+	XDL_ALLOC_ARRAY(xdf->recs, narec);
+
+	while (len > 0) {
+		xrecord_t *rec = NULL;
+		usize length;
+		u8 const* result = memchr(ptr, '\n', len);
+		if (result) {
+			length = result - ptr + 1;
+		} else {
+			length = len;
+		}
+		if (XDL_ALLOC_GROW(xdf->recs, xdf->nrec + 1, narec))
+			die("XDL_ALLOC_GROW failed");
+		rec = xdl_cha_alloc(&xdf->rcha);
+		rec->ptr = ptr;
+		rec->size = length;
+		rec->ha = 0;
+		xdf->recs[xdf->nrec++] = rec;
+		ptr += length;
+		len -= length;
+	}
+
+}
+
+
 static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
 			   xdlclassifier_t *cf, xdfile_t *xdf) {
-	long nrec, bsize;
-	unsigned long hav;
-	char const *blk, *cur, *top, *prev;
-	xrecord_t *crec;
-	xrecord_t **recs;
 	unsigned long *ha;
 	char *rchg;
 	long *rindex;
@@ -143,50 +169,37 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
 	ha = NULL;
 	rindex = NULL;
 	rchg = NULL;
-	recs = NULL;
 
 	if (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0)
 		goto abort;
-	if (!XDL_ALLOC_ARRAY(recs, narec))
-		goto abort;
 
-	nrec = 0;
-	if ((cur = blk = xdl_mmfile_first(mf, &bsize))) {
-		for (top = blk + bsize; cur < top; ) {
-			prev = cur;
-			hav = xdl_hash_record(&cur, top, xpp->flags);
-			if (XDL_ALLOC_GROW(recs, nrec + 1, narec))
-				goto abort;
-			if (!(crec = xdl_cha_alloc(&xdf->rcha)))
-				goto abort;
-			crec->ptr = (u8 const*) prev;
-			crec->size = (long) (cur - prev);
-			crec->ha = hav;
-			recs[nrec++] = crec;
-			if (xdl_classify_record(pass, cf, crec) < 0)
-				goto abort;
-		}
+	xdl_parse_lines(mf, narec, xdf);
+
+	for (usize i = 0; i < (usize) xdf->nrec; i++) {
+		xrecord_t *rec = xdf->recs[i];
+		char const* dump = (char const*) rec->ptr;
+		rec->ha = xdl_hash_record(&dump, (char const*) (rec->ptr + rec->size), xpp->flags);
+		xdl_classify_record(pass, cf, rec);
 	}
 
-	if (!XDL_CALLOC_ARRAY(rchg, nrec + 2))
+
+	if (!XDL_CALLOC_ARRAY(rchg, xdf->nrec + 2))
 		goto abort;
 
 	if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) &&
 	    (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF)) {
-		if (!XDL_ALLOC_ARRAY(rindex, nrec + 1))
+		if (!XDL_ALLOC_ARRAY(rindex, xdf->nrec + 1))
 			goto abort;
-		if (!XDL_ALLOC_ARRAY(ha, nrec + 1))
+		if (!XDL_ALLOC_ARRAY(ha, xdf->nrec + 1))
 			goto abort;
 	}
 
-	xdf->nrec = nrec;
-	xdf->recs = recs;
 	xdf->rchg = rchg + 1;
 	xdf->rindex = rindex;
 	xdf->nreff = 0;
 	xdf->ha = ha;
 	xdf->dstart = 0;
-	xdf->dend = nrec - 1;
+	xdf->dend = xdf->nrec - 1;
 
 	return 0;
 
@@ -194,7 +207,7 @@ abort:
 	xdl_free(ha);
 	xdl_free(rindex);
 	xdl_free(rchg);
-	xdl_free(recs);
+	xdl_free(xdf->recs);
 	xdl_cha_free(&xdf->rcha);
 	return -1;
 }
Signature · 1 line
gitgitgadget
#35 lore raw

PATCH 6/7 xdiff: conditionally use Rust's implementation of xxhash

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

When no whitespace flags are present use xxhash, for faster hashing, otherwise use DJB2a (which is what xdiff has been using all along).

The benchmark below compares my series with version v2.49.0 (built in build_release/ and build_v2.49.0/ respectively), running log commands on linux kernel with 3 different machines.

$ BASE=/path/to/git/root
    // laptop
    // CPU: 6-core Intel Core i7-8750H (-MT MCP-) speed/min/max: 726/800/4100 MHz
    $ hyperfine --warmup 3 -L exe $BASE/build_release/git,$BASE/build_v2.49.0/git '{exe} log --oneline --shortstat v6.8..v6.9 >/dev/null'
    Benchmark 1: /home/ezekiel/development/work/git/build_release/git log --oneline --shortstat v6.8..v6.9 >/dev/null
      Time (mean ± σ):     10.419 s ±  0.166 s    [User: 10.097 s, System: 0.284 s]
      Range (min … max):   10.215 s … 10.680 s    10 runs

    Benchmark 2: /home/ezekiel/development/work/git/build_v2.49.0/git log --oneline --shortstat v6.8..v6.9 >/dev/null
      Time (mean ± σ):     10.980 s ±  0.137 s    [User: 10.633 s, System: 0.308 s]
      Range (min … max):   10.791 s … 11.178 s    10 runs

    Summary
      /home/ezekiel/development/work/git/build_release/git log --oneline --shortstat v6.8..v6.9 >/dev/null ran
        1.05 ± 0.02 times faster than /home/ezekiel/development/work/git/build_v2.49.0/git log --oneline --shortstat v6.8..v6.9 >/dev/null

    // desktop
    // CPU: 8-core Intel Core i7-9700 (-MCP-) speed/min/max: 800/800/4700 MHz
    $ hyperfine --warmup 3 -L exe $BASE/build_release/git,$BASE/build_v2.49.0/git '{exe} log --oneline --shortstat v6.8..v6.9 >/dev/null'
    Benchmark 1: /home/steamuser/dev/git/build_release/git log --oneline --shortstat v6.8..v6.9 >/dev/null
      Time (mean ± σ):      6.823 s ±  0.020 s    [User: 6.624 s, System: 0.180 s]
      Range (min … max):    6.801 s …  6.858 s    10 runs

    Benchmark 2: /home/steamuser/dev/git/build_v2.49.0/git log --oneline --shortstat v6.8..v6.9 >/dev/null
      Time (mean ± σ):      8.151 s ±  0.024 s    [User: 7.928 s, System: 0.198 s]
      Range (min … max):    8.105 s …  8.184 s    10 runs

    Summary
      /home/steamuser/dev/git/build_release/git log --oneline --shortstat v6.8..v6.9 >/dev/null ran
        1.19 ± 0.01 times faster than /home/steamuser/dev/git/build_v2.49.0/git log --oneline --shortstat v6.8..v6.9 >/dev/null

    // router
    // CPU: dual core Intel Celeron 3965U (-MCP-) speed/min/max: 1300/400/2200 MHz
    $ hyperfine --warmup 3 -L exe $BASE/build_release/git,$BASE/build_v2.49.0/git '{exe} log --oneline --shortstat v6.8..v6.9 >/dev/null'
    Benchmark 1: /home/metal/dev/git/build_release/git log --oneline --shortstat v6.8..v6.9 >/dev/null
      Time (mean ± σ):     21.209 s ±  0.054 s    [User: 20.341 s, System: 0.605 s]
      Range (min … max):   21.135 s … 21.309 s    10 runs

    Benchmark 2: /home/metal/dev/git/build_v2.49.0/git log --oneline --shortstat v6.8..v6.9 >/dev/null
      Time (mean ± σ):     23.683 s ±  0.060 s    [User: 22.735 s, System: 0.672 s]
      Range (min … max):   23.566 s … 23.751 s    10 runs

    Summary
      /home/metal/dev/git/build_release/git log --oneline --shortstat v6.8..v6.9 >/dev/null ran
        1.12 ± 0.00 times faster than /home/metal/dev/git/build_v2.49.0/git log --oneline --shortstat v6.8..v6.9 >/dev/null
---
 rust/Cargo.lock       |  7 +++++++
 rust/xdiff/Cargo.toml |  1 +
 rust/xdiff/src/lib.rs |  7 +++++++
 xdiff/xprepare.c      | 19 +++++++++++++++++--
 4 files changed, 32 insertions(+), 2 deletions(-)
rust/Cargo.lock +7 -0
index fb1eac690b39..5f84617b1049 100644
@@ -11,4 +11,11 @@ name = "xdiff"
 version = "0.1.0"
 dependencies = [
  "interop",
+ "xxhash-rust",
 ]
+
+[[package]]
+name = "xxhash-rust"
+version = "0.8.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3"
rust/xdiff/Cargo.toml +1 -0
index eb7966aada64..1516e829db18 100644
@@ -13,3 +13,4 @@ crate-type = ["staticlib", "rlib"]
 
 [dependencies]
 interop = { path = "../interop" }
+xxhash-rust = { version = "0.8.15", features = ["xxh3"] }
rust/xdiff/src/lib.rs +7 -0
index e69de29bb2d1..96975975a1ba 100644
@@ -0,0 +1,7 @@
+
+
+#[no_mangle]
+unsafe extern "C" fn xxh3_64(ptr: *const u8, size: usize) -> u64 {
+    let slice = std::slice::from_raw_parts(ptr, size);
+    xxhash_rust::xxh3::xxh3_64(slice)
+}
xdiff/xprepare.c +17 -2
index c44005e9bbb8..5a2e52f102cf 100644
@@ -160,6 +160,9 @@ static void xdl_parse_lines(mmfile_t *mf, long narec, xdfile_t *xdf) {
 }
 
 
+extern u64 xxh3_64(u8 const* ptr, usize size);
+
+
 static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
 			   xdlclassifier_t *cf, xdfile_t *xdf) {
 	unsigned long *ha;
@@ -175,14 +178,26 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
 
 	xdl_parse_lines(mf, narec, xdf);
 
+	if ((xpp->flags & XDF_WHITESPACE_FLAGS) == 0) {
+		for (usize i = 0; i < (usize) xdf->nrec; i++) {
+			xrecord_t *rec = xdf->recs[i];
+			rec->ha = xxh3_64(rec->ptr, rec->size);
+		}
+	} else {
+		for (usize i = 0; i < (usize) xdf->nrec; i++) {
+			xrecord_t *rec = xdf->recs[i];
+			char const* dump = (char const*) rec->ptr;
+			rec->ha = xdl_hash_record(&dump, (char const*) (rec->ptr + rec->size), xpp->flags);
+		}
+	}
+
 	for (usize i = 0; i < (usize) xdf->nrec; i++) {
 		xrecord_t *rec = xdf->recs[i];
-		char const* dump = (char const*) rec->ptr;
-		rec->ha = xdl_hash_record(&dump, (char const*) (rec->ptr + rec->size), xpp->flags);
 		xdl_classify_record(pass, cf, rec);
 	}
 
 
+
 	if (!XDL_CALLOC_ARRAY(rchg, xdf->nrec + 2))
 		goto abort;
 
Signature · 1 line
gitgitgadget
#53 lore raw

PATCH 7/7 github_workflows: install rust

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

Since we have introduced rust, it needs to be installed for the continuous integration build targets. Create an install script (build_rust.sh) that needs to be run as the same user that builds git. Because of the limitations of meson, create build_rust.sh which makes it easy to centralize how rust is built between meson and make.

There are 2 interesting decisions worth calling out in this commit:

* The 'output' field of custom_target() does not allow specifying a file nested inside the build directory. Thus create build_rust.sh to build rust with all of its parameters and then moves libxdiff.a to the root of the build directory.

* Install curl, to facilitate the rustup install script.

---
 .github/workflows/main.yml |  1 +
 .gitignore                 |  1 +
 Makefile                   | 46 +++++++++++++++++++----------
 build_rust.sh              | 59 ++++++++++++++++++++++++++++++++++++++
 ci/install-dependencies.sh | 14 ++++-----
 ci/install-rust.sh         | 33 +++++++++++++++++++++
 ci/lib.sh                  |  8 ++++++
 ci/make-test-artifacts.sh  |  7 +++++
 ci/run-build-and-tests.sh  | 10 +++++++
 meson.build                | 40 +++++++++++---------------
 10 files changed, 173 insertions(+), 46 deletions(-)
 create mode 100755 build_rust.sh
 create mode 100644 ci/install-rust.sh
.github/workflows/main.yml +1 -0
index 7dbf9f7f123c..8aac18a6ba45 100644
@@ -4,6 +4,7 @@ on: [push, pull_request]
 
 env:
   DEVELOPER: 1
+  RUST_VERSION: 1.87.0
 
 # If more than one workflow run is triggered for the very same commit hash
 # (which happens when multiple branches pointing to the same commit), only
.gitignore +1 -0
index 04c444404e4b..a1c0d212541e 100644
@@ -254,3 +254,4 @@ Release/
 /contrib/buildsystems/out
 /contrib/libgit-rs/target
 /contrib/libgit-sys/target
+/rust/target
Makefile +31 -15

113 lines not on this page · raw message

build_rust.sh new file +59 -0

59 lines not on this page · raw message

ci/install-dependencies.sh +7 -7
index d061a4729339..7801075821ba 100755
@@ -24,14 +24,14 @@ fi
 
 case "$distro" in
 alpine-*)
-	apk add --update shadow sudo meson ninja-build gcc libc-dev curl-dev openssl-dev expat-dev gettext \
+	apk add --update shadow sudo meson ninja-build gcc libc-dev curl curl-dev openssl-dev expat-dev gettext \
 		zlib-ng-dev pcre2-dev python3 musl-libintl perl-utils ncurses \
 		apache2 apache2-http2 apache2-proxy apache2-ssl apache2-webdav apr-util-dbd_sqlite3 \
 		bash cvs gnupg perl-cgi perl-dbd-sqlite perl-io-tty >/dev/null
 	;;
 fedora-*|almalinux-*)
 	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 gcc findutils diffutils perl python3 gawk gettext zlib-devel expat-devel openssl-devel curl curl-devel pcre2-devel >/dev/null
 	;;
 ubuntu-*|i386/ubuntu-*|debian-*)
 	# Required so that apt doesn't wait for user input on certain packages.
@@ -55,8 +55,8 @@ ubuntu-*|i386/ubuntu-*|debian-*)
 	sudo apt-get -q update
 	sudo apt-get -q -y install \
 		$LANGUAGES apache2 cvs cvsps git gnupg $SVN \
-		make libssl-dev libcurl4-openssl-dev libexpat-dev wget sudo default-jre \
-		tcl tk gettext zlib1g-dev perl-modules liberror-perl libauthen-sasl-perl \
+		make libssl-dev curl libcurl4-openssl-dev libexpat-dev wget sudo default-jre \
+		tcl tk gettext zlib1g 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 \
 		${CC_PACKAGE:-${CC:-gcc}} $PYTHON_PACKAGE
@@ -121,13 +121,13 @@ ClangFormat)
 	;;
 StaticAnalysis)
 	sudo apt-get -q update
-	sudo apt-get -q -y install coccinelle libcurl4-openssl-dev libssl-dev \
+	sudo apt-get -q -y install coccinelle curl libcurl4-openssl-dev libssl-dev \
 		libexpat-dev gettext make
 	;;
 sparse)
 	sudo apt-get -q update -q
-	sudo apt-get -q -y install libssl-dev libcurl4-openssl-dev \
-		libexpat-dev gettext zlib1g-dev sparse
+	sudo apt-get -q -y install libssl-dev curl libcurl4-openssl-dev \
+		libexpat-dev gettext zlib1g zlib1g-dev sparse
 	;;
 Documentation)
 	sudo apt-get -q update
ci/install-rust.sh new file +33 -0
new file mode 100644
index 000000000000..141ceddb17cf
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+if [ "$(id -u)" -eq 0 ]; then
+  echo >&2 "::warning:: installing rust as root"
+fi
+
+if [ "$CARGO_HOME" = "" ]; then
+  echo >&2 "::warning:: CARGO_HOME is not set"
+  export CARGO_HOME=$HOME/.cargo
+fi
+
+export RUSTUP_HOME=$CARGO_HOME
+
+if [ "$RUST_VERSION" = "" ]; then
+  echo >&2 "::error:: RUST_VERSION is not set"
+  exit 2
+fi
+
+## install rustup
+curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --default-toolchain none -y
+if [ ! -f $CARGO_HOME/env ]; then
+  echo "PATH=$CARGO_HOME/bin:\$PATH" > $CARGO_HOME/env
+fi
+## install a specific version of rust
+if [ "$BITNESS" = "32" ]; then
+  $CARGO_HOME/bin/rustup set default-host i686-unknown-linux-gnu || exit $?
+  $CARGO_HOME/bin/rustup install $RUST_VERSION || exit $?
+  $CARGO_HOME/bin/rustup default --force-non-host $RUST_VERSION || exit $?
+else
+  $CARGO_HOME/bin/rustup default $RUST_VERSION || exit $?
+fi
+
+. $CARGO_HOME/env
ci/lib.sh +8 -0
index f561884d4016..ad0e49a68dcb 100755
@@ -1,5 +1,13 @@
 # Library of functions shared by all CI scripts
 
+
+export BITNESS="64"
+if command -v getconf >/dev/null && [ "$(getconf LONG_BIT 2>/dev/null)" = "32" ]; then
+  export BITNESS="32"
+fi
+echo "BITNESS=$BITNESS"
+
+
 if test true = "$GITHUB_ACTIONS"
 then
 	begin_group () {
ci/make-test-artifacts.sh +7 -0
index 74141af0cc74..56aa7efb1d53 100755
@@ -7,6 +7,13 @@ mkdir -p "$1" # in case ci/lib.sh decides to quit early
 
 . ${0%/*}/lib.sh
 
+## install rust per user rather than system wide
+. ${0%/*}/install-rust.sh
+
 group Build make artifacts-tar ARTIFACTS_DIRECTORY="$1"
 
+if [ -d "$CARGO_HOME" ]; then
+  rm -rf $CARGO_HOME
+fi
+
 check_unignored_build_artifacts
ci/run-build-and-tests.sh +10 -0
index 01823fd0f140..dbab1cb2f936 100755
@@ -5,6 +5,12 @@
 
 . ${0%/*}/lib.sh
 
+## install rust per user rather than system wide
+. ${0%/*}/install-rust.sh
+
+rustc -vV
+cargo --version || exit $?
+
 run_tests=t
 
 case "$jobname" in
@@ -72,5 +78,9 @@ case "$jobname" in
 	;;
 esac
 
+if [ -d "$CARGO_HOME" ]; then
+  rm -rf $CARGO_HOME
+fi
+
 check_unignored_build_artifacts
 save_good_tree
meson.build +16 -24
index 2d8da17f6515..047d7e5b6630 100644
@@ -277,26 +277,17 @@ else
   rustflags = '-Aunused_imports -Adead_code -C debuginfo=2 -C opt-level=1 -C force-frame-pointers=yes'
 endif
 
-
-rust_leaf = custom_target('rust_leaf',
+rust_build_xdiff = custom_target('rust_build_xdiff',
   output: 'libxdiff.a',
   build_by_default: true,
   build_always_stale: true,
-  command: ['cargo', 'build',
-            '--manifest-path', meson.project_source_root() / 'rust/Cargo.toml'
-  ] + rust_args,
-  env: {
-    'RUSTFLAGS': rustflags,
-  },
+  command: [
+    meson.project_source_root() / 'build_rust.sh',
+    meson.current_build_dir(), rust_target, 'xdiff',
+  ],
   install: false,
 )
 
-rust_xdiff_dep = declare_dependency(
-  link_args: ['-L' + meson.project_source_root() / 'rust/target' / rust_target, '-lxdiff'],
-#  include_directories: include_directories('xdiff/include'),  # Adjust if you expose headers
-)
-
-
 compiler = meson.get_compiler('c')
 
 libgit_sources = [
@@ -1707,17 +1698,18 @@ version_def_h = custom_target(
 )
 libgit_sources += version_def_h
 
-libgit_dependencies += rust_xdiff_dep
-
 libgit = declare_dependency(
-  link_with: static_library('git',
-    sources: libgit_sources,
-    c_args: libgit_c_args + [
-      '-DGIT_VERSION_H="' + version_def_h.full_path() + '"',
-    ],
-    dependencies: libgit_dependencies,
-    include_directories: libgit_include_directories,
-  ),
+  link_with: [
+    static_library('git',
+      sources: libgit_sources,
+      c_args: libgit_c_args + [
+        '-DGIT_VERSION_H="' + version_def_h.full_path() + '"',
+      ],
+      dependencies: libgit_dependencies,
+      include_directories: libgit_include_directories,
+    ),
+    rust_build_xdiff,
+  ],
   compile_args: libgit_c_args,
   dependencies: libgit_dependencies,
   include_directories: libgit_include_directories,
Signature · 1 line
gitgitgadget
#54 lore raw

Re: [PATCH 7/7] github_workflows: install rust

.github/workflows/main.yml on: [push, pull_request] · line 7

Quoted 9 lines · Ezekiel Newren via GitGitGadget
.github/workflows/main.yml +1 -0
index 7dbf9f7f123c..8aac18a6ba45 100644
@@ -4,6 +4,7 @@ on: [push, pull_request]
 
 env:
   DEVELOPER: 1
+  RUST_VERSION: 1.87.0

Our discussed plan is to support the version in Debian stable, plus a year. So we'd be supporting 1.63.0 for a year after trixie's release.

The reason for that is that people build backports and security updates for Git for stable releases of distros and they will use the distro toolchain for doing so. Forcing distros to constantly build with the latest toolchain is pretty hostile, especially since the lifespan of Rust release is six weeks.

If the Rust project provides LTS releases in the future, then we can consider adopting those.

.github/workflows/main.yml on: [push, pull_request]

Quoted 6 lines
.github/workflows/main.yml +6 -0
+if [ "$rust_target" = "release" ]; then
+  rust_args="--release"
+  export RUSTFLAGS='-Aunused_imports -Adead_code'
+elif [ "$rust_target" = "debug" ]; then
+  rust_args=""
+  export RUSTFLAGS='-Aunused_imports -Adead_code -C debuginfo=2 -C opt-level=1 -C force-frame-pointers=yes'

Can you say a little about why these options are needed and the defaults are inadequate? For instance, I build with the default options both in my personal projects and at work and don't see a problem.

I don't know if you plan to do this in a future series, but we'd also want cargo's tests to be run as part of CI and we'd want a lint job that ran clippy with both 1.63.0 and the latest stable version of Rust to make sure things were tidy.

Signature · 2 lines
brian m. carlson (they/them)
Toronto, Ontario, CA
#2 lore raw

Re: [PATCH 1/7] xdiff: introduce rust

In reply to Ezekiel Newren via GitGitGadget
Quoted 10 lines · Ezekiel Newren via GitGitGadget
rust/Cargo.lock new file +4 -0
new file mode 100644
index 000000000000..fb1eac690b39
@@ -0,0 +1,14 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+

rust/Cargo.lock line 14

Quoted 10 lines
rust/Cargo.lock new file +10 -0
+[[package]]
+name = "interop"
+version = "0.1.0"
+
+[[package]]
+name = "xdiff"
+version = "0.1.0"
+dependencies = [
+ "interop",
+]

I would prefer that we not check in Cargo.lock in Git. Part of the reason is that it changes across versions and so building with a different version of the toolchain can update the file.

In addition, as I mentioned downthread, because our intention is to support the Debian stable toolchain for a year after the new stable release, unless we are exceptionally careful about dependencies, we may end up with a case where distros need to use older dependencies patched for security but other users may want to update the versions to newer dependencies with security fixes but that do not work on our pinned Rust version. We can't possibly satisfy both sets of people if we pin dependencies in Cargo.lock, so we probably want to avoid checking it in and ignore it instead.

Signature · 2 lines
brian m. carlson (they/them)
Toronto, Ontario, CA
#58 lore raw
In reply to Ezekiel Newren via GitGitGadget
Quoted 1 line · Ezekiel Newren via GitGitGadget

This series accelerates xdiff by 5-19%.

That's great.

Quoted 1 line

It also introduces Rust as a hard dependency.

I think that's fine. We already discussed doing this at the last Contributor Summit in Berlin and everyone was in favour. While we did not have every contributor represented, I think that unanimity of the contributors present is a compelling enough reason.

Quoted 9 lines

…and it doesn’t yet pass a couple of the github workflows; hints from Windows experts, and opinions on ambiguous primitives would be appreciated (see below).

This is just the beginning of many patches that I have to convert portions of, maybe eventually all of, xdiff to Rust. While working on that conversion, I found several ways to clarify the code, along with some optimizations.

Quoted 7 lines

So...

This obviously raises the question of whether we are ready to accept a hard dependency on Rust. Previous discussions on the mailing list and at Git Merge 2024 have not answered that question. If not now, will we be willing to accept such a hard dependency later? And what route do we want to take to get there?

Again, I think that's fine.

I have a proposed policy at [0] (available from the rust branch on that remote). Included in that policy is a link to [1], which I summarize as "the U.S. government is proposing to classify development in memory unsafe languages as a Product Security Bad Practice." The proposal is that a memory safety roadmap be introduced by the end of 2025.

Now, do let me be clear that I don't agree with everything that the U.S. government says or does (far from it), but I do think this is a sensible proposal (or I wouldn't have cited it) and it will be showing up in a lot more security standards coming down the line, especially for those forges and companies which will be selling to governments around the world. There's no time like the present to do this.

I realize that that means that we will lose support for some platforms. I ultimately think that it's up to the porters and maintainers for a platform to maintain appropriate toolchains on that platform and that, while we should be cognizant of the requirements for adding new platforms or architectures, that shouldn't prevent the inclusion of important new tools like memory-safe languages.

I would like to see a change to our platform policy and a policy on Rust before we merge this. I know your series adds support for 1.87, but because distros don't run the latest toolchain, we had discussed in the past targeting Debian stable's version plus an additional year after the new release. This means we'll support a Rust version for three years, which is a reasonable amount of time for a toolchain and allows distros to easily backport security fixes.

If you would like, you are welcome to use my proposed policy as a basis for this, or I can send that out as a separate document if you don't want to write one or revise mine.

[0] https://github.com/bk2204/git/commit/fbeb1180c7473635a964daed2da642c53487782d
[1] https://www.cisa.gov/resources-tools/resources/product-security-bad-practices

Signature · 2 lines
brian m. carlson (they/them)
Toronto, Ontario, CA
#3 lore raw

Re: [PATCH 1/7] xdiff: introduce rust

In reply to brian m. carlson
Quoted 19 lines · brian m. carlson
Quoted 4 lines
rust/Cargo.lock new file +4 -0
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+

rust/Cargo.lock line 14

Quoted 10 lines
rust/Cargo.lock new file +10 -0
+[[package]]
+name = "interop"
+version = "0.1.0"
+
+[[package]]
+name = "xdiff"
+version = "0.1.0"
+dependencies = [
+ "interop",
+]

I would prefer that we not check in Cargo.lock in Git. Part of the reason is that it changes across versions and so building with a different version of the toolchain can update the file.

Quoted 9 lines

In addition, as I mentioned downthread, because our intention is to support the Debian stable toolchain for a year after the new stable release, unless we are exceptionally careful about dependencies, we may end up with a case where distros need to use older dependencies patched for security but other users may want to update the versions to newer dependencies with security fixes but that do not work on our pinned Rust version. We can't possibly satisfy both sets of people if we pin dependencies in Cargo.lock, so we probably want to avoid checking it in and ignore it instead.

Yup.

The comment in first few lines of the file says it very well ;-) Thanks for flagging it.

#59 lore raw
In reply to brian m. carlson
Quoted 11 lines · brian m. carlson
Quoted 1 line · Ezekiel Newren via GitGitGadget

This series accelerates xdiff by 5-19%.

That's great.

Quoted 1 line

It also introduces Rust as a hard dependency.

I think that's fine. We already discussed doing this at the last Contributor Summit in Berlin and everyone was in favour. While we did not have every contributor represented, I think that unanimity of the contributors present is a compelling enough reason.

I agree. I don't think that there is ever going to be a "perfect" time to introduce a hard dependency on Rust, and I don't think that should hold the project back from adopting it.

I am far from a Rust expert, but I think that a more modern, memory-safe language will attract newer contributors who may have a fresher perspective on the project, and I think that's a good thing.

The alternative, of course, is to continue to use C and not take any dependency on Rust. I think there is a middle-ground in there somewhere to be able to build with (e.g.) "make" or "make RUST=1", but I would really like to see the project take a firmer stance here.

I worry that having build support for both "with Rust" and "C only" will create a headache not just at the build system level, but also in the code itself. Having a patchwork of features, optimizations, or bug fixes that either are or aren't supported depending on whether Rust support was specified at build-time seems like a worst-of-all-worlds outcome.

Quoted 6 lines

I realize that that means that we will lose support for some platforms. I ultimately think that it's up to the porters and maintainers for a platform to maintain appropriate toolchains on that platform and that, while we should be cognizant of the requirements for adding new platforms or architectures, that shouldn't prevent the inclusion of important new tools like memory-safe languages.

Agreed. Of course, I think we would all like Git to be able to build and run on as many platforms as is reasonably possible. But we cannot support all platforms for all time. It is also not the Git project's responsibility to ensure that every platform is Rust-friendly.

Hopefully the platforms that we currently support but won't after this patch series have niche enough workloads that they do not need the absolute latest-and-greatest Git release at all times.

Quoted 11 lines

I would like to see a change to our platform policy and a policy on Rust before we merge this. I know your series adds support for 1.87, but because distros don't run the latest toolchain, we had discussed in the past targeting Debian stable's version plus an additional year after the new release. This means we'll support a Rust version for three years, which is a reasonable amount of time for a toolchain and allows distros to easily backport security fixes.

If you would like, you are welcome to use my proposed policy as a basis for this, or I can send that out as a separate document if you don't want to write one or revise mine.

Yeah, I think that this is the most interesting part of the discussion here. I am not knowledgeable enough about Rust's release cadence and platform compatibility to have an opinion here. But I trust brian's judgement ;-).

Thanks,
Taylor

#12 lore raw

Re: [PATCH 1/7] xdiff: introduce rust

In reply to Ezekiel Newren via GitGitGadget
Quoted 11 lines · Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

Upcoming patches will accelerate and simplify xdiff, while also porting parts of it to Rust. In preparation, add some stubs and setup the Rust build. For now, it is easier to let cargo build rust and have make or meson merely link against the static library that cargo builds. In line with ongoing libification efforts, use multiple crates to allow more modularity on the Rust side. xdiff is the crate that this series will focus on, but we also introduce the interop crate for future patch series.

Quoted 2 lines

In order to facilitate interoperability between C and Rust, introduce C definitions for Rust primitive types in git-compat-util.h.

Exciting ;-).

Makefile TEST_SHELL_PATH = $(SHELL_PATH) · line 926

Quoted 13 lines
Makefile +5 -0
index 70d1543b6b86..db39e6e1c28e 100644
@@ -919,6 +919,11 @@ TEST_SHELL_PATH = $(SHELL_PATH)
 
 LIB_FILE = libgit.a
 XDIFF_LIB = xdiff/lib.a
+ifeq ($(DEBUG), 1)
+RUST_LIB = rust/target/debug/libxdiff.a
+else
+RUST_LIB = rust/target/release/libxdiff.a
+endif

We do have a DEBUG variable in our Makefile introduced via dce7d29551 (msvc: support building Git using MS Visual C++, 2019-06-25), but I don't think that it is very widely used. Perhaps that is because I don't build Git with MSVC, but I suspect that this is generally true.

Much more common is the DEVELOPER=1 setting, which adds more compiler warnings and similar. I am not sure whether or not it would be appropriate to use DEVELOPER here to determine which libxdiff.a to use.

In any event, our convention would be to treat the defined-ness of DEBUG the same way that this patch treats DEBUG=1, so I might suggest replacing your "ifeq" with "ifdef DEBUG".

Quoted 13 lines
Makefile +2 -0
 REFTABLE_LIB = reftable/libreftable.a
 
 GENERATED_H += command-list.h
@@ -1392,6 +1397,8 @@ UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o
 GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(LIB_FILE)
 EXTLIBS =
 
+GITLIBS += $(RUST_LIB)
+
 GIT_USER_AGENT = git/$(GIT_VERSION)
 
 ifeq ($(wildcard sha1collisiondetection/lib/sha1.h),sha1collisiondetection/lib/sha1.h)

Makefile $(LIB_FILE): $(LIB_OBJS) · line 2938

Quoted 7 lines
Makefile +4 -0
@@ -2925,6 +2932,14 @@ $(LIB_FILE): $(LIB_OBJS)
 $(XDIFF_LIB): $(XDIFF_OBJS)
 	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
 
+.PHONY: $(RUST_LIB)
+$(RUST_LIB):
+ifeq ($(DEBUG), 1)
+	cd rust && RUSTFLAGS="-Aunused_imports -Adead_code" cargo build --verbose

A few thoughts here:

- Does "cargo" support a flag similar to our -C? If so, I wonder if it might be worth writing "cargo -C rust build ..." instead of "cd rust && ...".

- This conditional on DEBUG passes the "--verbose" option in both cases. Should we only pass the "--verbose" option when we have "V=1"?

- Regardless of whether or not we condition passing "--release" (or not) on "DEBUG", this line should also be "ifdef DEBUG" similar to above.

Makefile cocciclean: · line 3774

Quoted 13 lines
Makefile +5 -1
+else
+	cd rust && RUSTFLAGS="-Aunused_imports -Adead_code" cargo build --verbose --release
+endif
+
 $(REFTABLE_LIB): $(REFTABLE_OBJS)
 	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
 
@@ -3756,7 +3771,10 @@ cocciclean:
 	$(RM) -r .build/contrib/coccinelle
 	$(RM) contrib/coccinelle/*.cocci.patch
 
-clean: profile-clean coverage-clean cocciclean
+rustclean:

I'm nitpicking, and we don't *really* have a convention here between separating the clean target from "clean", as we have both "profile-clean" and "cocciclean". I prefer the former, and think that it would be nice to use that convention, but this is pretty much textbook bike-shedding and not something that I really care about ;-).

Makefile cocciclean: · line 3775

Quoted 1 line
Makefile +1 -0
+	cd rust && cargo clean

Same question here about whether or not this could be written as "cargo -C clean".

Quoted 15 lines
git-compat-util.h +7 -0
index 4678e21c4cb8..82dc99764ac0 100644
@@ -196,6 +196,23 @@ static inline int is_xplatform_dir_sep(int c)
 #include "compat/msvc.h"
 #endif
 
+/* rust types */
+typedef uint8_t   u8;
+typedef uint16_t  u16;
+typedef uint32_t  u32;
+typedef uint64_t  u64;
+
+typedef int8_t    i8;

git-compat-util.h static inline int is_xplatform_dir_sep(int c) · line 215

Quoted 10 lines
git-compat-util.h +10 -0
+typedef int16_t   i16;
+typedef int32_t   i32;
+typedef int64_t   i64;
+
+typedef float     f32;
+typedef double    f64;
+
+typedef size_t    usize;
+typedef ptrdiff_t isize;
+

Makes sense. Should we also have "bool" here (assuming that the series declaring the "use bool" experiment a success lands)? I guess maybe not either way, <stdbool.h> defines "bool" as the type name, identically to Rust.

Thanks,
Taylor

#4 lore raw

Re: [PATCH 1/7] xdiff: introduce rust

In reply to brian m. carlson
Quoted 7 lines · brian m. carlson

In addition, as I mentioned downthread, because our intention is to support the Debian stable toolchain for a year after the new stable release, unless we are exceptionally careful about dependencies, we may end up with a case where distros need to use older dependencies patched for security but other users may want to update the versions to newer dependencies with security fixes but that do not work on our pinned Rust version.

...or Debian users who have an older version of the toolchain installed and got an unfriendly "cannot parse 'version = 4'" error when trying to build with this patch series applied locally ;-).

Thanks,
Taylor

#14 lore raw

Re: [PATCH 2/7] xdiff/xprepare: remove superfluous forward declarations

In reply to Ezekiel Newren via GitGitGadget
Quoted 3 lines · Ezekiel Newren via GitGitGadget
---
 xdiff/xprepare.c | 116 ++++++++++++++++++++---------------------------
 1 file changed, 50 insertions(+), 66 deletions(-)

Makes sense. Reviewing with "--color-moved" makes it straightforward to see that the contents of xdl_prepare_env() were not modified by this patch.

Thanks,
Taylor

#17 lore raw

Re: [PATCH 4/7] xdiff: make fields of xrecord_t Rust friendly

In reply to Ezekiel Newren via GitGitGadget
Quoted 6 lines · Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

A few commits ago, we added definitions for Rust primitive types, to facilitate interoperability between C and Rust. Switch a few variables to use these types. Which, for now, will require adding some casts.

Hmm, interesting. I am not super familiar with how people typically handle interoperability between C and Rust, but having to change types on the C side to make it work with Rust is a bit surprising to me.

I would have expected that the Rust side would have declared its types using libc::c_int, libc::size_t, and so on. I think I have a vague preference towards putting the burden of casting on the Rust side, but, again, I am not super familiar with how transitions like these are typically approached.

Quoted 9 lines
---
 xdiff/xdiffi.c    |  8 ++++----
 xdiff/xemit.c     |  2 +-
 xdiff/xmerge.c    | 14 +++++++-------
 xdiff/xpatience.c |  2 +-
 xdiff/xprepare.c  |  6 +++---
 xdiff/xtypes.h    |  6 +++---
 xdiff/xutils.c    |  4 ++--
 7 files changed, 21 insertions(+), 21 deletions(-)

The rest of the patch looks good to me, assuming that the burden of casting is placed on the C side.

Thanks,
Taylor

#33 lore raw

Re: [PATCH 5/7] xdiff: separate parsing lines from hashing them

In reply to Ezekiel Newren via GitGitGadget
Quoted 3 lines · Ezekiel Newren via GitGitGadget
---
 xdiff/xprepare.c | 75 ++++++++++++++++++++++++++++--------------------
 1 file changed, 44 insertions(+), 31 deletions(-)

Not being all that familiar with the xdiff code, this patch took me a little while longer to read and understand, but the transformation looks correct to me.

Thanks,
Taylor

#18 lore raw

Re: [PATCH 4/7] xdiff: make fields of xrecord_t Rust friendly

In reply to Taylor Blau
Quoted 12 lines · Taylor Blau
Quoted 6 lines · Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

A few commits ago, we added definitions for Rust primitive types, to facilitate interoperability between C and Rust. Switch a few variables to use these types. Which, for now, will require adding some casts.

Hmm, interesting. I am not super familiar with how people typically handle interoperability between C and Rust, but having to change types on the C side to make it work with Rust is a bit surprising to me.

Quoted 5 lines

I would have expected that the Rust side would have declared its types using libc::c_int, libc::size_t, and so on. I think I have a vague preference towards putting the burden of casting on the Rust side, but, again, I am not super familiar with how transitions like these are typically approached.

Rust normally handles byte strings as slices or vectors of u8 (that is, C's uint8_t). C handles them as char, which may or may not be unsigned, as we all know, which leads to some "entertaining" problems from time to time.

Also, in general, Rust doesn't offer generic system-specific types, such as long, except for C FFI. This is actually a strong benefit, since it means we're not inclined to write unsigned long and then wonder why things are broken on Windows: instead, we write either usize (the equivalent of size_t) or u64 (for things like file sizes). This is much more ingrained than it is in Go, which has a tendency to use int (Rust's isize) a lot and much less often specific types.

If we're going to move this code entirely into Rust, then it makes sense to cast temporarily, and I'm fine doing that in C, since it's C that has the weird system-dependent behaviour (arbitrary decisions on the signedness of char). That actually allows us to have more confidence in the safety and maintainability of the Rust code since it is less system dependent and leave the suspect pieces in C. It may also, interestingly enough, also allow us to easily get rid of the weird 2 GB limit on diffs due to the unpleasant dependency on int in the xdiff code, which I would absolutely love to see.

However, I'm not dead set against casting in Rust if that's what everyone else wants instead.

Signature · 2 lines
brian m. carlson (they/them)
Toronto, Ontario, CA
#36 lore raw

Re: [PATCH 6/7] xdiff: conditionally use Rust's implementation of xxhash

In reply to Ezekiel Newren via GitGitGadget
Quoted 7 lines · Ezekiel Newren via GitGitGadget
    // desktop
    // CPU: 8-core Intel Core i7-9700 (-MCP-) speed/min/max: 800/800/4700 MHz
    $ hyperfine --warmup 3 -L exe $BASE/build_release/git,$BASE/build_v2.49.0/git '{exe} log --oneline --shortstat v6.8..v6.9 >/dev/null'
    Benchmark 1: /home/steamuser/dev/git/build_release/git log --oneline --shortstat v6.8..v6.9 >/dev/null
      Time (mean ± σ):      6.823 s ±  0.020 s    [User: 6.624 s, System: 0.180 s]
      Range (min … max):    6.801 s …  6.858 s    10 runs
Quoted 7 lines
    Benchmark 2: /home/steamuser/dev/git/build_v2.49.0/git log --oneline --shortstat v6.8..v6.9 >/dev/null
      Time (mean ± σ):      8.151 s ±  0.024 s    [User: 7.928 s, System: 0.198 s]
      Range (min … max):    8.105 s …  8.184 s    10 runs

    Summary
      /home/steamuser/dev/git/build_release/git log --oneline --shortstat v6.8..v6.9 >/dev/null ran
        1.19 ± 0.01 times faster than /home/steamuser/dev/git/build_v2.49.0/git log --oneline --shortstat v6.8..v6.9 >/dev/null

Very cool!

Quoted 7 lines
---
 rust/Cargo.lock       |  7 +++++++
 rust/xdiff/Cargo.toml |  1 +
 rust/xdiff/src/lib.rs |  7 +++++++
 xdiff/xprepare.c      | 19 +++++++++++++++++--
 4 files changed, 32 insertions(+), 2 deletions(-)

This patch is delightfully simple. Thank you for carefully preparing the previous five patches to make this one as tiny as it is.

xdiff/xprepare.c static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_ · line 181

Quoted 5 lines
xdiff/xprepare.c +1 -0
@@ -175,14 +178,26 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
 
 	xdl_parse_lines(mf, narec, xdf);
 
+	if ((xpp->flags & XDF_WHITESPACE_FLAGS) == 0) {

It may be worth adding a comment here to explain why we aren't using xdl_hash_record() when xpp->flags lacks XDF_WHITESPACE_FLAGS.

(As a meta-note for reviewing this series, there are a handful of style nits that I haven't mentioned, e.g., if (... == 0) instead of if (!...). But since the xdiff code doesn't match the project's style conventions, I have avoided mentioning it in my review.)

Quoted 7 lines
xdiff/xprepare.c +7 -0
+		for (usize i = 0; i < (usize) xdf->nrec; i++) {
+			xrecord_t *rec = xdf->recs[i];
+			rec->ha = xxh3_64(rec->ptr, rec->size);
+		}
+	} else {
+		for (usize i = 0; i < (usize) xdf->nrec; i++) {
+			xrecord_t *rec = xdf->recs[i];

xdiff/xprepare.c static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_ · line 196

Quoted 10 lines
xdiff/xprepare.c +5 -2
+			char const* dump = (char const*) rec->ptr;
+			rec->ha = xdl_hash_record(&dump, (char const*) (rec->ptr + rec->size), xpp->flags);
+		}
+	}
+
 	for (usize i = 0; i < (usize) xdf->nrec; i++) {
 		xrecord_t *rec = xdf->recs[i];
-		char const* dump = (char const*) rec->ptr;
-		rec->ha = xdl_hash_record(&dump, (char const*) (rec->ptr + rec->size), xpp->flags);
 		xdl_classify_record(pass, cf, rec);

I am curious why you are calling xdl_classify_record() here as a post-processing step rather than inline with the hash calculation above.

Thanks,
Taylor

#19 lore raw

Re: [PATCH 4/7] xdiff: make fields of xrecord_t Rust friendly

In reply to brian m. carlson
Quoted 43 lines · brian m. carlson
Quoted 12 lines · Taylor Blau
Quoted 6 lines · Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

A few commits ago, we added definitions for Rust primitive types, to facilitate interoperability between C and Rust. Switch a few variables to use these types. Which, for now, will require adding some casts.

Hmm, interesting. I am not super familiar with how people typically handle interoperability between C and Rust, but having to change types on the C side to make it work with Rust is a bit surprising to me.

Quoted 5 lines

I would have expected that the Rust side would have declared its types using libc::c_int, libc::size_t, and so on. I think I have a vague preference towards putting the burden of casting on the Rust side, but, again, I am not super familiar with how transitions like these are typically approached.

Rust normally handles byte strings as slices or vectors of u8 (that is, C's uint8_t). C handles them as char, which may or may not be unsigned, as we all know, which leads to some "entertaining" problems from time to time.

Also, in general, Rust doesn't offer generic system-specific types, such as long, except for C FFI. This is actually a strong benefit, since it means we're not inclined to write unsigned long and then wonder why things are broken on Windows: instead, we write either usize (the equivalent of size_t) or u64 (for things like file sizes). This is much more ingrained than it is in Go, which has a tendency to use int (Rust's isize) a lot and much less often specific types.

If we're going to move this code entirely into Rust, then it makes sense to cast temporarily, and I'm fine doing that in C, since it's C that has the weird system-dependent behaviour (arbitrary decisions on the signedness of char). That actually allows us to have more confidence in the safety and maintainability of the Rust code since it is less system dependent and leave the suspect pieces in C. It may also, interestingly enough, also allow us to easily get rid of the weird 2 GB limit on diffs due to the unpleasant dependency on int in the xdiff code, which I would absolutely love to see.

Quoted 2 lines

However, I'm not dead set against casting in Rust if that's what everyone else wants instead.

In general, I too would prefer to do the casting on the C side; after all, part of the reason for Rust is the language safety, which we compromise if we force it into using ambiguously sized variables.

However, I think it might be somewhat case-dependent...

Here, we have C calling into APIs that will be defined and implemented in Rust. Further along the road of adopting Rust in more places, we may have future cases where we have Rust calling into APIs defined and implemented in C. I'm wondering if in such a world the rule of thumb that makes the most sense would be to have a caller-must-cast-as-necessary guideline, rather than specifying the casting side by language.

#21 lore raw

Re: [PATCH 4/7] xdiff: make fields of xrecord_t Rust friendly

In reply to brian m. carlson
Quoted 19 lines · brian m. carlson
Quoted 12 lines · Taylor Blau
Quoted 6 lines · Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

A few commits ago, we added definitions for Rust primitive types, to facilitate interoperability between C and Rust. Switch a few variables to use these types. Which, for now, will require adding some casts.

Hmm, interesting. I am not super familiar with how people typically handle interoperability between C and Rust, but having to change types on the C side to make it work with Rust is a bit surprising to me.

Quoted 5 lines

I would have expected that the Rust side would have declared its types using libc::c_int, libc::size_t, and so on. I think I have a vague preference towards putting the burden of casting on the Rust side, but, again, I am not super familiar with how transitions like these are typically approached.

Quoted 4 lines

Rust normally handles byte strings as slices or vectors of u8 (that is, C's uint8_t). C handles them as char, which may or may not be unsigned, as we all know, which leads to some "entertaining" problems from time to time.

;-)

Quoted 8 lines

Also, in general, Rust doesn't offer generic system-specific types, such as long, except for C FFI. This is actually a strong benefit, since it means we're not inclined to write unsigned long and then wonder why things are broken on Windows: instead, we write either usize (the equivalent of size_t) or u64 (for things like file sizes). This is much more ingrained than it is in Go, which has a tendency to use int (Rust's isize) a lot and much less often specific types.

Quoted 9 lines

If we're going to move this code entirely into Rust, then it makes sense to cast temporarily, and I'm fine doing that in C, since it's C that has the weird system-dependent behaviour (arbitrary decisions on the signedness of char). That actually allows us to have more confidence in the safety and maintainability of the Rust code since it is less system dependent and leave the suspect pieces in C. It may also, interestingly enough, also allow us to easily get rid of the weird 2 GB limit on diffs due to the unpleasant dependency on int in the xdiff code, which I would absolutely love to see.

Ahhh. Thanks for the patient explanation. That makes a lot of sense, and casting on the Rust side seems like the right approach for this spot.

Thanks,
Taylor

#20 lore raw

Re: [PATCH 4/7] xdiff: make fields of xrecord_t Rust friendly

In reply to Elijah Newren
Quoted 9 lines · Elijah Newren
Quoted 2 lines

However, I'm not dead set against casting in Rust if that's what everyone else wants instead.

In general, I too would prefer to do the casting on the C side; after all, part of the reason for Rust is the language safety, which we compromise if we force it into using ambiguously sized variables.

However, I think it might be somewhat case-dependent...

Quoted 7 lines

Here, we have C calling into APIs that will be defined and implemented in Rust. Further along the road of adopting Rust in more places, we may have future cases where we have Rust calling into APIs defined and implemented in C. I'm wondering if in such a world the rule of thumb that makes the most sense would be to have a caller-must-cast-as-necessary guideline, rather than specifying the casting side by language.

Yeah, I think having some sort of general guidance here spelled out in CodingGuidelines would be helpful. I don't think that it needs to prescribe a specific rule, but having some of what you and brian wrote above captured more permanently would give contributors more information about why they may want to place the casts on one side versus the other.

Thanks,
Taylor

#60 lore raw
In reply to Taylor Blau
Quoted 7 lines · Taylor Blau

I agree. I don't think that there is ever going to be a "perfect" time to introduce a hard dependency on Rust, and I don't think that should hold the project back from adopting it.

I am far from a Rust expert, but I think that a more modern, memory-safe language will attract newer contributors who may have a fresher perspective on the project, and I think that's a good thing.

Yes, I think that's true. Rust is by far the most admired programming language to work with, according to the 2024 Stack Overflow Developer Survey. We will likely attract new contributors who find C intimidating or a bit of a hassle[0] but are excited about working on Rust, especially in a project as compelling as Git[1].

Quoted 10 lines

The alternative, of course, is to continue to use C and not take any dependency on Rust. I think there is a middle-ground in there somewhere to be able to build with (e.g.) "make" or "make RUST=1", but I would really like to see the project take a firmer stance here.

I worry that having build support for both "with Rust" and "C only" will create a headache not just at the build system level, but also in the code itself. Having a patchwork of features, optimizations, or bug fixes that either are or aren't supported depending on whether Rust support was specified at build-time seems like a worst-of-all-worlds outcome.

I definitely agree. I already find it terribly inconvenient when I end up when git grep doesn't support -P and I imagine that having lots of features that weren't available would be bothersome.

I also think that using a combination of C and Rust will end up with us still writing a lot of unsafe Rust code to interoperate with C. If we want to reap the benefits in terms of memory and thread safety[2], we'll be better off sticking with just Rust.

I will also say that while it may be more challenging to compile Git at first on Windows, as we move more towards an all-Rust codebase, Git may end up being easier to maintain there as we depend more on the standard library.

Quoted 8 lines

Agreed. Of course, I think we would all like Git to be able to build and run on as many platforms as is reasonably possible. But we cannot support all platforms for all time. It is also not the Git project's responsibility to ensure that every platform is Rust-friendly.

Hopefully the platforms that we currently support but won't after this patch series have niche enough workloads that they do not need the absolute latest-and-greatest Git release at all times.

I will also point out that many OS and CPU architectures are actually supported in Rust upstream. rustc --print target-list includes things like the following:

* m68k-unknown-linux-gnu (Amiga and other 68000 processors on Linux)
* wasm32-unknown-unknown (Git in your browser?)
* armv7a-nuttx-eabi (ARM processors running the embedded NuttX OS)
* x86_64-pc-cygwin (Cygwin[3])
* sparc64-unknown-openbsd (OpenBSD on UltraSPARC)

All Debian release architectures are supported, for instance, as well as several non-release architectures. The only Debian architectures that I don't believe are supported are alpha, hppa, ia64, sh4, and x32 (which is an amd64 variant that can run amd64 code just fine).

Quoted 4 lines

Yeah, I think that this is the most interesting part of the discussion here. I am not knowledgeable enough about Rust's release cadence and platform compatibility to have an opinion here. But I trust brian's judgement ;-).

I'll see what Ezekiel thinks about this and I can send out a patch for review if that's desired.

[0] I've been writing C for three-quarters of my life and I still find debugging segfaults and other memory problems to be annoying and tiresome, so I'm very interested in getting out of that business while working on Git. While the limiting factor for my contributions to Git is often time, I would feel more excited about working on Git in Rust than in C and I'm confident I'd write better quality code and more unit tests as well (which benefits us and our users as well).
[1] I definitely think there's a cool factor to working on Git.
[2] Having more thread-safe code might allow us to more easily add threading to other parts of Git that would benefit from it and thus improve performance in many cases.
[3] This was missing for a long time, but it's finally here now, which is also good news for Git for Windows.

Signature · 2 lines
brian m. carlson (they/them)
Toronto, Ontario, CA
#67 lore raw
In reply to Ezekiel Newren via GitGitGadget
Quoted 4 lines · Ezekiel Newren via GitGitGadget

This series accelerates xdiff by 5-19%.

It also introduces Rust as a hard dependency.

Quoted 8 lines

…and it doesn’t yet pass a couple of the github workflows; hints from Windows experts, and opinions on ambiguous primitives would be appreciated (see below).

This is just the beginning of many patches that I have to convert portions of, maybe eventually all of, xdiff to Rust. While working on that conversion, I found several ways to clarify the code, along with some optimizations.

Just a quick heads-up: We (as in Vim/Neovim) have been using gits xdiff library for use in Vim and Neovim.

Is the plan to get rid of xdiffs C source completely and replace it by a Rust implementation?

Thanks,
Chris

Signature · 1 line
Eine gute Stellung ist besser als jede Arbeit.
#70 lore raw
In reply to Ezekiel Newren via GitGitGadget

Hi Ezekiel

Thanks for working on this

Quoted 13 lines · Ezekiel Newren via GitGitGadget

This series accelerates xdiff by 5-19%.

It also introduces Rust as a hard dependency.

…and it doesn’t yet pass a couple of the github workflows; hints from Windows experts, and opinions on ambiguous primitives would be appreciated (see below).

This is just the beginning of many patches that I have to convert portions of, maybe eventually all of, xdiff to Rust. While working on that conversion, I found several ways to clarify the code, along with some optimizations.

Quoted 7 lines

So...

This obviously raises the question of whether we are ready to accept a hard dependency on Rust. Previous discussions on the mailing list and at Git Merge 2024 have not answered that question. If not now, will we be willing to accept such a hard dependency later? And what route do we want to take to get there?

As far as git goes I think introducing a hard dependency on rust is fine. It is widely supported, the only issue I'm aware of is the lack of support on NonStop and I don't think it is reasonable for such a minority platform to hold the rest of the project to ransom. There is a question about the other users of the xdiff code though. libgit2 carries a copy as do other projects like neovim. I've cc'd the libgit2 maintainer and posted a link to this thread in neovim github [1]

I've left a few comments on the patches

Thanks

Phillip

[1] https://github.com/neovim/neovim/discussions/34987

Quoted 64 lines

About the optimizations in this series:

1. xdiff currently uses DJB2a for hashing (even though it is not explicitly named as such). This is an older hashing algorithm, and modern alternatives are superior. I chose xxhash because it’s faster, more collision resistant, and designed to be a standard. Other hash algorithms like aHash, MurMurHash, SipHash, and Fnv1a were considered, but my local testing made me feel like xxhash was the best choice for usage in xdiff.

2. In support of switching to xxhash, parsing and hashing were split into separate steps. And it turns out that memchr() is faster for parsing than character-by-character iteration.

About the workflow builds/tests that aren’t working with this series:

1. Windows fails to build. I don’t know which rust toolchain is even correct for this or if multiple are needed. Example failed build: https://github.com/git/git/actions/runs/16353209191

2. I386/ubuntu:focal will build, but fails the tests. The kernel reports the bitness as 64 despite the container being 32. I believe the issue is that C uses ambiguous primitives (which differ in size between platforms). The new code should use unambiguous primitives from Rust (u32, u64, etc.) rather than perpetuating ambiguous primitive types. Since the current xdiff API hardcodes the ambiguous types, though, those places will need to be migrated to unambiguous primitives. Much of the C code needs a slight refactor to be compatible with the Rust FFI and usually requires converting ambiguous to unambiguous types. What does this community think of this approach?

My brother (Elijah, cc’ed) has been guiding and reviewing my work here.

Ezekiel Newren (7):
xdiff: introduce rust
xdiff/xprepare: remove superfluous forward declarations
xdiff: delete unnecessary fields from xrecord_t and xdfile_t
xdiff: make fields of xrecord_t Rust friendly
xdiff: separate parsing lines from hashing them
xdiff: conditionally use Rust's implementation of xxhash
github_workflows: install rust

  .github/workflows/main.yml |   1 +
  .gitignore                 |   1 +
  Makefile                   |  60 +++++++---
  build_rust.sh              |  59 ++++++++++
  ci/install-dependencies.sh |  14 +--
  ci/install-rust.sh         |  33 ++++++
  ci/lib.sh                  |   8 ++
  ci/make-test-artifacts.sh  |   7 ++
  ci/run-build-and-tests.sh  |  10 ++
  git-compat-util.h          |  17 +++
  meson.build                |  40 +++++--
  rust/Cargo.lock            |  21 ++++
  rust/Cargo.toml            |   6 +
  rust/interop/Cargo.toml    |  14 +++
  rust/interop/src/lib.rs    |   0
  rust/xdiff/Cargo.toml      |  16 +++
  rust/xdiff/src/lib.rs      |   7 ++
  xdiff/xdiffi.c             |   8 +-
  xdiff/xemit.c              |   2 +-
  xdiff/xmerge.c             |  14 +--
  xdiff/xpatience.c          |   2 +-
  xdiff/xprepare.c           | 226 ++++++++++++++++++-------------------
  xdiff/xtypes.h             |   9 +-
  xdiff/xutils.c             |   4 +-
  24 files changed, 414 insertions(+), 165 deletions(-)

create mode 100755 build_rust.sh
create mode 100644 ci/install-rust.sh
create mode 100644 rust/Cargo.lock
create mode 100644 rust/Cargo.toml
create mode 100644 rust/interop/Cargo.toml
create mode 100644 rust/interop/src/lib.rs
create mode 100644 rust/xdiff/Cargo.toml
create mode 100644 rust/xdiff/src/lib.rs

base-commit: 16bd9f20a403117f2e0d9bcda6c6e621d3763e77
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1980%2Fezekielnewren%2Fxdiff_rust_speedup-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1980/ezekielnewren/xdiff_rust_speedup-v1
Pull-Request: https://github.com/git/git/pull/1980

#34 lore raw

Re: [PATCH 5/7] xdiff: separate parsing lines from hashing them

In reply to Ezekiel Newren via GitGitGadget

Hi Ezekiel

Quoted 7 lines · Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

We want to use xxhash for faster hashing. To facilitate that and to simplify the code. Separate the concerns of parsing and hashing into discrete steps. This makes swapping the hash function much easier. Since xdl_hash_record() both parses and hashses lines, this requires some slight code restructuring.

That makes sense though unfortunately we seem to have lost some error handling in the restructuring. How much does this extra pass over the input data slow down the cases that don't end up using xxhash?

Quoted 10 lines
---
  xdiff/xprepare.c | 75 ++++++++++++++++++++++++++++--------------------
  1 file changed, 44 insertions(+), 31 deletions(-)
xdiff/xprepare.c +0 -0
index 747268e4fdf7..c44005e9bbb8 100644

xdiff/xprepare.c static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t · line 138

Quoted 10 lines
xdiff/xprepare.c +7 -0
@@ -129,13 +129,39 @@ static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t
  }
 
 
+static void xdl_parse_lines(mmfile_t *mf, long narec, xdfile_t *xdf) {
+	u8 const* ptr = (u8 const*) mf->ptr;
+	usize len = (usize) mf->size;
+
+	xdf->recs = NULL;
+	xdf->nrec = 0;
+	XDL_ALLOC_ARRAY(xdf->recs, narec);

This should return error if the allocation fails like the original code. Although that does not make any difference for git a number of other projects such as libgit2 carry a copy of our xdiff code and want to be able to handle allocation failures.

xdiff/xprepare.c static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t · line 150

Quoted 11 lines
xdiff/xprepare.c +11 -0
+	while (len > 0) {
+		xrecord_t *rec = NULL;
+		usize length;
+		u8 const* result = memchr(ptr, '\n', len);
+		if (result) {
+			length = result - ptr + 1;
+		} else {
+			length = len;
+		}
+		if (XDL_ALLOC_GROW(xdf->recs, xdf->nrec + 1, narec))
+			die("XDL_ALLOC_GROW failed");

We should return an error rather than dying here

xdiff/xprepare.c static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t · line 151

Quoted 1 line
xdiff/xprepare.c +1 -0
+		rec = xdl_cha_alloc(&xdf->rcha);

We should return an error if the call fails like the original code

Quoted 44 lines
xdiff/xprepare.c +11 -20
+		rec->ptr = ptr;
+		rec->size = length;
+		rec->ha = 0;
+		xdf->recs[xdf->nrec++] = rec;
+		ptr += length;
+		len -= length;
+	}
+
+}
+
+
  static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
  			   xdlclassifier_t *cf, xdfile_t *xdf) {
-	long nrec, bsize;
-	unsigned long hav;
-	char const *blk, *cur, *top, *prev;
-	xrecord_t *crec;
-	xrecord_t **recs;
  	unsigned long *ha;
  	char *rchg;
  	long *rindex;
@@ -143,50 +169,37 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
  	ha = NULL;
  	rindex = NULL;
  	rchg = NULL;
-	recs = NULL;
 
  	if (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0)
  		goto abort;
-	if (!XDL_ALLOC_ARRAY(recs, narec))
-		goto abort;
 
-	nrec = 0;
-	if ((cur = blk = xdl_mmfile_first(mf, &bsize))) {
-		for (top = blk + bsize; cur < top; ) {
-			prev = cur;
-			hav = xdl_hash_record(&cur, top, xpp->flags);
-			if (XDL_ALLOC_GROW(recs, nrec + 1, narec))
-				goto abort;
-			if (!(crec = xdl_cha_alloc(&xdf->rcha)))
-				goto abort;
-			crec->ptr = (u8 const*) prev;
-			crec->size = (long) (cur - prev);
-			crec->ha = hav;

xdiff/xprepare.c static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_ · line 181

Quoted 10 lines
xdiff/xprepare.c +6 -4
-			recs[nrec++] = crec;
-			if (xdl_classify_record(pass, cf, crec) < 0)
-				goto abort;
-		}
+	xdl_parse_lines(mf, narec, xdf);
+
+	for (usize i = 0; i < (usize) xdf->nrec; i++) {
+		xrecord_t *rec = xdf->recs[i];
+		char const* dump = (char const*) rec->ptr;
+		rec->ha = xdl_hash_record(&dump, (char const*) (rec->ptr + rec->size), xpp->flags);

I think we should update xdl_hash_record() to stop updating dump and use the length from xdl_parse_lines(). Now that we parse the lines before hashing we should use that length in the hash function so we have a single definition of line length.

xdiff/xprepare.c static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_ · line 182

Quoted 1 line
xdiff/xprepare.c +1 -0
+		xdl_classify_record(pass, cf, rec);

We should return an error if this call fails like the original code

Thanks

Phillip

Quoted 38 lines
xdiff/xprepare.c +6 -7
  	}
 
-	if (!XDL_CALLOC_ARRAY(rchg, nrec + 2))
+
+	if (!XDL_CALLOC_ARRAY(rchg, xdf->nrec + 2))
  		goto abort;
 
  	if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) &&
  	    (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF)) {
-		if (!XDL_ALLOC_ARRAY(rindex, nrec + 1))
+		if (!XDL_ALLOC_ARRAY(rindex, xdf->nrec + 1))
  			goto abort;
-		if (!XDL_ALLOC_ARRAY(ha, nrec + 1))
+		if (!XDL_ALLOC_ARRAY(ha, xdf->nrec + 1))
  			goto abort;
  	}
 
-	xdf->nrec = nrec;
-	xdf->recs = recs;
  	xdf->rchg = rchg + 1;
  	xdf->rindex = rindex;
  	xdf->nreff = 0;
  	xdf->ha = ha;
  	xdf->dstart = 0;
-	xdf->dend = nrec - 1;
+	xdf->dend = xdf->nrec - 1;
 
  	return 0;
 
@@ -194,7 +207,7 @@ abort:
  	xdl_free(ha);
  	xdl_free(rindex);
  	xdl_free(rchg);
-	xdl_free(recs);
+	xdl_free(xdf->recs);
  	xdl_cha_free(&xdf->rcha);
  	return -1;
  }
#22 lore raw

Re: [PATCH 4/7] xdiff: make fields of xrecord_t Rust friendly

In reply to Ezekiel Newren via GitGitGadget

Hi Ezekiel

Quoted 6 lines · Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

A few commits ago, we added definitions for Rust primitive types, to facilitate interoperability between C and Rust. Switch a few variables to use these types. Which, for now, will require adding some casts.

How necessary is it to change char' to 'u8' so long as the rust and C sides both use a type that is the same size? Also what's the advantage of using these typedefs rather than the normal C types like unit8_t ?

xdiff/xdiffi.c static int get_indent(xrecord_t *rec) · line 421

Quoted 10 lines
xdiff/xdiffi.c +1 -1
index 5a96e36dfbea..3b364c61f671 100644
@@ -418,7 +418,7 @@ static int get_indent(xrecord_t *rec)
  	long i;
  	int ret = 0;
 
-	for (i = 0; i < rec->size; i++) {
+	for (i = 0; i < (long) rec->size; i++) {

i is a loop counter and array index so we can lose this cast by changeing i to size_t

Thanks

Phillip

Quoted 162 lines
xdiff/xdiffi.c +3 -3
  		char c = rec->ptr[i];
 
  		if (!XDL_ISSPACE(c))
@@ -1005,11 +1005,11 @@ static void xdl_mark_ignorable_lines(xdchange_t *xscr, xdfenv_t *xe, long flags)
 
  		rec = &xe->xdf1.recs[xch->i1];
  		for (i = 0; i < xch->chg1 && ignore; i++)
-			ignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags);
+			ignore = xdl_blankline((const char*) rec[i]->ptr, rec[i]->size, flags);
 
  		rec = &xe->xdf2.recs[xch->i2];
  		for (i = 0; i < xch->chg2 && ignore; i++)
-			ignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags);
+			ignore = xdl_blankline((const char*)rec[i]->ptr, rec[i]->size, flags);
 
  		xch->ignore = ignore;
  	}
@@ -1020,7 +1020,7 @@ static int record_matches_regex(xrecord_t *rec, xpparam_t const *xpp) {
  	size_t i;
 
  	for (i = 0; i < xpp->ignore_regex_nr; i++)
-		if (!regexec_buf(xpp->ignore_regex[i], rec->ptr, rec->size, 1,
+		if (!regexec_buf(xpp->ignore_regex[i], (const char*) rec->ptr, rec->size, 1,
  				 &regmatch, 0))
  			return 1;
 
xdiff/xemit.c +1 -1
index 1d40c9cb4076..bbf7b7f8c862 100644
@@ -24,7 +24,7 @@
 
  static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec) {
 
-	*rec = xdf->recs[ri]->ptr;
+	*rec = (char const*) xdf->recs[ri]->ptr;
 
  	return xdf->recs[ri]->size;
  }
xdiff/xmerge.c +7 -7
index af40c88a5b36..6fa6ea61a208 100644
@@ -101,8 +101,8 @@ static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2,
  	xrecord_t **rec2 = xe2->xdf2.recs + i2;
 
  	for (i = 0; i < line_count; i++) {
-		int result = xdl_recmatch(rec1[i]->ptr, rec1[i]->size,
-			rec2[i]->ptr, rec2[i]->size, flags);
+		int result = xdl_recmatch((const char*) rec1[i]->ptr, rec1[i]->size,
+			(const char*) rec2[i]->ptr, rec2[i]->size, flags);
  		if (!result)
  			return -1;
  	}
@@ -324,8 +324,8 @@ static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1,
 
  static int recmatch(xrecord_t *rec1, xrecord_t *rec2, unsigned long flags)
  {
-	return xdl_recmatch(rec1->ptr, rec1->size,
-			    rec2->ptr, rec2->size, flags);
+	return xdl_recmatch((char const*) rec1->ptr, rec1->size,
+			    (char const*) rec2->ptr, rec2->size, flags);
  }
 
  /*
@@ -383,10 +383,10 @@ static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m,
  		 */
  		t1.ptr = (char *)xe1->xdf2.recs[m->i1]->ptr;
  		t1.size = xe1->xdf2.recs[m->i1 + m->chg1 - 1]->ptr
-			+ xe1->xdf2.recs[m->i1 + m->chg1 - 1]->size - t1.ptr;
+			+ xe1->xdf2.recs[m->i1 + m->chg1 - 1]->size - (u8 const*) t1.ptr;
  		t2.ptr = (char *)xe2->xdf2.recs[m->i2]->ptr;
  		t2.size = xe2->xdf2.recs[m->i2 + m->chg2 - 1]->ptr
-			+ xe2->xdf2.recs[m->i2 + m->chg2 - 1]->size - t2.ptr;
+			+ xe2->xdf2.recs[m->i2 + m->chg2 - 1]->size - (u8 const*) t2.ptr;
  		if (xdl_do_diff(&t1, &t2, xpp, &xe) < 0)
  			return -1;
  		if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 ||
@@ -440,7 +440,7 @@ static int line_contains_alnum(const char *ptr, long size)
  static int lines_contain_alnum(xdfenv_t *xe, int i, int chg)
  {
  	for (; chg; chg--, i++)
-		if (line_contains_alnum(xe->xdf2.recs[i]->ptr,
+		if (line_contains_alnum((char const*) xe->xdf2.recs[i]->ptr,
  				xe->xdf2.recs[i]->size))
  			return 1;
  	return 0;
xdiff/xpatience.c +1 -1
index 77dc411d1937..986a3a3f749a 100644
@@ -121,7 +121,7 @@ static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map,
  		return;
  	map->entries[index].line1 = line;
  	map->entries[index].hash = record->ha;
-	map->entries[index].anchor = is_anchor(xpp, map->env->xdf1.recs[line - 1]->ptr);
+	map->entries[index].anchor = is_anchor(xpp, (const char*) map->env->xdf1.recs[line - 1]->ptr);
  	if (!map->first)
  		map->first = map->entries + index;
  	if (map->last) {
xdiff/xprepare.c +3 -3
index ad356281f939..747268e4fdf7 100644
@@ -96,12 +96,12 @@ static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t
  	char const *line;
  	xdlclass_t *rcrec;
 
-	line = rec->ptr;
+	line = (char const*) rec->ptr;
  	hi = (long) XDL_HASHLONG(rec->ha, cf->hbits);
  	for (rcrec = cf->rchash[hi]; rcrec; rcrec = rcrec->next)
  		if (rcrec->ha == rec->ha &&
  				xdl_recmatch(rcrec->line, rcrec->size,
-					rec->ptr, rec->size, cf->flags))
+					(const char*) rec->ptr, rec->size, cf->flags))
  			break;
 
  	if (!rcrec) {
@@ -159,7 +159,7 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
  				goto abort;
  			if (!(crec = xdl_cha_alloc(&xdf->rcha)))
  				goto abort;
-			crec->ptr = prev;
+			crec->ptr = (u8 const*) prev;
  			crec->size = (long) (cur - prev);
  			crec->ha = hav;
  			recs[nrec++] = crec;
xdiff/xtypes.h +3 -3
index 8b8467360ecf..6e5f67ebf380 100644
@@ -39,9 +39,9 @@ typedef struct s_chastore {
  } chastore_t;
 
  typedef struct s_xrecord {
-	char const *ptr;
-	long size;
-	unsigned long ha;
+	u8 const* ptr;
+	usize size;
+	u64 ha;
  } xrecord_t;
 
  typedef struct s_xdfile {
xdiff/xutils.c +2 -2
index 444a108f87c0..10e4f20b7c31 100644
@@ -418,10 +418,10 @@ int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp,
 
  	subfile1.ptr = (char *)diff_env->xdf1.recs[line1 - 1]->ptr;
  	subfile1.size = diff_env->xdf1.recs[line1 + count1 - 2]->ptr +
-		diff_env->xdf1.recs[line1 + count1 - 2]->size - subfile1.ptr;
+		diff_env->xdf1.recs[line1 + count1 - 2]->size - (u8 const*) subfile1.ptr;
  	subfile2.ptr = (char *)diff_env->xdf2.recs[line2 - 1]->ptr;
  	subfile2.size = diff_env->xdf2.recs[line2 + count2 - 2]->ptr +
-		diff_env->xdf2.recs[line2 + count2 - 2]->size - subfile2.ptr;
+		diff_env->xdf2.recs[line2 + count2 - 2]->size - (u8 const*) subfile2.ptr;
  	if (xdl_do_diff(&subfile1, &subfile2, xpp, &env) < 0)
  		return -1;
 
#81 lore raw
In reply to Ezekiel Newren via GitGitGadget
Quoted 1 line · Ezekiel Newren via GitGitGadget

This series accelerates xdiff by 5-19%.

;-)

Do we know how much of that can be attributed to the hash algorithm difference, and how much for languages?

The earlier parts of the series to trim unused code and refactor look to me that they are good changes regardless of whether we introduce a different hash algorithm, and/or we use an implementation of that different hash algorithm written in Rust. IOW, even if neither of these two happens, I would think that the earlier parts are independently good pieces.

Thanks for starting this effort. And thanks Elijah for helping.

And in case nobody has said this yet, welcome to the Git development community.

#68 lore raw
In reply to Christian Brabandt
Quoted 15 lines · Christian Brabandt
Quoted 4 lines · Ezekiel Newren via GitGitGadget

This series accelerates xdiff by 5-19%.

It also introduces Rust as a hard dependency.

Quoted 8 lines

…and it doesn’t yet pass a couple of the github workflows; hints from Windows experts, and opinions on ambiguous primitives would be appreciated (see below).

This is just the beginning of many patches that I have to convert portions of, maybe eventually all of, xdiff to Rust. While working on that conversion, I found several ways to clarify the code, along with some optimizations.

Quoted 5 lines

Just a quick heads-up: We (as in Vim/Neovim) have been using gits xdiff library for use in Vim and Neovim.

Is the plan to get rid of xdiffs C source completely and replace it by a Rust implementation?

As far as I know, there is no such plan that is widely agreed upon (yet).

The discussion starter thread you are looking at only introduces a new code path that uses a different line hash function written in Rust when whitespace munging search is not enabled, and everything else still is written in C, but since it is just a discussion starter so far.

I would personally have liked the effort to start with xmerge code, not xdiff machinery, for various reasons, but that may be just me ;-)

#37 lore raw

Re: [PATCH 6/7] xdiff: conditionally use Rust's implementation of xxhash

In reply to Ezekiel Newren via GitGitGadget
Quoted 12 lines · Ezekiel Newren via GitGitGadget
xdiff/xprepare.c +5 -0
+extern u64 xxh3_64(u8 const* ptr, usize size);
+
+
 static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
 			   xdlclassifier_t *cf, xdfile_t *xdf) {
 	unsigned long *ha;
@@ -175,14 +178,26 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
 
 	xdl_parse_lines(mf, narec, xdf);
 
+	if ((xpp->flags & XDF_WHITESPACE_FLAGS) == 0) {
+		for (usize i = 0; i < (usize) xdf->nrec; i++) {

xdiff/xprepare.c static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_ · line 192

Quoted 10 lines
xdiff/xprepare.c +10 -0
+			xrecord_t *rec = xdf->recs[i];
+			rec->ha = xxh3_64(rec->ptr, rec->size);
+		}
+	} else {
+		for (usize i = 0; i < (usize) xdf->nrec; i++) {
+			xrecord_t *rec = xdf->recs[i];
+			char const* dump = (char const*) rec->ptr;
+			rec->ha = xdl_hash_record(&dump, (char const*) (rec->ptr + rec->size), xpp->flags);
+		}
+	}

As a technology demonstration and proof of concept patch, this is very nice, but to be upstreamed for real, we'd want a variant of xxhash that can work with the contents with whitespace squashed to be usable with various whitespace ignoring modes of operation. When that happens, and when the result turns out to be more performant, we can lose the xdl_hash_record() and require only the xxhash, which would be great.

And that variant of xxhash that understands whitespace squashing can of course be written in Rust as a part of this effort when the series loses its RFC status. At the same time, those who want to use our xdiff code in third-party software (like libgit2 and vim) may want to reimplement it in C in their copy.

Thanks.

#71 lore raw
In reply to Phillip Wood
Quoted 17 lines · Phillip Wood

Hi Ezekiel

Thanks for working on this

Quoted 10 lines · Ezekiel Newren via GitGitGadget

So...

This obviously raises the question of whether we are ready to accept a hard
dependency on Rust. Previous discussions on the mailing list and at Git Merge 2024 have not answered that question. If not now, will we be willing
to accept such a hard dependency later? And what route do we want to take to
get there?

Quoted 7 lines

As far as git goes I think introducing a hard dependency on rust is fine. It is widely supported, the only issue I'm aware of is the lack of support on NonStop and I don't think it is reasonable for such a minority platform to hold the rest of the project to ransom. There is a question about the other users of the xdiff code though. libgit2 carries a copy as do other projects like neovim. I've cc'd the libgit2 maintainer and posted a link to this thread in neovim github [1]

A hard dependency on rust for Gentoo amd64 would potentially require building https://github.com/thepowersgang/mrustc followed by building 13 and counting versions of rustc in order to get to the latest version. What is the minimum supported version in this series, by the way?

bin packages for rust do exist but not everyone wants to use non-distro provided binaries, sometimes for auditability reasons.

For Gentoo HPPA, Alpha, m68k it will simply mean the removal (or end of life and staying forever on 2.50, perhaps) of Git. There is no rust compiler there.

Even s390 support for rust is limited to a precompiled version not everyone is willing to use.

GCC-rs will probably fix this general issue.

Signature · 1 line
Eli Schwartz
#82 lore raw
In reply to Junio C Hamano
Quoted 9 lines · Junio C Hamano
Quoted 1 line · Ezekiel Newren via GitGitGadget

This series accelerates xdiff by 5-19%.

;-)

Do we know how much of that can be attributed to the hash algorithm difference, and how much for languages?

This is difficult to answer because xdl_hash_record() hashes the string as it determines its length. Xxhash uses simd instructions, so all data must be contiguous and processed as blocks rather than byte by byte. The components cannot be directly compared due to the nature of processing differences.

Quoted 11 lines

The earlier parts of the series to trim unused code and refactor look to me that they are good changes regardless of whether we introduce a different hash algorithm, and/or we use an implementation of that different hash algorithm written in Rust. IOW, even if neither of these two happens, I would think that the earlier parts are independently good pieces.

Thanks for starting this effort. And thanks Elijah for helping.

And in case nobody has said this yet, welcome to the Git development community.

Thanks to you and everyone else for your review comments. I'm going to need time to investigate and respond.

#55 lore raw

Re: [PATCH 7/7] github_workflows: install rust

In reply to brian m. carlson
Quoted 15 lines · brian m. carlson

.github/workflows/main.yml on: [push, pull_request] · line 7

Quoted 9 lines · Ezekiel Newren via GitGitGadget
.github/workflows/main.yml +1 -0
index 7dbf9f7f123c..8aac18a6ba45 100644
@@ -4,6 +4,7 @@ on: [push, pull_request]
 
 env:
   DEVELOPER: 1
+  RUST_VERSION: 1.87.0

Our discussed plan is to support the version in Debian stable, plus a year. So we'd be supporting 1.63.0 for a year after trixie's release.

Quoted 8 lines

The reason for that is that people build backports and security updates for Git for stable releases of distros and they will use the distro toolchain for doing so. Forcing distros to constantly build with the latest toolchain is pretty hostile, especially since the lifespan of Rust release is six weeks.

If the Rust project provides LTS releases in the future, then we can consider adopting those.

The RUST_VERSION variable in .github/workflows/main.yaml had to have a specific version. 1.87.0 was selected since that's what I was using locally. Elijah made me aware that an older version of rust might be desired, but didn't know which one. I'll switch to 1.63.0 or whatever the community decides.

Quoted 10 lines

.github/workflows/main.yml on: [push, pull_request]

Quoted 6 lines
.github/workflows/main.yml +6 -0
+if [ "$rust_target" = "release" ]; then
+  rust_args="--release"
+  export RUSTFLAGS='-Aunused_imports -Adead_code'
+elif [ "$rust_target" = "debug" ]; then
+  rust_args=""
+  export RUSTFLAGS='-Aunused_imports -Adead_code -C debuginfo=2 -C opt-level=1 -C force-frame-pointers=yes'

Can you say a little about why these options are needed and the defaults are inadequate? For instance, I build with the default options both in my personal projects and at work and don't see a problem.

What I found is that if I have a Rust function

#[no_mangle]
pub fn call_from_c(arg: u64) {}

which is only meant to be called from C and isn’t called from elsewhere in Rust, then cargo will misidentify this function as dead code. This was the reason for adding ‘-Adead_code’.

The reason for adding ‘-Aunused_imports’ is somewhat IDE related; if I paste code somewhere, RustRover will sometimes automatically add the necessary imports. However, if I delete a chunk of code, it’ll highlight the imports that are no longer used if I scroll to the top of the file, but it won’t automatically remove them. Since they aren’t automatically removed, it’s easier to build with ‘-Aunused_imports’.

The remaining arguments, ‘-C debuginfo=2 -C opt-level=1 -C force-frame-pointers=yes’ is to make /usr/bin/perf output more amenable to analysis.

Quoted 4 lines

I don't know if you plan to do this in a future series, but we'd also want cargo's tests to be run as part of CI and we'd want a lint job that ran clippy with both 1.63.0 and the latest stable version of Rust to make sure things were tidy.

Yeah I'd like that too; we can add that in a future patch series.

Quoted 3 lines

--
brian m. carlson (they/them)
Toronto, Ontario, CA

#5 lore raw

Re: [PATCH 1/7] xdiff: introduce rust

In reply to brian m. carlson
Quoted 18 lines · brian m. carlson
Quoted 16 lines · Ezekiel Newren via GitGitGadget
rust/Cargo.lock new file +10 -0
new file mode 100644
index 000000000000..fb1eac690b39
@@ -0,0 +1,14 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "interop"
+version = "0.1.0"
+
+[[package]]
+name = "xdiff"
Quoted 8 lines

rust/Cargo.lock line 14

Quoted 4 lines
rust/Cargo.lock new file +4 -0
+version = "0.1.0"
+dependencies = [
+ "interop",
+]

I would prefer that we not check in Cargo.lock in Git. Part of the reason is that it changes across versions and so building with a different version of the toolchain can update the file.

This goes against what I think is best practices. Don’t we need
Cargo.lock to audit and debug platform specific issues, and to ensure reproducibility? Without Cargo.lock, we might get different results
one minute to the next if one of our dependencies releases a new
version. Checking in Cargo.lock aligns with Cargo’s documented best practices (https://doc.rust-lang.org/cargo/faq.html#why-have-cargolock-in-version-control).

Quoted 9 lines

In addition, as I mentioned downthread, because our intention is to support the Debian stable toolchain for a year after the new stable release, unless we are exceptionally careful about dependencies, we may end up with a case where distros need to use older dependencies patched for security but other users may want to update the versions to newer dependencies with security fixes but that do not work on our pinned Rust version. We can't possibly satisfy both sets of people if we pin dependencies in Cargo.lock, so we probably want to avoid checking it in and ignore it instead.

I understand your concern and I agree that this could become a problem. I’m totally flexible on which rust version should be used, but without Cargo.lock checked in we lose the ability to audit why a build failed. I think that this will be a pain point, but numbing that pain means we can’t solve intermittent problems due to dependencies in the future.

Quoted 3 lines

--
brian m. carlson (they/them)
Toronto, Ontario, CA

#69 lore raw
In reply to Junio C Hamano
Quoted 3 lines · Junio C Hamano

Christian Brabandt <[email protected]> writes:

[...]

Quoted 8 lines
Quoted 5 lines

Just a quick heads-up: We (as in Vim/Neovim) have been using gits xdiff library for use in Vim and Neovim.

Is the plan to get rid of xdiffs C source completely and replace it by a Rust implementation?

As far as I know, there is no such plan that is widely agreed upon (yet).

Yeah, Ezekiel just barely notified the community of his efforts yesterday with this patch series. :-)

Quoted 9 lines

The discussion starter thread you are looking at only introduces a new code path that uses a different line hash function written in Rust when whitespace munging search is not enabled, and everything else still is written in C, but since it is just a discussion starter so far.

I would personally have liked the effort to start with xmerge code, not xdiff machinery, for various reasons, but that may be just me ;-)

We have both xdiff/xmerge.[ch] and xdiff/{xdiff.h,xdiffi.[ch]}. When you say "xdiff", I suspect that you're referring to the files within the directory rather than to the whole directory, yes?

I actually pointed Ezekiel at xhistogram to start (and thought he might only do that file), then he backed up to xprepare, and then he continued from there on to other bits of xdiff/, including xmerge. Different parts are at different stages of conversion and testing. He's not just transliterating but also trying to both clean up the code and look for performance improvements (and it's sometimes hard to do both; he's hit a few maintainability vs. performance tradeoffs and those will likely result in some questions on the list at some point). It's been a long slog, especially given how arcane xdiff sometimes feels. Anyway, along the way, he recognized the DJB2a hash -- something I certainly wouldn't have recognized or even thought to investigate. It led him to this optimization, which I thought was a really good find, and it seemed like it'd make for a good initial series to send to the list to get a feel for what people thought about possibly Rustifying xdiff/.

#72 lore raw
In reply to Eli Schwartz

[2025-07-18 17:25:01-0400] Eli Schwartz:

Quoted 36 lines · Phillip Wood
Quoted 17 lines

Hi Ezekiel

Thanks for working on this

Quoted 10 lines · Ezekiel Newren via GitGitGadget

So...

This obviously raises the question of whether we are ready to accept a hard
dependency on Rust. Previous discussions on the mailing list and at Git Merge 2024 have not answered that question. If not now, will we be willing
to accept such a hard dependency later? And what route do we want to take to
get there?

Quoted 7 lines

As far as git goes I think introducing a hard dependency on rust is fine. It is widely supported, the only issue I'm aware of is the lack of support on NonStop and I don't think it is reasonable for such a minority platform to hold the rest of the project to ransom. There is a question about the other users of the xdiff code though. libgit2 carries a copy as do other projects like neovim. I've cc'd the libgit2 maintainer and posted a link to this thread in neovim github [1]

A hard dependency on rust for Gentoo amd64 would potentially require building https://github.com/thepowersgang/mrustc followed by building 13 and counting versions of rustc in order to get to the latest version. What is the minimum supported version in this series, by the way?

bin packages for rust do exist but not everyone wants to use non-distro provided binaries, sometimes for auditability reasons.

Quoted 6 lines

For Gentoo HPPA, Alpha, m68k it will simply mean the removal (or end of life and staying forever on 2.50, perhaps) of Git. There is no rust compiler there.

Even s390 support for rust is limited to a precompiled version not everyone is willing to use.

Also in other distro concerns, if it trickles down to libgit2, extra care should be taken to avoid creating circular dependencies due to cargo depending on libgit2 (via git2 crate).

For example with making sure it can reasonably be built via meson's Rust support rather than through cargo.

Quoted 5 lines

GCC-rs will probably fix this general issue.

Signature · 1 line
Eli Schwartz
#85 lore raw
In reply to Ezekiel Newren via GitGitGadget

Hi Ezekiel,
pleasure to make your acquaintance!

Quoted 3 lines · Ezekiel Newren via GitGitGadget

1. Windows fails to build. I don’t know which rust toolchain is even correct for this or if multiple are needed. Example failed build:

   https://github.com/git/git/actions/runs/16353209191

There are a couple of problems, not just one. Here are the patches that I would like to ask you to take custody of (for your convenience, I have pushed them to https://github.com/dscho/git as the xdiff_rust_speedup branch). Please find them below. They _just_ fix the build, but the tests with win+Meson still fail (and as "win+Meson test" jobs keep the logs of the failed tests a well-guarded secret, due to time constraints I have to stop looking into this for now).

Thank you for working on this,
Johannes

-- snipsnap --
From 72c50ee3f9df5ccfe48bf6f44b2c6bba05a680bf Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <[email protected]>
Date: Sat, 19 Jul 2025 21:24:07 +0200
Subject: [PATCH 1/3] Do support Windows again after requiring Rust

By default, Rust wants to build MS Visual C-compatible libraries on Windows, because that is _the_ native C compiler.

Git is historically lacking in its MSVC support, and the official Git for Windows versions are built using GCC instead. As a consequence, a (subset of a) GCC toolchain is installed as part of the windows-build job of every CI build.

Naturally, this requires adjustments in how Rust is called, most importantly it requires installing support for a GCC-compatible build target.

Let's make the necessary adjustment both in the CI-specific code that installs Rust as well as in the Windows-specific configuration in config.mak.uname.

---
 ci/install-rust.sh | 3 +++
 config.mak.uname   | 9 +++++++++
 2 files changed, 12 insertions(+)
ci/install-rust.sh +3 -0
index 141ceddb17cfe..c22baa629ceb7 100644
@@ -28,6 +28,9 @@ if [ "$BITNESS" = "32" ]; then
   $CARGO_HOME/bin/rustup default --force-non-host $RUST_VERSION || exit $?
 else
   $CARGO_HOME/bin/rustup default $RUST_VERSION || exit $?
+  if [ "$CI_OS_NAME" = "windows" ]; then
+    $CARGO_HOME/bin/rustup target add x86_64-pc-windows-gnu || exit $?
+  fi
 fi
 
 . $CARGO_HOME/env
config.mak.uname +9 -0
index 3e26bb074a4b5..fbe7cebf40edd 100644
@@ -727,19 +727,28 @@ ifeq ($(uname_S),MINGW)
 		prefix = /mingw32
 		HOST_CPU = i686
 		BASIC_LDFLAGS += -Wl,--pic-executable,-e,_mainCRTStartup
+		CARGO_BUILD_TARGET = i686-pc-windows-gnu
         endif
         ifeq (MINGW64,$(MSYSTEM))
 		prefix = /mingw64
 		HOST_CPU = x86_64
 		BASIC_LDFLAGS += -Wl,--pic-executable,-e,mainCRTStartup
+		CARGO_BUILD_TARGET = x86_64-pc-windows-gnu
         else ifeq (CLANGARM64,$(MSYSTEM))
 		prefix = /clangarm64
 		HOST_CPU = aarch64
 		BASIC_LDFLAGS += -Wl,--pic-executable,-e,mainCRTStartup
+		CARGO_BUILD_TARGET = aarch64-pc-windows-gnu
         else
 		COMPAT_CFLAGS += -D_USE_32BIT_TIME_T
 		BASIC_LDFLAGS += -Wl,--large-address-aware
         endif
+
+	export CARGO_BUILD_TARGET
+	RUST_TARGET_DIR = rust/target/$(CARGO_BUILD_TARGET)/$(RUST_BUILD_MODE)
+	# Unfortunately now needed because of Rust
+	EXTLIBS += -luserenv
+
 	CC = gcc
 	COMPAT_CFLAGS += -D__USE_MINGW_ANSI_STDIO=0 -DDETECT_MSYS_TTY \
 		-fstack-protector-strong
Signature · 22 lines
2.50.1.windows.1


From ef6e4394ae26d8f28cb0d9e456810ce0818e623b Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <[email protected]>
Date: Sat, 19 Jul 2025 23:08:11 +0200
Subject: [PATCH 2/3] win+Meson: allow for xdiff to be compiled with MSVC

The `build_rust.sh` script is quite opinionated about the naming scheme
of the C compiler: It assumes that the xdiff library file will be named
`libxdiff.a`.

However, MS Visual C generates `xdiff.lib` files instead; This naming
scheme has been in use in a very, very long time.

Let's allow for that.

Signed-off-by: Johannes Schindelin <[email protected]>
---
 build_rust.sh |  7 ++++++-
 meson.build   | 12 +++++++++---
 2 files changed, 15 insertions(+), 4 deletions(-)
build_rust.sh +6 -1
index 4c12135cd2050..694d48d857a58 100755
@@ -44,7 +44,12 @@ fi
 
 cd $dir_rust && cargo clean && pwd && cargo build -p $crate $rust_args; cd ..
 
-libfile="lib${crate}.a"
+if grep x86_64-pc-windows-msvc rust/target/.rustc_info.json
+then
+  libfile="${crate}.lib"
+else
+  libfile="lib${crate}.a"
+fi
 dst=$dir_build/$libfile
 
 if [ "$dir_git_root" != "$dir_build" ]; then
meson.build +9 -3
index 047d7e5b66306..5e89a5dd0e00f 100644
@@ -277,8 +277,16 @@ else
   rustflags = '-Aunused_imports -Adead_code -C debuginfo=2 -C opt-level=1 -C force-frame-pointers=yes'
 endif
 
+compiler = meson.get_compiler('c')
+
+if compiler.get_id() == 'msvc'
+  xdiff_lib_filename = 'xdiff.lib'
+else
+  xdiff_lib_filename = 'libxdiff.a'
+endif
+
 rust_build_xdiff = custom_target('rust_build_xdiff',
-  output: 'libxdiff.a',
+  output: xdiff_lib_filename,
   build_by_default: true,
   build_always_stale: true,
   command: [
@@ -288,8 +296,6 @@ rust_build_xdiff = custom_target('rust_build_xdiff',
   install: false,
 )
 
-compiler = meson.get_compiler('c')
-
 libgit_sources = [
   'abspath.c',
   'add-interactive.c',
Signature · 23 lines
2.50.1.windows.1


From 9c3b017cfa069211027fbb1f6d3b97c8e7edda81 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <[email protected]>
Date: Sat, 19 Jul 2025 23:22:57 +0200
Subject: [PATCH 3/3] win+Meson: do allow linking with the Rust-built xdiff

When linking against the Rust-built `xdiff`, there is now a new required
dependency: Without _also_ linking to the system library `userenv`, the
compile would fail with this error message:

  xdiff.lib(std-c85e9beb7923f636.std.df32d1bc89881d89-cgu.0.rcgu.o) :
  error LNK2019: unresolved external symbol __imp_GetUserProfileDirectoryW
  referenced in function _ZN3std3env8home_dir17hfd1c3b6676cd78f6E

Therefore, just like we do in case of Makefile-based builds on Windows,
we now also link to that library when building with Meson.

Signed-off-by: Johannes Schindelin <[email protected]>
---
 meson.build | 1 +
 1 file changed, 1 insertion(+)
meson.build +1 -0
index 5e89a5dd0e00f..af015f04763fd 100644
@@ -1260,6 +1260,7 @@ elif host_machine.system() == 'windows'
   ]
 
   libgit_dependencies += compiler.find_library('ntdll')
+  libgit_dependencies += compiler.find_library('userenv')
   libgit_include_directories += 'compat/win32'
   if compiler.get_id() == 'msvc'
     libgit_include_directories += 'compat/vcbuild/include'
Signature · 1 line
2.50.1.windows.1
#40 lore raw

Re: [PATCH 6/7] xdiff: conditionally use Rust's implementation of xxhash

In reply to Ezekiel Newren via GitGitGadget

Hi Ezekiel,

rust/xdiff/src/lib.rs line 7

Quoted 12 lines · Ezekiel Newren via GitGitGadget
rust/xdiff/src/lib.rs +7 -0
index e69de29bb2d1..96975975a1ba 100644
@@ -0,0 +1,7 @@
+
+
+#[no_mangle]
+unsafe extern "C" fn xxh3_64(ptr: *const u8, size: usize) -> u64 {
+    let slice = std::slice::from_raw_parts(ptr, size);
+    xxhash_rust::xxh3::xxh3_64(slice)
+}

I know that this is a pretty small file, but I do notice that it does not have a license header.

This reminds me of the unfortunate oversight to be careful about making (and keeping) libgit.a's source files compatible with libgit2's license to nurture a fruitful exchange between those two projects.

With Rust, we still have a really good chance to learn from history and avoid that mistake: Gitoxide is a very exciting project with clear overlap in its mission to implement Git functionality in Rust. Gitoxide is dual-licensed under the Apache License v2 and the MIT license (see
https://github.com/GitoxideLabs/gitoxide?tab=readme-ov-file#license).

Would you mind adding a license header to that file that explicitly allows the contents of the file to be used in Gitoxide, to get the Rust effort started on a good foot?

Thank you,
Johannes

#57 lore raw

Re: [PATCH 7/7] github_workflows: install rust

In reply to Ezekiel Newren via GitGitGadget

Hi Ezekiel,

build_rust.sh line 55

Quoted 6 lines · Ezekiel Newren via GitGitGadget
build_rust.sh new file +6 -0
+if [ "$dir_git_root" != "$dir_build" ]; then
+  src=$dir_rust/target/$rust_target/$libfile
+  if [ ! -f $src ]; then
+    echo >&2 "::error:: cannot find path of static library"
+    exit 5
+  fi

As I found out the hard way, this error message could be more helpful if it specified a couple of those variables that play into the failure (or all of them).

Would you mind changing the error message accordingly?

Thank you,
Johannes

#31 lore raw

Re: [PATCH 4/7] xdiff: make fields of xrecord_t Rust friendly

In reply to Ezekiel Newren via GitGitGadget

Hi Ezekiel,

Quoted 7 lines · Ezekiel Newren via GitGitGadget
xdiff/xtypes.h +0 -0
index 8b8467360ecf..6e5f67ebf380 100644
@@ -39,9 +39,9 @@ typedef struct s_chastore {
 } chastore_t;
 

xdiff/xtypes.h typedef struct s_chastore { · line 47

Quoted 10 lines
xdiff/xtypes.h +3 -3
 typedef struct s_xrecord {
-	char const *ptr;
-	long size;
-	unsigned long ha;
+	u8 const* ptr;
+	usize size;
+	u64 ha;
 } xrecord_t;
 
 typedef struct s_xdfile {

You cannot do this on its own, you'll also have to do the following (which incidentally fixes the linux32 failures as well as the win test and win+Meson test failures, see
https://github.com/dscho/git/actions/runs/16394351471):

-- snipsnap --
From 8693c83858a7c9308e54fb470cd7e82bcf67c758 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <[email protected]>
Date: Sun, 20 Jul 2025 02:34:35 +0200
Subject: [PATCH] fixup! xdiff: make fields of xrecord_t Rust friendly

To make xdl_classify_record() work, the ha attributes of xrecord_t and of s_xdlclass _must_ have the same range. Otherwise the function won't be able to recognize previously-classified records correctly when the ha recorded in the xrecord_t is so wide that it won't fit into the s_xdlclass' attribute and therefore they won't match when they need to match.

---
 xdiff/xprepare.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
xdiff/xprepare.c +1 -1
index 5a2e52f102cf7..c0463bacd94b0 100644
@@ -32,7 +32,7 @@
 
 typedef struct s_xdlclass {
 	struct s_xdlclass *next;
-	unsigned long ha;
+	u64 ha;
 	char const *line;
 	long size;
 	long idx;
Signature · 1 line
2.50.1.windows.1
#86 lore raw
In reply to Johannes Schindelin
Quoted 67 lines · Johannes Schindelin

Hi Ezekiel,
pleasure to make your acquaintance!

Quoted 3 lines · Ezekiel Newren via GitGitGadget

1. Windows fails to build. I don’t know which rust toolchain is even correct for this or if multiple are needed. Example failed build:

   https://github.com/git/git/actions/runs/16353209191

There are a couple of problems, not just one. Here are the patches that I would like to ask you to take custody of (for your convenience, I have pushed them to https://github.com/dscho/git as the xdiff_rust_speedup branch). Please find them below. They _just_ fix the build, but the tests with win+Meson still fail (and as "win+Meson test" jobs keep the logs of the failed tests a well-guarded secret, due to time constraints I have to stop looking into this for now).

Thank you for working on this,
Johannes

-- snipsnap --
From 72c50ee3f9df5ccfe48bf6f44b2c6bba05a680bf Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <[email protected]>
Date: Sat, 19 Jul 2025 21:24:07 +0200
Subject: [PATCH 1/3] Do support Windows again after requiring Rust

By default, Rust wants to build MS Visual C-compatible libraries on Windows, because that is _the_ native C compiler.

Git is historically lacking in its MSVC support, and the official Git for Windows versions are built using GCC instead. As a consequence, a (subset of a) GCC toolchain is installed as part of the windows-build job of every CI build.

Naturally, this requires adjustments in how Rust is called, most importantly it requires installing support for a GCC-compatible build target.

Let's make the necessary adjustment both in the CI-specific code that installs Rust as well as in the Windows-specific configuration in config.mak.uname.

---
ci/install-rust.sh | 3 +++
config.mak.uname | 9 +++++++++

2 files changed, 12 insertions(+)
ci/install-rust.sh +0 -0
index 141ceddb17cfe..c22baa629ceb7 100644
@@ -28,6 +28,9 @@ if [ "$BITNESS" = "32" ]; then
  $CARGO_HOME/bin/rustup default --force-non-host $RUST_VERSION || exit $?

else
$CARGO_HOME/bin/rustup default $RUST_VERSION || exit $?

ci/install-rust.sh +3 -0
+  if [ "$CI_OS_NAME" = "windows" ]; then
+    $CARGO_HOME/bin/rustup target add x86_64-pc-windows-gnu || exit $?
+  fi

fi

. $CARGO_HOME/env

config.mak.uname +0 -0
index 3e26bb074a4b5..fbe7cebf40edd 100644

config.mak.uname ifeq ($(uname_S),MINGW) · line 730

Quoted 4 lines
config.mak.uname +1 -0
@@ -727,19 +727,28 @@ ifeq ($(uname_S),MINGW)
 		prefix = /mingw32
 		HOST_CPU = i686
 		BASIC_LDFLAGS += -Wl,--pic-executable,-e,_mainCRTStartup
+		CARGO_BUILD_TARGET = i686-pc-windows-gnu

While i686-pc-windows-gnu is fine for CI, it would mean we'd have to bump our supported Windows version up to Windows 10. If we want to keep supporting Windows 8.1, we'll need i686-win7-windows-gnu, at least on rust 1.78 and newer.[1][2] We'd probably build Windows versions on rust 1.88 currently.[3]

[1] https://blog.rust-lang.org/2024/02/26/Windows-7/
[2] https://doc.rust-lang.org/rustc/platform-support/win7-windows-gnu.html
[3] https://packages.msys2.org/base/mingw-w64-rust

config.mak.uname ifeq ($(uname_S),MINGW) · line 736

Quoted 6 lines
config.mak.uname +1 -0
        endif
        ifeq (MINGW64,$(MSYSTEM))
 		prefix = /mingw64
 		HOST_CPU = x86_64
 		BASIC_LDFLAGS += -Wl,--pic-executable,-e,mainCRTStartup
+		CARGO_BUILD_TARGET = x86_64-pc-windows-gnu

For x86_64 we'llprobably also want x86_64-win7-windows-gnu if we want to keep Windows 8.1 support.

config.mak.uname ifeq ($(uname_S),MINGW) · line 741

Quoted 5 lines
config.mak.uname +1 -0
        else ifeq (CLANGARM64,$(MSYSTEM))
 		prefix = /clangarm64
 		HOST_CPU = aarch64
 		BASIC_LDFLAGS += -Wl,--pic-executable,-e,mainCRTStartup
+		CARGO_BUILD_TARGET = aarch64-pc-windows-gnu

Are we sure this target currently exists? It's at least undocumented.[4] I think we might want aarch64-pc-windows-gnullvm for CLANGARM64, either way.[5]

[4] https://doc.rust-lang.org/rustc/platform-support/windows-gnu.html
[5] https://doc.rust-lang.org/rustc/platform-support/windows-gnullvm.html

Best regards

Matthias

Quoted 126 lines
config.mak.uname +6 -0
        else
 		COMPAT_CFLAGS += -D_USE_32BIT_TIME_T
 		BASIC_LDFLAGS += -Wl,--large-address-aware
        endif
+
+	export CARGO_BUILD_TARGET
+	RUST_TARGET_DIR = rust/target/$(CARGO_BUILD_TARGET)/$(RUST_BUILD_MODE)
+	# Unfortunately now needed because of Rust
+	EXTLIBS += -luserenv
+
 	CC = gcc
 	COMPAT_CFLAGS += -D__USE_MINGW_ANSI_STDIO=0 -DDETECT_MSYS_TTY \
 		-fstack-protector-strong
Signature · 22 lines
2.50.1.windows.1


From ef6e4394ae26d8f28cb0d9e456810ce0818e623b Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <[email protected]>
Date: Sat, 19 Jul 2025 23:08:11 +0200
Subject: [PATCH 2/3] win+Meson: allow for xdiff to be compiled with MSVC

The `build_rust.sh` script is quite opinionated about the naming scheme
of the C compiler: It assumes that the xdiff library file will be named
`libxdiff.a`.

However, MS Visual C generates `xdiff.lib` files instead; This naming
scheme has been in use in a very, very long time.

Let's allow for that.

Signed-off-by: Johannes Schindelin <[email protected]>
---
build_rust.sh |  7 ++++++-
meson.build   | 12 +++++++++---
2 files changed, 15 insertions(+), 4 deletions(-)
build_rust.sh +0 -0
index 4c12135cd2050..694d48d857a58 100755
@@ -44,7 +44,12 @@ fi
 

cd $dir_rust && cargo clean && pwd && cargo build -p $crate $rust_args; cd ..

build_rust.sh +6 -1
-libfile="lib${crate}.a"
+if grep x86_64-pc-windows-msvc rust/target/.rustc_info.json
+then
+  libfile="${crate}.lib"
+else
+  libfile="lib${crate}.a"
+fi

dst=$dir_build/$libfile

if [ "$dir_git_root" != "$dir_build" ]; then

meson.build +0 -0
index 047d7e5b66306..5e89a5dd0e00f 100644
@@ -277,8 +277,16 @@ else
  rustflags = '-Aunused_imports -Adead_code -C debuginfo=2 -C opt-level=1 -C force-frame-pointers=yes'

endif

meson.build +8 -0
+compiler = meson.get_compiler('c')
+
+if compiler.get_id() == 'msvc'
+  xdiff_lib_filename = 'xdiff.lib'
+else
+  xdiff_lib_filename = 'libxdiff.a'
+endif
+

rust_build_xdiff = custom_target('rust_build_xdiff',

meson.build +1 -3
-  output: 'libxdiff.a',
+  output: xdiff_lib_filename,
  build_by_default: true,
  build_always_stale: true,
  command: [
@@ -288,8 +296,6 @@ rust_build_xdiff = custom_target('rust_build_xdiff',
  install: false,
 )
 
-compiler = meson.get_compiler('c')
-

libgit_sources = [
'abspath.c',
'add-interactive.c',

Signature · 23 lines
2.50.1.windows.1


From 9c3b017cfa069211027fbb1f6d3b97c8e7edda81 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <[email protected]>
Date: Sat, 19 Jul 2025 23:22:57 +0200
Subject: [PATCH 3/3] win+Meson: do allow linking with the Rust-built xdiff

When linking against the Rust-built `xdiff`, there is now a new required
dependency: Without _also_ linking to the system library `userenv`, the
compile would fail with this error message:

 xdiff.lib(std-c85e9beb7923f636.std.df32d1bc89881d89-cgu.0.rcgu.o) :
 error LNK2019: unresolved external symbol __imp_GetUserProfileDirectoryW
 referenced in function _ZN3std3env8home_dir17hfd1c3b6676cd78f6E

Therefore, just like we do in case of Makefile-based builds on Windows,
we now also link to that library when building with Meson.

Signed-off-by: Johannes Schindelin <[email protected]>
---
meson.build | 1 +
1 file changed, 1 insertion(+)
meson.build +1 -0
index 5e89a5dd0e00f..af015f04763fd 100644
@@ -1260,6 +1260,7 @@ elif host_machine.system() == 'windows'
  ]
 
  libgit_dependencies += compiler.find_library('ntdll')
+  libgit_dependencies += compiler.find_library('userenv')
  libgit_include_directories += 'compat/win32'
  if compiler.get_id() == 'msvc'
    libgit_include_directories += 'compat/vcbuild/include'
Signature · 1 line
2.50.1.windows.1
#41 lore raw

Re: [PATCH 6/7] xdiff: conditionally use Rust's implementation of xxhash

In reply to Johannes Schindelin

Hi Johannes

Quoted 20 lines · Johannes Schindelin

Hi Ezekiel,

rust/xdiff/src/lib.rs line 7

Quoted 12 lines · Ezekiel Newren via GitGitGadget
rust/xdiff/src/lib.rs +7 -0
index e69de29bb2d1..96975975a1ba 100644
@@ -0,0 +1,7 @@
+
+
+#[no_mangle]
+unsafe extern "C" fn xxh3_64(ptr: *const u8, size: usize) -> u64 {
+    let slice = std::slice::from_raw_parts(ptr, size);
+    xxhash_rust::xxh3::xxh3_64(slice)
+}

I know that this is a pretty small file, but I do notice that it does not have a license header.

Quoted 3 lines

This reminds me of the unfortunate oversight to be careful about making (and keeping) libgit.a's source files compatible with libgit2's license to nurture a fruitful exchange between those two projects.

I'm not sure I follow your reasoning here. libgit2 was started after git and chose to use an incompatible license. I wasn't around at the time but isn't there a list of git contributors who are happy to re-license their contributions with the linking exception used by libgit2?

Quoted 9 lines

With Rust, we still have a really good chance to learn from history and avoid that mistake: Gitoxide is a very exciting project with clear overlap in its mission to implement Git functionality in Rust. Gitoxide is dual-licensed under the Apache License v2 and the MIT license (see
https://github.com/GitoxideLabs/gitoxide?tab=readme-ov-file#license).

Would you mind adding a license header to that file that explicitly allows the contents of the file to be used in Gitoxide, to get the Rust effort started on a good foot?

I wary of that for two reasons. Firstly over time it is de-facto re-licensing git as the amount of rust code grows and the amount of C code shrinks which deserves a wider discussion. Secondly it makes it harder to convert our C code which is licensed under GPL2 (or in the case of xdiff LGPL) to rust if the rust code uses a different license.

If someone wants to start a discussion about re-licensing git (and is prepared to do all of the associated admin in the event that it happens) then by all means do so but I don't think it we want to slip such a change into this series.

Thanks

Phillip

#83 lore raw
In reply to Junio C Hamano
Quoted 8 lines · Junio C Hamano
Quoted 1 line · Ezekiel Newren via GitGitGadget

This series accelerates xdiff by 5-19%.

;-)

Do we know how much of that can be attributed to the hash algorithm difference, and how much for languages?

That's an interesting question. The two patches below [1] switch xdiff to use xxhash from libxxhash. On my computer the rust and C implementations both speed up "git log --oneline --shortstat" by 15%. Just over half of that seems to come from hoisting the check for whitespace flags in xdl_hash_record() out of the loop in xdl_prepare_ctx() and the rest comes from the change in hash function. As I understand it the hash is implemented using SIMD compiler intrinsics and the rust implementation is basically a copy of the C code in libxxhash. I wonder how well xxhash performs compared to our existing hash on platforms without an optimized implementation.

Thanks

Phillip

[1] These patches are available in the xdiff-hashing-experiments branch at https://github.com/phillipwood/git

---- 8< ----
From 06e7abdcfb9fc3f143ef84644966d6fce128d8ae Mon Sep 17 00:00:00 2001
From: Phillip Wood <[email protected]>
Date: Sat, 19 Jul 2025 10:58:48 +0100
Subject: [PATCH 1/2] xdiff: refactor xdl_hash_record()
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Inline the check for whitespace flags so that the compiler can hoist it out of the loop in xdl_prepare_ctx(). This improves the performance by 8%.

$ hyperfine --warmup=1 -L rev HEAD,HEAD^  --setup='git checkout {rev} -- :/ && make git' ': {rev}; GIT_CONFIG_GLOBAL=/dev/null ./git log --oneline --shortstat v2.0.0..v2.5.0'
Benchmark 1: : HEAD; GIT_CONFIG_GLOBAL=/dev/null ./git log --oneline --shortstat v2.0.0..v2.5.0
  Time (mean ┬▒ ¤â):      1.670 s ┬▒  0.044 s    [User: 1.473 s, System: 0.196 s]
  Range (min  max):    1.619 s   1.754 s    10 runs

Benchmark 2: : HEAD^; GIT_CONFIG_GLOBAL=/dev/null ./git log --oneline --shortstat v2.0.0..v2.5.0

  Time (mean ┬▒ ¤â):      1.801 s ┬▒  0.021 s    [User: 1.605 s, System: 0.192 s]
  Range (min  max):    1.766 s   1.831 s    10 runs

Summary
': HEAD^; GIT_CONFIG_GLOBAL=/dev/null ./git log --oneline --shortstat v2.0.0..v2.5.0' ran

    1.08 ┬▒ 0.03 times faster than ': HEAD^^; GIT_CONFIG_GLOBAL=/dev/null ./git log --oneline --shortstat v2.0.0..v2.5.0'
---
 xdiff/xutils.c |  7 ++-----
 xdiff/xutils.h | 10 +++++++++-
 2 files changed, 11 insertions(+), 6 deletions(-)
xdiff/xutils.c +2 -5
index 444a108f87..e070ed649f 100644
@@ -249,7 +249,7 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
 	return 1;
 }
 
-static unsigned long xdl_hash_record_with_whitespace(char const **data,
+unsigned long xdl_hash_record_with_whitespace(char const **data,
 		char const *top, long flags) {
 	unsigned long ha = 5381;
 	char const *ptr = *data;
@@ -294,13 +294,10 @@ static unsigned long xdl_hash_record_with_whitespace(char const **data,
 	return ha;
 }
 
-unsigned long xdl_hash_record(char const **data, char const *top, long flags) {
+unsigned long xdl_hash_record_verbatim(char const **data, char const *top) {
 	unsigned long ha = 5381;
 	char const *ptr = *data;
 
-	if (flags & XDF_WHITESPACE_FLAGS)
-		return xdl_hash_record_with_whitespace(data, top, flags);
-
 	for (; ptr < top && *ptr != '\n'; ptr++) {
 		ha += (ha << 5);
 		ha ^= (unsigned long) *ptr;
xdiff/xutils.h +9 -1
index fd0bba94e8..13f6831047 100644
@@ -34,7 +34,15 @@ void *xdl_cha_alloc(chastore_t *cha);
 long xdl_guess_lines(mmfile_t *mf, long sample);
 int xdl_blankline(const char *line, long size, long flags);
 int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags);
-unsigned long xdl_hash_record(char const **data, char const *top, long flags);
+unsigned long xdl_hash_record_verbatim(char const **data, char const *top);
+unsigned long xdl_hash_record_with_whitespace(char const **data, char const *top, long flags);
+static inline unsigned long xdl_hash_record(char const **data, char const *top, long flags)
+{
+	if (flags & XDF_WHITESPACE_FLAGS)
+		return xdl_hash_record_with_whitespace(data, top, flags);
+	else
+		return xdl_hash_record_verbatim(data, top);
+}
 unsigned int xdl_hashbits(unsigned int size);
 int xdl_num_out(char *out, long val);
 int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2,
Signature · 43 lines
2.49.0.897.gfad3eb7d21


From 16f3b26624dc17002f3e507cd1e260deadfe1de8 Mon Sep 17 00:00:00 2001
From: Phillip Wood <[email protected]>
Date: Sat, 19 Jul 2025 14:52:48 +0100
Subject: [PATCH 2/2] xdiff: use xxhash
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Using XXH3_64bits() from libxxhash to hash the input lines improves
the performance by about 6% and equals the performance of using
xxhash-rust.

$ hyperfine --warmup=1 -L rev en/xdiff-rust/v1,HEAD,HEAD^,HEAD^^  --setup='git checkout {rev} -- :/ && make git' ': {rev}; GIT_CONFIG_GLOBAL=/dev/null ./git log --oneline --shortstat v2.0.0..v2.5.0'
Benchmark 1: : en/xdiff-rust/v1; GIT_CONFIG_GLOBAL=/dev/null ./git log --oneline --shortstat v2.0.0..v2.5.0
  Time (mean ┬▒ ¤â):      1.575 s ┬▒  0.032 s    [User: 1.406 s, System: 0.168 s]
  Range (min  max):    1.541 s   1.651 s    10 runs

Benchmark 2: : HEAD; GIT_CONFIG_GLOBAL=/dev/null ./git log --oneline --shortstat v2.0.0..v2.5.0
  Time (mean ┬▒ ¤â):      1.569 s ┬▒  0.018 s    [User: 1.382 s, System: 0.185 s]
  Range (min  max):    1.546 s   1.596 s    10 runs

Benchmark 3: : HEAD^; GIT_CONFIG_GLOBAL=/dev/null ./git log --oneline --shortstat v2.0.0..v2.5.0
  Time (mean ┬▒ ¤â):      1.661 s ┬▒  0.026 s    [User: 1.475 s, System: 0.186 s]
  Range (min  max):    1.630 s   1.696 s    10 runs

Benchmark 4: : HEAD^^; GIT_CONFIG_GLOBAL=/dev/null ./git log --oneline --shortstat v2.0.0..v2.5.0
  Time (mean ┬▒ ¤â):      1.800 s ┬▒  0.023 s    [User: 1.611 s, System: 0.187 s]
  Range (min  max):    1.772 s   1.837 s    10 runs

Summary
  ': HEAD; GIT_CONFIG_GLOBAL=/dev/null ./git log --oneline --shortstat v2.0.0..v2.5.0' ran
    1.00 ┬▒ 0.02 times faster than ': en/xdiff-rust/v1; GIT_CONFIG_GLOBAL=/dev/null ./git log --oneline --shortstat v2.0.0..v2.5.0'
    1.06 ┬▒ 0.02 times faster than ': HEAD^; GIT_CONFIG_GLOBAL=/dev/null ./git log --oneline --shortstat v2.0.0..v2.5.0'
    1.15 ┬▒ 0.02 times faster than ': HEAD^^; GIT_CONFIG_GLOBAL=/dev/null ./git log --oneline --shortstat v2.0.0..v2.5.0'

Signed-off-by: Phillip Wood <[email protected]>
---
 Makefile       |  1 +
 xdiff/xutils.c | 14 ++++++--------
 2 files changed, 7 insertions(+), 8 deletions(-)
Makefile +1 -0
index 5f7dd79dfa..6de7ccdf3b 100644
@@ -1390,6 +1390,7 @@ UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.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 =
+EXTLIBS += -lxxhash
 
 GIT_USER_AGENT = git/$(GIT_VERSION)
 
xdiff/xutils.c +6 -8
index e070ed649f..43fce4b5b1 100644
@@ -21,7 +21,7 @@
  */
 
 #include "xinclude.h"
-
+#include <xxhash.h>
 
 long xdl_bogosqrt(long n) {
 	long i;
@@ -295,14 +295,12 @@ unsigned long xdl_hash_record_with_whitespace(char const **data,
 }
 
 unsigned long xdl_hash_record_verbatim(char const **data, char const *top) {
-	unsigned long ha = 5381;
-	char const *ptr = *data;
+	long ha;
+	char const *eol = memchr(*data, '\n', top - *data);
+	size_t len = (eol ? eol : top) - *data;
 
-	for (; ptr < top && *ptr != '\n'; ptr++) {
-		ha += (ha << 5);
-		ha ^= (unsigned long) *ptr;
-	}
-	*data = ptr < top ? ptr + 1: ptr;
+	ha = XXH3_64bits(*data, len);
+	*data += len + !!eol;
 
 	return ha;
 }
Signature · 1 line
2.49.0.897.gfad3eb7d21
#84 lore raw
Quoted 4 lines · Phillip Wood

... by 15%. Just over half of that seems to come from hoisting the check for whitespace flags in xdl_hash_record() out of the loop in xdl_prepare_ctx() and the rest comes from the change in hash function.

The first half of that alone is interesting enough ;-).

Quoted 5 lines

As I understand it the hash is implemented using SIMD compiler intrinsics and the rust implementation is basically a copy of the C code in libxxhash. I wonder how well xxhash performs compared to our existing hash on platforms without an optimized implementation.

Yeah, that indeed is worth investigating.

Thanks.

#61 lore raw
In reply to brian m. carlson
Quoted 9 lines · brian m. carlson
Quoted 7 lines · Taylor Blau

I agree. I don't think that there is ever going to be a "perfect" time to introduce a hard dependency on Rust, and I don't think that should hold the project back from adopting it.

I am far from a Rust expert, but I think that a more modern, memory-safe language will attract newer contributors who may have a fresher perspective on the project, and I think that's a good thing.

Quoted 5 lines

Yes, I think that's true. Rust is by far the most admired programming language to work with, according to the 2024 Stack Overflow Developer Survey. We will likely attract new contributors who find C intimidating or a bit of a hassle[0] but are excited about working on Rust, especially in a project as compelling as Git[1].

I am also aligned with allowing Rust into Git. I think the ecosystem has kind of settled on Rust as the next system-level programming language, and it does have good interop with C.

I think with the ongoing efforts to reduce our reliance on global state we should eventually be able to encapsulate more and more of our subsystems. And once they are neatly encapsulated we would be able to swap out their respective implementation and plug in a Rust replacement.

Good candidates are for example the reftable library, as I've already proposed in the past.

Quoted 20 lines
Quoted 10 lines

The alternative, of course, is to continue to use C and not take any dependency on Rust. I think there is a middle-ground in there somewhere to be able to build with (e.g.) "make" or "make RUST=1", but I would really like to see the project take a firmer stance here.

I worry that having build support for both "with Rust" and "C only" will create a headache not just at the build system level, but also in the code itself. Having a patchwork of features, optimizations, or bug fixes that either are or aren't supported depending on whether Rust support was specified at build-time seems like a worst-of-all-worlds outcome.

I definitely agree. I already find it terribly inconvenient when I end up when git grep doesn't support -P and I imagine that having lots of features that weren't available would be bothersome.

I also think that using a combination of C and Rust will end up with us still writing a lot of unsafe Rust code to interoperate with C. If we want to reap the benefits in terms of memory and thread safety[2], we'll be better off sticking with just Rust.

Quoted 4 lines

I will also say that while it may be more challenging to compile Git at first on Windows, as we move more towards an all-Rust codebase, Git may end up being easier to maintain there as we depend more on the standard library.

Fully agreed. I've said so at the last contributors summit, but I think it would become awfully unmaintainable if we retain two implementations of every subsystem that we convert to Rust. If we decide to use Rust I would strongly advocate for going all-in.

Patrick

#73 lore raw
In reply to Haelwenn (lanodan) Monnier
Quoted 44 lines · Haelwenn (lanodan) Monnier

[2025-07-18 17:25:01-0400] Eli Schwartz:

Quoted 36 lines · Phillip Wood
Quoted 17 lines

Hi Ezekiel

Thanks for working on this

Quoted 10 lines · Ezekiel Newren via GitGitGadget

So...

This obviously raises the question of whether we are ready to accept a hard
dependency on Rust. Previous discussions on the mailing list and at Git Merge 2024 have not answered that question. If not now, will we be willing
to accept such a hard dependency later? And what route do we want to take to
get there?

Quoted 7 lines

As far as git goes I think introducing a hard dependency on rust is fine. It is widely supported, the only issue I'm aware of is the lack of support on NonStop and I don't think it is reasonable for such a minority platform to hold the rest of the project to ransom. There is a question about the other users of the xdiff code though. libgit2 carries a copy as do other projects like neovim. I've cc'd the libgit2 maintainer and posted a link to this thread in neovim github [1]

A hard dependency on rust for Gentoo amd64 would potentially require building https://github.com/thepowersgang/mrustc followed by building 13 and counting versions of rustc in order to get to the latest version. What is the minimum supported version in this series, by the way?

bin packages for rust do exist but not everyone wants to use non-distro provided binaries, sometimes for auditability reasons.

Quoted 6 lines

For Gentoo HPPA, Alpha, m68k it will simply mean the removal (or end of life and staying forever on 2.50, perhaps) of Git. There is no rust compiler there.

Even s390 support for rust is limited to a precompiled version not everyone is willing to use.

Quoted 6 lines

Also in other distro concerns, if it trickles down to libgit2, extra care should be taken to avoid creating circular dependencies due to cargo depending on libgit2 (via git2 crate).

For example with making sure it can reasonably be built via meson's Rust support rather than through cargo.

I think it's unlikely that this eventually trickles down into libgit2. The bundled versions of xdiff have already diverged for a long time, and unfortunately libgit2 is mostly in maintenance mode nowadays. So I guess that this change here just means that things will diverge even further in the future, which is probably okay-ish. After all, the whole xdiff library didn't really evolve in a fast pace over the last years.

That being said, there is an xdiff fork located at [1] that libgit2 maintains nowadays. So if the Rust dependency ever became a problem for any of the downstream users I think we could simply redirect them to that fork and make it the canonical upstream for C-only xdiff.

Patrick

[1]: https://github.com/libgit2/xdiff

#74 lore raw
In reply to Eli Schwartz
Quoted 36 lines · Eli Schwartz
Quoted 17 lines · Phillip Wood

Hi Ezekiel

Thanks for working on this

Quoted 10 lines · Ezekiel Newren via GitGitGadget

So...

This obviously raises the question of whether we are ready to accept a hard
dependency on Rust. Previous discussions on the mailing list and at Git Merge 2024 have not answered that question. If not now, will we be willing
to accept such a hard dependency later? And what route do we want to take to
get there?

Quoted 7 lines

As far as git goes I think introducing a hard dependency on rust is fine. It is widely supported, the only issue I'm aware of is the lack of support on NonStop and I don't think it is reasonable for such a minority platform to hold the rest of the project to ransom. There is a question about the other users of the xdiff code though. libgit2 carries a copy as do other projects like neovim. I've cc'd the libgit2 maintainer and posted a link to this thread in neovim github [1]

A hard dependency on rust for Gentoo amd64 would potentially require building https://github.com/thepowersgang/mrustc followed by building 13 and counting versions of rustc in order to get to the latest version. What is the minimum supported version in this series, by the way?

bin packages for rust do exist but not everyone wants to use non-distro provided binaries, sometimes for auditability reasons.

Quoted 8 lines

For Gentoo HPPA, Alpha, m68k it will simply mean the removal (or end of life and staying forever on 2.50, perhaps) of Git. There is no rust compiler there.

Even s390 support for rust is limited to a precompiled version not everyone is willing to use.

GCC-rs will probably fix this general issue.

Hm. It would be nice to assemble a list of common or semi-common distributions that do not have proper support for Rust for all or at least some platforms. Should we maybe consider reaching out to other distros (e.g. Debian, Fedora, BSDs) before we commit to any change that has an outsized impact on the larger ecosystem?

I would really love to start adopting Rust, and if it's only going to be architectures that are extremely niche I'm probably fine with that. But if there are many small systems that are impacted by such a change we might have to reconsider.

Meh :/

Patrick

#75 lore raw
Quoted 17 lines · Patrick Steinhardt
Quoted 8 lines · Eli Schwartz

For Gentoo HPPA, Alpha, m68k it will simply mean the removal (or end of life and staying forever on 2.50, perhaps) of Git. There is no rust compiler there.

Even s390 support for rust is limited to a precompiled version not everyone is willing to use.

GCC-rs will probably fix this general issue.

Hm. It would be nice to assemble a list of common or semi-common distributions that do not have proper support for Rust for all or at least some platforms. Should we maybe consider reaching out to other distros (e.g. Debian, Fedora, BSDs) before we commit to any change that has an outsized impact on the larger ecosystem?

Quoted 6 lines

I would really love to start adopting Rust, and if it's only going to be architectures that are extremely niche I'm probably fine with that. But if there are many small systems that are impacted by such a change we might have to reconsider.

Meh :/

To elaborate a bit w.r.t. Gentooo.

Gentoo Prefix-on-macOS and Prefix-on-Solaris don't support rust either. I think at least macOS is reasonably popular. Obviously Rust supports macOS, and the Prefix maintainer would like it to work but hasn't been able to -- no idea why. Arguably you can tell these users "install a better OS so you can use git".

musl has lots of issues with rust, and is disabled for Gentoo musl editions on arm (not arm64), ppc, i686, m68k, mips. Arguably you can tell these users "musl sucks, why are you using it, use glibc like a sensible person".

i486 is entirely disabled due to mandatory sse2. Hopefully those users are rare even compared to i686 users. ;)

s390 only works on s390x

sparc 64ul works, but 32ul does not.

riscv rv64gc works, rv32imac does not.

A general trend here is 32-bit issues.

For alpha/hppa, no references at all -- not even tier 3 support -- on
https://doc.rust-lang.org/beta/rustc/platform-support.html, and Gentoo doesn't support LLVM there either. ;) In general, porting rustc to a new arch means *first* porting LLVM, and then after that, *also* porting rustc, so who's going to try the latter before the former? ;)

Hence the interest in GCC-rs, which already has a backend supporting all this for C/C++/Fortran plus interest by users of these arches in a portable rust compiler.

If rust is added and doesn't have a fallback C impl, all this becomes a relevant topic for consideration. (I don't have strong opinions on optional rust.)

...

See

$ git clone https://github.com/gentoo/gentoo && cd gentoo
$ git grep --name-only features/wd40 profiles/| grep -v 17.0

(wd40 is the inheritance tree for disabling all features in any package that rely on a rust compiler. See README at
https://github.com/gentoo/gentoo/tree/master/profiles/features/wd40 for details)

Signature · 1 line
Eli Schwartz
#62 lore raw
In reply to Patrick Steinhardt
Quoted 4 lines · Patrick Steinhardt

Fully agreed. I've said so at the last contributors summit, but I think it would become awfully unmaintainable if we retain two implementations of every subsystem that we convert to Rust. If we decide to use Rust I would strongly advocate for going all-in.

True.

We do not have subsystems with clear boundaries yet, and introducing Rust in such a state would not allow us to pick some parts (e.g. merge backends, etc.) and do them optionally in Rust, while keeping and/or adding others in C.

#76 lore raw
In reply to Patrick Steinhardt

There's a few issues from our perspective:

* Old platforms which don't have LLVM can't yet have Rust either, as rustc is based on LLVM.

These need gccrs to be unblocked. I can understand not caring too much about these, though it is unfortunate, because I think if git hadn't supported many platforms to begin with, I doubt it'd have the adoption it does today.

(There is another effort which seeks to take rustc and bolt on libgccjit as a replacement backend, but that isn't feasible for use yet either.)

* New platforms where rustc or various Rust crates don't support it and we have to go around patching them.

The crate model makes this much harder. Not having git available when doing such porting if doing it natively is going to suck. It also means even more software needs Rust ported first.

* Platforms which aren't ancient, just not "the default", which tend not to work well with Rust.

For example, rustc assumes that all musl configurations will be statically linked, which isn't the case. Working around this is a hassle.

* rustc doesn't have LTS releases or the like.

The only supported release is the latest one. Upgrading to the latest release often means we have to deal with new portability problems but we can't not upgrade because:
a) some software will start to require bleeding-edge Rust immediately, and
b) it means we're missing out on bug fixes (miscompilations are serious)

* Crate creep

Rust projects tend to end up having a huge list of crates that they pull-in which makes us worried about something nasty creeping in, but there's also popular crates with serious portability problems like the 'ring' crate for TLS.

git is a fundamental piece of system software and making it harder to build it or use it is a real worry for us.

#63 lore raw
In reply to Taylor Blau
Quoted 3 lines

I am far from a Rust expert, but I think that a more modern, memory-safe language will attract newer contributors who may have a fresher perspective on the project, and I think that's a good thing.

Aren't they likely to contribute to gitoxide? There, they get a clean slate without having to deal with the least-fun part (bidings).

Quoted 2 lines

It is also not the Git project's responsibility to ensure that every platform is Rust-friendly.

That's true, of course. And nobody is entitled to indefinie updates, but on the other hand, there's still some implicit contract with users. I really don't think git would have the adoption it does today if it had adopted a Rust-like language in the same state Rust is now from the start.

(In exactly the same way, git doesn't gratuitously break compatibility every release either. Can it? Yes, and git can change the platforms it runs on, but it's something to be taken seriously.)

Quoted 3 lines

Hopefully the platforms that we currently support but won't after this patch series have niche enough workloads that they do not need the absolute latest-and-greatest Git release at all times.

I mention this in my other email, but it's not just about ancient platforms. It's also about new ones, or ones where Rust supports them poorly despite them being relevant.

Quoted 4 lines

Yeah, I think that this is the most interesting part of the discussion here. I am not knowledgeable enough about Rust's release cadence and platform compatibility to have an opinion here. But I trust brian's judgement ;-).

It gets a new release every 6 weeks and no other releases are supported.

#64 lore raw

Hi,

On Tue, Jul 22, 2025 at 9:03 AM Sam James <[email protected]> wrote:

First of all, thanks to all the Gentoo folks for chiming in and providing specifics about platforms and their state.

Quoted 6 lines
Quoted 3 lines

I am far from a Rust expert, but I think that a more modern, memory-safe language will attract newer contributors who may have a fresher perspective on the project, and I think that's a good thing.

Aren't they likely to contribute to gitoxide? There, they get a clean slate without having to deal with the least-fun part (bidings).

I'm sure some are. But clearly there are others where the draw is improving git itself because of its installed base; in fact, we need look no further than this exact series we are commenting on to find proof of that -- one such new contributor submitted patches to use Rust in git, and found a significant speedup while doing so.

Further, there's considerable interest from existing git developers to use Rust in git as well; last year at the Git contributor summit, usage of Rust in git was not only one of the topics of discussion, it was the top voted topic (meaning, the topic that the greatest number of git contributors wanted to discuss).

Quoted 9 lines
Quoted 2 lines

It is also not the Git project's responsibility to ensure that every platform is Rust-friendly.

That's true, of course. And nobody is entitled to indefinie updates, but on the other hand, there's still some implicit contract with users. I really don't think git would have the adoption it does today if it had adopted a Rust-like language in the same state Rust is now from the start.

Quoted 3 lines

(In exactly the same way, git doesn't gratuitously break compatibility every release either. Can it? Yes, and git can change the platforms it runs on, but it's something to be taken seriously.)

This feels kind of close to a false dichotomy between breaking compatibility every release and indefinite update entitlements. There is certainly some middle ground: discussing reducing the breadth of platform support in order to gain other benefits, then gathering feedback, making a plan, and announcing the upcoming change, etc.

And we're already pretty deep into it. Concerns about losing out on some platforms have repeatedly slowed us down from adopting Rust years ago. Yet, the desire for Rust adoption keeps coming up anyway; see the threads starting at

* https://lore.kernel.org/git/[email protected]/
* https://lore.kernel.org/git/Zu2D%[email protected]/
* https://lore.kernel.org/git/[email protected]/
(search for "Rust")
* https://lore.kernel.org/git/[email protected]/

The discussion has also been picked up and reported outside the Git mailing list, e.g. https://lwn.net/Articles/998115/.

And so, in addition to the optional contrib/libgit-rs and contrib/libgit-sys Rust components that have already been merged into git, and a new build system added in part to make it easier to adopt Rust, we now have the first patch series that proposes a hard dependency on Rust.

Further, I'd like to comment a bit on the support of our users from another angle. We're also responsible for security for our users, and feel Rust would help (see e.g.
https://litchipi.github.io/infosec/2023/01/24/git-code-audit-viewed-as-rust-programmer.html
and https://github.com/bk2204/git/commit/fbeb1180c7473635a964daed2da642c53487782d).
We're responsible for performance of Git for our users, and feel Rust would help (see the email that started this thread,
https://lore.kernel.org/git/CABPp-BFOmwV-xBtjvtenb6RFz9wx2VWVpTeho0k=D8wsCCVwqQ@mail.gmail.com/,
and brian's notes about [CPU multi-]threading elsewhere in this email thread we are in). And there are other benefits from using Rust that we believe would benefit our users. Thus, it's not just a question of responsibility to our users, because such a responsibility pulls us in different directions regarding usage of Rust. So we need to figure out how to weigh the needs of our different users. For many of us, and forgive the geeky comparison, we'll probably weigh those needs with something more akin to an L2 norm (most good for the most users) rather than an L-infinity norm (maximal difference in usability for a single user), which probably isn't to your liking.

Anyway, there's been lots of discussion already. We can certainly still discuss more about exactly how to announce, when to adopt Rust, whether we'll support an existing C-only version of git for a longer period of time than normal, and even whether to continue to delay adopting Rust for a little longer. But my personal guess is that attempting to stop adoption of Rust is unlikely to win at this point.

Quoted 7 lines
Quoted 3 lines

Hopefully the platforms that we currently support but won't after this patch series have niche enough workloads that they do not need the absolute latest-and-greatest Git release at all times.

I mention this in my other email, but it's not just about ancient platforms. It's also about new ones, or ones where Rust supports them poorly despite them being relevant.

This feels like you're trying to push the decision for a given platform to be a dichotomy between latest-and-greatest-Git or no-version-of-Git-at-all, despite the fact that Taylor suggested an alternative and you even quoted him. Can you comment on that alternative? Why would using the last C-only version of Git[1] until gccrs bridges the gap be a problem for these platforms?

Thanks,
Elijah

[1] Well, C-only other than optional Rust components like contrib/libgit-rs and contrib/libgit-sys that have already been released.

#65 lore raw
Quoted 6 lines · Elijah Newren

Hi,

On Tue, Jul 22, 2025 at 9:03 AM Sam James <[email protected]> wrote:

First of all, thanks to all the Gentoo folks for chiming in and providing specifics about platforms and their state.

Thanks. I've been trying to be very specific about what the issues are. I don't deny Rust is the future of many projects, but it still has rough parts, and I'd like to ensure they're discussed. It is not my intention to just scream whenever someone considers adopting Rust.

Quoted 5 lines
Quoted 5 lines
Quoted 4 lines

I am far from a Rust expert, but I think that a more modern, memory-safe language will attract newer contributors who may have a fresher perspective on the project, and I think that's a good thing.

Quoted 8 lines
Quoted 2 lines

Aren't they likely to contribute to gitoxide? There, they get a clean slate without having to deal with the least-fun part (bidings).

I'm sure some are. But clearly there are others where the draw is improving git itself because of its installed base; in fact, we need look no further than this exact series we are commenting on to find proof of that -- one such new contributor submitted patches to use Rust in git, and found a significant speedup while doing so.

Part of my opinion there is coloured by how generally working on a polylang codebase often has pain when dealing with bindings and the edges, so I figure that anyone most-keen on Rust would surely want to avoid that ;)

Quoted 20 lines

Further, there's considerable interest from existing git developers to use Rust in git as well; last year at the Git contributor summit, usage of Rust in git was not only one of the topics of discussion, it was the top voted topic (meaning, the topic that the greatest number of git contributors wanted to discuss).

Quoted 9 lines
Quoted 2 lines

It is also not the Git project's responsibility to ensure that every platform is Rust-friendly.

That's true, of course. And nobody is entitled to indefinie updates, but on the other hand, there's still some implicit contract with users. I really don't think git would have the adoption it does today if it had adopted a Rust-like language in the same state Rust is now from the start.

Quoted 3 lines

(In exactly the same way, git doesn't gratuitously break compatibility every release either. Can it? Yes, and git can change the platforms it runs on, but it's something to be taken seriously.)

Quoted 6 lines

This feels kind of close to a false dichotomy between breaking compatibility every release and indefinite update entitlements. There is certainly some middle ground: discussing reducing the breadth of platform support in order to gain other benefits, then gathering feedback, making a plan, and announcing the upcoming change, etc.

Of course. I'm just making the point that it is indeed a compatibility change, and perhaps that perspective is useful.

Quoted 11 lines

And we're already pretty deep into it. Concerns about losing out on some platforms have repeatedly slowed us down from adopting Rust years ago. Yet, the desire for Rust adoption keeps coming up anyway; see the threads starting at

* https://lore.kernel.org/git/[email protected]/
* https://lore.kernel.org/git/Zu2D%[email protected]/
* https://lore.kernel.org/git/[email protected]/
(search for "Rust")
* https://lore.kernel.org/git/[email protected]/

Quoted 8 lines

The discussion has also been picked up and reported outside the Git mailing list, e.g. https://lwn.net/Articles/998115/.

And so, in addition to the optional contrib/libgit-rs and contrib/libgit-sys Rust components that have already been merged into git, and a new build system added in part to make it easier to adopt Rust, we now have the first patch series that proposes a hard dependency on Rust.

Yes, that's why it's of concern. I have no issue with the optional parts.

Quoted 3 lines

Further, I'd like to comment a bit on the support of our users from another angle. We're also responsible for security for our users

Supply-chain issues become more of a problem with Rust if we end up making heavy use of crates. A policy moderating their use is something we should talk about.

Quoted 9 lines

and
feel Rust would help (see e.g.
https://litchipi.github.io/infosec/2023/01/24/git-code-audit-viewed-as-rust-programmer.html
and https://github.com/bk2204/git/commit/fbeb1180c7473635a964daed2da642c53487782d).
We're responsible for performance of Git for our users, and feel Rust would help (see the email that started this thread,
https://lore.kernel.org/git/CABPp-BFOmwV-xBtjvtenb6RFz9wx2VWVpTeho0k=D8wsCCVwqQ@mail.gmail.com/,
and brian's notes about [CPU multi-]threading elsewhere in this email thread we are in). And there are other benefits from using Rust that

Quoted 9 lines

we believe would benefit our users. Thus, it's not just a question of responsibility to our users, because such a responsibility pulls us in different directions regarding usage of Rust. So we need to figure out how to weigh the needs of our different users. For many of us, and forgive the geeky comparison, we'll probably weigh those needs with something more akin to an L2 norm (most good for the most users) rather than an L-infinity norm (maximal difference in usability for a single user), which probably isn't to your liking.

:)

Quoted 6 lines

Anyway, there's been lots of discussion already. We can certainly still discuss more about exactly how to announce, when to adopt Rust, whether we'll support an existing C-only version of git for a longer period of time than normal, and even whether to continue to delay adopting Rust for a little longer. But my personal guess is that attempting to stop adoption of Rust is unlikely to win at this point.

I wouldn't characterise my position as attempting to flat-out stop adoption of Rust (see beginning of this email).

Quoted 9 lines
Quoted 8 lines
Quoted 4 lines

Hopefully the platforms that we currently support but won't after this patch series have niche enough workloads that they do not need the absolute latest-and-greatest Git release at all times.

I mention this in my other email, but it's not just about ancient platforms. It's also about new ones, or ones where Rust supports them poorly despite them being relevant.

Quoted 6 lines

This feels like you're trying to push the decision for a given platform to be a dichotomy between latest-and-greatest-Git or no-version-of-Git-at-all, despite the fact that Taylor suggested an alternative and you even quoted him. Can you comment on that alternative? Why would using the last C-only version of Git[1] until gccrs bridges the gap be a problem for these platforms?

What I was saying there was: it matters for platforms where they may not have a git at all (because they're new, and we have a bit of a bootstrapping problem), not just old ones where they're stuck on an old git.

Part of what I had in mind here is that sticking on old versions even temporarily isn't necessarily a great option, see the recent issues w/ backports done (https://lore.kernel.org/git/[email protected]/,
and https://lore.kernel.org/git/[email protected]/).

---

As a final note: I am genuinely not trying to be a member of the peanut gallery wishing to prevent git's progress if the project wants to adopt Rust, just there's some real practical obstacles for us right now.

I hope it didn't come across that way, but "dichotomy" appearing a few times in your reply made me fear it did.

Quoted 3 lines

Thanks,
Elijah

thanks,
sam

Quoted 4 lines

[1] Well, C-only other than optional Rust components like contrib/libgit-rs and contrib/libgit-sys that have already been released.

#66 lore raw
Quoted 6 lines · Sam James
Quoted 2 lines

Further, I'd like to comment a bit on the support of our users from another angle. We're also responsible for security for our users

Supply-chain issues become more of a problem with Rust if we end up making heavy use of crates. A policy moderating their use is something we should talk about.

+1. I find it a bit worrying when I see 500+ dependencies (mostly transitive) being downloaded when running 'cargo build'.

Not saying we should go to the extreme of Not Invented Here syndrome [1], since easy use of packages via 'cargo' is a major reason why people enjoy Rust. But we should consider whether they provide enough value to be included.

Collin

[1] https://en.wikipedia.org/wiki/Not_invented_here

#10 lore raw

Re: [PATCH 1/7] xdiff: introduce rust

In reply to brian m. carlson
Quoted 17 lines · brian m. carlson
Quoted 16 lines · Ezekiel Newren via GitGitGadget
rust/Cargo.lock new file +10 -0
new file mode 100644
index 000000000000..fb1eac690b39
@@ -0,0 +1,14 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "interop"
+version = "0.1.0"
+
+[[package]]
+name = "xdiff"
Quoted 8 lines

rust/Cargo.lock line 14

Quoted 4 lines
rust/Cargo.lock new file +4 -0
+version = "0.1.0"
+dependencies = [
+ "interop",
+]

I would prefer that we not check in Cargo.lock in Git. Part of the reason is that it changes across versions and so building with a different version of the toolchain can update the file.

That actually doesn't happen unless the file needs to be updated for some reason, like Cargo.toml having new dependencies or cargo update being run.

Mike

#11 lore raw

Re: [PATCH 1/7] xdiff: introduce rust

Quoted 8 lines · Mike Hommey
Quoted 3 lines · brian m. carlson

I would prefer that we not check in Cargo.lock in Git. Part of the reason is that it changes across versions and so building with a different version of the toolchain can update the file.

That actually doesn't happen unless the file needs to be updated for some reason, like Cargo.toml having new dependencies or cargo update being run.

I've actually seen several cases in my local Rust development where Cargo wants to update the file despite it not being necessary and --locked simply refusing to work without good cause. Perhaps those cases have been fixed, but it has happened in older versions.

Signature · 2 lines
brian m. carlson (they/them)
Toronto, Ontario, CA
#77 lore raw
In reply to Sam James
Quoted 5 lines · Sam James

There's a few issues from our perspective:

* Old platforms which don't have LLVM can't yet have Rust either, as rustc is based on LLVM.

Quoted 8 lines

These need gccrs to be unblocked. I can understand not caring too much about these, though it is unfortunate, because I think if git hadn't supported many platforms to begin with, I doubt it'd have the adoption it does today.

(There is another effort which seeks to take rustc and bolt on libgccjit as a replacement backend, but that isn't feasible for use yet either.)

It would be great to know about the general timelines of these alternative implementations. If e.g. gccrs were to achieve compatibility with one of the editions of Rust next year it would be a good enough reason to defer the rustification from my point of view so that we don't break the ecosystem and have wider platform support. If the answer is "They'll land in 10 years" then I don't know...

I sifted through their project sites and found various status reports, and they do seem to be making steady progress. But as far as I see critical language features are still missing as of now.

[snip]

Quoted 9 lines

* rustc doesn't have LTS releases or the like.

The only supported release is the latest one. Upgrading to the latest release often means we have to deal with new portability problems but we can't not upgrade because:
a) some software will start to require bleeding-edge Rust immediately, and
b) it means we're missing out on bug fixes (miscompilations are serious)

I'm not a big fan of this in the Rust ecosystem indeed. It feels like every second project requires nightly features or at least a version of the compiler that was released in the last couple months. This may work for a language like Go, which is more targeted towards deploying server applications. But for a system-level language like Rust I think it's rather a sign of it being immature.

In any case, the burden would fall on us to ensure that we carefully consider which version of Rust to target. And as it was said elsewhere in the thread, we would need to make sure that things build on old versions of Debian. Which may be easier said than done if we also rely on lots of crates which may update to newer Rust versions at any point in time.

Quoted 6 lines

* Crate creep

Rust projects tend to end up having a huge list of crates that they pull-in which makes us worried about something nasty creeping in, but there's also popular crates with serious portability problems like the 'ring' crate for TLS.

True. I think if we were to adopt Rust we ought to be as conservative as we are now with picking up new dependencies. I don't want to have a big open door for supply chain attacks. And neither do I want to be forced into the situation where we cannot update a crate because they decided to drop support for older Rust versions.

Patrick

#6 lore raw

Re: [PATCH 1/7] xdiff: introduce rust

In reply to Ezekiel Newren
Quoted 6 lines · Ezekiel Newren

This goes against what I think is best practices. Don’t we need
Cargo.lock to audit and debug platform specific issues, and to ensure reproducibility? Without Cargo.lock, we might get different results
one minute to the next if one of our dependencies releases a new
version. Checking in Cargo.lock aligns with Cargo’s documented best practices (https://doc.rust-lang.org/cargo/faq.html#why-have-cargolock-in-version-control).

I appreciate that, but best practices also don't limit software to a six-week lifespan. Rust the language is a great tool, but we also have a special case here in that we need to support software that upstream does not and that we care about OS distros, which upstream does not.

Note that when someone builds locally, a Cargo.lock will be created and they will get reproducible builds from that point on. It is only on first build that they will get whatever's the latest.

Quoted 6 lines

I understand your concern and I agree that this could become a problem. I’m totally flexible on which rust version should be used, but without Cargo.lock checked in we lose the ability to audit why a build failed. I think that this will be a pain point, but numbing that pain means we can’t solve intermittent problems due to dependencies in the future.

I was one of the maintainers for Git LFS for several years. We routinely had people come to us and say, "This dependency you're using has a portion that you're not using, which has a CVE. I demand you update it and do a new release immediately because our security scanner is going off and our company policy is that there be no exceptions." This happens literally all the time and I absolutely in no case want to see those people on this list or the security list.

So the options as I see them are (a) we don't check in Cargo.lock, (b) we convince the Rust project and the ecosystem to provide LTS releases with security fixes, or (c) we only accept dependencies that have our same lifetime policy (which are very few and far between). I know this makes builds unreproducible (although not under the Reproducible Builds project's definitions), but we really don't have many alternatives.

Signature · 2 lines
brian m. carlson (they/them)
Toronto, Ontario, CA
#7 lore raw

Re: [PATCH 1/7] xdiff: introduce rust

Quoted 7 lines · brian m. carlson

I was one of the maintainers for Git LFS for several years. We routinely had people come to us and say, "This dependency you're using has a portion that you're not using, which has a CVE. I demand you update it and do a new release immediately because our security scanner is going off and our company policy is that there be no exceptions." This happens literally all the time and I absolutely in no case want to see those people on this list or the security list.

Ahh, the kind we love not to have.

Quoted 6 lines

So the options as I see them are (a) we don't check in Cargo.lock, (b) we convince the Rust project and the ecosystem to provide LTS releases with security fixes, or (c) we only accept dependencies that have our same lifetime policy (which are very few and far between). I know this makes builds unreproducible (although not under the Reproducible Builds project's definitions), but we really don't have many alternatives.

Thanks for a well reasoned argument.

Hopefully as Rust matures more, some of these issues (starting with "6 weeks and it is too old to bother") would resolve themselves, but until then we'd need to be careful.

#78 lore raw
In reply to Patrick Steinhardt
Quoted 2 lines · Patrick Steinhardt

It would be great to know about the general timelines of these alternative implementations.

We still think we'll be able to compile libcore before the end of the summer, we've made great progress and few items are left. But keep in mind we're targeting an older version of rust (1.49) and libcore is smaller than the standard library. We still have a lot of testing to do and we expect many bugs.

The next targeted version will probably be rust 1.78 as we want to keep up with rust for linux. This shouldn't be too long as most of the features are coming from either standard library modifications or nightly features we already had to support for 1.49.

We expect to be able to compile some 1.49 code correctly next year at best. I would like to bring to your attention rustc_codegen_gcc which adds a gcc backend to the rustc frontend, although not a full gcc compiler it could help supporting some architectures that are currently not supported by llvm.

Pierre-Emmanuel

#79 lore raw
Quoted 10 lines · Pierre-Emmanuel Patry
Quoted 2 lines · Patrick Steinhardt

It would be great to know about the general timelines of these alternative implementations.

We still think we'll be able to compile libcore before the end of the summer, we've made great progress and few items are left. But keep in mind we're targeting an older version of rust (1.49) and libcore is smaller than the standard library. We still have a lot of testing to do and we expect many bugs.

Understood. Given that we don't plan to roll with the latest version of Rust anyway I think it could be a viable tradeoff for us to also consider gccrs when we determine the minimum required Rust version.

Quoted 6 lines

The next targeted version will probably be rust 1.78 as we want to keep up with rust for linux. This shouldn't be too long as most of the features are coming from either standard library modifications or nightly features we already had to support for 1.49.

We expect to be able to compile some 1.49 code correctly next year at best.

And I expect that 1.78 will be another significant effort that won't land before the year after?

Quoted 3 lines

I would like to bring to your attention rustc_codegen_gcc which adds a gcc backend to the rustc frontend, although not a full gcc compiler it could help supporting some architectures that are currently not supported by llvm.

For my own understanding: is this something that the Git project would have to support or something that the distributor needs to set up?

Patrick

#56 lore raw

Re: [PATCH 7/7] github_workflows: install rust

In reply to Ezekiel Newren
Quoted 46 lines · Ezekiel Newren

On Thu, Jul 17, 2025 at 3:23 PM brian m. carlson

Quoted 15 lines · <[email protected]> wrote:
Quoted 11 lines · Ezekiel Newren via GitGitGadget
.github/workflows/main.yml +0 -0
index 7dbf9f7f123c..8aac18a6ba45 100644
@@ -4,6 +4,7 @@ on: [push, pull_request]
 

env:
DEVELOPER: 1
+ RUST_VERSION: 1.87.0

Our discussed plan is to support the version in Debian stable, plus a year. So we'd be supporting 1.63.0 for a year after trixie's release.

Quoted 8 lines

The reason for that is that people build backports and security updates for Git for stable releases of distros and they will use the distro toolchain for doing so. Forcing distros to constantly build with the latest toolchain is pretty hostile, especially since the lifespan of Rust release is six weeks.

If the Rust project provides LTS releases in the future, then we can consider adopting those.

The RUST_VERSION variable in .github/workflows/main.yaml had to have a specific version. 1.87.0 was selected since that's what I was using locally. Elijah made me aware that an older version of rust might be desired, but didn't know which one. I'll switch to 1.63.0 or whatever the community decides.

Quoted 10 lines

.github/workflows/main.yml

Quoted 6 lines
.github/workflows/main.yml +6 -0
+if [ "$rust_target" = "release" ]; then
+  rust_args="--release"
+  export RUSTFLAGS='-Aunused_imports -Adead_code'
+elif [ "$rust_target" = "debug" ]; then
+  rust_args=""
+  export RUSTFLAGS='-Aunused_imports -Adead_code -C debuginfo=2 -C opt-level=1 -C force-frame-pointers=yes'

Can you say a little about why these options are needed and the defaults are inadequate? For instance, I build with the default options both in my personal projects and at work and don't see a problem.

Quoted 8 lines

What I found is that if I have a Rust function

#[no_mangle]
pub fn call_from_c(arg: u64) {}

which is only meant to be called from C and isn’t called from elsewhere in Rust, then cargo will misidentify this function as dead code. This was the reason for adding ‘-Adead_code’.

Are functions that exist for C FFI callers supposed to be marked unsafe, and if so: does that prevent the dead code analyzer from removing them w/o the allow flag?

Or, alternatively, can we #[] annotate them as allowed? It might be noisy, but it also lets the checkers flag actually dead code?

Quoted 8 lines

The reason for adding ‘-Aunused_imports’ is somewhat IDE related; if I paste code somewhere, RustRover will sometimes automatically add the necessary imports. However, if I delete a chunk of code, it’ll highlight the imports that are no longer used if I scroll to the top of the file, but it won’t automatically remove them. Since they aren’t automatically removed, it’s easier to build with ‘-Aunused_imports’.

Similarly, I would think this is a problem with the IDE rather than something we want to end up with in the implementation.

I wonder if one of the cargo fix type things can remove them for you, too, so that it’s easier to just drop this allow.

#80 lore raw
In reply to Patrick Steinhardt
Quoted 3 lines

And I expect that 1.78 will be another significant effort that won't land before the year after?

Yes, even though it will be easier once the foundations are laid off, I wouldn't expect 1.78 before at least a year after that.

Quoted 2 lines

For my own understanding: is this something that the Git project would have to support or something that the distributor needs to set up?

I would say it is something the distributor needs to set up. From what I remember rustc requires a flag with the path to the rustc_codegen_gcc backend. This means that has long as the distributor has the alternative backend and a way to inject a flag to rustc through an environment variable it should be mostly fine for them.

Pierre-Emmanuel

#8 lore raw

Re: [PATCH 1/7] xdiff: introduce rust

In reply to brian m. carlson
Quoted 33 lines · brian m. carlson
Quoted 6 lines · Ezekiel Newren

This goes against what I think is best practices. Don’t we need
Cargo.lock to audit and debug platform specific issues, and to ensure reproducibility? Without Cargo.lock, we might get different results
one minute to the next if one of our dependencies releases a new
version. Checking in Cargo.lock aligns with Cargo’s documented best practices (https://doc.rust-lang.org/cargo/faq.html#why-have-cargolock-in-version-control).

I appreciate that, but best practices also don't limit software to a six-week lifespan. Rust the language is a great tool, but we also have a special case here in that we need to support software that upstream does not and that we care about OS distros, which upstream does not.

Note that when someone builds locally, a Cargo.lock will be created and they will get reproducible builds from that point on. It is only on first build that they will get whatever's the latest.

Quoted 6 lines

I understand your concern and I agree that this could become a problem. I’m totally flexible on which rust version should be used, but without Cargo.lock checked in we lose the ability to audit why a build failed. I think that this will be a pain point, but numbing that pain means we can’t solve intermittent problems due to dependencies in the future.

I was one of the maintainers for Git LFS for several years. We routinely had people come to us and say, "This dependency you're using has a portion that you're not using, which has a CVE. I demand you update it and do a new release immediately because our security scanner is going off and our company policy is that there be no exceptions." This happens literally all the time and I absolutely in no case want to see those people on this list or the security list.

Quoted 9 lines

So the options as I see them are (a) we don't check in Cargo.lock, (b) we convince the Rust project and the ecosystem to provide LTS releases with security fixes, or (c) we only accept dependencies that have our same lifetime policy (which are very few and far between). I know this makes builds unreproducible (although not under the Reproducible Builds project's definitions), but we really don't have many alternatives. --
brian m. carlson (they/them)
Toronto, Ontario, CA

I like having the Cargo.lock file to figure out why a build worked on one system, but not another. After talking with Elijah I've decided that a good solution would be to add Cargo.lock to .gitignore and change the github workflows to ensure that Cargo.lock is preserved for all builds. We should also add a comment to Cargo.toml stating that any build or test issues should include the Cargo.lock that was generated when asking for help. What does the community think of this solution?

#23 lore raw

Re: [PATCH 4/7] xdiff: make fields of xrecord_t Rust friendly

In reply to Phillip Wood
Quoted 6 lines · Phillip Wood

Hi Ezekiel

Quoted 2 lines · Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

Quoted 8 lines
Quoted 4 lines

A few commits ago, we added definitions for Rust primitive types, to facilitate interoperability between C and Rust. Switch a few variables to use these types. Which, for now, will require adding some casts.

How necessary is it to change char' to 'u8' so long as the rust and C sides both use a type that is the same size? Also what's the advantage of using these typedefs rather than the normal C types like unit8_t ?

Rust defines char as 32 bits. C treats char as signed 8 bits. What git really means by char* is treat everything like a byte string, and u8 is how raw bytes are handled in Rust.

Quoted 8 lines
Quoted 8 lines
xdiff/xdiffi.c +0 -0
index 5a96e36dfbea..3b364c61f671 100644
@@ -418,7 +418,7 @@ static int get_indent(xrecord_t *rec)
      long i;
      int ret = 0;
 
Quoted 5 lines

xdiff/xdiffi.c static int get_indent(xrecord_t *rec) · line 421

Quoted 2 lines
xdiff/xdiffi.c +1 -1
-     for (i = 0; i < rec->size; i++) {
+     for (i = 0; i < (long) rec->size; i++) {

i is a loop counter and array index so we can lose this cast by changeing i to size_t

Ok, but I'm going to change the type of i to usize and stuff it inside the loop i.e. for (usize i = 0; ...

Quoted 3 lines

Thanks

Phillip

#24 lore raw

Re: [PATCH 4/7] xdiff: make fields of xrecord_t Rust friendly

Quoted 9 lines · Ezekiel Newren
Quoted 8 lines · Phillip Wood
Quoted 6 lines · Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

A few commits ago, we added definitions for Rust primitive types, to facilitate interoperability between C and Rust. Switch a few variables to use these types. Which, for now, will require adding some casts.

Quoted 7 lines
Quoted 3 lines

How necessary is it to change char' to 'u8' so long as the rust and C sides both use a type that is the same size? Also what's the advantage of using these typedefs rather than the normal C types like unit8_t ?

Rust defines char as 32 bits. C treats char as signed 8 bits. What git really means by char* is treat everything like a byte string, and u8 is how raw bytes are handled in Rust.

Right - we need to use u8 on the rust side but I'm trying to understand why we need to change the type on the C side and why do we need typedefs like usize and u32 on the C side when we already have size_t and uint32_t?

Thanks

Phillip

Quoted 20 lines
Quoted 8 lines
Quoted 8 lines
xdiff/xdiffi.c +0 -0
index 5a96e36dfbea..3b364c61f671 100644
@@ -418,7 +418,7 @@ static int get_indent(xrecord_t *rec)
       long i;
       int ret = 0;
 
Quoted 5 lines

xdiff/xdiffi.c static int get_indent(xrecord_t *rec) · line 421

Quoted 2 lines
xdiff/xdiffi.c +1 -1
-     for (i = 0; i < rec->size; i++) {
+     for (i = 0; i < (long) rec->size; i++) {

i is a loop counter and array index so we can lose this cast by changeing i to size_t

Ok, but I'm going to change the type of i to usize and stuff it inside the loop i.e. for (usize i = 0; ...

Quoted 3 lines

Thanks

Phillip

#30 lore raw

Re: [PATCH 4/7] xdiff: make fields of xrecord_t Rust friendly

In reply to Ezekiel Newren
Quoted 3 lines · Ezekiel Newren

Rust defines char as 32 bits. C treats char as signed 8 bits. What git really means by char* is treat everything like a byte string, and u8 is how raw bytes are handled in Rust.

Minor correction, but the C standard leaves the signedness of 'char' up to the implementation. Portable code must be written to assume a plain 'char' can be signed or unsigned.

Using the test program below:

    #include <stdio.h>
    #define TYPE_SIGNED(t) (! ((t) 0 < (t) -1))
    int
    main (void)
    {
      printf ("%d\n", TYPE_SIGNED (char));
      return 0;
    }

On GNU/Linux x86_64:

    $ ./a.out 
    1

On GNU/Linux aarch64:

    $ ./a.out 
    0

Collin

#25 lore raw

Re: [PATCH 4/7] xdiff: make fields of xrecord_t Rust friendly

In reply to Phillip Wood
Quoted 15 lines · Phillip Wood
Quoted 13 lines · Ezekiel Newren
Quoted 11 lines · Phillip Wood
Quoted 6 lines · Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

A few commits ago, we added definitions for Rust primitive types, to facilitate interoperability between C and Rust. Switch a few variables to use these types. Which, for now, will require adding some casts.

How necessary is it to change char' to 'u8' so long as the rust and C sides both use a type that is the same size? Also what's the advantage of using these typedefs rather than the normal C types like unit8_t ?

Quoted 7 lines
Quoted 3 lines

Rust defines char as 32 bits. C treats char as signed 8 bits. What git really means by char* is treat everything like a byte string, and u8 is how raw bytes are handled in Rust.

Right - we need to use u8 on the rust side but I'm trying to understand why we need to change the type on the C side and why do we need typedefs like usize and u32 on the C side when we already have size_t and uint32_t?

Ah, I misunderstood the scope of your question. I could not fit an example of why this design pattern made sense into this patch series, so I'll explain with an example here:

If C defines a struct like below then it's obvious how to translate that into rust for ffi purposes. It also makes it clear that this C struct is expressly for the purpose of C <-> Rust interoperability.

struct some_struct {
    u8* ptr;
    usize length;
    u64 counter;
};

This is how that C struct needs to be defined in Rust so that it can interoperate with C, and making C use the Rust types reduces the chance of copy paste, and primitive type definition mismatch errors. #[repr(C)]

pub struct some_struct {
    ptr: *mut u8,
    length: usize,
    counter: u64,
};

The Rust function would look like:
#[no_mangle]
unsafe extern "C" fn do_something(data: *mut some_struct) {...}

And C would have a forward declaration like:
extern void do_something(struct some_struct *data);

void some_c_function() {
    struct some_struct x;
    do_something(&x);
}
Quoted 3 lines

Thanks

Phillip

#29 lore raw

Re: [PATCH 4/7] xdiff: make fields of xrecord_t Rust friendly

In reply to Phillip Wood
Quoted 18 lines · Phillip Wood
Quoted 9 lines · Ezekiel Newren
Quoted 8 lines · Phillip Wood
Quoted 6 lines · Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

A few commits ago, we added definitions for Rust primitive types, to facilitate interoperability between C and Rust. Switch a few variables to use these types. Which, for now, will require adding some casts.

Quoted 7 lines
Quoted 3 lines

How necessary is it to change char' to 'u8' so long as the rust and C sides both use a type that is the same size? Also what's the advantage of using these typedefs rather than the normal C types like unit8_t ?

Rust defines char as 32 bits. C treats char as signed 8 bits. What git
really means by char* is treat everything like a byte string, and u8 is how raw bytes are handled in Rust.

Quoted 4 lines

Right - we need to use u8 on the rust side but I'm trying to understand why we need to change the type on the C side and why do we need typedefs like usize and u32 on the C side when we already have size_t and uint32_t?

Or uint8_t? Ah, eh, that is "unsigned char" so it would be redundant, I guess?

#26 lore raw

Re: [PATCH 4/7] xdiff: make fields of xrecord_t Rust friendly

In reply to Ezekiel Newren
Quoted 19 lines · Ezekiel Newren

On Mon, Jul 28, 2025 at 1:52 PM Phillip Wood <[email protected]> wrote:

Ah, I misunderstood the scope of your question. I could not fit an example of why this design pattern made sense into this patch series, so I'll explain with an example here:

If C defines a struct like below then it's obvious how to translate that into rust for ffi purposes. It also makes it clear that this C struct is expressly for the purpose of C <-> Rust interoperability.

struct some_struct {
     u8* ptr;
     usize length;
     u64 counter;
};

This is how that C struct needs to be defined in Rust so that it can interoperate with C, and making C use the Rust types reduces the chance of copy paste, and primitive type definition mismatch errors. #[repr(C)]

Quoted 5 lines
pub struct some_struct {
     ptr: *mut u8,
     length: usize,
     counter: u64,
};

How is the pointer, length pair used in rust? Normally one would use a slice so do we have to construct a slice every time we want to use the data in this struct, or do we copy the data in this struct into to a an idiomatic struct with a slice member? If we end up copying there doesn't seem much point in changing all the types in the C struct as we can define a rust struct using *c_char, c_long etc. to interface with the C code and covert them to an appropriate rust type when we copy the data to the idiomatic version that is then used by the rust of the rust code. I can see the value of the typedefs for documenting C<->rust interop if the same struct is used by both but if we end up copying data on the rust side I'm not so sure.

Thanks

Phillip

#27 lore raw

Re: [PATCH 4/7] xdiff: make fields of xrecord_t Rust friendly

Quoted 39 lines · Phillip Wood
Quoted 19 lines · Ezekiel Newren

On Mon, Jul 28, 2025 at 1:52 PM Phillip Wood <[email protected]> wrote:

Ah, I misunderstood the scope of your question. I could not fit an example of why this design pattern made sense into this patch series, so I'll explain with an example here:

If C defines a struct like below then it's obvious how to translate that into rust for ffi purposes. It also makes it clear that this C struct is expressly for the purpose of C <-> Rust interoperability.

struct some_struct {
     u8* ptr;
     usize length;
     u64 counter;
};

This is how that C struct needs to be defined in Rust so that it can interoperate with C, and making C use the Rust types reduces the chance of copy paste, and primitive type definition mismatch errors. #[repr(C)]

Quoted 5 lines
pub struct some_struct {
     ptr: *mut u8,
     length: usize,
     counter: u64,
};

How is the pointer, length pair used in rust? Normally one would use a slice so do we have to construct a slice every time we want to use the data in this struct, or do we copy the data in this struct into to a an idiomatic struct with a slice member? If we end up copying there doesn't seem much point in changing all the types in the C struct as we can define a rust struct using *c_char, c_long etc. to interface with the C code and covert them to an appropriate rust type when we copy the data to the idiomatic version that is then used by the rust of the rust code. I can see the value of the typedefs for documenting C<->rust interop if the same struct is used by both but if we end up copying data on the rust side I'm not so sure.

Quoted 3 lines

Thanks

Phillip

Passing pointer + length from c to Rust does not incur a memory copy overhead. Take a look at rust/xdiff/src/lib.rs wich has the following rust function defined:

#[no_mangle]

unsafe extern "C" fn xxh3_64(ptr: *const u8, size: usize) -> u64 {
    let slice = std::slice::from_raw_parts(ptr, size);
    xxhash_rust::xxh3::xxh3_64(slice)
}

Creating a slice tells the compiler what assumptions it can make about that memory. On the C side in xdiff/xprepare.c:

extern u64 xxh3_64(u8 const* ptr, usize size);

and then it's called like this in that same file:

rec->ha = xxh3_64(rec->ptr, rec->size);

I really wanted to show my ivec type that made passing an interoperable vector type between C and Rust easy and fast, but this patch series is already getting very long.

#38 lore raw

Re: [PATCH 6/7] xdiff: conditionally use Rust's implementation of xxhash

In reply to Junio C Hamano
Quoted 34 lines · Junio C Hamano
Quoted 12 lines · Ezekiel Newren via GitGitGadget
xdiff/xprepare.c +5 -0
+extern u64 xxh3_64(u8 const* ptr, usize size);
+
+
 static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
                         xdlclassifier_t *cf, xdfile_t *xdf) {
      unsigned long *ha;
@@ -175,14 +178,26 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
 
      xdl_parse_lines(mf, narec, xdf);
 
+     if ((xpp->flags & XDF_WHITESPACE_FLAGS) == 0) {
+             for (usize i = 0; i < (usize) xdf->nrec; i++) {

xdiff/xprepare.c static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_ · line 192

Quoted 10 lines
xdiff/xprepare.c +10 -0
+                     xrecord_t *rec = xdf->recs[i];
+                     rec->ha = xxh3_64(rec->ptr, rec->size);
+             }
+     } else {
+             for (usize i = 0; i < (usize) xdf->nrec; i++) {
+                     xrecord_t *rec = xdf->recs[i];
+                     char const* dump = (char const*) rec->ptr;
+                     rec->ha = xdl_hash_record(&dump, (char const*) (rec->ptr + rec->size), xpp->flags);
+             }
+     }

As a technology demonstration and proof of concept patch, this is very nice, but to be upstreamed for real, we'd want a variant of xxhash that can work with the contents with whitespace squashed to be usable with various whitespace ignoring modes of operation. When that happens, and when the result turns out to be more performant, we can lose the xdl_hash_record() and require only the xxhash, which would be great.

Quoted 7 lines

And that variant of xxhash that understands whitespace squashing can of course be written in Rust as a part of this effort when the series loses its RFC status. At the same time, those who want to use our xdiff code in third-party software (like libgit2 and vim) may want to reimplement it in C in their copy.

Thanks.

What is the git precedent for replacement code that is easier to read and maintain while also being more secure, but is slower? I think hashing with whitespace handling in Rust might fall in that category.

As far as I can tell the Rust code for dealing with whitespace is going to be slower than the C code because xdiff used a hash algorithm (DJB2a) that can operate 1 byte at a time and combined hashing with determining the length. Xxhash requires that the length be known beforehand and the memory to be contiguous or to hash it in chunks. Hashing 1 byte at a time with Xxhash is VERY slow since it's just copying to an internal buffer until a full block is ready.

On a broader note. How do I show the mailing list the changes that I've made to this branch/patch series? I'm not sure what the proper procedure is or even how to do it. What commands would I run, or web browser steps would I take to show my newest commits?

#9 lore raw

Re: [PATCH 1/7] xdiff: introduce rust

In reply to Ezekiel Newren
Quoted 8 lines · Ezekiel Newren

I like having the Cargo.lock file to figure out why a build worked on one system, but not another. After talking with Elijah I've decided that a good solution would be to add Cargo.lock to .gitignore and change the github workflows to ensure that Cargo.lock is preserved for all builds. We should also add a comment to Cargo.toml stating that any build or test issues should include the Cargo.lock that was generated when asking for help. What does the community think of this solution?

That sounds like a good solution.

Signature · 2 lines
brian m. carlson (they/them)
Toronto, Ontario, CA
#28 lore raw

Re: [PATCH 4/7] xdiff: make fields of xrecord_t Rust friendly

In reply to Ezekiel Newren

Hi Ezekiel

Quoted 48 lines · Ezekiel Newren
Quoted 39 lines · Phillip Wood
Quoted 19 lines · Ezekiel Newren

On Mon, Jul 28, 2025 at 1:52 PM Phillip Wood <[email protected]> wrote:

Ah, I misunderstood the scope of your question. I could not fit an example of why this design pattern made sense into this patch series, so I'll explain with an example here:

If C defines a struct like below then it's obvious how to translate that into rust for ffi purposes. It also makes it clear that this C struct is expressly for the purpose of C <-> Rust interoperability.

struct some_struct {
      u8* ptr;
      usize length;
      u64 counter;
};

This is how that C struct needs to be defined in Rust so that it can interoperate with C, and making C use the Rust types reduces the chance of copy paste, and primitive type definition mismatch errors. #[repr(C)]

Quoted 5 lines
pub struct some_struct {
      ptr: *mut u8,
      length: usize,
      counter: u64,
};

How is the pointer, length pair used in rust? Normally one would use a slice so do we have to construct a slice every time we want to use the data in this struct, or do we copy the data in this struct into to a an idiomatic struct with a slice member? If we end up copying there doesn't seem much point in changing all the types in the C struct as we can define a rust struct using *c_char, c_long etc. to interface with the C code and covert them to an appropriate rust type when we copy the data to the idiomatic version that is then used by the rust of the rust code. I can see the value of the typedefs for documenting C<->rust interop if the same struct is used by both but if we end up copying data on the rust side I'm not so sure.

Quoted 3 lines

Thanks

Phillip

Passing pointer + length from c to Rust does not incur a memory copy overhead. Take a look at rust/xdiff/src/lib.rs wich has the following rust function defined:

Quoted 5 lines

#[no_mangle]

unsafe extern "C" fn xxh3_64(ptr: *const u8, size: usize) -> u64 {
     let slice = std::slice::from_raw_parts(ptr, size);
     xxhash_rust::xxh3::xxh3_64(slice)
}

I'm afraid I don't find this simple unsafe function example very illuminating. I'm trying to understand how we are going to use a struct containing a pointer, length pair in code that are more complex than this. For example if we implement an entire diff algorithm in rust are we going to call std::slice::from_raw_parts() every time we want to access a string passed from C? If we're doing that I assume we'd impl a safe method on the struct that wraps std::slice::from_raw_parts(). If that's the case the method can easily access a field that has type *c_char and we don't have to sprinkle casts throughout our C code.

For example (ignoring lifetimes)
#repr["C"]

pub struct SomeStruct {
    ptr *std::ffi::c_char,
    usize len,
    // more members
}
impl SomeStruct {
    get_line(&self) -> &[u8] {
        unsafe {
            std::slice::from_raw_parts(self.ptr as *u8, self.len);
        }
    }
}

On the other hand if at the interface between rust and C, we create a slice that we can pass to the rest of the rust code then we also don't need to change the C type as there is a single place in the rust code where we convert from c_char when we create the slice.

The casts on the C side are pretty invasive. At least casting from char to u8 is not going to break anything. The long -> usize and long -> u64 changes and their associated casts are going to need some careful review but in the long run I think the C code also benefits to using those types

Quoted 5 lines

Creating a slice tells the compiler what assumptions it can make about that memory. On the C side in xdiff/xprepare.c:

extern u64 xxh3_64(u8 const* ptr, usize size);

Quoted 7 lines

and then it's called like this in that same file:

rec->ha = xxh3_64(rec->ptr, rec->size);

I really wanted to show my ivec type that made passing an interoperable vector type between C and Rust easy and fast, but this patch series is already getting very long.

That sounds interesting

Thanks

Phillip

#39 lore raw

Re: [PATCH 6/7] xdiff: conditionally use Rust's implementation of xxhash

In reply to Ezekiel Newren
Quoted 55 lines · Ezekiel Newren
Quoted 34 lines · Junio C Hamano
Quoted 12 lines · Ezekiel Newren via GitGitGadget
xdiff/xprepare.c +5 -0
+extern u64 xxh3_64(u8 const* ptr, usize size);
+
+
 static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
                         xdlclassifier_t *cf, xdfile_t *xdf) {
      unsigned long *ha;
@@ -175,14 +178,26 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
 
      xdl_parse_lines(mf, narec, xdf);
 
+     if ((xpp->flags & XDF_WHITESPACE_FLAGS) == 0) {
+             for (usize i = 0; i < (usize) xdf->nrec; i++) {

xdiff/xprepare.c static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_ · line 192

Quoted 10 lines
xdiff/xprepare.c +10 -0
+                     xrecord_t *rec = xdf->recs[i];
+                     rec->ha = xxh3_64(rec->ptr, rec->size);
+             }
+     } else {
+             for (usize i = 0; i < (usize) xdf->nrec; i++) {
+                     xrecord_t *rec = xdf->recs[i];
+                     char const* dump = (char const*) rec->ptr;
+                     rec->ha = xdl_hash_record(&dump, (char const*) (rec->ptr + rec->size), xpp->flags);
+             }
+     }

As a technology demonstration and proof of concept patch, this is very nice, but to be upstreamed for real, we'd want a variant of xxhash that can work with the contents with whitespace squashed to be usable with various whitespace ignoring modes of operation. When that happens, and when the result turns out to be more performant, we can lose the xdl_hash_record() and require only the xxhash, which would be great.

Quoted 7 lines

And that variant of xxhash that understands whitespace squashing can of course be written in Rust as a part of this effort when the series loses its RFC status. At the same time, those who want to use our xdiff code in third-party software (like libgit2 and vim) may want to reimplement it in C in their copy.

Thanks.

What is the git precedent for replacement code that is easier to read and maintain while also being more secure, but is slower? I think hashing with whitespace handling in Rust might fall in that category.

As far as I can tell the Rust code for dealing with whitespace is going to be slower than the C code because xdiff used a hash algorithm (DJB2a) that can operate 1 byte at a time and combined hashing with determining the length. Xxhash requires that the length be known beforehand and the memory to be contiguous or to hash it in chunks. Hashing 1 byte at a time with Xxhash is VERY slow since it's just copying to an internal buffer until a full block is ready.

Quoted 5 lines

On a broader note. How do I show the mailing list the changes that I've made to this branch/patch series? I'm not sure what the proper procedure is or even how to do it. What commands would I run, or web browser steps would I take to show my newest commits?

Since you've used GitGitGadget for the original submission of this patch series, the easiest way is to force push your updated commits to your PR branch (xdiff_rust_speedup) and comment "/submit" on the PR again.

Alternatively you can send a version 2 of your patch series using git format-patch and git send-email, but that is a few more manual steps. MyFirstContribution.adoc has a detailed section about this process, including a part about a v2. [1]

[1] https://github.com/git/git/blob/master/Documentation/MyFirstContribution.adoc#sending-patches-with-git-send-email

Best regards

Matthias

#87 lore raw

v2 cover RFC: Accelerate xdiff and begin its rustification

In reply to Ezekiel Newren via GitGitGadget

Changes in this second round of this RFC:

* Now builds and passes tests on all platforms (example run:
https://github.com/ezekielnewren/git/actions/runs/16974821401). Special thanks to Johannes Schindelin for patches to things for Windows and linux32.
* Includes brian’s rust-support documentation as the new 1st patch
* Removed the Cargo.lock file from version control, but now CI will upload these files as build artifacts so we can audit dependencies and notice if/when new dependencies cause issues.
* Added handling of whitespace flags. These are slower; see below

Particular points I’m interested in feedback on:

* Code style: Should we adopt a Rust code style of some sort? Perhaps have the code always be formatted by rustfmt in its default configuration?
* Rust version: We are not using the same Rust version on all platforms in CI; 32-bit builds and Windows builds require a newer Rust version to successfully build.
* Performance with whitepsace flags: I originally intended to leave out the whitespace handling because I knew it was slower, and I think it’d be difficult to fix that until more of xdiff is converted to Rust, but since Junio requested it, I have an implementation here. I made sure that both –ignore-cr-at-eol (the default on Windows) and no whitespace flags remain fast (or are faster), but other whitespace flag combinations are currently significantly slower. Are folks okay with merging this, since it’ll only affect those that specify some special flag, should we perhaps only convert the code path with no whitespace flags for now, or something else?
* Types/Aliases/Data passing: The discussion between Phillip on I on types/translation; this longer series has examples with e.g. xdl_line_hash() and line_hash() which might give us more to talk about, though I think we can’t fully address that discussion until we have an example which I’m planning with a later series with an IVec type.
* There was lots of feedback on v1, and I might have missed some; let me know if there’s something I need to still look at.

==Original cover letter==

This series accelerates xdiff by 5-19%.

It also introduces Rust as a hard dependency.

…and it doesn’t yet pass a couple of the github workflows; hints from Windows experts, and opinions on ambiguous primitives would be appreciated (see below).

This is just the beginning of many patches that I have to convert portions of, maybe eventually all of, xdiff to Rust. While working on that conversion, I found several ways to clarify the code, along with some optimizations.

So...

This obviously raises the question of whether we are ready to accept a hard dependency on Rust. Previous discussions on the mailing list and at Git Merge 2024 have not answered that question. If not now, will we be willing to accept such a hard dependency later? And what route do we want to take to get there?

About the optimizations in this series:

1. xdiff currently uses DJB2a for hashing (even though it is not explicitly named as such). This is an older hashing algorithm, and modern alternatives are superior. I chose xxhash because it’s faster, more collision resistant, and designed to be a standard. Other hash algorithms like aHash, MurMurHash, SipHash, and Fnv1a were considered, but my local testing made me feel like xxhash was the best choice for usage in xdiff.

2. In support of switching to xxhash, parsing and hashing were split into separate steps. And it turns out that memchr() is faster for parsing than character-by-character iteration.

About the workflow builds/tests that aren’t working with this series:

1. Windows fails to build. I don’t know which rust toolchain is even correct for this or if multiple are needed. Example failed build: https://github.com/git/git/actions/runs/16353209191

2. I386/ubuntu:focal will build, but fails the tests. The kernel reports the bitness as 64 despite the container being 32. I believe the issue is that C uses ambiguous primitives (which differ in size between platforms). The new code should use unambiguous primitives from Rust (u32, u64, etc.) rather than perpetuating ambiguous primitive types. Since the current xdiff API hardcodes the ambiguous types, though, those places will need to be migrated to unambiguous primitives. Much of the C code needs a slight refactor to be compatible with the Rust FFI and usually requires converting ambiguous to unambiguous types. What does this community think of this approach?

My brother (Elijah, cc’ed) has been guiding and reviewing my work here.

Ezekiel Newren (13):
xdiff: introduce rust
xdiff/xprepare: remove superfluous forward declarations
xdiff: delete unnecessary fields from xrecord_t and xdfile_t
xdiff: make fields of xrecord_t Rust friendly
xdiff: separate parsing lines from hashing them
xdiff: conditionally use Rust's implementation of xxhash github workflows: install rust
github workflows: define rust versions and targets in the same place github workflows: upload Cargo.lock
xdiff: implement a white space iterator in Rust
xdiff: create line_hash() and line_equal()
xdiff: optimize case where --ignore-cr-at-eol is the only whitespace
flag
xdiff: use rust's version of whitespace processing

Johannes Schindelin (3):
Do support Windows again after requiring Rust win+Meson: allow for xdiff to be compiled with MSVC win+Meson: do allow linking with the Rust-built xdiff

brian m. carlson (1):
doc: add a policy for using Rust

 .github/workflows/main.yml                    |  61 +++
 .gitignore                                    |   3 +
 Documentation/Makefile                        |   1 +
 Documentation/technical/platform-support.adoc |   2 +
 Documentation/technical/rust-support.adoc     | 119 ++++++
 Makefile                                      |  60 ++-
 build_rust.sh                                 |  59 +++
 ci/install-dependencies.sh                    |  14 +-
 ci/install-rust.sh                            |  37 ++
 ci/lib.sh                                     |   1 +
 ci/make-test-artifacts.sh                     |   7 +
 ci/run-build-and-tests.sh                     |  12 +
 config.mak.uname                              |   9 +
 git-compat-util.h                             |  17 +
 meson.build                                   |  48 ++-
 rust/Cargo.toml                               |   6 +
 rust/interop/Cargo.toml                       |  14 +
 rust/interop/src/lib.rs                       |   0
 rust/xdiff/Cargo.toml                         |  16 +
 rust/xdiff/src/lib.rs                         |  30 ++
 rust/xdiff/src/xutils.rs                      | 354 ++++++++++++++++++
 xdiff-interface.c                             |   4 +-
 xdiff/xdiffi.c                                |   8 +-
 xdiff/xemit.c                                 |   2 +-
 xdiff/xmerge.c                                |  10 +-
 xdiff/xpatience.c                             |   2 +-
 xdiff/xprepare.c                              | 219 +++++------
 xdiff/xtypes.h                                |   9 +-
 xdiff/xutils.c                                | 162 +-------
 xdiff/xutils.h                                |   4 +-
 30 files changed, 961 insertions(+), 329 deletions(-)
 create mode 100644 Documentation/technical/rust-support.adoc
 create mode 100755 build_rust.sh
 create mode 100755 ci/install-rust.sh
 create mode 100644 rust/Cargo.toml
 create mode 100644 rust/interop/Cargo.toml
 create mode 100644 rust/interop/src/lib.rs
 create mode 100644 rust/xdiff/Cargo.toml
 create mode 100644 rust/xdiff/src/lib.rs
 create mode 100644 rust/xdiff/src/xutils.rs

base-commit: 16bd9f20a403117f2e0d9bcda6c6e621d3763e77
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1980%2Fezekielnewren%2Fxdiff_rust_speedup-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1980/ezekielnewren/xdiff_rust_speedup-v2
Pull-Request: https://github.com/git/git/pull/1980

Range-diff vs v1:

  -:  ----------- >  1:  75dfb40ead3 doc: add a policy for using Rust
  1:  2a1f4be13df !  2:  7709e5eddba xdiff: introduce rust
     @@ Commit message
      
          Signed-off-by: Ezekiel Newren <[email protected]>
      
     + ## .gitignore ##
     +@@ .gitignore: Release/
     + /contrib/buildsystems/out
     + /contrib/libgit-rs/target
     + /contrib/libgit-sys/target
     ++/.idea/
     ++/rust/target/
     ++/rust/Cargo.lock
     +
       ## Makefile ##
      @@ Makefile: TEST_SHELL_PATH = $(SHELL_PATH)
       
     @@ meson.build: version_def_h = custom_target(
         link_with: static_library('git',
           sources: libgit_sources,
      
     - ## rust/Cargo.lock (new) ##
     -@@
     -+# This file is automatically @generated by Cargo.
     -+# It is not intended for manual editing.
     -+version = 4
     -+
     -+[[package]]
     -+name = "interop"
     -+version = "0.1.0"
     -+
     -+[[package]]
     -+name = "xdiff"
     -+version = "0.1.0"
     -+dependencies = [
     -+ "interop",
     -+]
     -
       ## rust/Cargo.toml (new) ##
      @@
      +[workspace]
  2:  b0b744b9acf =  3:  56c96d35554 xdiff/xprepare: remove superfluous forward declarations
  3:  cc05150d6e1 =  4:  ebec3689dce xdiff: delete unnecessary fields from xrecord_t and xdfile_t
  4:  6df9f50a8f4 !  5:  769d1a5b9d2 xdiff: make fields of xrecord_t Rust friendly

@@ Commit message
few variables to use these types. Which, for now, will require adding some casts.

+ Also change xdlclass_t::ha to be u64 to match xrecord_t::ha, as
+ pointed out by Johannes.
+
+ Helped-by: Johannes Schindelin <[email protected]>

## xdiff/xdiffi.c ##
@@ xdiff/xpatience.c: static void insert_record(xpparam_t const *xpp, int line, str
if (map->last) {

## xdiff/xprepare.c ##
+@@
+
+ typedef struct s_xdlclass {
+ struct s_xdlclass *next;
+- unsigned long ha;
++ u64 ha;
+ char const *line;
+ long size;
+ long idx;
@@ xdiff/xprepare.c: static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t
char const *line;
xdlclass_t *rcrec;

  5:  2db30cc739e =  6:  87623495994 xdiff: separate parsing lines from hashing them
  6:  5a959c9bdad !  7:  d74fd4ef67a xdiff: conditionally use Rust's implementation of xxhash

@@ Commit message

- ## rust/Cargo.lock ##
-@@ rust/Cargo.lock: name = "xdiff"
- version = "0.1.0"
- dependencies = [
- "interop",
-+ "xxhash-rust",
- ]
-+
-+[[package]]
-+name = "xxhash-rust"
-+version = "0.8.15"
-+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" -
## rust/xdiff/Cargo.toml ##
@@ rust/xdiff/Cargo.toml: crate-type = ["staticlib", "rlib"]

7: 0de0867ab44 ! 8: 7dc241e6682 github_workflows: install rust
@@ Metadata
Author: Ezekiel Newren <[email protected]>

## Commit message ##
- github_workflows: install rust
+ github workflows: install rust

Since we have introduced rust, it needs to be installed for the continuous integration build targets. Create an install script
@@ .github/workflows/main.yml: on: [push, pull_request]
# If more than one workflow run is triggered for the very same commit hash # (which happens when multiple branches pointing to the same commit), only

- ## .gitignore ##
-@@ .gitignore: Release/
- /contrib/buildsystems/out
- /contrib/libgit-rs/target
- /contrib/libgit-sys/target -+/rust/target
-
## Makefile ##
@@ Makefile: TEST_SHELL_PATH = $(SHELL_PATH)

  -:  ----------- >  9:  96041a10d54 Do support Windows again after requiring Rust
  -:  ----------- > 10:  1194de3f39c win+Meson: allow for xdiff to be compiled with MSVC
  -:  ----------- > 11:  382067a09e3 win+Meson: do allow linking with the Rust-built xdiff
  -:  ----------- > 12:  fffdb326710 github workflows: define rust versions and targets in the same place
  -:  ----------- > 13:  44784f0d672 github workflows: upload Cargo.lock
  -:  ----------- > 14:  f20efdff7aa xdiff: implement a white space iterator in Rust
  -:  ----------- > 15:  c8d41173274 xdiff: create line_hash() and line_equal()
  -:  ----------- > 16:  f7829c55871 xdiff: optimize case where --ignore-cr-at-eol is the only whitespace flag
  -:  ----------- > 17:  395609aff4b xdiff: use rust's version of whitespace processing
Signature · 1 line
gitgitgadget
#88 lore raw

PATCH v2 1/17 doc: add a policy for using Rust

From: "brian m. carlson" <[email protected]>

Git has historically been written primarily in C, with some shell and Perl. However, C is not memory safe, which makes it more likely that security vulnerabilities or other bugs will be introduced, and it is also more verbose and less ergonomic than other, more modern languages.

One of the most common modern compiled languages which is easily interoperable with C is Rust. It is popular (the most admired language on the 2024 Stack Overflow Developer Survey), efficient, portable, and robust.

Introduce a document laying out the incremental introduction of Rust to Git and provide a detailed rationale for doing so, including the points above. Propose a design for this approach that addresses the needs of downstreams and distributors, as well as contributors.

Since we don't want to carry both a C and Rust version of code and want to be able to add new features only in Rust, mention that Rust is a required part of our platform support policy.

It should be noted that a recent discussion at the Berlin Git Merge Contributor Summit found widespread support for the addition of Rust to Git. While of course not all contributors were represented, the proposal appeared to have the support of a majority of active contributors.

---
 Documentation/Makefile                        |   1 +
 Documentation/technical/platform-support.adoc |   2 +
 Documentation/technical/rust-support.adoc     | 119 ++++++++++++++++++
 3 files changed, 122 insertions(+)
 create mode 100644 Documentation/technical/rust-support.adoc
Documentation/Makefile +1 -0
index b109d25e9c80..066b761c01b9 100644
@@ -127,6 +127,7 @@ TECH_DOCS += technical/parallel-checkout
 TECH_DOCS += technical/partial-clone
 TECH_DOCS += technical/platform-support
 TECH_DOCS += technical/racy-git
+TECH_DOCS += technical/rust-support
 TECH_DOCS += technical/reftable
 TECH_DOCS += technical/scalar
 TECH_DOCS += technical/send-pack-pipeline
Documentation/technical/platform-support.adoc +2 -0
index 0a2fb28d6277..42b04b186105 100644
@@ -33,6 +33,8 @@ meet the following minimum requirements:
 
 * Has active security support (taking security releases of dependencies, etc)
 
+* Supports Rust and the toolchain version specified in link:rust-support.txt[].
+
 These requirements are a starting point, and not sufficient on their own for the
 Git community to be enthusiastic about supporting your platform. Maintainers of
 platforms which do meet these requirements can follow the steps below to make it
Documentation/technical/rust-support.adoc new file +119 -0

119 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#93 lore raw

PATCH v2 2/17 xdiff: introduce rust

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

Upcoming patches will accelerate and simplify xdiff, while also porting parts of it to Rust. In preparation, add some stubs and setup the Rust build. For now, it is easier to let cargo build rust and have make or meson merely link against the static library that cargo builds. In line with ongoing libification efforts, use multiple crates to allow more modularity on the Rust side. xdiff is the crate that this series will focus on, but we also introduce the interop crate for future patch series.

In order to facilitate interoperability between C and Rust, introduce C definitions for Rust primitive types in git-compat-util.h.

---
 .gitignore              |  3 +++
 Makefile                | 20 +++++++++++++++++++-
 git-compat-util.h       | 17 +++++++++++++++++
 meson.build             | 32 ++++++++++++++++++++++++++++++++
 rust/Cargo.toml         |  6 ++++++
 rust/interop/Cargo.toml | 14 ++++++++++++++
 rust/interop/src/lib.rs |  0
 rust/xdiff/Cargo.toml   | 15 +++++++++++++++
 rust/xdiff/src/lib.rs   |  0
 9 files changed, 106 insertions(+), 1 deletion(-)
 create mode 100644 rust/Cargo.toml
 create mode 100644 rust/interop/Cargo.toml
 create mode 100644 rust/interop/src/lib.rs
 create mode 100644 rust/xdiff/Cargo.toml
 create mode 100644 rust/xdiff/src/lib.rs
.gitignore +3 -0
index 04c444404e4b..ff81e3580c4e 100644
@@ -254,3 +254,6 @@ Release/
 /contrib/buildsystems/out
 /contrib/libgit-rs/target
 /contrib/libgit-sys/target
+/.idea/
+/rust/target/
+/rust/Cargo.lock
Makefile +19 -1
index 70d1543b6b86..db39e6e1c28e 100644
@@ -919,6 +919,11 @@ TEST_SHELL_PATH = $(SHELL_PATH)
 
 LIB_FILE = libgit.a
 XDIFF_LIB = xdiff/lib.a
+ifeq ($(DEBUG), 1)
+RUST_LIB = rust/target/debug/libxdiff.a
+else
+RUST_LIB = rust/target/release/libxdiff.a
+endif
 REFTABLE_LIB = reftable/libreftable.a
 
 GENERATED_H += command-list.h
@@ -1392,6 +1397,8 @@ UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o
 GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(LIB_FILE)
 EXTLIBS =
 
+GITLIBS += $(RUST_LIB)
+
 GIT_USER_AGENT = git/$(GIT_VERSION)
 
 ifeq ($(wildcard sha1collisiondetection/lib/sha1.h),sha1collisiondetection/lib/sha1.h)
@@ -2925,6 +2932,14 @@ $(LIB_FILE): $(LIB_OBJS)
 $(XDIFF_LIB): $(XDIFF_OBJS)
 	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
 
+.PHONY: $(RUST_LIB)
+$(RUST_LIB):
+ifeq ($(DEBUG), 1)
+	cd rust && RUSTFLAGS="-Aunused_imports -Adead_code" cargo build --verbose
+else
+	cd rust && RUSTFLAGS="-Aunused_imports -Adead_code" cargo build --verbose --release
+endif
+
 $(REFTABLE_LIB): $(REFTABLE_OBJS)
 	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
 
@@ -3756,7 +3771,10 @@ cocciclean:
 	$(RM) -r .build/contrib/coccinelle
 	$(RM) contrib/coccinelle/*.cocci.patch
 
-clean: profile-clean coverage-clean cocciclean
+rustclean:
+	cd rust && cargo clean
+
+clean: profile-clean coverage-clean cocciclean rustclean
 	$(RM) -r .build $(UNIT_TEST_BIN)
 	$(RM) GIT-TEST-SUITES
 	$(RM) po/git.pot po/git-core.pot
git-compat-util.h +17 -0
index 4678e21c4cb8..82dc99764ac0 100644
@@ -196,6 +196,23 @@ static inline int is_xplatform_dir_sep(int c)
 #include "compat/msvc.h"
 #endif
 
+/* rust types */
+typedef uint8_t   u8;
+typedef uint16_t  u16;
+typedef uint32_t  u32;
+typedef uint64_t  u64;
+
+typedef int8_t    i8;
+typedef int16_t   i16;
+typedef int32_t   i32;
+typedef int64_t   i64;
+
+typedef float     f32;
+typedef double    f64;
+
+typedef size_t    usize;
+typedef ptrdiff_t isize;
+
 /* used on Mac OS X */
 #ifdef PRECOMPOSE_UNICODE
 #include "compat/precompose_utf8.h"
meson.build +32 -0
index 596f5ac7110e..2d8da17f6515 100644
@@ -267,6 +267,36 @@ version_gen_environment.set('GIT_DATE', get_option('build_date'))
 version_gen_environment.set('GIT_USER_AGENT', get_option('user_agent'))
 version_gen_environment.set('GIT_VERSION', get_option('version'))
 
+if get_option('optimization') in ['2', '3', 's', 'z']
+  rust_target = 'release'
+  rust_args = ['--release']
+  rustflags = '-Aunused_imports -Adead_code'
+else
+  rust_target = 'debug'
+  rust_args = []
+  rustflags = '-Aunused_imports -Adead_code -C debuginfo=2 -C opt-level=1 -C force-frame-pointers=yes'
+endif
+
+
+rust_leaf = custom_target('rust_leaf',
+  output: 'libxdiff.a',
+  build_by_default: true,
+  build_always_stale: true,
+  command: ['cargo', 'build',
+            '--manifest-path', meson.project_source_root() / 'rust/Cargo.toml'
+  ] + rust_args,
+  env: {
+    'RUSTFLAGS': rustflags,
+  },
+  install: false,
+)
+
+rust_xdiff_dep = declare_dependency(
+  link_args: ['-L' + meson.project_source_root() / 'rust/target' / rust_target, '-lxdiff'],
+#  include_directories: include_directories('xdiff/include'),  # Adjust if you expose headers
+)
+
+
 compiler = meson.get_compiler('c')
 
 libgit_sources = [
@@ -1677,6 +1707,8 @@ version_def_h = custom_target(
 )
 libgit_sources += version_def_h
 
+libgit_dependencies += rust_xdiff_dep
+
 libgit = declare_dependency(
   link_with: static_library('git',
     sources: libgit_sources,
rust/Cargo.toml new file +6 -0
new file mode 100644
index 000000000000..ed3d79d7f827
@@ -0,0 +1,6 @@
+[workspace]
+members = [
+    "xdiff",
+    "interop",
+]
+resolver = "2"
rust/interop/Cargo.toml new file +14 -0
new file mode 100644
index 000000000000..045e3b01cfad
@@ -0,0 +1,14 @@
+[package]
+name = "interop"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+name = "interop"
+path = "src/lib.rs"
+## staticlib to generate xdiff.a for use by gcc
+## cdylib (optional) to generate xdiff.so for use by gcc
+## rlib is required by the rust unit tests
+crate-type = ["staticlib", "rlib"]
+
+[dependencies]
rust/interop/src/lib.rs new file +0 -0
new file mode 100644
index 000000000000..e69de29bb2d1
rust/xdiff/Cargo.toml new file +15 -0
new file mode 100644
index 000000000000..eb7966aada64
@@ -0,0 +1,15 @@
+[package]
+name = "xdiff"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+name = "xdiff"
+path = "src/lib.rs"
+## staticlib to generate xdiff.a for use by gcc
+## cdylib (optional) to generate xdiff.so for use by gcc
+## rlib is required by the rust unit tests
+crate-type = ["staticlib", "rlib"]
+
+[dependencies]
+interop = { path = "../interop" }
rust/xdiff/src/lib.rs new file +0 -0
new file mode 100644
index 000000000000..e69de29bb2d1
Signature · 1 line
gitgitgadget
#94 lore raw

PATCH v2 3/17 xdiff/xprepare: remove superfluous forward declarations

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

Move xdl_prepare_env() later in the file to avoid the need for forward declarations.

---
 xdiff/xprepare.c | 116 ++++++++++++++++++++---------------------------
 1 file changed, 50 insertions(+), 66 deletions(-)
xdiff/xprepare.c +50 -66
index e1d4017b2dde..a45c5ee208c8 100644
@@ -53,21 +53,6 @@ typedef struct s_xdlclassifier {
 
 
 
-static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags);
-static void xdl_free_classifier(xdlclassifier_t *cf);
-static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash,
-			       unsigned int hbits, xrecord_t *rec);
-static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
-			   xdlclassifier_t *cf, xdfile_t *xdf);
-static void xdl_free_ctx(xdfile_t *xdf);
-static int xdl_clean_mmatch(char const *dis, long i, long s, long e);
-static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2);
-static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2);
-static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2);
-
-
-
-
 static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags) {
 	cf->flags = flags;
 
@@ -242,57 +227,6 @@ static void xdl_free_ctx(xdfile_t *xdf) {
 }
 
 
-int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
-		    xdfenv_t *xe) {
-	long enl1, enl2, sample;
-	xdlclassifier_t cf;
-
-	memset(&cf, 0, sizeof(cf));
-
-	/*
-	 * For histogram diff, we can afford a smaller sample size and
-	 * thus a poorer estimate of the number of lines, as the hash
-	 * table (rhash) won't be filled up/grown. The number of lines
-	 * (nrecs) will be updated correctly anyway by
-	 * xdl_prepare_ctx().
-	 */
-	sample = (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF
-		  ? XDL_GUESS_NLINES2 : XDL_GUESS_NLINES1);
-
-	enl1 = xdl_guess_lines(mf1, sample) + 1;
-	enl2 = xdl_guess_lines(mf2, sample) + 1;
-
-	if (xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0)
-		return -1;
-
-	if (xdl_prepare_ctx(1, mf1, enl1, xpp, &cf, &xe->xdf1) < 0) {
-
-		xdl_free_classifier(&cf);
-		return -1;
-	}
-	if (xdl_prepare_ctx(2, mf2, enl2, xpp, &cf, &xe->xdf2) < 0) {
-
-		xdl_free_ctx(&xe->xdf1);
-		xdl_free_classifier(&cf);
-		return -1;
-	}
-
-	if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) &&
-	    (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF) &&
-	    xdl_optimize_ctxs(&cf, &xe->xdf1, &xe->xdf2) < 0) {
-
-		xdl_free_ctx(&xe->xdf2);
-		xdl_free_ctx(&xe->xdf1);
-		xdl_free_classifier(&cf);
-		return -1;
-	}
-
-	xdl_free_classifier(&cf);
-
-	return 0;
-}
-
-
 void xdl_free_env(xdfenv_t *xe) {
 
 	xdl_free_ctx(&xe->xdf2);
@@ -460,3 +394,53 @@ static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2
 
 	return 0;
 }
+
+int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+		    xdfenv_t *xe) {
+	long enl1, enl2, sample;
+	xdlclassifier_t cf;
+
+	memset(&cf, 0, sizeof(cf));
+
+	/*
+	 * For histogram diff, we can afford a smaller sample size and
+	 * thus a poorer estimate of the number of lines, as the hash
+	 * table (rhash) won't be filled up/grown. The number of lines
+	 * (nrecs) will be updated correctly anyway by
+	 * xdl_prepare_ctx().
+	 */
+	sample = (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF
+		  ? XDL_GUESS_NLINES2 : XDL_GUESS_NLINES1);
+
+	enl1 = xdl_guess_lines(mf1, sample) + 1;
+	enl2 = xdl_guess_lines(mf2, sample) + 1;
+
+	if (xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0)
+		return -1;
+
+	if (xdl_prepare_ctx(1, mf1, enl1, xpp, &cf, &xe->xdf1) < 0) {
+
+		xdl_free_classifier(&cf);
+		return -1;
+	}
+	if (xdl_prepare_ctx(2, mf2, enl2, xpp, &cf, &xe->xdf2) < 0) {
+
+		xdl_free_ctx(&xe->xdf1);
+		xdl_free_classifier(&cf);
+		return -1;
+	}
+
+	if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) &&
+	    (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF) &&
+	    xdl_optimize_ctxs(&cf, &xe->xdf1, &xe->xdf2) < 0) {
+
+		xdl_free_ctx(&xe->xdf2);
+		xdl_free_ctx(&xe->xdf1);
+		xdl_free_classifier(&cf);
+		return -1;
+	    }
+
+	xdl_free_classifier(&cf);
+
+	return 0;
+}
Signature · 1 line
gitgitgadget
#95 lore raw

PATCH v2 4/17 xdiff: delete unnecessary fields from xrecord_t and xdfile_t

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

xrecord_t.next, xdfile_t.hbits, xdfile_t.rhash are initialized, but never used for anything by the code. Remove them.

---
 xdiff/xprepare.c | 24 +++---------------------
 xdiff/xtypes.h   |  3 ---
 2 files changed, 3 insertions(+), 24 deletions(-)
xdiff/xprepare.c +3 -21
index a45c5ee208c8..ad356281f939 100644
@@ -91,8 +91,7 @@ static void xdl_free_classifier(xdlclassifier_t *cf) {
 }
 
 
-static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash,
-			       unsigned int hbits, xrecord_t *rec) {
+static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t *rec) {
 	long hi;
 	char const *line;
 	xdlclass_t *rcrec;
@@ -126,23 +125,17 @@ static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t
 
 	rec->ha = (unsigned long) rcrec->idx;
 
-	hi = (long) XDL_HASHLONG(rec->ha, hbits);
-	rec->next = rhash[hi];
-	rhash[hi] = rec;
-
 	return 0;
 }
 
 
 static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
 			   xdlclassifier_t *cf, xdfile_t *xdf) {
-	unsigned int hbits;
-	long nrec, hsize, bsize;
+	long nrec, bsize;
 	unsigned long hav;
 	char const *blk, *cur, *top, *prev;
 	xrecord_t *crec;
 	xrecord_t **recs;
-	xrecord_t **rhash;
 	unsigned long *ha;
 	char *rchg;
 	long *rindex;
@@ -150,7 +143,6 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
 	ha = NULL;
 	rindex = NULL;
 	rchg = NULL;
-	rhash = NULL;
 	recs = NULL;
 
 	if (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0)
@@ -158,11 +150,6 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
 	if (!XDL_ALLOC_ARRAY(recs, narec))
 		goto abort;
 
-	hbits = xdl_hashbits((unsigned int) narec);
-	hsize = 1 << hbits;
-	if (!XDL_CALLOC_ARRAY(rhash, hsize))
-		goto abort;
-
 	nrec = 0;
 	if ((cur = blk = xdl_mmfile_first(mf, &bsize))) {
 		for (top = blk + bsize; cur < top; ) {
@@ -176,7 +163,7 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
 			crec->size = (long) (cur - prev);
 			crec->ha = hav;
 			recs[nrec++] = crec;
-			if (xdl_classify_record(pass, cf, rhash, hbits, crec) < 0)
+			if (xdl_classify_record(pass, cf, crec) < 0)
 				goto abort;
 		}
 	}
@@ -194,8 +181,6 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
 
 	xdf->nrec = nrec;
 	xdf->recs = recs;
-	xdf->hbits = hbits;
-	xdf->rhash = rhash;
 	xdf->rchg = rchg + 1;
 	xdf->rindex = rindex;
 	xdf->nreff = 0;
@@ -209,7 +194,6 @@ abort:
 	xdl_free(ha);
 	xdl_free(rindex);
 	xdl_free(rchg);
-	xdl_free(rhash);
 	xdl_free(recs);
 	xdl_cha_free(&xdf->rcha);
 	return -1;
@@ -217,8 +201,6 @@ abort:
 
 
 static void xdl_free_ctx(xdfile_t *xdf) {
-
-	xdl_free(xdf->rhash);
 	xdl_free(xdf->rindex);
 	xdl_free(xdf->rchg - 1);
 	xdl_free(xdf->ha);
xdiff/xtypes.h +0 -3
index 8442bd436efe..8b8467360ecf 100644
@@ -39,7 +39,6 @@ typedef struct s_chastore {
 } chastore_t;
 
 typedef struct s_xrecord {
-	struct s_xrecord *next;
 	char const *ptr;
 	long size;
 	unsigned long ha;
@@ -48,8 +47,6 @@ typedef struct s_xrecord {
 typedef struct s_xdfile {
 	chastore_t rcha;
 	long nrec;
-	unsigned int hbits;
-	xrecord_t **rhash;
 	long dstart, dend;
 	xrecord_t **recs;
 	char *rchg;
Signature · 1 line
gitgitgadget
#96 lore raw

PATCH v2 5/17 xdiff: make fields of xrecord_t Rust friendly

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

A few commits ago, we added definitions for Rust primitive types, to facilitate interoperability between C and Rust. Switch a few variables to use these types. Which, for now, will require adding some casts.

Also change xdlclass_t::ha to be u64 to match xrecord_t::ha, as pointed out by Johannes.

---
 xdiff/xdiffi.c    |  8 ++++----
 xdiff/xemit.c     |  2 +-
 xdiff/xmerge.c    | 14 +++++++-------
 xdiff/xpatience.c |  2 +-
 xdiff/xprepare.c  |  8 ++++----
 xdiff/xtypes.h    |  6 +++---
 xdiff/xutils.c    |  4 ++--
 7 files changed, 22 insertions(+), 22 deletions(-)
xdiff/xdiffi.c +4 -4
index 5a96e36dfbea..3b364c61f671 100644
@@ -418,7 +418,7 @@ static int get_indent(xrecord_t *rec)
 	long i;
 	int ret = 0;
 
-	for (i = 0; i < rec->size; i++) {
+	for (i = 0; i < (long) rec->size; i++) {
 		char c = rec->ptr[i];
 
 		if (!XDL_ISSPACE(c))
@@ -1005,11 +1005,11 @@ static void xdl_mark_ignorable_lines(xdchange_t *xscr, xdfenv_t *xe, long flags)
 
 		rec = &xe->xdf1.recs[xch->i1];
 		for (i = 0; i < xch->chg1 && ignore; i++)
-			ignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags);
+			ignore = xdl_blankline((const char*) rec[i]->ptr, rec[i]->size, flags);
 
 		rec = &xe->xdf2.recs[xch->i2];
 		for (i = 0; i < xch->chg2 && ignore; i++)
-			ignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags);
+			ignore = xdl_blankline((const char*)rec[i]->ptr, rec[i]->size, flags);
 
 		xch->ignore = ignore;
 	}
@@ -1020,7 +1020,7 @@ static int record_matches_regex(xrecord_t *rec, xpparam_t const *xpp) {
 	size_t i;
 
 	for (i = 0; i < xpp->ignore_regex_nr; i++)
-		if (!regexec_buf(xpp->ignore_regex[i], rec->ptr, rec->size, 1,
+		if (!regexec_buf(xpp->ignore_regex[i], (const char*) rec->ptr, rec->size, 1,
 				 &regmatch, 0))
 			return 1;
 
xdiff/xemit.c +1 -1
index 1d40c9cb4076..bbf7b7f8c862 100644
@@ -24,7 +24,7 @@
 
 static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec) {
 
-	*rec = xdf->recs[ri]->ptr;
+	*rec = (char const*) xdf->recs[ri]->ptr;
 
 	return xdf->recs[ri]->size;
 }
xdiff/xmerge.c +7 -7
index af40c88a5b36..6fa6ea61a208 100644
@@ -101,8 +101,8 @@ static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2,
 	xrecord_t **rec2 = xe2->xdf2.recs + i2;
 
 	for (i = 0; i < line_count; i++) {
-		int result = xdl_recmatch(rec1[i]->ptr, rec1[i]->size,
-			rec2[i]->ptr, rec2[i]->size, flags);
+		int result = xdl_recmatch((const char*) rec1[i]->ptr, rec1[i]->size,
+			(const char*) rec2[i]->ptr, rec2[i]->size, flags);
 		if (!result)
 			return -1;
 	}
@@ -324,8 +324,8 @@ static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1,
 
 static int recmatch(xrecord_t *rec1, xrecord_t *rec2, unsigned long flags)
 {
-	return xdl_recmatch(rec1->ptr, rec1->size,
-			    rec2->ptr, rec2->size, flags);
+	return xdl_recmatch((char const*) rec1->ptr, rec1->size,
+			    (char const*) rec2->ptr, rec2->size, flags);
 }
 
 /*
@@ -383,10 +383,10 @@ static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m,
 		 */
 		t1.ptr = (char *)xe1->xdf2.recs[m->i1]->ptr;
 		t1.size = xe1->xdf2.recs[m->i1 + m->chg1 - 1]->ptr
-			+ xe1->xdf2.recs[m->i1 + m->chg1 - 1]->size - t1.ptr;
+			+ xe1->xdf2.recs[m->i1 + m->chg1 - 1]->size - (u8 const*) t1.ptr;
 		t2.ptr = (char *)xe2->xdf2.recs[m->i2]->ptr;
 		t2.size = xe2->xdf2.recs[m->i2 + m->chg2 - 1]->ptr
-			+ xe2->xdf2.recs[m->i2 + m->chg2 - 1]->size - t2.ptr;
+			+ xe2->xdf2.recs[m->i2 + m->chg2 - 1]->size - (u8 const*) t2.ptr;
 		if (xdl_do_diff(&t1, &t2, xpp, &xe) < 0)
 			return -1;
 		if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 ||
@@ -440,7 +440,7 @@ static int line_contains_alnum(const char *ptr, long size)
 static int lines_contain_alnum(xdfenv_t *xe, int i, int chg)
 {
 	for (; chg; chg--, i++)
-		if (line_contains_alnum(xe->xdf2.recs[i]->ptr,
+		if (line_contains_alnum((char const*) xe->xdf2.recs[i]->ptr,
 				xe->xdf2.recs[i]->size))
 			return 1;
 	return 0;
xdiff/xpatience.c +1 -1
index 77dc411d1937..986a3a3f749a 100644
@@ -121,7 +121,7 @@ static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map,
 		return;
 	map->entries[index].line1 = line;
 	map->entries[index].hash = record->ha;
-	map->entries[index].anchor = is_anchor(xpp, map->env->xdf1.recs[line - 1]->ptr);
+	map->entries[index].anchor = is_anchor(xpp, (const char*) map->env->xdf1.recs[line - 1]->ptr);
 	if (!map->first)
 		map->first = map->entries + index;
 	if (map->last) {
xdiff/xprepare.c +4 -4
index ad356281f939..00cdf7d8a038 100644
@@ -32,7 +32,7 @@
 
 typedef struct s_xdlclass {
 	struct s_xdlclass *next;
-	unsigned long ha;
+	u64 ha;
 	char const *line;
 	long size;
 	long idx;
@@ -96,12 +96,12 @@ static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t
 	char const *line;
 	xdlclass_t *rcrec;
 
-	line = rec->ptr;
+	line = (char const*) rec->ptr;
 	hi = (long) XDL_HASHLONG(rec->ha, cf->hbits);
 	for (rcrec = cf->rchash[hi]; rcrec; rcrec = rcrec->next)
 		if (rcrec->ha == rec->ha &&
 				xdl_recmatch(rcrec->line, rcrec->size,
-					rec->ptr, rec->size, cf->flags))
+					(const char*) rec->ptr, rec->size, cf->flags))
 			break;
 
 	if (!rcrec) {
@@ -159,7 +159,7 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
 				goto abort;
 			if (!(crec = xdl_cha_alloc(&xdf->rcha)))
 				goto abort;
-			crec->ptr = prev;
+			crec->ptr = (u8 const*) prev;
 			crec->size = (long) (cur - prev);
 			crec->ha = hav;
 			recs[nrec++] = crec;
xdiff/xtypes.h +3 -3
index 8b8467360ecf..6e5f67ebf380 100644
@@ -39,9 +39,9 @@ typedef struct s_chastore {
 } chastore_t;
 
 typedef struct s_xrecord {
-	char const *ptr;
-	long size;
-	unsigned long ha;
+	u8 const* ptr;
+	usize size;
+	u64 ha;
 } xrecord_t;
 
 typedef struct s_xdfile {
xdiff/xutils.c +2 -2
index 444a108f87c0..10e4f20b7c31 100644
@@ -418,10 +418,10 @@ int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp,
 
 	subfile1.ptr = (char *)diff_env->xdf1.recs[line1 - 1]->ptr;
 	subfile1.size = diff_env->xdf1.recs[line1 + count1 - 2]->ptr +
-		diff_env->xdf1.recs[line1 + count1 - 2]->size - subfile1.ptr;
+		diff_env->xdf1.recs[line1 + count1 - 2]->size - (u8 const*) subfile1.ptr;
 	subfile2.ptr = (char *)diff_env->xdf2.recs[line2 - 1]->ptr;
 	subfile2.size = diff_env->xdf2.recs[line2 + count2 - 2]->ptr +
-		diff_env->xdf2.recs[line2 + count2 - 2]->size - subfile2.ptr;
+		diff_env->xdf2.recs[line2 + count2 - 2]->size - (u8 const*) subfile2.ptr;
 	if (xdl_do_diff(&subfile1, &subfile2, xpp, &env) < 0)
 		return -1;
 
Signature · 1 line
gitgitgadget
#97 lore raw

PATCH v2 6/17 xdiff: separate parsing lines from hashing them

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

We want to use xxhash for faster hashing. To facilitate that and to simplify the code. Separate the concerns of parsing and hashing into discrete steps. This makes swapping the hash function much easier. Since xdl_hash_record() both parses and hashses lines, this requires some slight code restructuring.

---
 xdiff/xprepare.c | 75 ++++++++++++++++++++++++++++--------------------
 1 file changed, 44 insertions(+), 31 deletions(-)
xdiff/xprepare.c +44 -31
index 00cdf7d8a038..031c1752cc1a 100644
@@ -129,13 +129,39 @@ static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t
 }
 
 
+static void xdl_parse_lines(mmfile_t *mf, long narec, xdfile_t *xdf) {
+	u8 const* ptr = (u8 const*) mf->ptr;
+	usize len = (usize) mf->size;
+
+	xdf->recs = NULL;
+	xdf->nrec = 0;
+	XDL_ALLOC_ARRAY(xdf->recs, narec);
+
+	while (len > 0) {
+		xrecord_t *rec = NULL;
+		usize length;
+		u8 const* result = memchr(ptr, '\n', len);
+		if (result) {
+			length = result - ptr + 1;
+		} else {
+			length = len;
+		}
+		if (XDL_ALLOC_GROW(xdf->recs, xdf->nrec + 1, narec))
+			die("XDL_ALLOC_GROW failed");
+		rec = xdl_cha_alloc(&xdf->rcha);
+		rec->ptr = ptr;
+		rec->size = length;
+		rec->ha = 0;
+		xdf->recs[xdf->nrec++] = rec;
+		ptr += length;
+		len -= length;
+	}
+
+}
+
+
 static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
 			   xdlclassifier_t *cf, xdfile_t *xdf) {
-	long nrec, bsize;
-	unsigned long hav;
-	char const *blk, *cur, *top, *prev;
-	xrecord_t *crec;
-	xrecord_t **recs;
 	unsigned long *ha;
 	char *rchg;
 	long *rindex;
@@ -143,50 +169,37 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
 	ha = NULL;
 	rindex = NULL;
 	rchg = NULL;
-	recs = NULL;
 
 	if (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0)
 		goto abort;
-	if (!XDL_ALLOC_ARRAY(recs, narec))
-		goto abort;
 
-	nrec = 0;
-	if ((cur = blk = xdl_mmfile_first(mf, &bsize))) {
-		for (top = blk + bsize; cur < top; ) {
-			prev = cur;
-			hav = xdl_hash_record(&cur, top, xpp->flags);
-			if (XDL_ALLOC_GROW(recs, nrec + 1, narec))
-				goto abort;
-			if (!(crec = xdl_cha_alloc(&xdf->rcha)))
-				goto abort;
-			crec->ptr = (u8 const*) prev;
-			crec->size = (long) (cur - prev);
-			crec->ha = hav;
-			recs[nrec++] = crec;
-			if (xdl_classify_record(pass, cf, crec) < 0)
-				goto abort;
-		}
+	xdl_parse_lines(mf, narec, xdf);
+
+	for (usize i = 0; i < (usize) xdf->nrec; i++) {
+		xrecord_t *rec = xdf->recs[i];
+		char const* dump = (char const*) rec->ptr;
+		rec->ha = xdl_hash_record(&dump, (char const*) (rec->ptr + rec->size), xpp->flags);
+		xdl_classify_record(pass, cf, rec);
 	}
 
-	if (!XDL_CALLOC_ARRAY(rchg, nrec + 2))
+
+	if (!XDL_CALLOC_ARRAY(rchg, xdf->nrec + 2))
 		goto abort;
 
 	if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) &&
 	    (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF)) {
-		if (!XDL_ALLOC_ARRAY(rindex, nrec + 1))
+		if (!XDL_ALLOC_ARRAY(rindex, xdf->nrec + 1))
 			goto abort;
-		if (!XDL_ALLOC_ARRAY(ha, nrec + 1))
+		if (!XDL_ALLOC_ARRAY(ha, xdf->nrec + 1))
 			goto abort;
 	}
 
-	xdf->nrec = nrec;
-	xdf->recs = recs;
 	xdf->rchg = rchg + 1;
 	xdf->rindex = rindex;
 	xdf->nreff = 0;
 	xdf->ha = ha;
 	xdf->dstart = 0;
-	xdf->dend = nrec - 1;
+	xdf->dend = xdf->nrec - 1;
 
 	return 0;
 
@@ -194,7 +207,7 @@ abort:
 	xdl_free(ha);
 	xdl_free(rindex);
 	xdl_free(rchg);
-	xdl_free(recs);
+	xdl_free(xdf->recs);
 	xdl_cha_free(&xdf->rcha);
 	return -1;
 }
Signature · 1 line
gitgitgadget
#98 lore raw

PATCH v2 7/17 xdiff: conditionally use Rust's implementation of xxhash

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

When no whitespace flags are present use xxhash, for faster hashing, otherwise use DJB2a (which is what xdiff has been using all along).

The benchmark below compares my series with version v2.49.0 (built in build_release/ and build_v2.49.0/ respectively), running log commands on linux kernel with 3 different machines.

$ BASE=/path/to/git/root
    // laptop
    // CPU: 6-core Intel Core i7-8750H (-MT MCP-) speed/min/max: 726/800/4100 MHz
    $ hyperfine --warmup 3 -L exe $BASE/build_release/git,$BASE/build_v2.49.0/git '{exe} log --oneline --shortstat v6.8..v6.9 >/dev/null'
    Benchmark 1: /home/ezekiel/development/work/git/build_release/git log --oneline --shortstat v6.8..v6.9 >/dev/null
      Time (mean ± σ):     10.419 s ±  0.166 s    [User: 10.097 s, System: 0.284 s]
      Range (min … max):   10.215 s … 10.680 s    10 runs

    Benchmark 2: /home/ezekiel/development/work/git/build_v2.49.0/git log --oneline --shortstat v6.8..v6.9 >/dev/null
      Time (mean ± σ):     10.980 s ±  0.137 s    [User: 10.633 s, System: 0.308 s]
      Range (min … max):   10.791 s … 11.178 s    10 runs

    Summary
      /home/ezekiel/development/work/git/build_release/git log --oneline --shortstat v6.8..v6.9 >/dev/null ran
        1.05 ± 0.02 times faster than /home/ezekiel/development/work/git/build_v2.49.0/git log --oneline --shortstat v6.8..v6.9 >/dev/null

    // desktop
    // CPU: 8-core Intel Core i7-9700 (-MCP-) speed/min/max: 800/800/4700 MHz
    $ hyperfine --warmup 3 -L exe $BASE/build_release/git,$BASE/build_v2.49.0/git '{exe} log --oneline --shortstat v6.8..v6.9 >/dev/null'
    Benchmark 1: /home/steamuser/dev/git/build_release/git log --oneline --shortstat v6.8..v6.9 >/dev/null
      Time (mean ± σ):      6.823 s ±  0.020 s    [User: 6.624 s, System: 0.180 s]
      Range (min … max):    6.801 s …  6.858 s    10 runs

    Benchmark 2: /home/steamuser/dev/git/build_v2.49.0/git log --oneline --shortstat v6.8..v6.9 >/dev/null
      Time (mean ± σ):      8.151 s ±  0.024 s    [User: 7.928 s, System: 0.198 s]
      Range (min … max):    8.105 s …  8.184 s    10 runs

    Summary
      /home/steamuser/dev/git/build_release/git log --oneline --shortstat v6.8..v6.9 >/dev/null ran
        1.19 ± 0.01 times faster than /home/steamuser/dev/git/build_v2.49.0/git log --oneline --shortstat v6.8..v6.9 >/dev/null

    // router
    // CPU: dual core Intel Celeron 3965U (-MCP-) speed/min/max: 1300/400/2200 MHz
    $ hyperfine --warmup 3 -L exe $BASE/build_release/git,$BASE/build_v2.49.0/git '{exe} log --oneline --shortstat v6.8..v6.9 >/dev/null'
    Benchmark 1: /home/metal/dev/git/build_release/git log --oneline --shortstat v6.8..v6.9 >/dev/null
      Time (mean ± σ):     21.209 s ±  0.054 s    [User: 20.341 s, System: 0.605 s]
      Range (min … max):   21.135 s … 21.309 s    10 runs

    Benchmark 2: /home/metal/dev/git/build_v2.49.0/git log --oneline --shortstat v6.8..v6.9 >/dev/null
      Time (mean ± σ):     23.683 s ±  0.060 s    [User: 22.735 s, System: 0.672 s]
      Range (min … max):   23.566 s … 23.751 s    10 runs

    Summary
      /home/metal/dev/git/build_release/git log --oneline --shortstat v6.8..v6.9 >/dev/null ran
        1.12 ± 0.00 times faster than /home/metal/dev/git/build_v2.49.0/git log --oneline --shortstat v6.8..v6.9 >/dev/null
---
 rust/xdiff/Cargo.toml |  1 +
 rust/xdiff/src/lib.rs |  7 +++++++
 xdiff/xprepare.c      | 19 +++++++++++++++++--
 3 files changed, 25 insertions(+), 2 deletions(-)
rust/xdiff/Cargo.toml +1 -0
index eb7966aada64..1516e829db18 100644
@@ -13,3 +13,4 @@ crate-type = ["staticlib", "rlib"]
 
 [dependencies]
 interop = { path = "../interop" }
+xxhash-rust = { version = "0.8.15", features = ["xxh3"] }
rust/xdiff/src/lib.rs +7 -0
index e69de29bb2d1..96975975a1ba 100644
@@ -0,0 +1,7 @@
+
+
+#[no_mangle]
+unsafe extern "C" fn xxh3_64(ptr: *const u8, size: usize) -> u64 {
+    let slice = std::slice::from_raw_parts(ptr, size);
+    xxhash_rust::xxh3::xxh3_64(slice)
+}
xdiff/xprepare.c +17 -2
index 031c1752cc1a..c0463bacd94b 100644
@@ -160,6 +160,9 @@ static void xdl_parse_lines(mmfile_t *mf, long narec, xdfile_t *xdf) {
 }
 
 
+extern u64 xxh3_64(u8 const* ptr, usize size);
+
+
 static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
 			   xdlclassifier_t *cf, xdfile_t *xdf) {
 	unsigned long *ha;
@@ -175,14 +178,26 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
 
 	xdl_parse_lines(mf, narec, xdf);
 
+	if ((xpp->flags & XDF_WHITESPACE_FLAGS) == 0) {
+		for (usize i = 0; i < (usize) xdf->nrec; i++) {
+			xrecord_t *rec = xdf->recs[i];
+			rec->ha = xxh3_64(rec->ptr, rec->size);
+		}
+	} else {
+		for (usize i = 0; i < (usize) xdf->nrec; i++) {
+			xrecord_t *rec = xdf->recs[i];
+			char const* dump = (char const*) rec->ptr;
+			rec->ha = xdl_hash_record(&dump, (char const*) (rec->ptr + rec->size), xpp->flags);
+		}
+	}
+
 	for (usize i = 0; i < (usize) xdf->nrec; i++) {
 		xrecord_t *rec = xdf->recs[i];
-		char const* dump = (char const*) rec->ptr;
-		rec->ha = xdl_hash_record(&dump, (char const*) (rec->ptr + rec->size), xpp->flags);
 		xdl_classify_record(pass, cf, rec);
 	}
 
 
+
 	if (!XDL_CALLOC_ARRAY(rchg, xdf->nrec + 2))
 		goto abort;
 
Signature · 1 line
gitgitgadget
#99 lore raw

PATCH v2 8/17 github workflows: install rust

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

Since we have introduced rust, it needs to be installed for the continuous integration build targets. Create an install script (build_rust.sh) that needs to be run as the same user that builds git. Because of the limitations of meson, create build_rust.sh which makes it easy to centralize how rust is built between meson and make.

There are 2 interesting decisions worth calling out in this commit:

* The 'output' field of custom_target() does not allow specifying a file nested inside the build directory. Thus create build_rust.sh to build rust with all of its parameters and then moves libxdiff.a to the root of the build directory.

* Install curl, to facilitate the rustup install script.

---
 .github/workflows/main.yml |  1 +
 Makefile                   | 46 +++++++++++++++++++----------
 build_rust.sh              | 59 ++++++++++++++++++++++++++++++++++++++
 ci/install-dependencies.sh | 14 ++++-----
 ci/install-rust.sh         | 33 +++++++++++++++++++++
 ci/lib.sh                  |  8 ++++++
 ci/make-test-artifacts.sh  |  7 +++++
 ci/run-build-and-tests.sh  | 10 +++++++
 meson.build                | 40 +++++++++++---------------
 9 files changed, 172 insertions(+), 46 deletions(-)
 create mode 100755 build_rust.sh
 create mode 100644 ci/install-rust.sh
.github/workflows/main.yml +1 -0
index 7dbf9f7f123c..8aac18a6ba45 100644
@@ -4,6 +4,7 @@ on: [push, pull_request]
 
 env:
   DEVELOPER: 1
+  RUST_VERSION: 1.87.0
 
 # If more than one workflow run is triggered for the very same commit hash
 # (which happens when multiple branches pointing to the same commit), only
Makefile +31 -15

113 lines not on this page · raw message

build_rust.sh new file +59 -0

59 lines not on this page · raw message

ci/install-dependencies.sh +7 -7
index d061a4729339..7801075821ba 100755
@@ -24,14 +24,14 @@ fi
 
 case "$distro" in
 alpine-*)
-	apk add --update shadow sudo meson ninja-build gcc libc-dev curl-dev openssl-dev expat-dev gettext \
+	apk add --update shadow sudo meson ninja-build gcc libc-dev curl curl-dev openssl-dev expat-dev gettext \
 		zlib-ng-dev pcre2-dev python3 musl-libintl perl-utils ncurses \
 		apache2 apache2-http2 apache2-proxy apache2-ssl apache2-webdav apr-util-dbd_sqlite3 \
 		bash cvs gnupg perl-cgi perl-dbd-sqlite perl-io-tty >/dev/null
 	;;
 fedora-*|almalinux-*)
 	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 gcc findutils diffutils perl python3 gawk gettext zlib-devel expat-devel openssl-devel curl curl-devel pcre2-devel >/dev/null
 	;;
 ubuntu-*|i386/ubuntu-*|debian-*)
 	# Required so that apt doesn't wait for user input on certain packages.
@@ -55,8 +55,8 @@ ubuntu-*|i386/ubuntu-*|debian-*)
 	sudo apt-get -q update
 	sudo apt-get -q -y install \
 		$LANGUAGES apache2 cvs cvsps git gnupg $SVN \
-		make libssl-dev libcurl4-openssl-dev libexpat-dev wget sudo default-jre \
-		tcl tk gettext zlib1g-dev perl-modules liberror-perl libauthen-sasl-perl \
+		make libssl-dev curl libcurl4-openssl-dev libexpat-dev wget sudo default-jre \
+		tcl tk gettext zlib1g 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 \
 		${CC_PACKAGE:-${CC:-gcc}} $PYTHON_PACKAGE
@@ -121,13 +121,13 @@ ClangFormat)
 	;;
 StaticAnalysis)
 	sudo apt-get -q update
-	sudo apt-get -q -y install coccinelle libcurl4-openssl-dev libssl-dev \
+	sudo apt-get -q -y install coccinelle curl libcurl4-openssl-dev libssl-dev \
 		libexpat-dev gettext make
 	;;
 sparse)
 	sudo apt-get -q update -q
-	sudo apt-get -q -y install libssl-dev libcurl4-openssl-dev \
-		libexpat-dev gettext zlib1g-dev sparse
+	sudo apt-get -q -y install libssl-dev curl libcurl4-openssl-dev \
+		libexpat-dev gettext zlib1g zlib1g-dev sparse
 	;;
 Documentation)
 	sudo apt-get -q update
ci/install-rust.sh new file +33 -0
new file mode 100644
index 000000000000..141ceddb17cf
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+if [ "$(id -u)" -eq 0 ]; then
+  echo >&2 "::warning:: installing rust as root"
+fi
+
+if [ "$CARGO_HOME" = "" ]; then
+  echo >&2 "::warning:: CARGO_HOME is not set"
+  export CARGO_HOME=$HOME/.cargo
+fi
+
+export RUSTUP_HOME=$CARGO_HOME
+
+if [ "$RUST_VERSION" = "" ]; then
+  echo >&2 "::error:: RUST_VERSION is not set"
+  exit 2
+fi
+
+## install rustup
+curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --default-toolchain none -y
+if [ ! -f $CARGO_HOME/env ]; then
+  echo "PATH=$CARGO_HOME/bin:\$PATH" > $CARGO_HOME/env
+fi
+## install a specific version of rust
+if [ "$BITNESS" = "32" ]; then
+  $CARGO_HOME/bin/rustup set default-host i686-unknown-linux-gnu || exit $?
+  $CARGO_HOME/bin/rustup install $RUST_VERSION || exit $?
+  $CARGO_HOME/bin/rustup default --force-non-host $RUST_VERSION || exit $?
+else
+  $CARGO_HOME/bin/rustup default $RUST_VERSION || exit $?
+fi
+
+. $CARGO_HOME/env
ci/lib.sh +8 -0
index f561884d4016..ad0e49a68dcb 100755
@@ -1,5 +1,13 @@
 # Library of functions shared by all CI scripts
 
+
+export BITNESS="64"
+if command -v getconf >/dev/null && [ "$(getconf LONG_BIT 2>/dev/null)" = "32" ]; then
+  export BITNESS="32"
+fi
+echo "BITNESS=$BITNESS"
+
+
 if test true = "$GITHUB_ACTIONS"
 then
 	begin_group () {
ci/make-test-artifacts.sh +7 -0
index 74141af0cc74..56aa7efb1d53 100755
@@ -7,6 +7,13 @@ mkdir -p "$1" # in case ci/lib.sh decides to quit early
 
 . ${0%/*}/lib.sh
 
+## install rust per user rather than system wide
+. ${0%/*}/install-rust.sh
+
 group Build make artifacts-tar ARTIFACTS_DIRECTORY="$1"
 
+if [ -d "$CARGO_HOME" ]; then
+  rm -rf $CARGO_HOME
+fi
+
 check_unignored_build_artifacts
ci/run-build-and-tests.sh +10 -0
index 01823fd0f140..dbab1cb2f936 100755
@@ -5,6 +5,12 @@
 
 . ${0%/*}/lib.sh
 
+## install rust per user rather than system wide
+. ${0%/*}/install-rust.sh
+
+rustc -vV
+cargo --version || exit $?
+
 run_tests=t
 
 case "$jobname" in
@@ -72,5 +78,9 @@ case "$jobname" in
 	;;
 esac
 
+if [ -d "$CARGO_HOME" ]; then
+  rm -rf $CARGO_HOME
+fi
+
 check_unignored_build_artifacts
 save_good_tree
meson.build +16 -24
index 2d8da17f6515..047d7e5b6630 100644
@@ -277,26 +277,17 @@ else
   rustflags = '-Aunused_imports -Adead_code -C debuginfo=2 -C opt-level=1 -C force-frame-pointers=yes'
 endif
 
-
-rust_leaf = custom_target('rust_leaf',
+rust_build_xdiff = custom_target('rust_build_xdiff',
   output: 'libxdiff.a',
   build_by_default: true,
   build_always_stale: true,
-  command: ['cargo', 'build',
-            '--manifest-path', meson.project_source_root() / 'rust/Cargo.toml'
-  ] + rust_args,
-  env: {
-    'RUSTFLAGS': rustflags,
-  },
+  command: [
+    meson.project_source_root() / 'build_rust.sh',
+    meson.current_build_dir(), rust_target, 'xdiff',
+  ],
   install: false,
 )
 
-rust_xdiff_dep = declare_dependency(
-  link_args: ['-L' + meson.project_source_root() / 'rust/target' / rust_target, '-lxdiff'],
-#  include_directories: include_directories('xdiff/include'),  # Adjust if you expose headers
-)
-
-
 compiler = meson.get_compiler('c')
 
 libgit_sources = [
@@ -1707,17 +1698,18 @@ version_def_h = custom_target(
 )
 libgit_sources += version_def_h
 
-libgit_dependencies += rust_xdiff_dep
-
 libgit = declare_dependency(
-  link_with: static_library('git',
-    sources: libgit_sources,
-    c_args: libgit_c_args + [
-      '-DGIT_VERSION_H="' + version_def_h.full_path() + '"',
-    ],
-    dependencies: libgit_dependencies,
-    include_directories: libgit_include_directories,
-  ),
+  link_with: [
+    static_library('git',
+      sources: libgit_sources,
+      c_args: libgit_c_args + [
+        '-DGIT_VERSION_H="' + version_def_h.full_path() + '"',
+      ],
+      dependencies: libgit_dependencies,
+      include_directories: libgit_include_directories,
+    ),
+    rust_build_xdiff,
+  ],
   compile_args: libgit_c_args,
   dependencies: libgit_dependencies,
   include_directories: libgit_include_directories,
Signature · 1 line
gitgitgadget
#100 lore raw

PATCH v2 9/17 Do support Windows again after requiring Rust

In reply to Ezekiel Newren via GitGitGadget

From: Johannes Schindelin <[email protected]>

By default, Rust wants to build MS Visual C-compatible libraries on Windows, because that is _the_ native C compiler.

Git is historically lacking in its MSVC support, and the official Git for Windows versions are built using GCC instead. As a consequence, a (subset of a) GCC toolchain is installed as part of the windows-build job of every CI build.

Naturally, this requires adjustments in how Rust is called, most importantly it requires installing support for a GCC-compatible build target.

Let's make the necessary adjustment both in the CI-specific code that installs Rust as well as in the Windows-specific configuration in config.mak.uname.

[en: Moved lib userenv handling to a later patch]
---
 ci/install-rust.sh | 3 +++
 config.mak.uname   | 7 +++++++
 2 files changed, 10 insertions(+)
ci/install-rust.sh +3 -0
index 141ceddb17cf..c22baa629ceb 100644
@@ -28,6 +28,9 @@ if [ "$BITNESS" = "32" ]; then
   $CARGO_HOME/bin/rustup default --force-non-host $RUST_VERSION || exit $?
 else
   $CARGO_HOME/bin/rustup default $RUST_VERSION || exit $?
+  if [ "$CI_OS_NAME" = "windows" ]; then
+    $CARGO_HOME/bin/rustup target add x86_64-pc-windows-gnu || exit $?
+  fi
 fi
 
 . $CARGO_HOME/env
config.mak.uname +7 -0
index 3e26bb074a4b..a22703284b56 100644
@@ -727,19 +727,26 @@ ifeq ($(uname_S),MINGW)
 		prefix = /mingw32
 		HOST_CPU = i686
 		BASIC_LDFLAGS += -Wl,--pic-executable,-e,_mainCRTStartup
+		CARGO_BUILD_TARGET = i686-pc-windows-gnu
         endif
         ifeq (MINGW64,$(MSYSTEM))
 		prefix = /mingw64
 		HOST_CPU = x86_64
 		BASIC_LDFLAGS += -Wl,--pic-executable,-e,mainCRTStartup
+		CARGO_BUILD_TARGET = x86_64-pc-windows-gnu
         else ifeq (CLANGARM64,$(MSYSTEM))
 		prefix = /clangarm64
 		HOST_CPU = aarch64
 		BASIC_LDFLAGS += -Wl,--pic-executable,-e,mainCRTStartup
+		CARGO_BUILD_TARGET = aarch64-pc-windows-gnu
         else
 		COMPAT_CFLAGS += -D_USE_32BIT_TIME_T
 		BASIC_LDFLAGS += -Wl,--large-address-aware
         endif
+
+	export CARGO_BUILD_TARGET
+	RUST_TARGET_DIR = rust/target/$(CARGO_BUILD_TARGET)/$(RUST_BUILD_MODE)
+
 	CC = gcc
 	COMPAT_CFLAGS += -D__USE_MINGW_ANSI_STDIO=0 -DDETECT_MSYS_TTY \
 		-fstack-protector-strong
Signature · 1 line
gitgitgadget
#109 lore raw

PATCH v2 10/17 win+Meson: allow for xdiff to be compiled with MSVC

In reply to Ezekiel Newren via GitGitGadget

From: Johannes Schindelin <[email protected]>

The build_rust.sh script is quite opinionated about the naming scheme of the C compiler: It assumes that the xdiff library file will be named libxdiff.a.

However, MS Visual C generates xdiff.lib files instead; This naming scheme has been in use in a very, very long time.

Let's allow for that.

---
 build_rust.sh |  7 ++++++-
 meson.build   | 12 +++++++++---
 2 files changed, 15 insertions(+), 4 deletions(-)
build_rust.sh +6 -1
index 4c12135cd205..694d48d857a5 100755
@@ -44,7 +44,12 @@ fi
 
 cd $dir_rust && cargo clean && pwd && cargo build -p $crate $rust_args; cd ..
 
-libfile="lib${crate}.a"
+if grep x86_64-pc-windows-msvc rust/target/.rustc_info.json
+then
+  libfile="${crate}.lib"
+else
+  libfile="lib${crate}.a"
+fi
 dst=$dir_build/$libfile
 
 if [ "$dir_git_root" != "$dir_build" ]; then
meson.build +9 -3
index 047d7e5b6630..5e89a5dd0e00 100644
@@ -277,8 +277,16 @@ else
   rustflags = '-Aunused_imports -Adead_code -C debuginfo=2 -C opt-level=1 -C force-frame-pointers=yes'
 endif
 
+compiler = meson.get_compiler('c')
+
+if compiler.get_id() == 'msvc'
+  xdiff_lib_filename = 'xdiff.lib'
+else
+  xdiff_lib_filename = 'libxdiff.a'
+endif
+
 rust_build_xdiff = custom_target('rust_build_xdiff',
-  output: 'libxdiff.a',
+  output: xdiff_lib_filename,
   build_by_default: true,
   build_always_stale: true,
   command: [
@@ -288,8 +296,6 @@ rust_build_xdiff = custom_target('rust_build_xdiff',
   install: false,
 )
 
-compiler = meson.get_compiler('c')
-
 libgit_sources = [
   'abspath.c',
   'add-interactive.c',
Signature · 1 line
gitgitgadget
#110 lore raw

PATCH v2 11/17 win+Meson: do allow linking with the Rust-built xdiff

In reply to Ezekiel Newren via GitGitGadget

From: Johannes Schindelin <[email protected]>

When linking against the Rust-built xdiff, there is now a new required
dependency: Without _also_ linking to the system library userenv, the compile would fail with this error message:

xdiff.lib(std-c85e9beb7923f636.std.df32d1bc89881d89-cgu.0.rcgu.o) :
error LNK2019: unresolved external symbol __imp_GetUserProfileDirectoryW referenced in function _ZN3std3env8home_dir17hfd1c3b6676cd78f6E

Therefore, just like we do in case of Makefile-based builds on Windows, we now also link to that library when building with Meson.

Note that if we only have Rust depend upon libuserenv then at link time GCC would complain about:

undefined reference to `GetUserProfileDirectoryW'

Apparently there is _some_ closure that gets compiled in that requires this function, and that in turn forces Git to link to libuserenv.

This is a new requirement, and therefore has not been made part of the "minimal Git for Windows SDK".

In the near future, I intend to include it, but for now let's just ensure that the file is added manually if it is missing.

[en: Squashed a few of Johannes's patches, and moved lib userenv
handling from an earlier patch]

---
 .github/workflows/main.yml | 8 ++++++++
 config.mak.uname           | 2 ++
 meson.build                | 1 +
 3 files changed, 11 insertions(+)
.github/workflows/main.yml +8 -0
index 8aac18a6ba45..aa18742f08c4 100644
@@ -115,6 +115,14 @@ jobs:
     steps:
     - uses: actions/checkout@v4
     - uses: git-for-windows/setup-git-for-windows-sdk@v1
+    - name: ensure that libuserenv.a is present
+      shell: bash
+      run: |
+        cd /mingw64/lib && {
+          test -f libuserenv.a ||
+          /c/Program\ Files/Git/mingw64/bin/curl -Lo libuserenv.a \
+            https://github.com/git-for-windows/git-sdk-64/raw/HEAD/mingw64/lib/libuserenv.a
+        }
     - name: build
       shell: bash
       env:
config.mak.uname +2 -0
index a22703284b56..fbe7cebf40ed 100644
@@ -746,6 +746,8 @@ ifeq ($(uname_S),MINGW)
 
 	export CARGO_BUILD_TARGET
 	RUST_TARGET_DIR = rust/target/$(CARGO_BUILD_TARGET)/$(RUST_BUILD_MODE)
+	# Unfortunately now needed because of Rust
+	EXTLIBS += -luserenv
 
 	CC = gcc
 	COMPAT_CFLAGS += -D__USE_MINGW_ANSI_STDIO=0 -DDETECT_MSYS_TTY \
meson.build +1 -0
index 5e89a5dd0e00..af015f04763f 100644
@@ -1260,6 +1260,7 @@ elif host_machine.system() == 'windows'
   ]
 
   libgit_dependencies += compiler.find_library('ntdll')
+  libgit_dependencies += compiler.find_library('userenv')
   libgit_include_directories += 'compat/win32'
   if compiler.get_id() == 'msvc'
     libgit_include_directories += 'compat/vcbuild/include'
Signature · 1 line
gitgitgadget
#111 lore raw

PATCH v2 12/17 github workflows: define rust versions and targets in the same place

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

Consolidate the Rust toolchain definitions in main.yaml. Prefer using actions-rs/toolchain@v1 where possible, but for docker targets use a script to install the Rust toolchain. Four overrides are used in main.yaml:

* On Windows: Rust didn't resolve the bcrypt library on Windows correctly until version 1.78.0. Also since rustup mis-identifies the Rust toolchain, the Rust target triple must be set to x86_64-pc-windows-gnu.
* On musl: libc differences, such as ftruncate64 vs ftruncate, were not accounted for until Rust version 1.72.0. No older version of Rust will work on musl for our needs.
* In a 32-bit docker container running on a 64-bit host, we need to override the Rust target triple. This is because rustup asks the kernel for the bitness of the system and it says 64, even though the container will only run 32-bit. This also allows us to remove the BITNESS environment variable in ci/lib.sh.

---
 .github/workflows/main.yml | 34 +++++++++++++++++++++++++++++++++-
 build_rust.sh              | 11 +++--------
 ci/install-rust.sh         | 33 +++++++++++++++++----------------
 ci/lib.sh                  |  7 -------
 ci/make-test-artifacts.sh  | 12 ++++++------
 ci/run-build-and-tests.sh  |  8 +++++---
 meson.build                |  1 +
 7 files changed, 65 insertions(+), 41 deletions(-)
 mode change 100644 => 100755 ci/install-rust.sh
.github/workflows/main.yml +33 -1
index aa18742f08c4..ef4d6348edcd 100644
@@ -4,7 +4,6 @@ on: [push, pull_request]
 
 env:
   DEVELOPER: 1
-  RUST_VERSION: 1.87.0
 
 # If more than one workflow run is triggered for the very same commit hash
 # (which happens when multiple branches pointing to the same commit), only
@@ -27,6 +26,12 @@ jobs:
     outputs:
       enabled: ${{ steps.check-ref.outputs.enabled }}${{ steps.skip-if-redundant.outputs.enabled }}
       skip_concurrent: ${{ steps.check-ref.outputs.skip_concurrent }}
+      rust_version_minimum: 1.61.0
+      rust_version_windows: 1.78.0
+      rust_version_musl: 1.72.0
+      ## the rust target is inferred by rustup unless specified
+      rust_target_windows: x86_64-pc-windows-gnu
+      rust_target_32bit_linux: i686-unknown-linux-gnu
     steps:
       - name: try to clone ci-config branch
         run: |
@@ -123,11 +128,19 @@ jobs:
           /c/Program\ Files/Git/mingw64/bin/curl -Lo libuserenv.a \
             https://github.com/git-for-windows/git-sdk-64/raw/HEAD/mingw64/lib/libuserenv.a
         }
+    - name: Install Rust
+      uses: actions-rs/toolchain@v1
+      with:
+        toolchain: ${{ needs.ci-config.outputs.rust_version_windows }}
+        target: ${{ needs.ci-config.outputs.rust_target_windows }}
+        profile: minimal
+        override: true
     - name: build
       shell: bash
       env:
         HOME: ${{runner.workspace}}
         NO_PERL: 1
+        CARGO_HOME: "/c/Users/runneradmin/.cargo"
       run: . /etc/profile && ci/make-test-artifacts.sh artifacts
     - name: zip up tracked files
       run: git archive -o artifacts/tracked.tar.gz HEAD
@@ -269,6 +282,13 @@ jobs:
     steps:
     - uses: actions/checkout@v4
     - uses: actions/setup-python@v5
+    - name: Install Rust
+      uses: actions-rs/toolchain@v1
+      with:
+        toolchain: ${{ needs.ci-config.outputs.rust_version_windows }}
+        target: ${{ needs.ci-config.outputs.rust_target_windows }}
+        profile: minimal
+        override: true
     - name: Set up dependencies
       shell: pwsh
       run: pip install meson ninja
@@ -342,6 +362,12 @@ jobs:
     steps:
     - uses: actions/checkout@v4
     - run: ci/install-dependencies.sh
+    - name: Install Rust
+      uses: actions-rs/toolchain@v1
+      with:
+        toolchain: ${{ needs.ci-config.outputs.rust_version_minimum }}
+        profile: minimal
+        override: true
     - run: ci/run-build-and-tests.sh
     - name: print test failures
       if: failure() && env.FAILED_TEST_ARTIFACTS != ''
@@ -402,9 +428,11 @@ jobs:
           cc: gcc
         - jobname: linux-musl-meson
           image: alpine:latest
+          rust_version_override: ${{ needs.ci-config.outputs.rust_version_musl }}
         # Supported until 2025-04-02.
         - jobname: linux32
           image: i386/ubuntu:focal
+          rust_target_override: ${{ needs.ci-config.outputs.rust_target_32bit_linux }}
         - jobname: pedantic
           image: fedora:latest
         # A RHEL 8 compatible distro.  Supported until 2029-05-31.
@@ -417,7 +445,11 @@ jobs:
       jobname: ${{matrix.vector.jobname}}
       CC: ${{matrix.vector.cc}}
       CI_JOB_IMAGE: ${{matrix.vector.image}}
+      CI_IS_DOCKER: "true"
       CUSTOM_PATH: /custom
+      RUST_VERSION: ${{ matrix.vector.rust_version_override || needs.ci-config.outputs.rust_version_minimum }}
+      RUST_TARGET: ${{ matrix.vector.rust_target_override || '' }}
+      CARGO_HOME: /home/builder/.cargo
     runs-on: ubuntu-latest
     container: ${{matrix.vector.image}}
     steps:
build_rust.sh +3 -8
index 694d48d857a5..80ce7eae3b00 100755
@@ -1,13 +1,8 @@
 #!/bin/sh
 
-if [ -z "$CARGO_HOME" ]; then
-  export CARGO_HOME=$HOME/.cargo
-  echo >&2 "::warning:: CARGO_HOME is not set"
-fi
-echo "CARGO_HOME=$CARGO_HOME"
 
-rustc -vV
-cargo --version
+rustc -vV || exit $?
+cargo --version || exit $?
 
 dir_git_root=${0%/*}
 dir_build=$1
@@ -55,7 +50,7 @@ dst=$dir_build/$libfile
 if [ "$dir_git_root" != "$dir_build" ]; then
   src=$dir_rust/target/$rust_target/$libfile
   if [ ! -f $src ]; then
-    echo >&2 "::error:: cannot find path of static library"
+    echo >&2 "::error:: cannot find path of static library $src is not a file or does not exist"
     exit 5
   fi
 
ci/install-rust.sh +17 -16
old mode 100644
new mode 100755
index c22baa629ceb..133aa8cc878a
@@ -1,36 +1,37 @@
 #!/bin/sh
 
+## github workflows actions-rs/toolchain@v1 doesn't work for docker
+## targets. This script should only be used if the ci pipeline
+## doesn't support installing rust on a particular target.
+
 if [ "$(id -u)" -eq 0 ]; then
   echo >&2 "::warning:: installing rust as root"
 fi
 
-if [ "$CARGO_HOME" = "" ]; then
-  echo >&2 "::warning:: CARGO_HOME is not set"
-  export CARGO_HOME=$HOME/.cargo
-fi
-
-export RUSTUP_HOME=$CARGO_HOME
-
 if [ "$RUST_VERSION" = "" ]; then
   echo >&2 "::error:: RUST_VERSION is not set"
+  exit 1
+fi
+
+if [ "$CARGO_HOME" = "" ]; then
+  echo >&2 "::error:: CARGO_HOME is not set"
   exit 2
 fi
 
+export RUSTUP_HOME=$CARGO_HOME
+
 ## install rustup
 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --default-toolchain none -y
 if [ ! -f $CARGO_HOME/env ]; then
   echo "PATH=$CARGO_HOME/bin:\$PATH" > $CARGO_HOME/env
 fi
+. $CARGO_HOME/env
+
 ## install a specific version of rust
-if [ "$BITNESS" = "32" ]; then
-  $CARGO_HOME/bin/rustup set default-host i686-unknown-linux-gnu || exit $?
-  $CARGO_HOME/bin/rustup install $RUST_VERSION || exit $?
-  $CARGO_HOME/bin/rustup default --force-non-host $RUST_VERSION || exit $?
+if [ "$RUST_TARGET" != "" ]; then
+  rustup default --force-non-host "$RUST_VERSION-$RUST_TARGET" || exit $?
 else
-  $CARGO_HOME/bin/rustup default $RUST_VERSION || exit $?
-  if [ "$CI_OS_NAME" = "windows" ]; then
-    $CARGO_HOME/bin/rustup target add x86_64-pc-windows-gnu || exit $?
-  fi
+  rustup default "$RUST_VERSION" || exit $?
 fi
 
-. $CARGO_HOME/env
+rustc -vV || exit $?
ci/lib.sh +0 -7
index ad0e49a68dcb..a7992b22fdc9 100755
@@ -1,13 +1,6 @@
 # Library of functions shared by all CI scripts
 
 
-export BITNESS="64"
-if command -v getconf >/dev/null && [ "$(getconf LONG_BIT 2>/dev/null)" = "32" ]; then
-  export BITNESS="32"
-fi
-echo "BITNESS=$BITNESS"
-
-
 if test true = "$GITHUB_ACTIONS"
 then
 	begin_group () {
ci/make-test-artifacts.sh +6 -6
index 56aa7efb1d53..70d0dfbc0e8b 100755
@@ -7,13 +7,13 @@ mkdir -p "$1" # in case ci/lib.sh decides to quit early
 
 . ${0%/*}/lib.sh
 
-## install rust per user rather than system wide
-. ${0%/*}/install-rust.sh
+if [ -z "$CARGO_HOME" ]; then
+  echo >&2 "::error:: CARGO_HOME is not set"
+  exit 1
+fi
 
-group Build make artifacts-tar ARTIFACTS_DIRECTORY="$1"
+export PATH="$CARGO_HOME/bin:$PATH"
 
-if [ -d "$CARGO_HOME" ]; then
-  rm -rf $CARGO_HOME
-fi
+group Build make artifacts-tar ARTIFACTS_DIRECTORY="$1"
 
 check_unignored_build_artifacts
ci/run-build-and-tests.sh +5 -3
index dbab1cb2f936..0f1f704d583e 100755
@@ -5,10 +5,12 @@
 
 . ${0%/*}/lib.sh
 
-## install rust per user rather than system wide
-. ${0%/*}/install-rust.sh
+## actions-rs/toolchain@v1 doesn't work for docker targets.
+if [ "$CI_IS_DOCKER" = "true" ]; then
+  . ${0%/*}/install-rust.sh
+fi
 
-rustc -vV
+rustc -vV || exit $?
 cargo --version || exit $?
 
 run_tests=t
meson.build +1 -0
index af015f04763f..a2f9f063bef2 100644
@@ -293,6 +293,7 @@ rust_build_xdiff = custom_target('rust_build_xdiff',
     meson.project_source_root() / 'build_rust.sh',
     meson.current_build_dir(), rust_target, 'xdiff',
   ],
+  env: script_environment,
   install: false,
 )
 
Signature · 1 line
gitgitgadget
#112 lore raw

PATCH v2 13/17 github workflows: upload Cargo.lock

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

Make each ci workflow upload its Cargo.lock file as a build artifact so that we can audit build dependencies.

---
 .github/workflows/main.yml | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)
.github/workflows/main.yml +20 -0
index ef4d6348edcd..ba61bd516639 100644
@@ -149,6 +149,11 @@ jobs:
       with:
         name: windows-artifacts
         path: artifacts
+    - name: upload Cargo.lock
+      uses: actions/upload-artifact@v4
+      with:
+        name: cargo-lock-windows
+        path: rust/Cargo.lock
   windows-test:
     name: win test
     runs-on: windows-latest
@@ -303,6 +308,11 @@ jobs:
       with:
         name: windows-meson-artifacts
         path: build
+    - name: Upload Cargo.lock
+      uses: actions/upload-artifact@v4
+      with:
+        name: cargo-lock-windows-meson
+        path: rust/Cargo.lock
   windows-meson-test:
     name: win+Meson test
     runs-on: windows-latest
@@ -378,6 +388,11 @@ jobs:
       with:
         name: failed-tests-${{matrix.vector.jobname}}
         path: ${{env.FAILED_TEST_ARTIFACTS}}
+    - name: Upload Cargo.lock
+      uses: actions/upload-artifact@v4
+      with:
+        name: cargo-lock-${{matrix.vector.jobname}}
+        path: rust/Cargo.lock
   fuzz-smoke-test:
     name: fuzz smoke test
     needs: ci-config
@@ -484,6 +499,11 @@ jobs:
       with:
         name: failed-tests-${{matrix.vector.jobname}}
         path: ${{env.FAILED_TEST_ARTIFACTS}}
+    - name: Upload Cargo.lock
+      uses: actions/upload-artifact@v4
+      with:
+        name: cargo-lock-${{matrix.vector.jobname}}
+        path: rust/Cargo.lock
   static-analysis:
     needs: ci-config
     if: needs.ci-config.outputs.enabled == 'yes'
Signature · 1 line
gitgitgadget
#113 lore raw

PATCH v2 14/17 xdiff: implement a white space iterator in Rust

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

Xdiff has traditionally implemented the logic for iterating over whitespace in every location that needed to do so. Create a consolidated iterator in Rust that we can call from each location. Write Rust unit tests to ensure the correctness of the Rust whitespace iterator and the chunked_iter_equal() function.

---
 rust/xdiff/src/lib.rs    |  10 ++
 rust/xdiff/src/xutils.rs | 292 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 302 insertions(+)
 create mode 100644 rust/xdiff/src/xutils.rs
rust/xdiff/src/lib.rs +10 -0
index 96975975a1ba..9cf0462bcdb9 100644
@@ -1,3 +1,13 @@
+pub mod xutils;
+
+pub const XDF_IGNORE_WHITESPACE: u64 = 1 << 1;
+pub const XDF_IGNORE_WHITESPACE_CHANGE: u64 = 1 << 2;
+pub const XDF_IGNORE_WHITESPACE_AT_EOL: u64 = 1 << 3;
+pub const XDF_IGNORE_CR_AT_EOL: u64 = 1 << 4;
+pub const XDF_WHITESPACE_FLAGS: u64 = XDF_IGNORE_WHITESPACE |
+    XDF_IGNORE_WHITESPACE_CHANGE |
+    XDF_IGNORE_WHITESPACE_AT_EOL |
+    XDF_IGNORE_CR_AT_EOL;
 
 
 #[no_mangle]
rust/xdiff/src/xutils.rs new file +292 -0

292 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#114 lore raw

PATCH v2 15/17 xdiff: create line_hash() and line_equal()

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

These functions use the whitespace iterator, when applicable, to hash, and compare lines.

---
 rust/xdiff/src/lib.rs    | 19 +++++++++++++++++++
 rust/xdiff/src/xutils.rs | 28 ++++++++++++++++++++++++++++
 2 files changed, 47 insertions(+)
rust/xdiff/src/lib.rs +19 -0
index 9cf0462bcdb9..809c5573c6e7 100644
@@ -1,3 +1,7 @@
+use std::hash::Hasher;
+use xxhash_rust::xxh3::Xxh3Default;
+use crate::xutils::*;
+
 pub mod xutils;
 
 pub const XDF_IGNORE_WHITESPACE: u64 = 1 << 1;
@@ -15,3 +19,18 @@ unsafe extern "C" fn xxh3_64(ptr: *const u8, size: usize) -> u64 {
     let slice = std::slice::from_raw_parts(ptr, size);
     xxhash_rust::xxh3::xxh3_64(slice)
 }
+
+#[no_mangle]
+unsafe extern "C" fn xdl_line_hash(ptr: *const u8, size: usize, flags: u64) -> u64 {
+    let line = std::slice::from_raw_parts(ptr, size);
+
+    line_hash(line, flags)
+}
+
+#[no_mangle]
+unsafe extern "C" fn xdl_line_equal(lhs: *const u8, lhs_len: usize, rhs: *const u8, rhs_len: usize, flags: u64) -> bool {
+    let lhs_line = std::slice::from_raw_parts(lhs, lhs_len);
+    let rhs_line = std::slice::from_raw_parts(rhs, rhs_len);
+
+    line_equal(lhs_line, rhs_line, flags)
+}
rust/xdiff/src/xutils.rs +28 -0
index 38126b47292f..796a5708b6bf 100644
@@ -1,4 +1,5 @@
 use crate::*;
+use xxhash_rust::xxh3::xxh3_64;
 
 pub(crate) fn xdl_isspace(v: u8) -> bool {
     match v {
@@ -151,6 +152,33 @@ where
     run_option0.is_none() && run_option1.is_none()
 }
 
+
+pub fn line_hash(line: &[u8], flags: u64) -> u64 {
+    if (flags & XDF_WHITESPACE_FLAGS) == 0 {
+        return xxh3_64(line);
+    }
+
+    let mut hasher = Xxh3Default::new();
+    for chunk in WhitespaceIter::new(line, flags) {
+        hasher.update(chunk);
+    }
+
+    hasher.finish()
+}
+
+
+pub fn line_equal(lhs: &[u8], rhs: &[u8], flags: u64) -> bool {
+    if (flags & XDF_WHITESPACE_FLAGS) == 0 {
+        return lhs == rhs;
+    }
+
+    let lhs_it = WhitespaceIter::new(lhs, flags);
+    let rhs_it = WhitespaceIter::new(rhs, flags);
+
+    chunked_iter_equal(lhs_it, rhs_it)
+}
+
+
 #[cfg(test)]
 mod tests {
     use crate::*;
Signature · 1 line
gitgitgadget
#115 lore raw

PATCH v2 16/17 xdiff: optimize case where --ignore-cr-at-eol is the only whitespace flag

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

Currently the whitespace iterator is slower than git's C implementation so we skip using the whitespace iterator if there are no whitespace flags. Special case the --ignore-cr-at-eol similarly to make it performant. For the rest of the whitespace flags they will be slower for now, but as more of Xdiff is translated into Rust it'll be easier to revisit and optimize whitespace processing. Optimizing the other whitespace flags now would be difficult because:

* Xxhash uses chunk based processing.
* The same iterator is used for hashing and equality, which means the iterator could be optimized for returning large chunks for fast hashing or could return each byte making equality testing faster. I opted for faster hashing. The data structures in C need to be cleaned up before they're interoperable with Rust. Once that's done I believe a faster method of whitespace processing will be possible.
* Trying to make heavliy optimized code between 2 languages that aren't easily interoperable in their current state makes the code either fast or easy to maintain. But once enough of Xdiff is written in Rust I believe that a fast and maintainable method can be implemented.

---
 rust/xdiff/src/xutils.rs | 34 ++++++++++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)
rust/xdiff/src/xutils.rs +34 -0
index 796a5708b6bf..1ea9cfa02db5 100644
@@ -33,6 +33,18 @@ impl<'a> Iterator for WhitespaceIter<'a> {
             return None;
         }
 
+        // optimize case where --ignore-cr-at-eol is the only whitespace flag
+        if (self.flags & XDF_WHITESPACE_FLAGS) == XDF_IGNORE_CR_AT_EOL {
+            if self.index == 0 && self.line.ends_with(b"\r\n") {
+                self.index = self.line.len() - 1;
+                return Some(&self.line[..self.line.len() - 2])
+            } else {
+                let off = self.index;
+                self.index = self.line.len();
+                return Some(&self.line[off..])
+            }
+        }
+
         loop {
             let start = self.index;
             if self.index == self.line.len() {
@@ -172,6 +184,28 @@ pub fn line_equal(lhs: &[u8], rhs: &[u8], flags: u64) -> bool {
         return lhs == rhs;
     }
 
+    // optimize case where --ignore-cr-at-eol is the only whitespace flag
+    if (flags & XDF_WHITESPACE_FLAGS) == XDF_IGNORE_CR_AT_EOL {
+        let a = lhs.ends_with(b"\r\n");
+        let b = rhs.ends_with(b"\r\n");
+
+        if !(a ^ b) {
+            return lhs == rhs;
+        } else {
+            let lm = if a { 1 } else { 0 };
+            let rm = if b { 1 } else { 0 };
+
+            if lhs.len() - lm != rhs.len() - rm {
+                return false;
+            } else if &lhs[..lhs.len() - 1 - lm] != &rhs[..rhs.len() - 1 - rm] {
+                return false;
+            } else if lhs[lhs.len() - 1] != rhs[rhs.len() - 1] {
+                return false;
+            }
+            return true;
+        }
+    }
+
     let lhs_it = WhitespaceIter::new(lhs, flags);
     let rhs_it = WhitespaceIter::new(rhs, flags);
 
Signature · 1 line
gitgitgadget
#116 lore raw

PATCH v2 17/17 xdiff: use rust's version of whitespace processing

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

Delete xdl_hash_record() and xdl_recmatch() in favor of xdl_line_hash() and xdl_line_equal().

---
 rust/xdiff/src/lib.rs |   6 --
 xdiff-interface.c     |   4 +-
 xdiff/xmerge.c        |   8 +--
 xdiff/xprepare.c      |  29 ++------
 xdiff/xutils.c        | 158 ------------------------------------------
 xdiff/xutils.h        |   4 +-
 6 files changed, 15 insertions(+), 194 deletions(-)
rust/xdiff/src/lib.rs +0 -6
index 809c5573c6e7..634b453a21b6 100644
@@ -14,12 +14,6 @@ pub const XDF_WHITESPACE_FLAGS: u64 = XDF_IGNORE_WHITESPACE |
     XDF_IGNORE_CR_AT_EOL;
 
 
-#[no_mangle]
-unsafe extern "C" fn xxh3_64(ptr: *const u8, size: usize) -> u64 {
-    let slice = std::slice::from_raw_parts(ptr, size);
-    xxhash_rust::xxh3::xxh3_64(slice)
-}
-
 #[no_mangle]
 unsafe extern "C" fn xdl_line_hash(ptr: *const u8, size: usize, flags: u64) -> u64 {
     let line = std::slice::from_raw_parts(ptr, size);
xdiff-interface.c +2 -2
index 1edcd319e6ef..71ddccf2cc15 100644
@@ -299,13 +299,13 @@ void xdiff_clear_find_func(xdemitconf_t *xecfg)
 
 unsigned long xdiff_hash_string(const char *s, size_t len, long flags)
 {
-	return xdl_hash_record(&s, s + len, flags);
+	return xdl_line_hash((u8 const*) s, len, flags);
 }
 
 int xdiff_compare_lines(const char *l1, long s1,
 			const char *l2, long s2, long flags)
 {
-	return xdl_recmatch(l1, s1, l2, s2, flags);
+	return xdl_line_equal((u8 const*) l1, s1, (u8 const*) l2, s2, flags);
 }
 
 int parse_conflict_style_name(const char *value)
xdiff/xmerge.c +4 -4
index 6fa6ea61a208..2f64651a839b 100644
@@ -101,8 +101,8 @@ static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2,
 	xrecord_t **rec2 = xe2->xdf2.recs + i2;
 
 	for (i = 0; i < line_count; i++) {
-		int result = xdl_recmatch((const char*) rec1[i]->ptr, rec1[i]->size,
-			(const char*) rec2[i]->ptr, rec2[i]->size, flags);
+		bool result = xdl_line_equal(rec1[i]->ptr, rec1[i]->size,
+			rec2[i]->ptr, rec2[i]->size, flags);
 		if (!result)
 			return -1;
 	}
@@ -324,8 +324,8 @@ static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1,
 
 static int recmatch(xrecord_t *rec1, xrecord_t *rec2, unsigned long flags)
 {
-	return xdl_recmatch((char const*) rec1->ptr, rec1->size,
-			    (char const*) rec2->ptr, rec2->size, flags);
+	return xdl_line_equal(rec1->ptr, rec1->size,
+			    rec2->ptr, rec2->size, flags);
 }
 
 /*
xdiff/xprepare.c +7 -22
index c0463bacd94b..b9f12184b1bb 100644
@@ -33,8 +33,8 @@
 typedef struct s_xdlclass {
 	struct s_xdlclass *next;
 	u64 ha;
-	char const *line;
-	long size;
+	u8 const *line;
+	usize size;
 	long idx;
 	long len1, len2;
 } xdlclass_t;
@@ -93,15 +93,15 @@ static void xdl_free_classifier(xdlclassifier_t *cf) {
 
 static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t *rec) {
 	long hi;
-	char const *line;
+	u8 const *line;
 	xdlclass_t *rcrec;
 
-	line = (char const*) rec->ptr;
+	line = rec->ptr;
 	hi = (long) XDL_HASHLONG(rec->ha, cf->hbits);
 	for (rcrec = cf->rchash[hi]; rcrec; rcrec = rcrec->next)
 		if (rcrec->ha == rec->ha &&
-				xdl_recmatch(rcrec->line, rcrec->size,
-					(const char*) rec->ptr, rec->size, cf->flags))
+				xdl_line_equal(rcrec->line, rcrec->size,
+					rec->ptr, rec->size, cf->flags))
 			break;
 
 	if (!rcrec) {
@@ -160,9 +160,6 @@ static void xdl_parse_lines(mmfile_t *mf, long narec, xdfile_t *xdf) {
 }
 
 
-extern u64 xxh3_64(u8 const* ptr, usize size);
-
-
 static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
 			   xdlclassifier_t *cf, xdfile_t *xdf) {
 	unsigned long *ha;
@@ -178,21 +175,9 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
 
 	xdl_parse_lines(mf, narec, xdf);
 
-	if ((xpp->flags & XDF_WHITESPACE_FLAGS) == 0) {
-		for (usize i = 0; i < (usize) xdf->nrec; i++) {
-			xrecord_t *rec = xdf->recs[i];
-			rec->ha = xxh3_64(rec->ptr, rec->size);
-		}
-	} else {
-		for (usize i = 0; i < (usize) xdf->nrec; i++) {
-			xrecord_t *rec = xdf->recs[i];
-			char const* dump = (char const*) rec->ptr;
-			rec->ha = xdl_hash_record(&dump, (char const*) (rec->ptr + rec->size), xpp->flags);
-		}
-	}
-
 	for (usize i = 0; i < (usize) xdf->nrec; i++) {
 		xrecord_t *rec = xdf->recs[i];
+		rec->ha = xdl_line_hash(rec->ptr, rec->size, xpp->flags);
 		xdl_classify_record(pass, cf, rec);
 	}
 
xdiff/xutils.c +0 -158

164 lines not on this page · raw message

xdiff/xutils.h +2 -2
index fd0bba94e8b4..8f524b72c491 100644
@@ -33,8 +33,8 @@ void xdl_cha_free(chastore_t *cha);
 void *xdl_cha_alloc(chastore_t *cha);
 long xdl_guess_lines(mmfile_t *mf, long sample);
 int xdl_blankline(const char *line, long size, long flags);
-int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags);
-unsigned long xdl_hash_record(char const **data, char const *top, long flags);
+u64 xdl_line_hash(u8 const* ptr, usize size, u64 flags);
+bool xdl_line_equal(u8 const* lhs, usize lhs_len, u8 const* rhs, usize rhs_len, u64 flags);
 unsigned int xdl_hashbits(unsigned int size);
 int xdl_num_out(char *out, long val);
 int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2,
Signature · 1 line
gitgitgadget
#117 lore raw
In reply to Ezekiel Newren via GitGitGadget
Quoted 6 lines · Ezekiel Newren via GitGitGadget

Changes in this second round of this RFC:

* Now builds and passes tests on all platforms (example run:

   https://github.com/ezekielnewren/git/actions/runs/16974821401). Special
   thanks to Johannes Schindelin for patches to things for Windows and
   linux32.

Hmm, builds on *all* platforms may be a bit optimistic (it doesn't on cygwin, for instance), so I'm guessing you mean all platforms which have CI defined. Perhaps you could mention the platforms which you have tested on. :)

ATB,
Ramsay Jones

#89 lore raw

Re: [PATCH v2 01/17] doc: add a policy for using Rust

In reply to brian m. carlson via GitGitGadget
Quoted 53 lines · brian m. carlson via GitGitGadget

From: "brian m. carlson" <[email protected]>

Git has historically been written primarily in C, with some shell and Perl. However, C is not memory safe, which makes it more likely that security vulnerabilities or other bugs will be introduced, and it is also more verbose and less ergonomic than other, more modern languages.

One of the most common modern compiled languages which is easily interoperable with C is Rust. It is popular (the most admired language on the 2024 Stack Overflow Developer Survey), efficient, portable, and robust.

Introduce a document laying out the incremental introduction of Rust to Git and provide a detailed rationale for doing so, including the points above. Propose a design for this approach that addresses the needs of downstreams and distributors, as well as contributors.

Since we don't want to carry both a C and Rust version of code and want to be able to add new features only in Rust, mention that Rust is a required part of our platform support policy.

It should be noted that a recent discussion at the Berlin Git Merge Contributor Summit found widespread support for the addition of Rust to Git. While of course not all contributors were represented, the proposal appeared to have the support of a majority of active contributors.

---

Documentation/Makefile                        |   1 +
Documentation/technical/platform-support.adoc |   2 +
Documentation/technical/rust-support.adoc     | 119 ++++++++++++++++++
3 files changed, 122 insertions(+)

create mode 100644 Documentation/technical/rust-support.adoc

Documentation/Makefile +0 -0
index b109d25e9c80..066b761c01b9 100644
@@ -127,6 +127,7 @@ TECH_DOCS += technical/parallel-checkout

TECH_DOCS += technical/partial-clone
TECH_DOCS += technical/platform-support
TECH_DOCS += technical/racy-git
+TECH_DOCS += technical/rust-support
TECH_DOCS += technical/reftable
TECH_DOCS += technical/scalar
TECH_DOCS += technical/send-pack-pipeline

Documentation/technical/platform-support.adoc +0 -0
index 0a2fb28d6277..42b04b186105 100644

Documentation/technical/platform-support.adoc meet the following minimum requirements: · line 36

Quoted 4 lines
Documentation/technical/platform-support.adoc +1 -0
@@ -33,6 +33,8 @@ meet the following minimum requirements:
 
 * Has active security support (taking security releases of dependencies, etc)
 
+* Supports Rust and the toolchain version specified in link:rust-support.txt[].

s/rust-support.txt/rust-support.adoc/

Quoted 131 lines
Documentation/technical/platform-support.adoc +1 -0
+

These requirements are a starting point, and not sufficient on their own for the Git community to be enthusiastic about supporting your platform. Maintainers of platforms which do meet these requirements can follow the steps below to make it

Documentation/technical/rust-support.adoc new file +119 -0
new file mode 100644
index 000000000000..a63327ebc575
@@ -0,0 +1,119 @@
+Usage of Rust in Git
+====================
+
+Objective
+---------
+Introduce Rust into Git incrementally to improve security and maintainability.
+
+Background
+----------
+Git has historically been written primarily in C, with some portions in shell,
+Perl, or other languages.  At the time it was originally written, this was
+important for portability and was a logical choice for software development.
+
+:0: link:https://security.googleblog.com/2024/09/eliminating-memory-safety-vulnerabilities-Android.html
+:1: link:https://www.cisa.gov/resources-tools/resources/product-security-bad-practices
+
+However, as time has progressed, we've seen an increased concern with memory
+safety vulnerabilities and the development of newer languages, such as Rust,
+that substantially limit or eliminate this class of vulnerabilities.
+Development in a variety of projects has found that memory safety
+vulnerabilities constitute about 70% of vulnerabilities of software in
+languages that are not memory safe.  For instance, {0}[one survey of Android]
+found that memory safety vulnerabilities decreased from 76% to 24% over six
+years due to an increase in memory safe code.  Similarly, the U.S. government
+is {1}[proposing to classify development in memory unsafe languages as a
+Product Security Bad Practice"].
+
+These risks are even more substantial when we consider the fact that Git is a
+network-facing service.  Many organizations run Git servers internally or use a
+cloud-based forge, and the risk of accidental exposure or compromise of user
+data is substantial.  It's important to ensure that Git, whether it's used
+locally or remotely, is robustly secure.
+
+In addition, C is a difficult language to write well and concisely.  While it
+is of course possible to do anything with C, it lacks built-in support for
+niceties found in modern languages, such as hash tables, generics, typed
+errors, and automatic destruction, and most modern language offer shorter, more
+ergonomic syntax for expressing code.  This is valuable functionality that can
+allow Git to be developed more rapidly, more easily, by more developers of a
+variety of levels, and with more confidence in the correctness of the code.
+
+For these reasons, adding Rust to Git is a sensible and prudent move that will
+allow us to improve the quality of the code and potentially attract new developers.
+
+Goals
+-----
+1. Git continues to build, run, and pass tests on a wide variety of operating
+   systems and architectures.
+2. Transition from C to Rust is incremental; that is, code can be ported as it
+   is convenient and Git does not need to transition all at once.
+3. Git continues to support older operating systems in conformance with the
+   platform support policy.
+
+Non-Goals
+---------
+1. Support for every possible operating system and architecture.  Git already
+   has a platform support policy which defines what is supported and we already
+   exclude some operating systems for various reasons (e.g., lacking enough POSIX
+   tools to pass the test suite).
+2. Implementing C-only versions of Rust code or compiling a C-only Git.  This
+   would be difficult to maintain and would not offer the ergonomic benefits we
+   desire.
+
+Design
+------
+Git will adopt Rust incrementally.  This transition will start with the
+creation of a static library that can be linked into the existing Git binaries.
+At some point, we may wish to expose a dynamic library and compile the Git
+binaries themselves using Rust.  Using an incremental approach allows us to
+determine as we go along how to structure our code in the best way for the
+project and avoids the need to make hard, potentially disruptive, transitions
+caused by porting a binary wholesale from one language to another that might
+introduce bugs.
+
+We will use the `bindgen` and `cbindgen` crates for handling C-compatible
+bindings and the `rustix` crate for POSIX-compatible interfaces.  The `libc`
+crate, which is used by `rustix`, does not expose safe interfaces and does not
+handle differences between platforms, such as differing 64-bit `stat` call
+names, and so is less desirable as a target than `rustix`.  We may still choose
+to use it in some cases if `rustix` does not offer suitable interfaces.
+
+Rust upstream releases every six weeks and only supports the latest stable
+release.  While it is nice that upstream is active, we would like our software
+releases to have a lifespan exceeding six weeks.  To allow compiling our code
+on a variety of systems, we will support the version of Rust in Debian stable,
+plus, for a year after a new Debian stable is released, the version in Debian
+oldstable.
+
+This provides an approximately three-year lifespan of support for a Rust
+release and allows us to support a variety of operating systems and
+architectures, including those for which Rust upstream does not build binaries.
+Debian stable is the benchmark distribution used by many Rust projects when
+determining supported Rust versions, and it is an extremely portable and
+popular free software operating system that is available to the public at no
+charge, which makes it a sensible choice for us as well.
+
+We may change this policy if the Rust project issues long-term support releases
+or the Rust community and distributors agree on releases to target as if they
+were long-term support releases.
+
+This version support policy necessitates that we be very careful about the
+dependencies we include, since many Rust projects support only the latest
+stable version.  However, we typically have been careful about dependencies in
+the first place, so this should not be a major departure from existing policy,
+although it may be a change for some existing Rust developers.
+
+We will avoid including the `Cargo.lock` file in the repository and instead
+specify minimum dependency versions in the `Cargo.toml` file.  We want to allow
+people to use newer versions of dependencies if necessary to support newer
+platforms without needing to force upgrades of dependencies on all users, and
+it provides additional flexibility for distribution maintainers.
+
+We do not plan to support beta or nightly versions of the Rust compiler.  These
+versions may change rapidly and especially parts of the toolchain such as
+Clippy, the lint tool, can have false positives or add additional warnings with
+too great of a frequency to be supportable by the project.  However, we do plan
+to support alternate compilers, such as the rust_codegen_gcc backend and gccrs
+when they are stable and support our desired release versions.  This will
+provide greater support for more operating systems and architectures.
Signature · 1 line
gitgitgadget

best regards

Matthias

#101 lore raw

Re: [PATCH v2 09/17] Do support Windows again after requiring Rust

In reply to Johannes Schindelin via GitGitGadget
Quoted 45 lines · Johannes Schindelin via GitGitGadget

From: Johannes Schindelin <[email protected]>

By default, Rust wants to build MS Visual C-compatible libraries on Windows, because that is _the_ native C compiler.

Git is historically lacking in its MSVC support, and the official Git for Windows versions are built using GCC instead. As a consequence, a (subset of a) GCC toolchain is installed as part of the windows-build job of every CI build.

Naturally, this requires adjustments in how Rust is called, most importantly it requires installing support for a GCC-compatible build target.

Let's make the necessary adjustment both in the CI-specific code that installs Rust as well as in the Windows-specific configuration in config.mak.uname.

[en: Moved lib userenv handling to a later patch]

---
ci/install-rust.sh | 3 +++
config.mak.uname | 7 +++++++

2 files changed, 10 insertions(+)
ci/install-rust.sh +0 -0
index 141ceddb17cf..c22baa629ceb 100644
@@ -28,6 +28,9 @@ if [ "$BITNESS" = "32" ]; then
  $CARGO_HOME/bin/rustup default --force-non-host $RUST_VERSION || exit $?

else
$CARGO_HOME/bin/rustup default $RUST_VERSION || exit $?

ci/install-rust.sh +3 -0
+  if [ "$CI_OS_NAME" = "windows" ]; then
+    $CARGO_HOME/bin/rustup target add x86_64-pc-windows-gnu || exit $?
+  fi

fi

. $CARGO_HOME/env

config.mak.uname +0 -0
index 3e26bb074a4b..a22703284b56 100644

config.mak.uname ifeq ($(uname_S),MINGW) · line 736

Quoted 10 lines
config.mak.uname +2 -0
@@ -727,19 +727,26 @@ ifeq ($(uname_S),MINGW)
 		prefix = /mingw32
 		HOST_CPU = i686
 		BASIC_LDFLAGS += -Wl,--pic-executable,-e,_mainCRTStartup
+		CARGO_BUILD_TARGET = i686-pc-windows-gnu
        endif
        ifeq (MINGW64,$(MSYSTEM))
 		prefix = /mingw64
 		HOST_CPU = x86_64
 		BASIC_LDFLAGS += -Wl,--pic-executable,-e,mainCRTStartup
+		CARGO_BUILD_TARGET = x86_64-pc-windows-gnu

I've said it when Johannes originally sent this patch[1], but it bears repeating: The *-pc-windows-gnu targets will pass CI, but would mean raising the required Windows version from 8.1 to 10. We'd want to use the *-win7-windows-gnu targets[2] to keep Windows 8.1 supported.

[1] https://lore.kernel.org/git/[email protected]/T/#ma10be2ed0a0e776b0af2fdd0de63d51ba51609e4
[2] https://doc.rust-lang.org/nightly/rustc/platform-support/win7-windows-gnu.html

config.mak.uname ifeq ($(uname_S),MINGW) · line 741

Quoted 5 lines
config.mak.uname +1 -0
        else ifeq (CLANGARM64,$(MSYSTEM))
 		prefix = /clangarm64
 		HOST_CPU = aarch64
 		BASIC_LDFLAGS += -Wl,--pic-executable,-e,mainCRTStartup
+		CARGO_BUILD_TARGET = aarch64-pc-windows-gnu

As I've also mentioned before [1], this target doesn't seem to exist. The correct target seems to be aarch64-pc-windows-gnullvm. [3]

[3] https://doc.rust-lang.org/rustc/platform-support/windows-gnullvm.html

Quoted 13 lines
config.mak.uname +4 -0
        else
 		COMPAT_CFLAGS += -D_USE_32BIT_TIME_T
 		BASIC_LDFLAGS += -Wl,--large-address-aware
        endif
+
+	export CARGO_BUILD_TARGET
+	RUST_TARGET_DIR = rust/target/$(CARGO_BUILD_TARGET)/$(RUST_BUILD_MODE)
+
 	CC = gcc
 	COMPAT_CFLAGS += -D__USE_MINGW_ANSI_STDIO=0 -DDETECT_MSYS_TTY \
 		-fstack-protector-strong
Signature · 1 line
gitgitgadget

Best regards

Matthias

#90 lore raw

Re: [PATCH v2 01/17] doc: add a policy for using Rust

In reply to Matthias Aßhauer
Quoted 11 lines · Matthias Aßhauer

Documentation/technical/platform-support.adoc meet the following minimum requirements: · line 36

Quoted 9 lines
Documentation/technical/platform-support.adoc +1 -0
index 0a2fb28d6277..42b04b186105 100644
@@ -33,6 +33,8 @@ meet the following minimum requirements:
 
 * Has active security support (taking security releases of dependencies, etc)
 
+* Supports Rust and the toolchain version specified in link:rust-support.txt[].

s/rust-support.txt/rust-support.adoc/

Your review is very much appreciated, but ...

Quoted 4 lines
Quoted 4 lines
Documentation/technical/platform-support.adoc +1 -0
+

These requirements are a starting point, and not sufficient on their own for the Git community to be enthusiastic about supporting your platform. Maintainers of platforms which do meet these requirements can follow the steps below to make it

...could you trim your quotes to relevant parts that is needed to help readers understand the point? It is a bit brutal to force readers wade through 200 lines of text only to find this "you got .txt suffix for a document with .adoc suffix" comment.

Thanks.

#102 lore raw

Re: [PATCH v2 09/17] Do support Windows again after requiring Rust

In reply to Matthias Aßhauer
Quoted 10 lines · Matthias Aßhauer

config.mak.uname ifeq ($(uname_S),MINGW) · line 736

Quoted 5 lines
        ifeq (MINGW64,$(MSYSTEM))
		prefix = /mingw64
		HOST_CPU = x86_64
		BASIC_LDFLAGS += -Wl,--pic-executable,-e,mainCRTStartup
config.mak.uname +1 -0
+		CARGO_BUILD_TARGET = x86_64-pc-windows-gnu

I've said it when Johannes originally sent this patch[1], but it bears
repeating: The *-pc-windows-gnu targets will pass CI, but would mean raising the required Windows version from 8.1 to 10. We'd want to use the *-win7-windows-gnu targets[2] to keep Windows 8.1 supported.

It seems that Dscho did not respond on the list to your initial objection in the discussion you cited.

I do not think we spell out which releases of various platforms are still supported by us (we do list requirements for platforms in the Platform Support Policy document, though), but in general we should not be attempting to give extended support to systems that the vendor no longer supports. As Windows 8.1 is no longer supported by Microsoft since Jan 2023, and Windows 10 will go out of support in a few month after Oct 2025, if I am reading the table correctly, so as long as we document our intention of dropping a commercial system that is no longer supported by its vender clearly, I do not mind the above that discards 8.1 [*].

But I may be biased, as I do not live in the Microsoft ecosystem.

* https://learn.microsoft.com/en-us/lifecycle/products/windows-81
* https://learn.microsoft.com/en-us/lifecycle/products/windows-10-home-and-pro
* https://learn.microsoft.com/en-us/lifecycle/products/windows-10-enterprise-and-education

#103 lore raw

Re: [PATCH v2 09/17] Do support Windows again after requiring Rust

Hi,

Quoted 13 lines · Junio C Hamano
Quoted 10 lines · Matthias Aßhauer

config.mak.uname ifeq ($(uname_S),MINGW) · line 736

Quoted 5 lines
        ifeq (MINGW64,$(MSYSTEM))
		prefix = /mingw64
		HOST_CPU = x86_64
		BASIC_LDFLAGS += -Wl,--pic-executable,-e,mainCRTStartup
config.mak.uname +1 -0
+		CARGO_BUILD_TARGET = x86_64-pc-windows-gnu

I've said it when Johannes originally sent this patch[1], but it bears
repeating: The *-pc-windows-gnu targets will pass CI, but would mean raising the required Windows version from 8.1 to 10. We'd want to use the *-win7-windows-gnu targets[2] to keep Windows 8.1 supported.

Quoted 2 lines

It seems that Dscho did not respond on the list to your initial objection in the discussion you cited.

I would have hoped that it is clear by now that Matthias is as much to be trusted with Git for Windows concerns as I am (just like the other active Git for Windows contributors, if you can get them onto this here mailing list). Just in case that it really needs my explicit ACK: What he said about Windows 8.1 support in Git for Windows is accurate.

Quoted 10 lines

I do not think we spell out which releases of various platforms are still supported by us (we do list requirements for platforms in the Platform Support Policy document, though), but in general we should not be attempting to give extended support to systems that the vendor no longer supports. As Windows 8.1 is no longer supported by Microsoft since Jan 2023, and Windows 10 will go out of support in a few month after Oct 2025, if I am reading the table correctly, so as long as we document our intention of dropping a commercial system that is no longer supported by its vender clearly, I do not mind the above that discards 8.1 [*].

While there is obviously some connection between the official EOL of Windows versions (see https://endoflife.date/windows) and which versions Git for Windows supports, the balance we try to strike (and by "we" I don't apply the pluralis majestatis, it is very much a consensus between all active Git for Windows contributors, including Matthias and myself) is to support older Windows versions as much as can be done with a reasonable amount of effort (where "reasonable" is obviously as subjective as the definition of "taste").

The consensus of what Windows versions can be reasonably supported is documented at https://gitforwindows.org/requirements.html#windows-version.
Currently that is — you may have guessed it — as Matthias has stated: Git for Windows will support Windows 8.1 for the time being. The hope is that we will be able to notify users when support for that version will be phased out well in advance, much as we did for Windows 7 and 8, where deprecation notices were included in the release notes of several Git for Windows versions prior to v2.46.2, which was the last Git for Windows version to support Windows 7 and 8.

Quoted 1 line

But I may be biased, as I do not live in the Microsoft ecosystem.

You do point that out frequently, so I believe that you made the point.

Personally, I would like to see a more open-minded approach here.

Ciao,
Johannes

#105 lore raw

Re: [PATCH v2 09/17] Do support Windows again after requiring Rust

In reply to Junio C Hamano
Quoted 9 lines · Junio C Hamano

I do not think we spell out which releases of various platforms are still supported by us (we do list requirements for platforms in the Platform Support Policy document, though), but in general we should not be attempting to give extended support to systems that the vendor no longer supports.
... so as
long as we document our intention of dropping a commercial system that is no longer supported by its vender clearly, I do not mind the above that discards 8.1 [*].

Apologies to authors of the PSP document. We do have this as part of "minimum requirement":

* Has active security support (taking security releases of dependencies, etc)

So, being implicit about dropping 8.1, while it may be less than nice as we could, is perhaps fine.

If we wanted to support a tad older releases that are still used widely, that is fine as well. I didn't check what additional documents and policies GfW (Git for Windows) project says about this issue, so perhaps it is all documented there, in which case our PSP document is fine as-is, too. In other words, if GfW project takes responsibility of supporting ports for an older release that is out of support, what our PSP document says does not matter.

#104 lore raw

Re: [PATCH v2 09/17] Do support Windows again after requiring Rust

In reply to Johannes Schindelin
Quoted 5 lines · Johannes Schindelin
Quoted 1 line

But I may be biased, as I do not live in the Microsoft ecosystem.

You do point that out frequently, so I believe that you made the point.

Personally, I would like to see a more open-minded approach here.

I gave it as an explanation for the reason why my conclusion may be different from what those in the Microsoft ecosystem decided to keep supporting, and I have no reason to object what they want to do.

It has nothing to do with open-mindedness and such a comment was uncalled for.

#91 lore raw

Re: [PATCH v2 01/17] doc: add a policy for using Rust

In reply to Junio C Hamano
Quoted 7 lines · Junio C Hamano

...could you trim your quotes to relevant parts that is needed to help readers understand the point? It is a bit brutal to force readers wade through 200 lines of text only to find this "you got .txt suffix for a document with .adoc suffix" comment.

Thanks.

Sorry, I'll try to be more mindful of trimming my mails.

Best regards

Matthias

#106 lore raw

Re: [PATCH v2 09/17] Do support Windows again after requiring Rust

In reply to Junio C Hamano
Quoted 13 lines · Junio C Hamano
Quoted 10 lines · Matthias Aßhauer

config.mak.uname ifeq ($(uname_S),MINGW) · line 736

Quoted 5 lines
        ifeq (MINGW64,$(MSYSTEM))
		prefix = /mingw64
		HOST_CPU = x86_64
		BASIC_LDFLAGS += -Wl,--pic-executable,-e,mainCRTStartup
config.mak.uname +1 -0
+		CARGO_BUILD_TARGET = x86_64-pc-windows-gnu

I've said it when Johannes originally sent this patch[1], but it bears
repeating: The *-pc-windows-gnu targets will pass CI, but would mean raising the required Windows version from 8.1 to 10. We'd want to use the *-win7-windows-gnu targets[2] to keep Windows 8.1 supported.

Quoted 2 lines

It seems that Dscho did not respond on the list to your initial objection in the discussion you cited.

He didn't, but from various interactions surrounding Git for Windows, I do think he's currently in favour of keeping Windows 8.1 supported in Git for Windows.

Quoted 3 lines

I do not think we spell out which releases of various platforms are still supported by us (we do list requirements for platforms in the Platform Support Policy document, though),

We don't do that in git.git, no. Git for Windows very explicitly spells out which versions of Windows are supported (though usually we just mention the Desktop versions and imply the corresponding Windows Server versions). Since 2.47.0 that is Windows 8.1 and newer Desktop releases [1] (Windows 11 on ARM64). We even tend to announce in advance when we intend to drop support for a Windows version.

[1] https://gitforwindows.org/faq.html#which-versions-of-windows-are-supported

Quoted 4 lines

but in general we should not be attempting to give extended support to systems that the vendor no longer supports. As Windows 8.1 is no longer supported by Microsoft since Jan 2023, and Windows 10 will go out of support in a few month after Oct 2025, if I am reading the table correctly,

Git for Windows has historically supported Windows Versions beyond this date.

* XP was supported for 2 years beyond the official extended EOL. [1][2]
* Vista was supported for 5 years beyond the official extended EOL [1][3]
* 7 was supported for 4 years beyond the official extended EOL [1][4]
* 8 was supported for 8 years beyond the official extended EOL [1][5]

git.git has historically roughly followed Git for Windows in this.

You're reading the tables correctly, but there are so called LTSC releases of Windows 10 with support until 2026/2029. [6]

[2] https://learn.microsoft.com/en-us/lifecycle/products/windows-xp
[3] https://learn.microsoft.com/en-us/lifecycle/products/windows-vista
[4] https://learn.microsoft.com/en-us/lifecycle/products/windows-7
[5] https://learn.microsoft.com/en-us/lifecycle/products/windows-8
[6] https://learn.microsoft.com/en-us/windows/whats-new/ltsc/whats-new-windows-10-2021#lifecycle

Quoted 3 lines

so as long as we document our intention of dropping a commercial system that is no longer supported by its vender clearly, I do not mind the above that discards 8.1 [*].

I'm not completely opposed, but I do think it should be a concious decision and not an unintended side effect of some change that our CI didn't catch.

Best regards

Matthias

#107 lore raw

Re: [PATCH v2 09/17] Do support Windows again after requiring Rust

Quoted 6 lines · Matthias Aßhauer
Quoted 2 lines

It seems that Dscho did not respond on the list to your initial objection in the discussion you cited.

He didn't, but from various interactions surrounding Git for Windows, I do think he's currently in favour of keeping Windows 8.1 supported in Git for Windows.

OK, as long as folks with stakes in Git for Windows are in agreement, I have no problem (except that in principle we should avoid doing disservice to the end user population by doing things that encourage their prolonged use of out-of-security-support platforms).

Quoted 4 lines

We don't do that in git.git, no. Git for Windows very explicitly spells out which versions of Windows are supported (though usually we just mention the Desktop versions and imply the corresponding Windows Server versions).

Yup, thanks for clarifying it to me. Could you do the same for future readers of the updated version of the commit 09/17 by telling the author about that in your review comment, so that the log message can talk about the reasons why a specific CARGO_BUILD_TARGET was chosen (e.g. "as described in Git for Windows documentation at $URL, we support Windows versions X or newer, so we use this cargo build target to ensure we still work with that version").

Quoted 3 lines

I'm not completely opposed, but I do think it should be a concious decision and not an unintended side effect of some change that our CI didn't catch.

Oh, absolutely.

Thanks for clarification.

#120 lore raw
In reply to Ezekiel Newren via GitGitGadget
Quoted 2 lines · Ezekiel Newren via GitGitGadget

* Code style: Should we adopt a Rust code style of some sort? Perhaps have the code always be formatted by rustfmt in its default configuration?

Sounds sensible. I'll let folks with more Rust inclination to figure out what _the_ style should be, but having _a_ style we all stick to is good.

Quoted 3 lines

* Rust version: We are not using the same Rust version on all platforms in CI; 32-bit builds and Windows builds require a newer Rust version to successfully build.

As long as we do not have to bend backwards on the code with "if using version X or older, use this alternative codepath" all over the place, "pick a version that works on each platform" that results in "due to the quality of ports, some platform's older port is unusable and newer version is required" is not too bad, especially for a system that is still rapidly getting improved and a bit on the unstable side, I think.

Quoted 2 lines

* Performance with whitepsace flags: I originally intended to leave out the whitespace handling because I knew it was slower,...

If the Rust guinea pig were different from how each line is hashed in xdiff, which is targetted by am/xdiff-hash-tweak topic, then we can leave out the whitespace-ignoring hashing from this topic altogether.

Quite honestly, I do not like throwing away the other optimization efforts that can be reviewed and integrated trivially, but it is practically impossible to do so while still have a "let's start playing with Rust" topic that targets exactly the same area. Yes, this topic licked the same corner of the cake first, but still, I was hoping that the second iteration of this series would use a different code paths as a Rust guinea pig.

After all, the primary objective of our first Rust topic is to set the framework right (like the platform and version support policies, how foreign interfaces like type systems get impedance-matched, what the impact to our build infrature looks like, etc.). It would be a huge plus if it can at the same time demonstrate how much safer code we can write with less effort if we switched writing some (and gradually larger, posibly) parts in the language.

The result this cover letter has in its title, 'accelerate xdiff', is not primarily due to use of Rust, is it? As the other topic demonstrates, it is to use an implementation of a faster hash function (we can consider it to be an impressive technology demonstration that a rust reimplementation of original C code can be done in a very performant way). And nobody is expecting that we would be using Rust for speed anyway, no?

#121 lore raw
Quoted 11 lines · Junio C Hamano
Quoted 2 lines · "Ezekiel Newren via GitGitGadget

* Code style: Should we adopt a Rust code style of some sort? Perhaps have the code always be formatted by rustfmt in its default configuration?

Sounds sensible. I'll let folks with more Rust inclination to figure out what _the_ style should be, but having _a_ style we all stick to is good.

While there can be room for configuring the formatter if we have particularly idiosyncratic needs, I’d second going with cargo fmt / rustfmt in default configurations to start (the former is a shortcut for the latter over a whole crate AIUI).

#122 lore raw
In reply to Junio C Hamano
Quoted 30 lines · Junio C Hamano
Quoted 2 lines · Ezekiel Newren via GitGitGadget

* Code style: Should we adopt a Rust code style of some sort? Perhaps have the code always be formatted by rustfmt in its default configuration?

Sounds sensible. I'll let folks with more Rust inclination to figure out what _the_ style should be, but having _a_ style we all stick to is good.

Quoted 3 lines

* Rust version: We are not using the same Rust version on all platforms in CI; 32-bit builds and Windows builds require a newer Rust version to successfully build.

As long as we do not have to bend backwards on the code with "if using version X or older, use this alternative codepath" all over the place, "pick a version that works on each platform" that results in "due to the quality of ports, some platform's older port is unusable and newer version is required" is not too bad, especially for a system that is still rapidly getting improved and a bit on the unstable side, I think.

Quoted 2 lines

* Performance with whitepsace flags: I originally intended to leave out the whitespace handling because I knew it was slower,...

If the Rust guinea pig were different from how each line is hashed in xdiff, which is targetted by am/xdiff-hash-tweak topic, then we can leave out the whitespace-ignoring hashing from this topic altogether.

Quoted 7 lines

Quite honestly, I do not like throwing away the other optimization efforts that can be reviewed and integrated trivially, but it is practically impossible to do so while still have a "let's start playing with Rust" topic that targets exactly the same area. Yes, this topic licked the same corner of the cake first, but still, I was hoping that the second iteration of this series would use a different code paths as a Rust guinea pig.

I agree it would be nice to merge those down first. One possibility here would be having Ezekiel rebase his work on top of am/xdiff-hash-tweak...

Quoted 8 lines

After all, the primary objective of our first Rust topic is to set the framework right (like the platform and version support policies, how foreign interfaces like type systems get impedance-matched, what the impact to our build infrature looks like, etc.). It would be a huge plus if it can at the same time demonstrate how much safer code we can write with less effort if we switched writing some (and gradually larger, posibly) parts in the language.

Quoted 7 lines

The result this cover letter has in its title, 'accelerate xdiff', is not primarily due to use of Rust, is it? As the other topic demonstrates, it is to use an implementation of a faster hash function (we can consider it to be an impressive technology demonstration that a rust reimplementation of original C code can be done in a very performant way). And nobody is expecting that we would be using Rust for speed anyway, no?

You are correct that it is not due to Rust. My original objective that I tried to trick/coax/nerd-snipe/whatever Ezekiel into looking into was cleaning up xdiff, to allow various features in git replay and rebasing merges. Ezekiel was interested in Rust and in a challenge. xdiff is quite a knot to untangle, and Ezekiel's been at work on it for quite some time. But, he happened to notice this speedup, and found a way to turn it into a short series without all his other patches.

We could perhaps shift focus, but I'm curious if you're wanting the xdiff work to be thrown away or shelved in favor of some completely different area of the code, or if perhaps some other aspects of the xdiff code would still be amenable.

One big challenge in finding another area, whether in xdiff or elsewhere, is that Ezekiel really wants to showcase how nice Rust's unittesting is, but that only works if we start at a low-level and build up. If we make Rust code call C code, that'd either not be readily unit-testable, or would require us stubbing out the entire implementation behind that C interface or doing something more complex.

What if Ezekiel rebased his series on am/xdiff-hash-tweak, and then instead of further modifying the hashing in the first series, he:
- introduced brian's patch with the platform support
- setup the CI builds to test building with Rust (including Johannes' patches)
- started working on transitioning xdfile_t data structure to be FFI friendly

One issue here is that it probably wouldn't be too long before we'd want to rip out the xdlclassifier struct (mostly a glorified hashtable), which is kind of tied up in a knot with the hashing and line equality, so it would probably only be a few more series down the road before we'd want to start tweaking the code in am/xdiff-hash-tweak to make use of the new data structures. Would that be agreeable?

#118 lore raw
In reply to Ramsay Jones
Quoted 9 lines · Ramsay Jones
Quoted 6 lines · Ezekiel Newren via GitGitGadget

Changes in this second round of this RFC:

* Now builds and passes tests on all platforms (example run:

   https://github.com/ezekielnewren/git/actions/runs/16974821401). Special
   thanks to Johannes Schindelin for patches to things for Windows and
   linux32.
Quoted 4 lines

Hmm, builds on *all* platforms may be a bit optimistic (it doesn't on cygwin, for instance), so I'm guessing you mean all platforms which have CI defined. Perhaps you could mention the platforms which you have tested on. :)

Ezekiel says this email didn't show up in his inbox (no idea why), but yes what was meant was all platforms where gitgitgadget CI runs. If you follow the github.com link in the text that you quoted, you can see all those platforms (various windows flavors, various osx builds, musl, sparse, static analysis, etc.).

#92 lore raw

Re: [PATCH v2 01/17] doc: add a policy for using Rust

In reply to Matthias Aßhauer
Quoted 4 lines · Matthias Aßhauer
Quoted 2 lines

+* Supports Rust and the toolchain version specified in link:rust-support.txt[].

s/rust-support.txt/rust-support.adoc/

Thanks for spotting that, I'll fix it up.

#108 lore raw

Re: [PATCH v2 09/17] Do support Windows again after requiring Rust

In reply to Matthias Aßhauer
Quoted 46 lines · Matthias Aßhauer
Quoted 19 lines
ci/install-rust.sh +0 -0
index 141ceddb17cf..c22baa629ceb 100644
@@ -28,6 +28,9 @@ if [ "$BITNESS" = "32" ]; then
  $CARGO_HOME/bin/rustup default --force-non-host $RUST_VERSION || exit $?

else
$CARGO_HOME/bin/rustup default $RUST_VERSION || exit $?

ci/install-rust.sh +3 -0
+  if [ "$CI_OS_NAME" = "windows" ]; then
+    $CARGO_HOME/bin/rustup target add x86_64-pc-windows-gnu || exit $?
+  fi

fi

. $CARGO_HOME/env

config.mak.uname +0 -0
index 3e26bb074a4b..a22703284b56 100644

config.mak.uname ifeq ($(uname_S),MINGW) · line 736

Quoted 10 lines
config.mak.uname +2 -0
@@ -727,19 +727,26 @@ ifeq ($(uname_S),MINGW)
              prefix = /mingw32
              HOST_CPU = i686
              BASIC_LDFLAGS += -Wl,--pic-executable,-e,_mainCRTStartup
+             CARGO_BUILD_TARGET = i686-pc-windows-gnu
        endif
        ifeq (MINGW64,$(MSYSTEM))
              prefix = /mingw64
              HOST_CPU = x86_64
              BASIC_LDFLAGS += -Wl,--pic-executable,-e,mainCRTStartup
+             CARGO_BUILD_TARGET = x86_64-pc-windows-gnu

I've said it when Johannes originally sent this patch[1], but it bears
repeating: The *-pc-windows-gnu targets will pass CI, but would mean raising the required Windows version from 8.1 to 10. We'd want to use the *-win7-windows-gnu targets[2] to keep Windows 8.1 supported.

[1]
https://lore.kernel.org/git/[email protected]/T/#ma10be2ed0a0e776b0af2fdd0de63d51ba51609e4
[2]
https://doc.rust-lang.org/nightly/rustc/platform-support/win7-windows-gnu.html

config.mak.uname ifeq ($(uname_S),MINGW) · line 741

Quoted 5 lines
config.mak.uname +1 -0
        else ifeq (CLANGARM64,$(MSYSTEM))
              prefix = /clangarm64
              HOST_CPU = aarch64
              BASIC_LDFLAGS += -Wl,--pic-executable,-e,mainCRTStartup
+             CARGO_BUILD_TARGET = aarch64-pc-windows-gnu
Quoted 4 lines

As I've also mentioned before [1], this target doesn't seem to exist. The correct target seems to be aarch64-pc-windows-gnullvm. [3]

[3] https://doc.rust-lang.org/rustc/platform-support/windows-gnullvm.html

I'll be happy to make that change for the next round.

#123 lore raw
In reply to Elijah Newren
Quoted 5 lines · Elijah Newren

What if Ezekiel rebased his series on am/xdiff-hash-tweak, and then instead of further modifying the hashing in the first series, he:
- introduced brian's patch with the platform support
- setup the CI builds to test building with Rust (including Johannes' patches)
- started working on transitioning xdfile_t data structure to be FFI friendly

Yup, that matches my understanding of what our first Rust topic would want to achieve, i.e. get the framework right.

Quoted 6 lines

One issue here is that it probably wouldn't be too long before we'd want to rip out the xdlclassifier struct (mostly a glorified hashtable), which is kind of tied up in a knot with the hashing and line equality, so it would probably only be a few more series down the road before we'd want to start tweaking the code in am/xdiff-hash-tweak to make use of the new data structures.

I understand that at this point we do not expect to import any (security or otherwise) fixes to xdiff code from "upstream", as we are practically the upstream for other folks? For our consumption, that would allow us to take a quite different stance from our historical attitude, which was to keep the modification to the minimum, and apply whatever clean-ups and optimizations only to suit our needs. So what you outline does make certain sense to me.

I am however not sure if we owe anything to our downstream projects, though (e.g., I understand that libgit2 extracted xdiff part from our source, so if we have serious security fixes in ours, they would want to be able to import them?).

Thanks.

#124 lore raw

v3 cover RFC: Cleanup xdiff and begin its rustification

In reply to Ezekiel Newren via GitGitGadget

In order to facilitate the quick adoption of am/xdiff-hash-tweak, this round drops the changes to hashing in xdiff and instead modifies another part of xdiff.

A high level overview of v3:

* patch 1: add a policy for using Rust (brian's patch, with a small tweak)
* patch 2: introduce Rust to the codebase
* patches 3-5: adapt CI (github workflows) to build Git with Rust
* patch 6: introduce the ivec type
* patches 7-14: xdiff code cleanup in preparation for translating to Rust
* patch 15: translate a C function into Rust and call it from C

I'm particularly interested in what folks think of the new ivec type for sharing data across the language barrier. Thoughts?

Build results for these changes:
https://github.com/git/git/actions/runs/17170212383

Links to older versions, which focused on hashing in xdiff:

* v1:
https://lore.kernel.org/git/[email protected]/
* v2:
https://lore.kernel.org/git/[email protected]/

Ezekiel Newren (13):
xdiff: introduce rust
github workflows: install rust
github workflows: upload Cargo.lock
ivec: create a vector type that is interoperable between C and Rust xdiff/xprepare: remove superfluous forward declarations
xdiff: delete unnecessary fields from xrecord_t and xdfile_t
xdiff: make fields of xrecord_t Rust friendly
xdiff: use one definition for freeing xdfile_t
xdiff: replace chastore with an ivec in xdfile_t
xdiff: delete nrec field from xdfile_t
xdiff: delete recs field from xdfile_t
xdiff: make xdfile_t more rust friendly
xdiff: implement xdl_trim_ends() in Rust

Johannes Schindelin (1):
win+Meson: do allow linking with the Rust-built xdiff

brian m. carlson (1):
doc: add a policy for using Rust

 .github/workflows/main.yml                    |  89 +++-
 .gitignore                                    |   3 +
 Documentation/Makefile                        |   1 +
 Documentation/technical/platform-support.adoc |   2 +
 Documentation/technical/rust-support.adoc     | 142 ++++++
 Makefile                                      |  69 ++-
 build_rust.sh                                 |  57 +++
 ci/install-dependencies.sh                    |  14 +-
 ci/install-rust-toolchain.sh                  |  30 ++
 ci/install-rustup.sh                          |  25 +
 ci/lib.sh                                     |   1 +
 ci/make-test-artifacts.sh                     |   9 +
 ci/run-build-and-tests.sh                     |  13 +
 config.mak.uname                              |   4 +
 git-compat-util.h                             |  17 +
 interop/ivec.c                                | 151 ++++++
 interop/ivec.h                                |  52 ++
 meson.build                                   |  54 +-
 rust/Cargo.toml                               |   6 +
 rust/interop/Cargo.toml                       |  14 +
 rust/interop/src/ivec.rs                      | 462 ++++++++++++++++++
 rust/interop/src/lib.rs                       |  10 +
 rust/xdiff/Cargo.toml                         |  15 +
 rust/xdiff/src/lib.rs                         |  15 +
 rust/xdiff/src/xprepare.rs                    |  27 +
 rust/xdiff/src/xtypes.rs                      |  19 +
 xdiff/xdiffi.c                                |  60 +--
 xdiff/xdiffi.h                                |   8 +-
 xdiff/xemit.c                                 |  24 +-
 xdiff/xhistogram.c                            |   2 +-
 xdiff/xmerge.c                                |  72 +--
 xdiff/xpatience.c                             |  16 +-
 xdiff/xprepare.c                              | 271 ++++------
 xdiff/xtypes.h                                |  27 +-
 xdiff/xutils.c                                |  12 +-
 35 files changed, 1474 insertions(+), 319 deletions(-)
 create mode 100644 Documentation/technical/rust-support.adoc
 create mode 100755 build_rust.sh
 create mode 100755 ci/install-rust-toolchain.sh
 create mode 100755 ci/install-rustup.sh
 create mode 100644 interop/ivec.c
 create mode 100644 interop/ivec.h
 create mode 100644 rust/Cargo.toml
 create mode 100644 rust/interop/Cargo.toml
 create mode 100644 rust/interop/src/ivec.rs
 create mode 100644 rust/interop/src/lib.rs
 create mode 100644 rust/xdiff/Cargo.toml
 create mode 100644 rust/xdiff/src/lib.rs
 create mode 100644 rust/xdiff/src/xprepare.rs
 create mode 100644 rust/xdiff/src/xtypes.rs

base-commit: 16bd9f20a403117f2e0d9bcda6c6e621d3763e77
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1980%2Fezekielnewren%2Fxdiff_rust_speedup-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1980/ezekielnewren/xdiff_rust_speedup-v3
Pull-Request: https://github.com/git/git/pull/1980

Range-diff vs v2:

1: 75dfb40ead3 ! 1: 6d065f550fe doc: add a policy for using Rust
@@ Commit message
contributors.

+ [en: Added some comments about types, and changed the recommondations
+ about cbindgen, bindgen, rustix, libc.]

## Documentation/Makefile ##
@@ Documentation/technical/platform-support.adoc: meet the following minimum requir

* Has active security support (taking security releases of dependencies, etc)

-+* Supports Rust and the toolchain version specified in link:rust-support.txt[]. ++* Supports Rust and the toolchain version specified in link:rust-support.adoc[].
+
These requirements are a starting point, and not sufficient on their own for the Git community to be enthusiastic about supporting your platform. Maintainers of
@@ Documentation/technical/rust-support.adoc (new)
+caused by porting a binary wholesale from one language to another that might +introduce bugs.
+
-+We will use the bindgen and cbindgen crates for handling C-compatible -+bindings and the rustix crate for POSIX-compatible interfaces. The libc -+crate, which is used by rustix, does not expose safe interfaces and does not -+handle differences between platforms, such as differing 64-bit stat call -+names, and so is less desirable as a target than rustix. We may still choose -+to use it in some cases if rustix does not offer suitable interfaces. ++Crates like libc or rustix define types like c_long, but in ways that are not ++safe across platforms.
++From https://docs.rs/rustix/latest/rustix/ffi/type.c_long.html:
++
++ This type will always be i32 or i64. Most notably, many Linux-based
++ systems assume an i64, but Windows assumes i32. The C standard technically
++ only requires that this type be a signed integer that is at least 32 bits
++ and at least the size of an int, although in practice, no system would
++ have a long that is neither an i32 nor i64.
++
++Also, note that other locations, such as ++https://docs.rs/libc/latest/libc/type.c_long.html, just hardcode c_long as i64 ++even though C may mean i32 on some platforms.
++
++As such, using the c_long type would give us portability issues, and ++perpetuate some of the bugs git has faced across platforms. Avoid using C's ++types (long, unsigned, char, etc.), and switch to unambiguous types (e.g. i32 ++or i64) before trying to make C and Rust interoperate.
++
++Crates like libc and rustix may have also traditionally aided interoperability ++with older versions of Rust (e.g. when worrying about stat[64] system calls), ++but the Rust standard library in newer versions of Rust handle these concerns ++in a platform agnostic way. There may arise cases where we need to consider ++these crates, but for now we omit them.
++
++Tools like bindgen and cbindgen create C-styled unsafe Rust code rather than ++idiomatic Rust; where possible, we prefer to switch to idiomatic Rust. Any ++standard C library functions that are needed can be manually wrapped on the ++Rust side.
+
+Rust upstream releases every six weeks and only supports the latest stable +release. While it is nice that upstream is active, we would like our software

  2:  7709e5eddba <  -:  ----------- xdiff: introduce rust
  8:  7dc241e6682 !  2:  03939951256 github workflows: install rust
     @@ Metadata
      Author: Ezekiel Newren <[email protected]>
      
       ## Commit message ##
     -    github workflows: install rust
     +    xdiff: introduce rust
      
     -    Since we have introduced rust, it needs to be installed for the
     -    continuous integration build targets. Create an install script
     -    (build_rust.sh) that needs to be run as the same user that builds git.
     -    Because of the limitations of meson, create build_rust.sh which makes
     -    it easy to centralize how rust is built between meson and make.
     +    Upcoming patches will simplify xdiff, while also porting parts of it to
     +    Rust. In preparation, add some stubs and setup the Rust build. For now,
     +    it is easier to let cargo build rust and have make or meson merely link
     +    against the static library that cargo builds. In line with ongoing
     +    libification efforts, use multiple crates to allow more modularity on
     +    the Rust side. xdiff is the crate that this series will focus on, but
     +    we also introduce the interop crate for future patch series.
      
     -    There are 2 interesting decisions worth calling out in this commit:
     -
     -    * The 'output' field of custom_target() does not allow specifying a
     -      file nested inside the build directory. Thus create build_rust.sh to
     -      build rust with all of its parameters and then moves libxdiff.a to
     -      the root of the build directory.
     -
     -    * Install curl, to facilitate the rustup install script.
     +    In order to facilitate interoperability between C and Rust, introduce C
     +    definitions for Rust primitive types in git-compat-util.h.
      
          Signed-off-by: Ezekiel Newren <[email protected]>
      
     - ## .github/workflows/main.yml ##
     -@@ .github/workflows/main.yml: on: [push, pull_request]
     - 
     - env:
     -   DEVELOPER: 1
     -+  RUST_VERSION: 1.87.0
     - 
     - # If more than one workflow run is triggered for the very same commit hash
     - # (which happens when multiple branches pointing to the same commit), only
     + ## .gitignore ##
     +@@ .gitignore: Release/
     + /contrib/buildsystems/out
     + /contrib/libgit-rs/target
     + /contrib/libgit-sys/target
     ++/.idea/
     ++/rust/target/
     ++/rust/Cargo.lock
      
       ## Makefile ##
      @@ Makefile: TEST_SHELL_PATH = $(SHELL_PATH)
     @@ Makefile: TEST_SHELL_PATH = $(SHELL_PATH)
      +
      +EXTLIBS =
      +
     - ifeq ($(DEBUG), 1)
     --RUST_LIB = rust/target/debug/libxdiff.a
     ++ifeq ($(DEBUG), 1)
      +  RUST_BUILD_MODE = debug
     - else
     --RUST_LIB = rust/target/release/libxdiff.a
     ++else
      +  RUST_BUILD_MODE = release
      +endif
      +
     @@ Makefile: TEST_SHELL_PATH = $(SHELL_PATH)
      +UNAME_S := $(shell uname -s)
      +ifeq ($(UNAME_S),Linux)
      +  EXTLIBS += -ldl
     - endif
     ++endif
      +
       REFTABLE_LIB = reftable/libreftable.a
       
     @@ Makefile: UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.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 =
     - 
     --GITLIBS += $(RUST_LIB)
     ++
       
       GIT_USER_AGENT = git/$(GIT_VERSION)
       
     @@ Makefile: $(REMOTE_CURL_ALIASES): $(REMOTE_CURL_PRIMARY)
       	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
       		$(filter %.o,$^) $(LIBS)
       
     +@@ Makefile: $(LIB_FILE): $(LIB_OBJS)
     + $(XDIFF_LIB): $(XDIFF_OBJS)
     + 	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
     + 
     ++
     + $(REFTABLE_LIB): $(REFTABLE_OBJS)
     + 	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
     + 
      @@ Makefile: perf: all
       
       t/helper/test-tool$X: $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS)) $(UNIT_TEST_DIR)/test-lib.o
     @@ Makefile: perf: all
       	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS)
       
       check-sha1:: t/helper/test-tool$X
     +@@ Makefile: cocciclean:
     + 	$(RM) -r .build/contrib/coccinelle
     + 	$(RM) contrib/coccinelle/*.cocci.patch
     + 
     +-clean: profile-clean coverage-clean cocciclean
     ++rustclean:
     ++	cd rust && cargo clean
     ++
     ++clean: profile-clean coverage-clean cocciclean rustclean
     + 	$(RM) -r .build $(UNIT_TEST_BIN)
     + 	$(RM) GIT-TEST-SUITES
     + 	$(RM) po/git.pot po/git-core.pot
      @@ Makefile: FUZZ_CXXFLAGS ?= $(ALL_CFLAGS)
       .PHONY: fuzz-all
       fuzz-all: $(FUZZ_PROGRAMS)
     @@ build_rust.sh (new)
      @@
      +#!/bin/sh
      +
     -+if [ -z "$CARGO_HOME" ]; then
     -+  export CARGO_HOME=$HOME/.cargo
     -+  echo >&2 "::warning:: CARGO_HOME is not set"
     -+fi
     -+echo "CARGO_HOME=$CARGO_HOME"
      +
     -+rustc -vV
     -+cargo --version
     ++rustc -vV || exit $?
     ++cargo --version || exit $?
      +
      +dir_git_root=${0%/*}
      +dir_build=$1
     -+rust_target=$2
     ++rust_build_profile=$2
      +crate=$3
      +
      +dir_rust=$dir_git_root/rust
     @@ build_rust.sh (new)
      +  exit 1
      +fi
      +
     -+if [ "$rust_target" = "" ]; then
     -+  echo "did not specify the rust_target"
     ++if [ "$rust_build_profile" = "" ]; then
     ++  echo "did not specify the rust_build_profile"
      +  exit 1
      +fi
      +
     -+if [ "$rust_target" = "release" ]; then
     ++if [ "$rust_build_profile" = "release" ]; then
      +  rust_args="--release"
     -+  export RUSTFLAGS='-Aunused_imports -Adead_code'
     -+elif [ "$rust_target" = "debug" ]; then
     ++  export RUSTFLAGS=''
     ++elif [ "$rust_build_profile" = "debug" ]; then
      +  rust_args=""
     -+  export RUSTFLAGS='-Aunused_imports -Adead_code -C debuginfo=2 -C opt-level=1 -C force-frame-pointers=yes'
     ++  export RUSTFLAGS='-C debuginfo=2 -C opt-level=1 -C force-frame-pointers=yes'
      +else
     -+  echo "illegal rust_target value $rust_target"
     ++  echo "illegal rust_build_profile value $rust_build_profile"
      +  exit 1
      +fi
      +
     -+cd $dir_rust && cargo clean && pwd && cargo build -p $crate $rust_args; cd ..
     ++cd $dir_rust && cargo clean && pwd && cargo build -p $crate $rust_args; cd $dir_git_root
      +
      +libfile="lib${crate}.a"
     ++if rustup show active-toolchain | grep windows-msvc; then
     ++  libfile="${crate}.lib"
     ++fi
      +dst=$dir_build/$libfile
      +
      +if [ "$dir_git_root" != "$dir_build" ]; then
     -+  src=$dir_rust/target/$rust_target/$libfile
     ++  src=$dir_rust/target/$rust_build_profile/$libfile
      +  if [ ! -f $src ]; then
     -+    echo >&2 "::error:: cannot find path of static library"
     ++    echo >&2 "::error:: cannot find path of static library $src is not a file or does not exist"
      +    exit 5
      +  fi
      +
     @@ build_rust.sh (new)
      +  mv $src $dst
      +fi
      
     - ## ci/install-dependencies.sh ##
     -@@ ci/install-dependencies.sh: fi
     - 
     - case "$distro" in
     - alpine-*)
     --	apk add --update shadow sudo meson ninja-build gcc libc-dev curl-dev openssl-dev expat-dev gettext \
     -+	apk add --update shadow sudo meson ninja-build gcc libc-dev curl curl-dev openssl-dev expat-dev gettext \
     - 		zlib-ng-dev pcre2-dev python3 musl-libintl perl-utils ncurses \
     - 		apache2 apache2-http2 apache2-proxy apache2-ssl apache2-webdav apr-util-dbd_sqlite3 \
     - 		bash cvs gnupg perl-cgi perl-dbd-sqlite perl-io-tty >/dev/null
     - 	;;
     - fedora-*|almalinux-*)
     - 	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 gcc findutils diffutils perl python3 gawk gettext zlib-devel expat-devel openssl-devel curl curl-devel pcre2-devel >/dev/null
     - 	;;
     - ubuntu-*|i386/ubuntu-*|debian-*)
     - 	# Required so that apt doesn't wait for user input on certain packages.
     -@@ ci/install-dependencies.sh: ubuntu-*|i386/ubuntu-*|debian-*)
     - 	sudo apt-get -q update
     - 	sudo apt-get -q -y install \
     - 		$LANGUAGES apache2 cvs cvsps git gnupg $SVN \
     --		make libssl-dev libcurl4-openssl-dev libexpat-dev wget sudo default-jre \
     --		tcl tk gettext zlib1g-dev perl-modules liberror-perl libauthen-sasl-perl \
     -+		make libssl-dev curl libcurl4-openssl-dev libexpat-dev wget sudo default-jre \
     -+		tcl tk gettext zlib1g 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 \
     - 		${CC_PACKAGE:-${CC:-gcc}} $PYTHON_PACKAGE
     -@@ ci/install-dependencies.sh: ClangFormat)
     - 	;;
     - StaticAnalysis)
     - 	sudo apt-get -q update
     --	sudo apt-get -q -y install coccinelle libcurl4-openssl-dev libssl-dev \
     -+	sudo apt-get -q -y install coccinelle curl libcurl4-openssl-dev libssl-dev \
     - 		libexpat-dev gettext make
     - 	;;
     - sparse)
     - 	sudo apt-get -q update -q
     --	sudo apt-get -q -y install libssl-dev libcurl4-openssl-dev \
     --		libexpat-dev gettext zlib1g-dev sparse
     -+	sudo apt-get -q -y install libssl-dev curl libcurl4-openssl-dev \
     -+		libexpat-dev gettext zlib1g zlib1g-dev sparse
     - 	;;
     - Documentation)
     - 	sudo apt-get -q update
     + ## git-compat-util.h ##
     +@@ git-compat-util.h: static inline int is_xplatform_dir_sep(int c)
     + #include "compat/msvc.h"
     + #endif
     + 
     ++/* rust types */
     ++typedef uint8_t   u8;
     ++typedef uint16_t  u16;
     ++typedef uint32_t  u32;
     ++typedef uint64_t  u64;
     ++
     ++typedef int8_t    i8;
     ++typedef int16_t   i16;
     ++typedef int32_t   i32;
     ++typedef int64_t   i64;
     ++
     ++typedef float     f32;
     ++typedef double    f64;
     ++
     ++typedef size_t    usize;
     ++typedef ptrdiff_t isize;
     ++
     + /* used on Mac OS X */
     + #ifdef PRECOMPOSE_UNICODE
     + #include "compat/precompose_utf8.h"
      
     - ## ci/install-rust.sh (new) ##
     -@@
     -+#!/bin/sh
     -+
     -+if [ "$(id -u)" -eq 0 ]; then
     -+  echo >&2 "::warning:: installing rust as root"
     -+fi
     -+
     -+if [ "$CARGO_HOME" = "" ]; then
     -+  echo >&2 "::warning:: CARGO_HOME is not set"
     -+  export CARGO_HOME=$HOME/.cargo
     -+fi
     -+
     -+export RUSTUP_HOME=$CARGO_HOME
     -+
     -+if [ "$RUST_VERSION" = "" ]; then
     -+  echo >&2 "::error:: RUST_VERSION is not set"
     -+  exit 2
     -+fi
     -+
     -+## install rustup
     -+curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --default-toolchain none -y
     -+if [ ! -f $CARGO_HOME/env ]; then
     -+  echo "PATH=$CARGO_HOME/bin:\$PATH" > $CARGO_HOME/env
     -+fi
     -+## install a specific version of rust
     -+if [ "$BITNESS" = "32" ]; then
     -+  $CARGO_HOME/bin/rustup set default-host i686-unknown-linux-gnu || exit $?
     -+  $CARGO_HOME/bin/rustup install $RUST_VERSION || exit $?
     -+  $CARGO_HOME/bin/rustup default --force-non-host $RUST_VERSION || exit $?
     -+else
     -+  $CARGO_HOME/bin/rustup default $RUST_VERSION || exit $?
     -+fi
     -+
     -+. $CARGO_HOME/env
     -
     - ## ci/lib.sh ##
     -@@
     - # Library of functions shared by all CI scripts
     - 
     -+
     -+export BITNESS="64"
     -+if command -v getconf >/dev/null && [ "$(getconf LONG_BIT 2>/dev/null)" = "32" ]; then
     -+  export BITNESS="32"
     -+fi
     -+echo "BITNESS=$BITNESS"
     -+
     -+
     - if test true = "$GITHUB_ACTIONS"
     - then
     - 	begin_group () {
     -
     - ## ci/make-test-artifacts.sh ##
     -@@ ci/make-test-artifacts.sh: mkdir -p "$1" # in case ci/lib.sh decides to quit early
     - 
     - . ${0%/*}/lib.sh
     - 
     -+## install rust per user rather than system wide
     -+. ${0%/*}/install-rust.sh
     -+
     - group Build make artifacts-tar ARTIFACTS_DIRECTORY="$1"
     - 
     -+if [ -d "$CARGO_HOME" ]; then
     -+  rm -rf $CARGO_HOME
     -+fi
     -+
     - check_unignored_build_artifacts
     -
     - ## ci/run-build-and-tests.sh ##
     -@@
     - 
     - . ${0%/*}/lib.sh
     + ## meson.build ##
     +@@ meson.build: version_gen_environment.set('GIT_DATE', get_option('build_date'))
     + version_gen_environment.set('GIT_USER_AGENT', get_option('user_agent'))
     + version_gen_environment.set('GIT_VERSION', get_option('version'))
       
     -+## install rust per user rather than system wide
     -+. ${0%/*}/install-rust.sh
     ++if get_option('optimization') in ['2', '3', 's', 'z']
     ++  rust_build_profile = 'release'
     ++else
     ++  rust_build_profile = 'debug'
     ++endif
      +
     -+rustc -vV
     -+cargo --version || exit $?
     ++# Run `rustup show active-toolchain` and capture output
     ++rustup_out = run_command('rustup', 'show', 'active-toolchain',
     ++                         check: true).stdout().strip()
     ++
     ++rust_crates = ['xdiff']
     ++rust_builds = []
     ++
     ++foreach crate : rust_crates
     ++  if rustup_out.contains('windows-msvc')
     ++    libfile = crate + '.lib'
     ++  else
     ++    libfile = 'lib' + crate + '.a'
     ++  endif
     ++
     ++  rust_builds += custom_target(
     ++    'rust_build_'+crate,
     ++    output: libfile,
     ++    build_by_default: true,
     ++    build_always_stale: true,
     ++    command: [
     ++      meson.project_source_root() / 'build_rust.sh',
     ++      meson.current_build_dir(), rust_build_profile, crate,
     ++    ],
     ++    install: false,
     ++  )
     ++endforeach
      +
     - run_tests=t
     - 
     - case "$jobname" in
     -@@ ci/run-build-and-tests.sh: case "$jobname" in
     - 	;;
     - esac
     - 
     -+if [ -d "$CARGO_HOME" ]; then
     -+  rm -rf $CARGO_HOME
     -+fi
      +
     - check_unignored_build_artifacts
     - save_good_tree
     -
     - ## meson.build ##
     -@@ meson.build: else
     -   rustflags = '-Aunused_imports -Adead_code -C debuginfo=2 -C opt-level=1 -C force-frame-pointers=yes'
     - endif
     - 
     --
     --rust_leaf = custom_target('rust_leaf',
     -+rust_build_xdiff = custom_target('rust_build_xdiff',
     -   output: 'libxdiff.a',
     -   build_by_default: true,
     -   build_always_stale: true,
     --  command: ['cargo', 'build',
     --            '--manifest-path', meson.project_source_root() / 'rust/Cargo.toml'
     --  ] + rust_args,
     --  env: {
     --    'RUSTFLAGS': rustflags,
     --  },
     -+  command: [
     -+    meson.project_source_root() / 'build_rust.sh',
     -+    meson.current_build_dir(), rust_target, 'xdiff',
     -+  ],
     -   install: false,
     - )
     - 
     --rust_xdiff_dep = declare_dependency(
     --  link_args: ['-L' + meson.project_source_root() / 'rust/target' / rust_target, '-lxdiff'],
     --#  include_directories: include_directories('xdiff/include'),  # Adjust if you expose headers
     --)
     --
     --
       compiler = meson.get_compiler('c')
       
       libgit_sources = [
      @@ meson.build: version_def_h = custom_target(
     - )
       libgit_sources += version_def_h
       
     --libgit_dependencies += rust_xdiff_dep
     --
       libgit = declare_dependency(
      -  link_with: static_library('git',
      -    sources: libgit_sources,
     @@ meson.build: version_def_h = custom_target(
      +      dependencies: libgit_dependencies,
      +      include_directories: libgit_include_directories,
      +    ),
     -+    rust_build_xdiff,
     -+  ],
     ++  ] + rust_builds,
         compile_args: libgit_c_args,
         dependencies: libgit_dependencies,
         include_directories: libgit_include_directories,
     +
     + ## rust/Cargo.toml (new) ##
     +@@
     ++[workspace]
     ++members = [
     ++    "xdiff",
     ++    "interop",
     ++]
     ++resolver = "2"
     +
     + ## rust/interop/Cargo.toml (new) ##
     +@@
     ++[package]
     ++name = "interop"
     ++version = "0.1.0"
     ++edition = "2021"
     ++
     ++[lib]
     ++name = "interop"
     ++path = "src/lib.rs"
     ++## staticlib to generate xdiff.a for use by gcc
     ++## cdylib (optional) to generate xdiff.so for use by gcc
     ++## rlib is required by the rust unit tests
     ++crate-type = ["staticlib", "rlib"]
     ++
     ++[dependencies]
     +
     + ## rust/interop/src/lib.rs (new) ##
     +
     + ## rust/xdiff/Cargo.toml (new) ##
     +@@
     ++[package]
     ++name = "xdiff"
     ++version = "0.1.0"
     ++edition = "2021"
     ++
     ++[lib]
     ++name = "xdiff"
     ++path = "src/lib.rs"
     ++## staticlib to generate xdiff.a for use by gcc
     ++## cdylib (optional) to generate xdiff.so for use by gcc
     ++## rlib is required by the rust unit tests
     ++crate-type = ["staticlib", "rlib"]
     ++
     ++[dependencies]
     ++interop = { path = "../interop" }
     +
     + ## rust/xdiff/src/lib.rs (new) ##

12: fffdb326710 ! 3: a98d9e4d21b github workflows: define rust versions and targets in the same place
@@ Metadata
Author: Ezekiel Newren <[email protected]>

## Commit message ##
- github workflows: define rust versions and targets in the same place
+ github workflows: install rust

- Consolidate the Rust toolchain definitions in main.yaml. Prefer using
- actions-rs/toolchain@v1 where possible, but for docker targets use
- a script to install the Rust toolchain. Four overrides are used in
+ Prefer using actions-rs/toolchain@v1 where possible to install rustup,
+ but for docker targets use a script to install rustup. Consolidate the
+ Rust toolchain definitions in main.yaml. Use install-rust-toolchain.sh
+ to ensure the correct toolchain is used. Five overrides are used in
main.yaml:

* On Windows: Rust didn't resolve the bcrypt library on Windows correctly until version 1.78.0. Also since rustup mis-identifies the Rust toolchain, the Rust target triple must be set to
- x86_64-pc-windows-gnu.
+ x86_64-pc-windows-gnu for make (win build), and
+ x86_64-pc-windows-msvc for meson (win+Meson build).
* On musl: libc differences, such as ftruncate64 vs ftruncate, were not accounted for until Rust version 1.72.0. No older version of Rust will work on musl for our needs.
* In a 32-bit docker container running on a 64-bit host, we need to override the Rust target triple. This is because rustup asks the kernel for the bitness of the system and it says 64, even though
- the container will only run 32-bit. This also allows us to remove
- the BITNESS environment variable in ci/lib.sh.
+ the container is 32-bit. This also allows us to remove the
+ BITNESS environment variable in ci/lib.sh.

+ The logic for selecting library names was initially provided in a patch
+ from Johannes, but was reworked and squashed into this commit.
+
+ Helped-by: Johannes Schindelin <[email protected]>

## .github/workflows/main.yml ##
-@@ .github/workflows/main.yml: on: [push, pull_request]
-
- env:
- DEVELOPER: 1
-- RUST_VERSION: 1.87.0
-
- # If more than one workflow run is triggered for the very same commit hash
- # (which happens when multiple branches pointing to the same commit), only
@@ .github/workflows/main.yml: jobs:
outputs:
enabled: ${{ steps.check-ref.outputs.enabled }}${{ steps.skip-if-redundant.outputs.enabled }}
@@ .github/workflows/main.yml: jobs:
+ rust_version_windows: 1.78.0
+ rust_version_musl: 1.72.0
+ ## the rust target is inferred by rustup unless specified
-+ rust_target_windows: x86_64-pc-windows-gnu
++ rust_target_windows_make: x86_64-pc-windows-gnu
++ rust_target_windows_meson: x86_64-pc-windows-msvc
+ rust_target_32bit_linux: i686-unknown-linux-gnu
steps:
- name: try to clone ci-config branch
run: |
@@ .github/workflows/main.yml: jobs:
- /c/Program\ Files/Git/mingw64/bin/curl -Lo libuserenv.a \
- https://github.com/git-for-windows/git-sdk-64/raw/HEAD/mingw64/lib/libuserenv.a
- }
-+ - name: Install Rust
+ needs: ci-config
+ if: needs.ci-config.outputs.enabled == 'yes'
+ runs-on: windows-latest
++ env:
++ CARGO_HOME: "/c/Users/runneradmin/.cargo"
+ concurrency:
+ group: windows-build-${{ github.ref }}
+ cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }}
+ steps:
+ - uses: actions/checkout@v4
+ - uses: git-for-windows/setup-git-for-windows-sdk@v1
++ - name: Install rustup via github actions
+ uses: actions-rs/toolchain@v1
+ with:
-+ toolchain: ${{ needs.ci-config.outputs.rust_version_windows }}
-+ target: ${{ needs.ci-config.outputs.rust_target_windows }}
++ toolchain: stable
+ profile: minimal
-+ override: true
++ override: false
++ - name: Install Rust toolchain
++ shell: bash
++ env:
++ RUST_VERSION: ${{ needs.ci-config.outputs.rust_version_windows }}
++ RUST_TARGET: ${{ needs.ci-config.outputs.rust_target_windows_make }}
++ run: ci/install-rust-toolchain.sh
- name: build
shell: bash
env:
- HOME: ${{runner.workspace}}
- NO_PERL: 1
-+ CARGO_HOME: "/c/Users/runneradmin/.cargo"
- run: . /etc/profile && ci/make-test-artifacts.sh artifacts
- - name: zip up tracked files
- run: git archive -o artifacts/tracked.tar.gz HEAD
@@ .github/workflows/main.yml: jobs:
+ needs: ci-config
+ if: needs.ci-config.outputs.enabled == 'yes'
+ runs-on: windows-latest
++ env:
++ CARGO_HOME: "/c/Users/runneradmin/.cargo"
+ concurrency:
+ group: windows-meson-build-${{ github.ref }}
+ cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
-+ - name: Install Rust
++ - name: Install rustup via github actions
+ uses: actions-rs/toolchain@v1
+ with:
-+ toolchain: ${{ needs.ci-config.outputs.rust_version_windows }}
-+ target: ${{ needs.ci-config.outputs.rust_target_windows }}
++ toolchain: stable
+ profile: minimal
-+ override: true
++ override: false
++ - name: Install Rust toolchain
++ shell: bash
++ env:
++ RUST_VERSION: ${{ needs.ci-config.outputs.rust_version_windows }}
++ RUST_TARGET: ${{ needs.ci-config.outputs.rust_target_windows_meson }}
++ run: ci/install-rust-toolchain.sh
- name: Set up dependencies
shell: pwsh
run: pip install meson ninja
@@ .github/workflows/main.yml: jobs:
+ jobname: ${{matrix.vector.jobname}}
+ CI_JOB_IMAGE: ${{matrix.vector.pool}}
+ TEST_OUTPUT_DIRECTORY: ${{github.workspace}}/t
++ CARGO_HOME: "/Users/runner/.cargo"
+ runs-on: ${{matrix.vector.pool}}
steps:
- uses: actions/checkout@v4
- run: ci/install-dependencies.sh
-+ - name: Install Rust
+- - run: ci/run-build-and-tests.sh
++ - name: Install rustup via github actions
+ uses: actions-rs/toolchain@v1
+ with:
-+ toolchain: ${{ needs.ci-config.outputs.rust_version_minimum }}
++ toolchain: stable
+ profile: minimal
-+ override: true
- - run: ci/run-build-and-tests.sh
++ override: false
++ - name: Install Rust toolchain
++ shell: bash
++ env:
++ RUST_VERSION: ${{ needs.ci-config.outputs.rust_version_minimum }}
++ run: ci/install-rust-toolchain.sh
++ - name: Run build and tests
++ run: ci/run-build-and-tests.sh
- name: print test failures
if: failure() && env.FAILED_TEST_ARTIFACTS != ''
+ run: ci/print-test-failures.sh
@@ .github/workflows/main.yml: jobs:
cc: gcc
- jobname: linux-musl-meson
@@ .github/workflows/main.yml: jobs:
CI_JOB_IMAGE: ${{matrix.vector.image}}
+ CI_IS_DOCKER: "true"
CUSTOM_PATH: /custom
-+ RUST_VERSION: ${{ matrix.vector.rust_version_override || needs.ci-config.outputs.rust_version_minimum }}
-+ RUST_TARGET: ${{ matrix.vector.rust_target_override || '' }}
+ CARGO_HOME: /home/builder/.cargo
runs-on: ubuntu-latest
container: ${{matrix.vector.image}}
steps:
+@@ .github/workflows/main.yml: jobs:
+ - run: ci/install-dependencies.sh
+ - run: useradd builder --create-home
+ - run: chown -R builder .
++ - name: Install rustup via script
++ run: sudo --preserve-env --set-home --user=builder ci/install-rustup.sh
++ - name: Install Rust toolchain
++ env:
++ RUST_VERSION: ${{ matrix.vector.rust_version_override || needs.ci-config.outputs.rust_version_minimum }}
++ RUST_TARGET: ${{ matrix.vector.rust_target_override || '' }}
++ run: sudo --preserve-env --set-home --user=builder ci/install-rust-toolchain.sh
+ - run: sudo --preserve-env --set-home --user=builder ci/run-build-and-tests.sh
+ - name: print test failures
+ if: failure() && env.FAILED_TEST_ARTIFACTS != ''

- ## build_rust.sh ##
-@@
- #!/bin/sh
-
--if [ -z "$CARGO_HOME" ]; then
-- export CARGO_HOME=$HOME/.cargo
-- echo >&2 "::warning:: CARGO_HOME is not set" --fi
--echo "CARGO_HOME=$CARGO_HOME"
+ ## ci/install-dependencies.sh ##
+@@ ci/install-dependencies.sh: fi

--rustc -vV
--cargo --version
+ case "$distro" in
+ alpine-*)
+- apk add --update shadow sudo meson ninja-build gcc libc-dev curl-dev openssl-dev expat-dev gettext \
++ apk add --update shadow sudo meson ninja-build gcc libc-dev curl curl-dev openssl-dev expat-dev gettext \
+ zlib-ng-dev pcre2-dev python3 musl-libintl perl-utils ncurses \
+ apache2 apache2-http2 apache2-proxy apache2-ssl apache2-webdav apr-util-dbd_sqlite3 \
+ bash cvs gnupg perl-cgi perl-dbd-sqlite perl-io-tty >/dev/null
+ ;;
+ fedora-*|almalinux-*)
+ 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 gcc findutils diffutils perl python3 gawk gettext zlib-devel expat-devel openssl-devel curl curl-devel pcre2-devel >/dev/null
+ ;;
+ ubuntu-*|i386/ubuntu-*|debian-*)
+ # Required so that apt doesn't wait for user input on certain packages.
+@@ ci/install-dependencies.sh: ubuntu-*|i386/ubuntu-*|debian-*)
+ sudo apt-get -q update
+ sudo apt-get -q -y install \
+ $LANGUAGES apache2 cvs cvsps git gnupg $SVN \
+- make libssl-dev libcurl4-openssl-dev libexpat-dev wget sudo default-jre \
+- tcl tk gettext zlib1g-dev perl-modules liberror-perl libauthen-sasl-perl \
++ make libssl-dev curl libcurl4-openssl-dev libexpat-dev wget sudo default-jre \
++ tcl tk gettext zlib1g 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 \
+ ${CC_PACKAGE:-${CC:-gcc}} $PYTHON_PACKAGE
+@@ ci/install-dependencies.sh: ClangFormat)
+ ;;
+ StaticAnalysis)
+ sudo apt-get -q update
+- sudo apt-get -q -y install coccinelle libcurl4-openssl-dev libssl-dev \
++ sudo apt-get -q -y install coccinelle curl libcurl4-openssl-dev libssl-dev \
+ libexpat-dev gettext make
+ ;;
+ sparse)
+ sudo apt-get -q update -q
+- sudo apt-get -q -y install libssl-dev libcurl4-openssl-dev \
+- libexpat-dev gettext zlib1g-dev sparse
++ sudo apt-get -q -y install libssl-dev curl libcurl4-openssl-dev \
++ libexpat-dev gettext zlib1g zlib1g-dev sparse
+ ;;
+ Documentation)
+ sudo apt-get -q update
+
+ ## ci/install-rust-toolchain.sh (new) ##
+@@
++#!/bin/sh
++
++if [ "$CARGO_HOME" = "" ]; then
++ echo >&2 "::error:: CARGO_HOME is not set"
++ exit 2
++fi
++export PATH="$CARGO_HOME/bin:$PATH"
++rustup -vV || exit $?
++
++## Enforce the correct Rust toolchain
++rustup override unset || true
++
++## install a specific version of rust
++if [ "$RUST_TARGET" != "" ]; then
++ rustup default --force-non-host "$RUST_VERSION-$RUST_TARGET" || exit $? ++else
++ rustup default "$RUST_VERSION" || exit $?
++fi
++
+rustc -vV || exit $?
-+cargo --version || exit $?
-
- dir_git_root=${0%/*}
- dir_build=$1
-@@ build_rust.sh: dst=$dir_build/$libfile
- if [ "$dir_git_root" != "$dir_build" ]; then
- src=$dir_rust/target/$rust_target/$libfile
- if [ ! -f $src ]; then
-- echo >&2 "::error:: cannot find path of static library"
-+ echo >&2 "::error:: cannot find path of static library $src is not a file or does not exist"
- exit 5
- fi
-
++
++RE_RUST_TARGET="$RUST_TARGET"
++if [ "$RUST_TARGET" = "" ]; then
++ RE_RUST_TARGET="[^ ]+"
++fi
++
++if ! rustup show active-toolchain | grep -E "^$RUST_VERSION-$RE_RUST_TARGET \(default\)$"; then ++ echo >&2 "::error:: wrong Rust toolchain, active-toolchain: $(rustup show active-toolchain)" ++ exit 3
++fi

- ## ci/install-rust.sh (mode change 100644 => 100755) ##
+ ## ci/install-rustup.sh (new) ##
@@
- #!/bin/sh
-
++#!/bin/sh
++
+## github workflows actions-rs/toolchain@v1 doesn't work for docker
+## targets. This script should only be used if the ci pipeline
+## doesn't support installing rust on a particular target.
+
- if [ "$(id -u)" -eq 0 ]; then
- echo >&2 "::warning:: installing rust as root"
- fi
-
--if [ "$CARGO_HOME" = "" ]; then
-- echo >&2 "::warning:: CARGO_HOME is not set"
-- export CARGO_HOME=$HOME/.cargo
--fi
--
--export RUSTUP_HOME=$CARGO_HOME
--
- if [ "$RUST_VERSION" = "" ]; then
- echo >&2 "::error:: RUST_VERSION is not set"
-+ exit 1
++if [ "$(id -u)" -eq 0 ]; then
++ echo >&2 "::warning:: installing rust as root"
+fi
+
+if [ "$CARGO_HOME" = "" ]; then
+ echo >&2 "::error:: CARGO_HOME is not set"
- exit 2
- fi
-
++ exit 2
++fi
++
+export RUSTUP_HOME=$CARGO_HOME
+
- ## install rustup
- curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --default-toolchain none -y
- if [ ! -f $CARGO_HOME/env ]; then
- echo "PATH=$CARGO_HOME/bin:\$PATH" > $CARGO_HOME/env
- fi
++## install rustup
++curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --default-toolchain none -y ++if [ ! -f $CARGO_HOME/env ]; then
++ echo "PATH=$CARGO_HOME/bin:\$PATH" > $CARGO_HOME/env
++fi
+. $CARGO_HOME/env
+
- ## install a specific version of rust
--if [ "$BITNESS" = "32" ]; then
-- $CARGO_HOME/bin/rustup set default-host i686-unknown-linux-gnu || exit $?
-- $CARGO_HOME/bin/rustup install $RUST_VERSION || exit $?
-- $CARGO_HOME/bin/rustup default --force-non-host $RUST_VERSION || exit $?
-+if [ "$RUST_TARGET" != "" ]; then
-+ rustup default --force-non-host "$RUST_VERSION-$RUST_TARGET" || exit $?
- else
-- $CARGO_HOME/bin/rustup default $RUST_VERSION || exit $?
-- if [ "$CI_OS_NAME" = "windows" ]; then
-- $CARGO_HOME/bin/rustup target add x86_64-pc-windows-gnu || exit $?
-- fi
-+ rustup default "$RUST_VERSION" || exit $?
- fi
-
--. $CARGO_HOME/env
-+rustc -vV || exit $?
++rustup -vV

## ci/lib.sh ##
@@
# Library of functions shared by all CI scripts

-
--export BITNESS="64"
--if command -v getconf >/dev/null && [ "$(getconf LONG_BIT 2>/dev/null)" = "32" ]; then -- export BITNESS="32"
--fi
--echo "BITNESS=$BITNESS"
--
--
++
if test true = "$GITHUB_ACTIONS"
then
begin_group () {
@@ ci/make-test-artifacts.sh: mkdir -p "$1" # in case ci/lib.sh decides to quit ear

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

--## install rust per user rather than system wide --. ${0%/*}/install-rust.sh
-+if [ -z "$CARGO_HOME" ]; then
++## ensure rustup is in the PATH variable
++if [ "$CARGO_HOME" = "" ]; then
+ echo >&2 "::error:: CARGO_HOME is not set"
-+ exit 1
++ exit 2
+fi
-
--group Build make artifacts-tar ARTIFACTS_DIRECTORY="$1"
+export PATH="$CARGO_HOME/bin:$PATH"
-
--if [ -d "$CARGO_HOME" ]; then
-- rm -rf $CARGO_HOME
--fi
-+group Build make artifacts-tar ARTIFACTS_DIRECTORY="$1" ++
++rustc -vV
++
+ group Build make artifacts-tar ARTIFACTS_DIRECTORY="$1"

check_unignored_build_artifacts

@@ ci/run-build-and-tests.sh

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

--## install rust per user rather than system wide
--. ${0%/*}/install-rust.sh
-+## actions-rs/toolchain@v1 doesn't work for docker targets. -+if [ "$CI_IS_DOCKER" = "true" ]; then
-+ . ${0%/*}/install-rust.sh
++## ensure rustup is in the PATH variable
++if [ "$CARGO_HOME" = "" ]; then
++ echo >&2 "::error:: CARGO_HOME is not set"
++ exit 2
+fi
-
--rustc -vV
++. $CARGO_HOME/env
++
+rustc -vV || exit $?
- cargo --version || exit $?
-
++
run_tests=t
-
- ## meson.build ##
-@@ meson.build: rust_build_xdiff = custom_target('rust_build_xdiff',
- meson.project_source_root() / 'build_rust.sh',
- meson.current_build_dir(), rust_target, 'xdiff',
- ],
-+ env: script_environment,
- install: false,
- )

+ case "$jobname" in
+@@ ci/run-build-and-tests.sh: case "$jobname" in
+ ;;
+ esac
+
++if [ -d "$CARGO_HOME" ]; then
++ rm -rf $CARGO_HOME
++fi
++
+ check_unignored_build_artifacts
+ save_good_tree
11: 382067a09e3 ! 4: 0d2b39c3e03 win+Meson: do allow linking with the Rust-built xdiff
@@ .github/workflows/main.yml: jobs:
+ /c/Program\ Files/Git/mingw64/bin/curl -Lo libuserenv.a \
+ https://github.com/git-for-windows/git-sdk-64/raw/HEAD/mingw64/lib/libuserenv.a
+ }
- - name: build
- shell: bash
- env:
+ - name: Install rustup via github actions
+ uses: actions-rs/toolchain@v1
+ with:

## config.mak.uname ##
@@ config.mak.uname: ifeq ($(uname_S),MINGW)
-
- export CARGO_BUILD_TARGET
- RUST_TARGET_DIR = rust/target/$(CARGO_BUILD_TARGET)/$(RUST_BUILD_MODE)
+ COMPAT_CFLAGS += -D_USE_32BIT_TIME_T
+ BASIC_LDFLAGS += -Wl,--large-address-aware
+ endif
++
+ # Unfortunately now needed because of Rust
+ EXTLIBS += -luserenv
-
++
CC = gcc
COMPAT_CFLAGS += -D__USE_MINGW_ANSI_STDIO=0 -DDETECT_MSYS_TTY \
+ -fstack-protector-strong

## meson.build ##
@@ meson.build: elif host_machine.system() == 'windows'

 13:  44784f0d672 =  5:  e65488ab993 github workflows: upload Cargo.lock
  -:  ----------- >  6:  db5d22b1887 ivec: create a vector type that is interoperable between C and Rust
  3:  56c96d35554 =  7:  d4bed954632 xdiff/xprepare: remove superfluous forward declarations
  4:  ebec3689dce =  8:  7c68ce5349c xdiff: delete unnecessary fields from xrecord_t and xdfile_t
  5:  769d1a5b9d2 =  9:  e516ccc8c0a xdiff: make fields of xrecord_t Rust friendly
  6:  87623495994 ! 10:  21bfb9f0883 xdiff: separate parsing lines from hashing them
     @@ Metadata
      Author: Ezekiel Newren <[email protected]>
      
       ## Commit message ##
     -    xdiff: separate parsing lines from hashing them
     +    xdiff: use one definition for freeing xdfile_t
      
     -    We want to use xxhash for faster hashing. To facilitate that
     -    and to simplify the code. Separate the concerns of parsing
     -    and hashing into discrete steps. This makes swapping the hash
     -    function much easier. Since xdl_hash_record() both parses and
     -    hashses lines, this requires some slight code restructuring.
     +    Simplify xdl_prepare_ctx() by using xdl_free_ctx() instead of using
     +    local variables with hand rolled memory management.
      
          Signed-off-by: Ezekiel Newren <[email protected]>
      
     @@ xdiff/xprepare.c: static int xdl_classify_record(unsigned int pass, xdlclassifie
       }
       
       
     -+static void xdl_parse_lines(mmfile_t *mf, long narec, xdfile_t *xdf) {
     -+	u8 const* ptr = (u8 const*) mf->ptr;
     -+	usize len = (usize) mf->size;
     -+
     -+	xdf->recs = NULL;
     -+	xdf->nrec = 0;
     -+	XDL_ALLOC_ARRAY(xdf->recs, narec);
     -+
     -+	while (len > 0) {
     -+		xrecord_t *rec = NULL;
     -+		usize length;
     -+		u8 const* result = memchr(ptr, '\n', len);
     -+		if (result) {
     -+			length = result - ptr + 1;
     -+		} else {
     -+			length = len;
     -+		}
     -+		if (XDL_ALLOC_GROW(xdf->recs, xdf->nrec + 1, narec))
     -+			die("XDL_ALLOC_GROW failed");
     -+		rec = xdl_cha_alloc(&xdf->rcha);
     -+		rec->ptr = ptr;
     -+		rec->size = length;
     -+		rec->ha = 0;
     -+		xdf->recs[xdf->nrec++] = rec;
     -+		ptr += length;
     -+		len -= length;
     -+	}
     -+
     ++static void xdl_free_ctx(xdfile_t *xdf) {
     ++	xdl_free(xdf->rindex);
     ++	xdl_free(xdf->rchg - 1);
     ++	xdl_free(xdf->ha);
     ++	xdl_free(xdf->recs);
     ++	xdl_cha_free(&xdf->rcha);
      +}
      +
      +
       static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
       			   xdlclassifier_t *cf, xdfile_t *xdf) {
      -	long nrec, bsize;
     --	unsigned long hav;
     --	char const *blk, *cur, *top, *prev;
     --	xrecord_t *crec;
     ++	long bsize;
     + 	unsigned long hav;
     + 	char const *blk, *cur, *top, *prev;
     + 	xrecord_t *crec;
      -	xrecord_t **recs;
     - 	unsigned long *ha;
     - 	char *rchg;
     - 	long *rindex;
     -@@ xdiff/xprepare.c: static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
     - 	ha = NULL;
     - 	rindex = NULL;
     - 	rchg = NULL;
     +-	unsigned long *ha;
     +-	char *rchg;
     +-	long *rindex;
     + 
     +-	ha = NULL;
     +-	rindex = NULL;
     +-	rchg = NULL;
      -	recs = NULL;
     ++	xdf->ha = NULL;
     ++	xdf->rindex = NULL;
     ++	xdf->rchg = NULL;
     ++	xdf->recs = NULL;
     ++	xdf->nrec = 0;
       
       	if (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0)
       		goto abort;
      -	if (!XDL_ALLOC_ARRAY(recs, narec))
     --		goto abort;
     ++	if (!XDL_ALLOC_ARRAY(xdf->recs, narec))
     + 		goto abort;
       
      -	nrec = 0;
     --	if ((cur = blk = xdl_mmfile_first(mf, &bsize))) {
     --		for (top = blk + bsize; cur < top; ) {
     --			prev = cur;
     --			hav = xdl_hash_record(&cur, top, xpp->flags);
     + 	if ((cur = blk = xdl_mmfile_first(mf, &bsize))) {
     + 		for (top = blk + bsize; cur < top; ) {
     + 			prev = cur;
     + 			hav = xdl_hash_record(&cur, top, xpp->flags);
      -			if (XDL_ALLOC_GROW(recs, nrec + 1, narec))
     --				goto abort;
     --			if (!(crec = xdl_cha_alloc(&xdf->rcha)))
     --				goto abort;
     --			crec->ptr = (u8 const*) prev;
     --			crec->size = (long) (cur - prev);
     --			crec->ha = hav;
     ++			if (XDL_ALLOC_GROW(xdf->recs, xdf->nrec + 1, narec))
     + 				goto abort;
     + 			if (!(crec = xdl_cha_alloc(&xdf->rcha)))
     + 				goto abort;
     + 			crec->ptr = (u8 const*) prev;
     + 			crec->size = (long) (cur - prev);
     + 			crec->ha = hav;
      -			recs[nrec++] = crec;
     --			if (xdl_classify_record(pass, cf, crec) < 0)
     --				goto abort;
     --		}
     -+	xdl_parse_lines(mf, narec, xdf);
     -+
     -+	for (usize i = 0; i < (usize) xdf->nrec; i++) {
     -+		xrecord_t *rec = xdf->recs[i];
     -+		char const* dump = (char const*) rec->ptr;
     -+		rec->ha = xdl_hash_record(&dump, (char const*) (rec->ptr + rec->size), xpp->flags);
     -+		xdl_classify_record(pass, cf, rec);
     ++			xdf->recs[xdf->nrec++] = crec;
     + 			if (xdl_classify_record(pass, cf, crec) < 0)
     + 				goto abort;
     + 		}
       	}
       
      -	if (!XDL_CALLOC_ARRAY(rchg, nrec + 2))
     -+
     -+	if (!XDL_CALLOC_ARRAY(rchg, xdf->nrec + 2))
     ++	if (!XDL_CALLOC_ARRAY(xdf->rchg, xdf->nrec + 2))
       		goto abort;
       
       	if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) &&
       	    (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF)) {
      -		if (!XDL_ALLOC_ARRAY(rindex, nrec + 1))
     -+		if (!XDL_ALLOC_ARRAY(rindex, xdf->nrec + 1))
     ++		if (!XDL_ALLOC_ARRAY(xdf->rindex, xdf->nrec + 1))
       			goto abort;
      -		if (!XDL_ALLOC_ARRAY(ha, nrec + 1))
     -+		if (!XDL_ALLOC_ARRAY(ha, xdf->nrec + 1))
     ++		if (!XDL_ALLOC_ARRAY(xdf->ha, xdf->nrec + 1))
       			goto abort;
       	}
       
      -	xdf->nrec = nrec;
      -	xdf->recs = recs;
     - 	xdf->rchg = rchg + 1;
     - 	xdf->rindex = rindex;
     +-	xdf->rchg = rchg + 1;
     +-	xdf->rindex = rindex;
     ++	xdf->rchg += 1;
       	xdf->nreff = 0;
     - 	xdf->ha = ha;
     +-	xdf->ha = ha;
       	xdf->dstart = 0;
      -	xdf->dend = nrec - 1;
      +	xdf->dend = xdf->nrec - 1;
       
       	return 0;
       
     -@@ xdiff/xprepare.c: abort:
     - 	xdl_free(ha);
     - 	xdl_free(rindex);
     - 	xdl_free(rchg);
     + abort:
     +-	xdl_free(ha);
     +-	xdl_free(rindex);
     +-	xdl_free(rchg);
      -	xdl_free(recs);
     -+	xdl_free(xdf->recs);
     - 	xdl_cha_free(&xdf->rcha);
     +-	xdl_cha_free(&xdf->rcha);
     ++	xdl_free_ctx(xdf);
       	return -1;
       }
     + 
     + 
     +-static void xdl_free_ctx(xdfile_t *xdf) {
     +-	xdl_free(xdf->rindex);
     +-	xdl_free(xdf->rchg - 1);
     +-	xdl_free(xdf->ha);
     +-	xdl_free(xdf->recs);
     +-	xdl_cha_free(&xdf->rcha);
     +-}
     +-
     +-
     + void xdl_free_env(xdfenv_t *xe) {
     + 
     + 	xdl_free_ctx(&xe->xdf2);
  7:  d74fd4ef67a <  -:  ----------- xdiff: conditionally use Rust's implementation of xxhash
  9:  96041a10d54 <  -:  ----------- Do support Windows again after requiring Rust
 10:  1194de3f39c <  -:  ----------- win+Meson: allow for xdiff to be compiled with MSVC
 14:  f20efdff7aa <  -:  ----------- xdiff: implement a white space iterator in Rust
  -:  ----------- > 11:  6ce0e252b38 xdiff: replace chastore with an ivec in xdfile_t
  -:  ----------- > 12:  0cfc6cf26b7 xdiff: delete nrec field from xdfile_t
  -:  ----------- > 13:  cf0387d851c xdiff: delete recs field from xdfile_t
  -:  ----------- > 14:  ea699135f95 xdiff: make xdfile_t more rust friendly
 15:  c8d41173274 ! 15:  b18544b74f3 xdiff: create line_hash() and line_equal()
     @@ Metadata
      Author: Ezekiel Newren <[email protected]>
      
       ## Commit message ##
     -    xdiff: create line_hash() and line_equal()
     +    xdiff: implement xdl_trim_ends() in Rust
      
     -    These functions use the whitespace iterator, when applicable, to hash,
     -    and compare lines.
     +    Replace the C implementation of xdl_trim_ends() with a Rust
     +    implementation.
      
          Signed-off-by: Ezekiel Newren <[email protected]>
      
       ## rust/xdiff/src/lib.rs ##
      @@
     -+use std::hash::Hasher;
     -+use xxhash_rust::xxh3::Xxh3Default;
     -+use crate::xutils::*;
     -+
     - pub mod xutils;
     ++pub mod xprepare;
     ++pub mod xtypes;
       
     - pub const XDF_IGNORE_WHITESPACE: u64 = 1 << 1;
     -@@ rust/xdiff/src/lib.rs: unsafe extern "C" fn xxh3_64(ptr: *const u8, size: usize) -> u64 {
     -     let slice = std::slice::from_raw_parts(ptr, size);
     -     xxhash_rust::xxh3::xxh3_64(slice)
     - }
     ++use crate::xprepare::trim_ends;
     ++use crate::xtypes::xdfile;
      +
      +#[no_mangle]
     -+unsafe extern "C" fn xdl_line_hash(ptr: *const u8, size: usize, flags: u64) -> u64 {
     -+    let line = std::slice::from_raw_parts(ptr, size);
     -+
     -+    line_hash(line, flags)
     -+}
     ++unsafe extern "C" fn xdl_trim_ends(xdf1: *mut xdfile, xdf2: *mut xdfile) -> i32 {
     ++    let xdf1 = xdf1.as_mut().expect("null pointer");
     ++    let xdf2 = xdf2.as_mut().expect("null pointer");
      +
     -+#[no_mangle]
     -+unsafe extern "C" fn xdl_line_equal(lhs: *const u8, lhs_len: usize, rhs: *const u8, rhs_len: usize, flags: u64) -> bool {
     -+    let lhs_line = std::slice::from_raw_parts(lhs, lhs_len);
     -+    let rhs_line = std::slice::from_raw_parts(rhs, rhs_len);
     ++    trim_ends(xdf1, xdf2);
      +
     -+    line_equal(lhs_line, rhs_line, flags)
     ++    0
      +}
      
     - ## rust/xdiff/src/xutils.rs ##
     + ## rust/xdiff/src/xprepare.rs (new) ##
      @@
     - use crate::*;
     -+use xxhash_rust::xxh3::xxh3_64;
     - 
     - pub(crate) fn xdl_isspace(v: u8) -> bool {
     -     match v {
     -@@ rust/xdiff/src/xutils.rs: where
     -     run_option0.is_none() && run_option1.is_none()
     - }
     - 
     ++use crate::xtypes::xdfile;
      +
     -+pub fn line_hash(line: &[u8], flags: u64) -> u64 {
     -+    if (flags & XDF_WHITESPACE_FLAGS) == 0 {
     -+        return xxh3_64(line);
     -+    }
     ++///
     ++/// Early trim initial and terminal matching records.
     ++///
     ++pub(crate) fn trim_ends(xdf1: &mut xdfile, xdf2: &mut xdfile) {
     ++    let mut lim = std::cmp::min(xdf1.record.len(), xdf2.record.len());
      +
     -+    let mut hasher = Xxh3Default::new();
     -+    for chunk in WhitespaceIter::new(line, flags) {
     -+        hasher.update(chunk);
     ++    for i in 0..lim {
     ++        if xdf1.record[i].ha != xdf2.record[i].ha {
     ++            xdf1.dstart = i as isize;
     ++            xdf2.dstart = i as isize;
     ++            lim -= i;
     ++            break;
     ++        }
      +    }
      +
     -+    hasher.finish()
     -+}
     -+
     -+
     -+pub fn line_equal(lhs: &[u8], rhs: &[u8], flags: u64) -> bool {
     -+    if (flags & XDF_WHITESPACE_FLAGS) == 0 {
     -+        return lhs == rhs;
     ++    for i in 0..lim {
     ++        let f1i = xdf1.record.len() - 1 - i;
     ++        let f2i = xdf2.record.len() - 1 - i;
     ++        if xdf1.record[f1i].ha != xdf2.record[f2i].ha {
     ++            xdf1.dend = f1i as isize;
     ++            xdf2.dend = f2i as isize;
     ++            break;
     ++        }
      +    }
     -+
     -+    let lhs_it = WhitespaceIter::new(lhs, flags);
     -+    let rhs_it = WhitespaceIter::new(rhs, flags);
     -+
     -+    chunked_iter_equal(lhs_it, rhs_it)
      +}
     +
     + ## rust/xdiff/src/xtypes.rs (new) ##
     +@@
     ++use interop::ivec::IVec;
      +
     ++#[repr(C)]
     ++pub(crate) struct xrecord {
     ++    pub(crate) ptr: *const u8,
     ++    pub(crate) size: usize,
     ++    pub(crate) ha: u64,
     ++}
      +
     - #[cfg(test)]
     - mod tests {
     -     use crate::*;
     ++#[repr(C)]
     ++pub(crate) struct xdfile {
     ++    pub(crate) record: IVec<xrecord>,
     ++    pub(crate) dstart: isize,
     ++    pub(crate) dend: isize,
     ++    pub(crate) rchg: *mut u8,
     ++    pub(crate) rindex: *mut usize,
     ++    pub(crate) nreff: usize,
     ++    pub(crate) ha: *mut u64,
     ++}
     +
     + ## xdiff/xprepare.c ##
     +@@ xdiff/xprepare.c: static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xd
     + }
     + 
     + 
     +-/*
     +- * Early trim initial and terminal matching records.
     +- */
     +-static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2) {
     +-	long i, lim;
     +-	xrecord_t *recs1, *recs2;
     +-
     +-	recs1 = xdf1->record.ptr;
     +-	recs2 = xdf2->record.ptr;
     +-	for (i = 0, lim = XDL_MIN(xdf1->record.length, xdf2->record.length); i < lim;
     +-	     i++, recs1++, recs2++)
     +-		if (recs1->ha != recs2->ha)
     +-			break;
     +-
     +-	xdf1->dstart = xdf2->dstart = i;
     +-
     +-	recs1 = xdf1->record.ptr + xdf1->record.length - 1;
     +-	recs2 = xdf2->record.ptr + xdf2->record.length - 1;
     +-	for (lim -= i, i = 0; i < lim; i++, recs1--, recs2--)
     +-		if (recs1->ha != recs2->ha)
     +-			break;
     +-
     +-	xdf1->dend = xdf1->record.length - i - 1;
     +-	xdf2->dend = xdf2->record.length - i - 1;
     +-
     +-	return 0;
     +-}
     ++extern i32 xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2);
     + 
     + 
     + static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) {
 16:  f7829c55871 <  -:  ----------- xdiff: optimize case where --ignore-cr-at-eol is the only whitespace flag
 17:  395609aff4b <  -:  ----------- xdiff: use rust's version of whitespace processing
Signature · 1 line
gitgitgadget
#125 lore raw

PATCH v3 1/15 doc: add a policy for using Rust

From: "brian m. carlson" <[email protected]>

Git has historically been written primarily in C, with some shell and Perl. However, C is not memory safe, which makes it more likely that security vulnerabilities or other bugs will be introduced, and it is also more verbose and less ergonomic than other, more modern languages.

One of the most common modern compiled languages which is easily interoperable with C is Rust. It is popular (the most admired language on the 2024 Stack Overflow Developer Survey), efficient, portable, and robust.

Introduce a document laying out the incremental introduction of Rust to Git and provide a detailed rationale for doing so, including the points above. Propose a design for this approach that addresses the needs of downstreams and distributors, as well as contributors.

Since we don't want to carry both a C and Rust version of code and want to be able to add new features only in Rust, mention that Rust is a required part of our platform support policy.

It should be noted that a recent discussion at the Berlin Git Merge Contributor Summit found widespread support for the addition of Rust to Git. While of course not all contributors were represented, the proposal appeared to have the support of a majority of active contributors.

[en: Added some comments about types, and changed the recommondations
about cbindgen, bindgen, rustix, libc.]

---
 Documentation/Makefile                        |   1 +
 Documentation/technical/platform-support.adoc |   2 +
 Documentation/technical/rust-support.adoc     | 142 ++++++++++++++++++
 3 files changed, 145 insertions(+)
 create mode 100644 Documentation/technical/rust-support.adoc
Documentation/Makefile +1 -0
index b109d25e9c80..066b761c01b9 100644
@@ -127,6 +127,7 @@ TECH_DOCS += technical/parallel-checkout
 TECH_DOCS += technical/partial-clone
 TECH_DOCS += technical/platform-support
 TECH_DOCS += technical/racy-git
+TECH_DOCS += technical/rust-support
 TECH_DOCS += technical/reftable
 TECH_DOCS += technical/scalar
 TECH_DOCS += technical/send-pack-pipeline
Documentation/technical/platform-support.adoc +2 -0
index 0a2fb28d6277..dc71672dcb57 100644
@@ -33,6 +33,8 @@ meet the following minimum requirements:
 
 * Has active security support (taking security releases of dependencies, etc)
 
+* Supports Rust and the toolchain version specified in link:rust-support.adoc[].
+
 These requirements are a starting point, and not sufficient on their own for the
 Git community to be enthusiastic about supporting your platform. Maintainers of
 platforms which do meet these requirements can follow the steps below to make it
Documentation/technical/rust-support.adoc new file +142 -0

142 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#126 lore raw

PATCH v3 2/15 xdiff: introduce rust

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

Upcoming patches will simplify xdiff, while also porting parts of it to Rust. In preparation, add some stubs and setup the Rust build. For now, it is easier to let cargo build rust and have make or meson merely link against the static library that cargo builds. In line with ongoing libification efforts, use multiple crates to allow more modularity on the Rust side. xdiff is the crate that this series will focus on, but we also introduce the interop crate for future patch series.

In order to facilitate interoperability between C and Rust, introduce C definitions for Rust primitive types in git-compat-util.h.

---
 .gitignore              |  3 +++
 Makefile                | 53 ++++++++++++++++++++++++++++----------
 build_rust.sh           | 57 +++++++++++++++++++++++++++++++++++++++++
 git-compat-util.h       | 17 ++++++++++++
 meson.build             | 52 +++++++++++++++++++++++++++++++------
 rust/Cargo.toml         |  6 +++++
 rust/interop/Cargo.toml | 14 ++++++++++
 rust/interop/src/lib.rs |  0
 rust/xdiff/Cargo.toml   | 15 +++++++++++
 rust/xdiff/src/lib.rs   |  0
 10 files changed, 196 insertions(+), 21 deletions(-)
 create mode 100755 build_rust.sh
 create mode 100644 rust/Cargo.toml
 create mode 100644 rust/interop/Cargo.toml
 create mode 100644 rust/interop/src/lib.rs
 create mode 100644 rust/xdiff/Cargo.toml
 create mode 100644 rust/xdiff/src/lib.rs
.gitignore +3 -0
index 04c444404e4b..ff81e3580c4e 100644
@@ -254,3 +254,6 @@ Release/
 /contrib/buildsystems/out
 /contrib/libgit-rs/target
 /contrib/libgit-sys/target
+/.idea/
+/rust/target/
+/rust/Cargo.lock
Makefile +40 -13

128 lines not on this page · raw message

build_rust.sh new file +57 -0

57 lines not on this page · raw message

git-compat-util.h +17 -0
index 4678e21c4cb8..82dc99764ac0 100644
@@ -196,6 +196,23 @@ static inline int is_xplatform_dir_sep(int c)
 #include "compat/msvc.h"
 #endif
 
+/* rust types */
+typedef uint8_t   u8;
+typedef uint16_t  u16;
+typedef uint32_t  u32;
+typedef uint64_t  u64;
+
+typedef int8_t    i8;
+typedef int16_t   i16;
+typedef int32_t   i32;
+typedef int64_t   i64;
+
+typedef float     f32;
+typedef double    f64;
+
+typedef size_t    usize;
+typedef ptrdiff_t isize;
+
 /* used on Mac OS X */
 #ifdef PRECOMPOSE_UNICODE
 #include "compat/precompose_utf8.h"
meson.build +44 -8

64 lines not on this page · raw message

rust/Cargo.toml new file +6 -0
new file mode 100644
index 000000000000..ed3d79d7f827
@@ -0,0 +1,6 @@
+[workspace]
+members = [
+    "xdiff",
+    "interop",
+]
+resolver = "2"
rust/interop/Cargo.toml new file +14 -0
new file mode 100644
index 000000000000..045e3b01cfad
@@ -0,0 +1,14 @@
+[package]
+name = "interop"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+name = "interop"
+path = "src/lib.rs"
+## staticlib to generate xdiff.a for use by gcc
+## cdylib (optional) to generate xdiff.so for use by gcc
+## rlib is required by the rust unit tests
+crate-type = ["staticlib", "rlib"]
+
+[dependencies]
rust/interop/src/lib.rs new file +0 -0
new file mode 100644
index 000000000000..e69de29bb2d1
rust/xdiff/Cargo.toml new file +15 -0
new file mode 100644
index 000000000000..eb7966aada64
@@ -0,0 +1,15 @@
+[package]
+name = "xdiff"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+name = "xdiff"
+path = "src/lib.rs"
+## staticlib to generate xdiff.a for use by gcc
+## cdylib (optional) to generate xdiff.so for use by gcc
+## rlib is required by the rust unit tests
+crate-type = ["staticlib", "rlib"]
+
+[dependencies]
+interop = { path = "../interop" }
rust/xdiff/src/lib.rs new file +0 -0
new file mode 100644
index 000000000000..e69de29bb2d1
Signature · 1 line
gitgitgadget
#178 lore raw

PATCH v3 3/15 github workflows: install rust

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

Prefer using actions-rs/toolchain@v1 where possible to install rustup, but for docker targets use a script to install rustup. Consolidate the Rust toolchain definitions in main.yaml. Use install-rust-toolchain.sh to ensure the correct toolchain is used. Five overrides are used in main.yaml:

* On Windows: Rust didn't resolve the bcrypt library on Windows correctly until version 1.78.0. Also since rustup mis-identifies the Rust toolchain, the Rust target triple must be set to x86_64-pc-windows-gnu for make (win build), and x86_64-pc-windows-msvc for meson (win+Meson build).
* On musl: libc differences, such as ftruncate64 vs ftruncate, were not accounted for until Rust version 1.72.0. No older version of Rust will work on musl for our needs.
* In a 32-bit docker container running on a 64-bit host, we need to override the Rust target triple. This is because rustup asks the kernel for the bitness of the system and it says 64, even though the container is 32-bit. This also allows us to remove the BITNESS environment variable in ci/lib.sh.

The logic for selecting library names was initially provided in a patch from Johannes, but was reworked and squashed into this commit.

---
 .github/workflows/main.yml   | 61 +++++++++++++++++++++++++++++++++++-
 ci/install-dependencies.sh   | 14 ++++-----
 ci/install-rust-toolchain.sh | 30 ++++++++++++++++++
 ci/install-rustup.sh         | 25 +++++++++++++++
 ci/lib.sh                    |  1 +
 ci/make-test-artifacts.sh    |  9 ++++++
 ci/run-build-and-tests.sh    | 13 ++++++++
 7 files changed, 145 insertions(+), 8 deletions(-)
 create mode 100755 ci/install-rust-toolchain.sh
 create mode 100755 ci/install-rustup.sh
.github/workflows/main.yml +60 -1
index 7dbf9f7f123c..2fa5fab0fa83 100644
@@ -26,6 +26,13 @@ jobs:
     outputs:
       enabled: ${{ steps.check-ref.outputs.enabled }}${{ steps.skip-if-redundant.outputs.enabled }}
       skip_concurrent: ${{ steps.check-ref.outputs.skip_concurrent }}
+      rust_version_minimum: 1.61.0
+      rust_version_windows: 1.78.0
+      rust_version_musl: 1.72.0
+      ## the rust target is inferred by rustup unless specified
+      rust_target_windows_make: x86_64-pc-windows-gnu
+      rust_target_windows_meson: x86_64-pc-windows-msvc
+      rust_target_32bit_linux: i686-unknown-linux-gnu
     steps:
       - name: try to clone ci-config branch
         run: |
@@ -108,12 +115,26 @@ jobs:
     needs: ci-config
     if: needs.ci-config.outputs.enabled == 'yes'
     runs-on: windows-latest
+    env:
+      CARGO_HOME: "/c/Users/runneradmin/.cargo"
     concurrency:
       group: windows-build-${{ github.ref }}
       cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }}
     steps:
     - uses: actions/checkout@v4
     - uses: git-for-windows/setup-git-for-windows-sdk@v1
+    - name: Install rustup via github actions
+      uses: actions-rs/toolchain@v1
+      with:
+        toolchain: stable
+        profile: minimal
+        override: false
+    - name: Install Rust toolchain
+      shell: bash
+      env:
+        RUST_VERSION: ${{ needs.ci-config.outputs.rust_version_windows }}
+        RUST_TARGET: ${{ needs.ci-config.outputs.rust_target_windows_make }}
+      run: ci/install-rust-toolchain.sh
     - name: build
       shell: bash
       env:
@@ -254,12 +275,26 @@ jobs:
     needs: ci-config
     if: needs.ci-config.outputs.enabled == 'yes'
     runs-on: windows-latest
+    env:
+      CARGO_HOME: "/c/Users/runneradmin/.cargo"
     concurrency:
       group: windows-meson-build-${{ github.ref }}
       cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }}
     steps:
     - uses: actions/checkout@v4
     - uses: actions/setup-python@v5
+    - name: Install rustup via github actions
+      uses: actions-rs/toolchain@v1
+      with:
+        toolchain: stable
+        profile: minimal
+        override: false
+    - name: Install Rust toolchain
+      shell: bash
+      env:
+        RUST_VERSION: ${{ needs.ci-config.outputs.rust_version_windows }}
+        RUST_TARGET: ${{ needs.ci-config.outputs.rust_target_windows_meson }}
+      run: ci/install-rust-toolchain.sh
     - name: Set up dependencies
       shell: pwsh
       run: pip install meson ninja
@@ -329,11 +364,24 @@ jobs:
       jobname: ${{matrix.vector.jobname}}
       CI_JOB_IMAGE: ${{matrix.vector.pool}}
       TEST_OUTPUT_DIRECTORY: ${{github.workspace}}/t
+      CARGO_HOME: "/Users/runner/.cargo"
     runs-on: ${{matrix.vector.pool}}
     steps:
     - uses: actions/checkout@v4
     - run: ci/install-dependencies.sh
-    - run: ci/run-build-and-tests.sh
+    - name: Install rustup via github actions
+      uses: actions-rs/toolchain@v1
+      with:
+        toolchain: stable
+        profile: minimal
+        override: false
+    - name: Install Rust toolchain
+      shell: bash
+      env:
+        RUST_VERSION: ${{ needs.ci-config.outputs.rust_version_minimum }}
+      run: ci/install-rust-toolchain.sh
+    - name: Run build and tests
+      run: ci/run-build-and-tests.sh
     - name: print test failures
       if: failure() && env.FAILED_TEST_ARTIFACTS != ''
       run: ci/print-test-failures.sh
@@ -393,9 +441,11 @@ jobs:
           cc: gcc
         - jobname: linux-musl-meson
           image: alpine:latest
+          rust_version_override: ${{ needs.ci-config.outputs.rust_version_musl }}
         # Supported until 2025-04-02.
         - jobname: linux32
           image: i386/ubuntu:focal
+          rust_target_override: ${{ needs.ci-config.outputs.rust_target_32bit_linux }}
         - jobname: pedantic
           image: fedora:latest
         # A RHEL 8 compatible distro.  Supported until 2029-05-31.
@@ -408,7 +458,9 @@ jobs:
       jobname: ${{matrix.vector.jobname}}
       CC: ${{matrix.vector.cc}}
       CI_JOB_IMAGE: ${{matrix.vector.image}}
+      CI_IS_DOCKER: "true"
       CUSTOM_PATH: /custom
+      CARGO_HOME: /home/builder/.cargo
     runs-on: ubuntu-latest
     container: ${{matrix.vector.image}}
     steps:
@@ -433,6 +485,13 @@ jobs:
     - run: ci/install-dependencies.sh
     - run: useradd builder --create-home
     - run: chown -R builder .
+    - name: Install rustup via script
+      run: sudo --preserve-env --set-home --user=builder ci/install-rustup.sh
+    - name: Install Rust toolchain
+      env:
+        RUST_VERSION: ${{ matrix.vector.rust_version_override || needs.ci-config.outputs.rust_version_minimum }}
+        RUST_TARGET: ${{ matrix.vector.rust_target_override || '' }}
+      run: sudo --preserve-env --set-home --user=builder ci/install-rust-toolchain.sh
     - run: sudo --preserve-env --set-home --user=builder ci/run-build-and-tests.sh
     - name: print test failures
       if: failure() && env.FAILED_TEST_ARTIFACTS != ''
ci/install-dependencies.sh +7 -7
index d061a4729339..7801075821ba 100755
@@ -24,14 +24,14 @@ fi
 
 case "$distro" in
 alpine-*)
-	apk add --update shadow sudo meson ninja-build gcc libc-dev curl-dev openssl-dev expat-dev gettext \
+	apk add --update shadow sudo meson ninja-build gcc libc-dev curl curl-dev openssl-dev expat-dev gettext \
 		zlib-ng-dev pcre2-dev python3 musl-libintl perl-utils ncurses \
 		apache2 apache2-http2 apache2-proxy apache2-ssl apache2-webdav apr-util-dbd_sqlite3 \
 		bash cvs gnupg perl-cgi perl-dbd-sqlite perl-io-tty >/dev/null
 	;;
 fedora-*|almalinux-*)
 	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 gcc findutils diffutils perl python3 gawk gettext zlib-devel expat-devel openssl-devel curl curl-devel pcre2-devel >/dev/null
 	;;
 ubuntu-*|i386/ubuntu-*|debian-*)
 	# Required so that apt doesn't wait for user input on certain packages.
@@ -55,8 +55,8 @@ ubuntu-*|i386/ubuntu-*|debian-*)
 	sudo apt-get -q update
 	sudo apt-get -q -y install \
 		$LANGUAGES apache2 cvs cvsps git gnupg $SVN \
-		make libssl-dev libcurl4-openssl-dev libexpat-dev wget sudo default-jre \
-		tcl tk gettext zlib1g-dev perl-modules liberror-perl libauthen-sasl-perl \
+		make libssl-dev curl libcurl4-openssl-dev libexpat-dev wget sudo default-jre \
+		tcl tk gettext zlib1g 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 \
 		${CC_PACKAGE:-${CC:-gcc}} $PYTHON_PACKAGE
@@ -121,13 +121,13 @@ ClangFormat)
 	;;
 StaticAnalysis)
 	sudo apt-get -q update
-	sudo apt-get -q -y install coccinelle libcurl4-openssl-dev libssl-dev \
+	sudo apt-get -q -y install coccinelle curl libcurl4-openssl-dev libssl-dev \
 		libexpat-dev gettext make
 	;;
 sparse)
 	sudo apt-get -q update -q
-	sudo apt-get -q -y install libssl-dev libcurl4-openssl-dev \
-		libexpat-dev gettext zlib1g-dev sparse
+	sudo apt-get -q -y install libssl-dev curl libcurl4-openssl-dev \
+		libexpat-dev gettext zlib1g zlib1g-dev sparse
 	;;
 Documentation)
 	sudo apt-get -q update
ci/install-rust-toolchain.sh new file +30 -0
new file mode 100755
index 000000000000..06a29c4cfa17
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+if [ "$CARGO_HOME" = "" ]; then
+  echo >&2 "::error:: CARGO_HOME is not set"
+  exit 2
+fi
+export PATH="$CARGO_HOME/bin:$PATH"
+rustup -vV || exit $?
+
+## Enforce the correct Rust toolchain
+rustup override unset || true
+
+## install a specific version of rust
+if [ "$RUST_TARGET" != "" ]; then
+  rustup default --force-non-host "$RUST_VERSION-$RUST_TARGET" || exit $?
+else
+  rustup default "$RUST_VERSION" || exit $?
+fi
+
+rustc -vV || exit $?
+
+RE_RUST_TARGET="$RUST_TARGET"
+if [ "$RUST_TARGET" = "" ]; then
+  RE_RUST_TARGET="[^ ]+"
+fi
+
+if ! rustup show active-toolchain | grep -E "^$RUST_VERSION-$RE_RUST_TARGET \(default\)$"; then
+  echo >&2 "::error:: wrong Rust toolchain, active-toolchain: $(rustup show active-toolchain)"
+  exit 3
+fi
ci/install-rustup.sh new file +25 -0
new file mode 100755
index 000000000000..0036231aeea7
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+## github workflows actions-rs/toolchain@v1 doesn't work for docker
+## targets. This script should only be used if the ci pipeline
+## doesn't support installing rust on a particular target.
+
+if [ "$(id -u)" -eq 0 ]; then
+  echo >&2 "::warning:: installing rust as root"
+fi
+
+if [ "$CARGO_HOME" = "" ]; then
+  echo >&2 "::error:: CARGO_HOME is not set"
+  exit 2
+fi
+
+export RUSTUP_HOME=$CARGO_HOME
+
+## install rustup
+curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --default-toolchain none -y
+if [ ! -f $CARGO_HOME/env ]; then
+  echo "PATH=$CARGO_HOME/bin:\$PATH" > $CARGO_HOME/env
+fi
+. $CARGO_HOME/env
+
+rustup -vV
ci/lib.sh +1 -0
index f561884d4016..a7992b22fdc9 100755
@@ -1,5 +1,6 @@
 # Library of functions shared by all CI scripts
 
+
 if test true = "$GITHUB_ACTIONS"
 then
 	begin_group () {
ci/make-test-artifacts.sh +9 -0
index 74141af0cc74..e37ed7030cdf 100755
@@ -7,6 +7,15 @@ mkdir -p "$1" # in case ci/lib.sh decides to quit early
 
 . ${0%/*}/lib.sh
 
+## ensure rustup is in the PATH variable
+if [ "$CARGO_HOME" = "" ]; then
+  echo >&2 "::error:: CARGO_HOME is not set"
+  exit 2
+fi
+export PATH="$CARGO_HOME/bin:$PATH"
+
+rustc -vV
+
 group Build make artifacts-tar ARTIFACTS_DIRECTORY="$1"
 
 check_unignored_build_artifacts
ci/run-build-and-tests.sh +13 -0
index 01823fd0f140..22b61e2812db 100755
@@ -5,6 +5,15 @@
 
 . ${0%/*}/lib.sh
 
+## ensure rustup is in the PATH variable
+if [ "$CARGO_HOME" = "" ]; then
+  echo >&2 "::error:: CARGO_HOME is not set"
+  exit 2
+fi
+. $CARGO_HOME/env
+
+rustc -vV || exit $?
+
 run_tests=t
 
 case "$jobname" in
@@ -72,5 +81,9 @@ case "$jobname" in
 	;;
 esac
 
+if [ -d "$CARGO_HOME" ]; then
+  rm -rf $CARGO_HOME
+fi
+
 check_unignored_build_artifacts
 save_good_tree
Signature · 1 line
gitgitgadget
#179 lore raw

PATCH v3 4/15 win+Meson: do allow linking with the Rust-built xdiff

In reply to Ezekiel Newren via GitGitGadget

From: Johannes Schindelin <[email protected]>

When linking against the Rust-built xdiff, there is now a new required
dependency: Without _also_ linking to the system library userenv, the compile would fail with this error message:

xdiff.lib(std-c85e9beb7923f636.std.df32d1bc89881d89-cgu.0.rcgu.o) :
error LNK2019: unresolved external symbol __imp_GetUserProfileDirectoryW referenced in function _ZN3std3env8home_dir17hfd1c3b6676cd78f6E

Therefore, just like we do in case of Makefile-based builds on Windows, we now also link to that library when building with Meson.

Note that if we only have Rust depend upon libuserenv then at link time GCC would complain about:

undefined reference to `GetUserProfileDirectoryW'

Apparently there is _some_ closure that gets compiled in that requires this function, and that in turn forces Git to link to libuserenv.

This is a new requirement, and therefore has not been made part of the "minimal Git for Windows SDK".

In the near future, I intend to include it, but for now let's just ensure that the file is added manually if it is missing.

[en: Squashed a few of Johannes's patches, and moved lib userenv
handling from an earlier patch]

---
 .github/workflows/main.yml | 8 ++++++++
 config.mak.uname           | 4 ++++
 meson.build                | 1 +
 3 files changed, 13 insertions(+)
.github/workflows/main.yml +8 -0
index 2fa5fab0fa83..0f7396621df8 100644
@@ -123,6 +123,14 @@ jobs:
     steps:
     - uses: actions/checkout@v4
     - uses: git-for-windows/setup-git-for-windows-sdk@v1
+    - name: ensure that libuserenv.a is present
+      shell: bash
+      run: |
+        cd /mingw64/lib && {
+          test -f libuserenv.a ||
+          /c/Program\ Files/Git/mingw64/bin/curl -Lo libuserenv.a \
+            https://github.com/git-for-windows/git-sdk-64/raw/HEAD/mingw64/lib/libuserenv.a
+        }
     - name: Install rustup via github actions
       uses: actions-rs/toolchain@v1
       with:
config.mak.uname +4 -0
index 3e26bb074a4b..6805e3778a16 100644
@@ -740,6 +740,10 @@ ifeq ($(uname_S),MINGW)
 		COMPAT_CFLAGS += -D_USE_32BIT_TIME_T
 		BASIC_LDFLAGS += -Wl,--large-address-aware
         endif
+
+	# Unfortunately now needed because of Rust
+	EXTLIBS += -luserenv
+
 	CC = gcc
 	COMPAT_CFLAGS += -D__USE_MINGW_ANSI_STDIO=0 -DDETECT_MSYS_TTY \
 		-fstack-protector-strong
meson.build +1 -0
index 324f968338b9..5aa9901bfc0f 100644
@@ -1267,6 +1267,7 @@ elif host_machine.system() == 'windows'
   ]
 
   libgit_dependencies += compiler.find_library('ntdll')
+  libgit_dependencies += compiler.find_library('userenv')
   libgit_include_directories += 'compat/win32'
   if compiler.get_id() == 'msvc'
     libgit_include_directories += 'compat/vcbuild/include'
Signature · 1 line
gitgitgadget
#180 lore raw

PATCH v3 5/15 github workflows: upload Cargo.lock

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

Make each ci workflow upload its Cargo.lock file as a build artifact so that we can audit build dependencies.

---
 .github/workflows/main.yml | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)
.github/workflows/main.yml +20 -0
index 0f7396621df8..0f8785a676c3 100644
@@ -156,6 +156,11 @@ jobs:
       with:
         name: windows-artifacts
         path: artifacts
+    - name: upload Cargo.lock
+      uses: actions/upload-artifact@v4
+      with:
+        name: cargo-lock-windows
+        path: rust/Cargo.lock
   windows-test:
     name: win test
     runs-on: windows-latest
@@ -317,6 +322,11 @@ jobs:
       with:
         name: windows-meson-artifacts
         path: build
+    - name: Upload Cargo.lock
+      uses: actions/upload-artifact@v4
+      with:
+        name: cargo-lock-windows-meson
+        path: rust/Cargo.lock
   windows-meson-test:
     name: win+Meson test
     runs-on: windows-latest
@@ -399,6 +409,11 @@ jobs:
       with:
         name: failed-tests-${{matrix.vector.jobname}}
         path: ${{env.FAILED_TEST_ARTIFACTS}}
+    - name: Upload Cargo.lock
+      uses: actions/upload-artifact@v4
+      with:
+        name: cargo-lock-${{matrix.vector.jobname}}
+        path: rust/Cargo.lock
   fuzz-smoke-test:
     name: fuzz smoke test
     needs: ci-config
@@ -510,6 +525,11 @@ jobs:
       with:
         name: failed-tests-${{matrix.vector.jobname}}
         path: ${{env.FAILED_TEST_ARTIFACTS}}
+    - name: Upload Cargo.lock
+      uses: actions/upload-artifact@v4
+      with:
+        name: cargo-lock-${{matrix.vector.jobname}}
+        path: rust/Cargo.lock
   static-analysis:
     needs: ci-config
     if: needs.ci-config.outputs.enabled == 'yes'
Signature · 1 line
gitgitgadget
#181 lore raw

PATCH v3 6/15 ivec: create a vector type that is interoperable between C and Rust

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

Trying to use Rust's Vec in C, or git's ALLOC_GROW() macros (via wrapper functions) in Rust is painful because:

* C doing vector things the Rust way would require wrapper functions, and Rust doing vector things the C way would require wrapper functions, so ivec was created to ensure a consistent contract between the 2 languages for how to manipulate a vector.
* Currently, Rust defines its own 'Vec' type that is generic, but its memory allocator and struct layout weren't designed for interoperability with C (or any language for that matter), meaning that the C side cannot push to or expand a 'Vec' without defining wrapper functions in Rust that C can call. Without special care, the two languages might use different allocators (malloc/free on the C side, and possibly something else in Rust), which would make it difficult for a function in one language to free elements allocated by a call from a function in the other language.
* Similarly, git defines ALLOC_GROW() and related macros in git-compat-util.h. While we could add functions allowing Rust to invoke something similar to those macros, passing three variables (pointer, length, allocated_size) instead of a single variable (vector) across the language boundary requires more cognitive overhead for readers to keep track of and makes it easier to make mistakes. Further, for low-level components that we want to eventually convert to pure Rust, such triplets would feel very out of place.

To address these issue, introduce a new type, ivec -- short for interoperable vector. (We refer to it as 'ivec' generally, though on the Rust side the struct is called IVec to match Rust style.) This new type is specifically designed for FFI purposes, so that both languages handle the vector in the same way, though it could be used on either side independently. This type is designed such that it can easily be replaced by a standard Rust 'Vec' once interoperability is no longer a concern.

One particular item to note is that Git's macros to handle vec operations infer the amount that a vec needs to grow from the size of a pointer, but that makes it somewhat specific to the macros used in C. To avoid defining every ivec function as a macro I opted to also include an element_size field that allows concrete functions like push() to know how much to grow the memory. This element_size also helps in verifying that the ivec is correct when passing from C to Rust.

---
 Makefile                 |  16 +-
 interop/ivec.c           | 151 +++++++++++++
 interop/ivec.h           |  52 +++++
 meson.build              |   1 +
 rust/interop/src/ivec.rs | 462 +++++++++++++++++++++++++++++++++++++++
 rust/interop/src/lib.rs  |  10 +
 rust/xdiff/src/lib.rs    |   1 +
 7 files changed, 690 insertions(+), 3 deletions(-)
 create mode 100644 interop/ivec.c
 create mode 100644 interop/ivec.h
 create mode 100644 rust/interop/src/ivec.rs
Makefile +13 -3
index 1ec0c1ee6603..29a53520fd28 100644
@@ -672,6 +672,7 @@ BUILTIN_OBJS =
 BUILT_INS =
 COMPAT_CFLAGS =
 COMPAT_OBJS =
+INTEROP_OBJS =
 XDIFF_OBJS =
 GENERATED_H =
 EXTRA_CPPFLAGS =
@@ -918,6 +919,7 @@ export PYTHON_PATH
 TEST_SHELL_PATH = $(SHELL_PATH)
 
 LIB_FILE = libgit.a
+INTEROP_LIB = interop/lib.a
 XDIFF_LIB = xdiff/lib.a
 
 EXTLIBS =
@@ -1412,7 +1414,7 @@ UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o
 UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.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)
+GITLIBS = common-main.o $(LIB_FILE) $(INTEROP_LIB) $(XDIFF_LIB) $(REFTABLE_LIB) $(LIB_FILE)
 
 
 GIT_USER_AGENT = git/$(GIT_VERSION)
@@ -2747,6 +2749,10 @@ reconfigure config.mak.autogen: config.status
 .PHONY: reconfigure # This is a convenience target.
 endif
 
+INTEROP_OBJS += interop/ivec.o
+.PHONY: interop-objs
+interop-objs: $(INTEROP_OBJS)
+
 XDIFF_OBJS += xdiff/xdiffi.o
 XDIFF_OBJS += xdiff/xemit.o
 XDIFF_OBJS += xdiff/xhistogram.o
@@ -2791,6 +2797,7 @@ OBJECTS += $(GIT_OBJS)
 OBJECTS += $(SCALAR_OBJS)
 OBJECTS += $(PROGRAM_OBJS)
 OBJECTS += $(TEST_OBJS)
+OBJECTS += $(INTEROP_OBJS)
 OBJECTS += $(XDIFF_OBJS)
 OBJECTS += $(FUZZ_OBJS)
 OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS)
@@ -2945,7 +2952,10 @@ scalar$X: scalar.o GIT-LDFLAGS $(GITLIBS) compile_rust
 $(LIB_FILE): $(LIB_OBJS)
 	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
 
-$(XDIFF_LIB): $(XDIFF_OBJS)
+$(INTEROP_LIB): $(INTEROP_OBJS)
+	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
+
+$(XDIFF_LIB): $(INTEROP_OBJS) $(XDIFF_OBJS)
 	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
 
 
@@ -3790,7 +3800,7 @@ clean: profile-clean coverage-clean cocciclean rustclean
 	$(RM) git.rc git.res
 	$(RM) $(OBJECTS)
 	$(RM) headless-git.o
-	$(RM) $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB)
+	$(RM) $(LIB_FILE) $(INTEROP_LIB) $(XDIFF_LIB) $(REFTABLE_LIB)
 	$(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS)
 	$(RM) $(TEST_PROGRAMS)
 	$(RM) $(FUZZ_PROGRAMS)
interop/ivec.c new file +151 -0

151 lines not on this page · raw message

interop/ivec.h new file +52 -0

52 lines not on this page · raw message

meson.build +1 -0
index 5aa9901bfc0f..fc7c133f79d8 100644
@@ -395,6 +395,7 @@ libgit_sources = [
   'hex-ll.c',
   'hook.c',
   'ident.c',
+  'interop/ivec.c',
   'json-writer.c',
   'kwset.c',
   'levenshtein.c',
rust/interop/src/ivec.rs new file +462 -0

462 lines not on this page · raw message

rust/interop/src/lib.rs +10 -0
index e69de29bb2d1..4850f66e5bd9 100644
@@ -0,0 +1,10 @@
+pub mod ivec;
+
+use std::ffi::c_void;
+
+extern "C" {
+    pub fn malloc(size: usize) -> *mut c_void;
+    pub fn calloc(nmemb: usize, size: usize) -> *mut c_void;
+    pub fn realloc(ptr: *mut c_void, size: usize) -> *mut c_void;
+    pub fn free(ptr: *mut c_void);
+}
rust/xdiff/src/lib.rs +1 -0
index e69de29bb2d1..8b137891791f 100644
@@ -0,0 +1 @@
+
Signature · 1 line
gitgitgadget
#195 lore raw

PATCH v3 7/15 xdiff/xprepare: remove superfluous forward declarations

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

Move xdl_prepare_env() later in the file to avoid the need for forward declarations.

---
 xdiff/xprepare.c | 116 ++++++++++++++++++++---------------------------
 1 file changed, 50 insertions(+), 66 deletions(-)
xdiff/xprepare.c +50 -66
index e1d4017b2dde..a45c5ee208c8 100644
@@ -53,21 +53,6 @@ typedef struct s_xdlclassifier {
 
 
 
-static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags);
-static void xdl_free_classifier(xdlclassifier_t *cf);
-static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash,
-			       unsigned int hbits, xrecord_t *rec);
-static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
-			   xdlclassifier_t *cf, xdfile_t *xdf);
-static void xdl_free_ctx(xdfile_t *xdf);
-static int xdl_clean_mmatch(char const *dis, long i, long s, long e);
-static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2);
-static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2);
-static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2);
-
-
-
-
 static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags) {
 	cf->flags = flags;
 
@@ -242,57 +227,6 @@ static void xdl_free_ctx(xdfile_t *xdf) {
 }
 
 
-int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
-		    xdfenv_t *xe) {
-	long enl1, enl2, sample;
-	xdlclassifier_t cf;
-
-	memset(&cf, 0, sizeof(cf));
-
-	/*
-	 * For histogram diff, we can afford a smaller sample size and
-	 * thus a poorer estimate of the number of lines, as the hash
-	 * table (rhash) won't be filled up/grown. The number of lines
-	 * (nrecs) will be updated correctly anyway by
-	 * xdl_prepare_ctx().
-	 */
-	sample = (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF
-		  ? XDL_GUESS_NLINES2 : XDL_GUESS_NLINES1);
-
-	enl1 = xdl_guess_lines(mf1, sample) + 1;
-	enl2 = xdl_guess_lines(mf2, sample) + 1;
-
-	if (xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0)
-		return -1;
-
-	if (xdl_prepare_ctx(1, mf1, enl1, xpp, &cf, &xe->xdf1) < 0) {
-
-		xdl_free_classifier(&cf);
-		return -1;
-	}
-	if (xdl_prepare_ctx(2, mf2, enl2, xpp, &cf, &xe->xdf2) < 0) {
-
-		xdl_free_ctx(&xe->xdf1);
-		xdl_free_classifier(&cf);
-		return -1;
-	}
-
-	if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) &&
-	    (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF) &&
-	    xdl_optimize_ctxs(&cf, &xe->xdf1, &xe->xdf2) < 0) {
-
-		xdl_free_ctx(&xe->xdf2);
-		xdl_free_ctx(&xe->xdf1);
-		xdl_free_classifier(&cf);
-		return -1;
-	}
-
-	xdl_free_classifier(&cf);
-
-	return 0;
-}
-
-
 void xdl_free_env(xdfenv_t *xe) {
 
 	xdl_free_ctx(&xe->xdf2);
@@ -460,3 +394,53 @@ static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2
 
 	return 0;
 }
+
+int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+		    xdfenv_t *xe) {
+	long enl1, enl2, sample;
+	xdlclassifier_t cf;
+
+	memset(&cf, 0, sizeof(cf));
+
+	/*
+	 * For histogram diff, we can afford a smaller sample size and
+	 * thus a poorer estimate of the number of lines, as the hash
+	 * table (rhash) won't be filled up/grown. The number of lines
+	 * (nrecs) will be updated correctly anyway by
+	 * xdl_prepare_ctx().
+	 */
+	sample = (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF
+		  ? XDL_GUESS_NLINES2 : XDL_GUESS_NLINES1);
+
+	enl1 = xdl_guess_lines(mf1, sample) + 1;
+	enl2 = xdl_guess_lines(mf2, sample) + 1;
+
+	if (xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0)
+		return -1;
+
+	if (xdl_prepare_ctx(1, mf1, enl1, xpp, &cf, &xe->xdf1) < 0) {
+
+		xdl_free_classifier(&cf);
+		return -1;
+	}
+	if (xdl_prepare_ctx(2, mf2, enl2, xpp, &cf, &xe->xdf2) < 0) {
+
+		xdl_free_ctx(&xe->xdf1);
+		xdl_free_classifier(&cf);
+		return -1;
+	}
+
+	if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) &&
+	    (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF) &&
+	    xdl_optimize_ctxs(&cf, &xe->xdf1, &xe->xdf2) < 0) {
+
+		xdl_free_ctx(&xe->xdf2);
+		xdl_free_ctx(&xe->xdf1);
+		xdl_free_classifier(&cf);
+		return -1;
+	    }
+
+	xdl_free_classifier(&cf);
+
+	return 0;
+}
Signature · 1 line
gitgitgadget
#196 lore raw

PATCH v3 8/15 xdiff: delete unnecessary fields from xrecord_t and xdfile_t

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

xrecord_t.next, xdfile_t.hbits, xdfile_t.rhash are initialized, but never used for anything by the code. Remove them.

---
 xdiff/xprepare.c | 24 +++---------------------
 xdiff/xtypes.h   |  3 ---
 2 files changed, 3 insertions(+), 24 deletions(-)
xdiff/xprepare.c +3 -21
index a45c5ee208c8..ad356281f939 100644
@@ -91,8 +91,7 @@ static void xdl_free_classifier(xdlclassifier_t *cf) {
 }
 
 
-static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash,
-			       unsigned int hbits, xrecord_t *rec) {
+static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t *rec) {
 	long hi;
 	char const *line;
 	xdlclass_t *rcrec;
@@ -126,23 +125,17 @@ static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t
 
 	rec->ha = (unsigned long) rcrec->idx;
 
-	hi = (long) XDL_HASHLONG(rec->ha, hbits);
-	rec->next = rhash[hi];
-	rhash[hi] = rec;
-
 	return 0;
 }
 
 
 static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
 			   xdlclassifier_t *cf, xdfile_t *xdf) {
-	unsigned int hbits;
-	long nrec, hsize, bsize;
+	long nrec, bsize;
 	unsigned long hav;
 	char const *blk, *cur, *top, *prev;
 	xrecord_t *crec;
 	xrecord_t **recs;
-	xrecord_t **rhash;
 	unsigned long *ha;
 	char *rchg;
 	long *rindex;
@@ -150,7 +143,6 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
 	ha = NULL;
 	rindex = NULL;
 	rchg = NULL;
-	rhash = NULL;
 	recs = NULL;
 
 	if (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0)
@@ -158,11 +150,6 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
 	if (!XDL_ALLOC_ARRAY(recs, narec))
 		goto abort;
 
-	hbits = xdl_hashbits((unsigned int) narec);
-	hsize = 1 << hbits;
-	if (!XDL_CALLOC_ARRAY(rhash, hsize))
-		goto abort;
-
 	nrec = 0;
 	if ((cur = blk = xdl_mmfile_first(mf, &bsize))) {
 		for (top = blk + bsize; cur < top; ) {
@@ -176,7 +163,7 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
 			crec->size = (long) (cur - prev);
 			crec->ha = hav;
 			recs[nrec++] = crec;
-			if (xdl_classify_record(pass, cf, rhash, hbits, crec) < 0)
+			if (xdl_classify_record(pass, cf, crec) < 0)
 				goto abort;
 		}
 	}
@@ -194,8 +181,6 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
 
 	xdf->nrec = nrec;
 	xdf->recs = recs;
-	xdf->hbits = hbits;
-	xdf->rhash = rhash;
 	xdf->rchg = rchg + 1;
 	xdf->rindex = rindex;
 	xdf->nreff = 0;
@@ -209,7 +194,6 @@ abort:
 	xdl_free(ha);
 	xdl_free(rindex);
 	xdl_free(rchg);
-	xdl_free(rhash);
 	xdl_free(recs);
 	xdl_cha_free(&xdf->rcha);
 	return -1;
@@ -217,8 +201,6 @@ abort:
 
 
 static void xdl_free_ctx(xdfile_t *xdf) {
-
-	xdl_free(xdf->rhash);
 	xdl_free(xdf->rindex);
 	xdl_free(xdf->rchg - 1);
 	xdl_free(xdf->ha);
xdiff/xtypes.h +0 -3
index 8442bd436efe..8b8467360ecf 100644
@@ -39,7 +39,6 @@ typedef struct s_chastore {
 } chastore_t;
 
 typedef struct s_xrecord {
-	struct s_xrecord *next;
 	char const *ptr;
 	long size;
 	unsigned long ha;
@@ -48,8 +47,6 @@ typedef struct s_xrecord {
 typedef struct s_xdfile {
 	chastore_t rcha;
 	long nrec;
-	unsigned int hbits;
-	xrecord_t **rhash;
 	long dstart, dend;
 	xrecord_t **recs;
 	char *rchg;
Signature · 1 line
gitgitgadget
#197 lore raw

PATCH v3 9/15 xdiff: make fields of xrecord_t Rust friendly

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

A few commits ago, we added definitions for Rust primitive types, to facilitate interoperability between C and Rust. Switch a few variables to use these types. Which, for now, will require adding some casts.

Also change xdlclass_t::ha to be u64 to match xrecord_t::ha, as pointed out by Johannes.

---
 xdiff/xdiffi.c    |  8 ++++----
 xdiff/xemit.c     |  2 +-
 xdiff/xmerge.c    | 14 +++++++-------
 xdiff/xpatience.c |  2 +-
 xdiff/xprepare.c  |  8 ++++----
 xdiff/xtypes.h    |  6 +++---
 xdiff/xutils.c    |  4 ++--
 7 files changed, 22 insertions(+), 22 deletions(-)
xdiff/xdiffi.c +4 -4
index 5a96e36dfbea..3b364c61f671 100644
@@ -418,7 +418,7 @@ static int get_indent(xrecord_t *rec)
 	long i;
 	int ret = 0;
 
-	for (i = 0; i < rec->size; i++) {
+	for (i = 0; i < (long) rec->size; i++) {
 		char c = rec->ptr[i];
 
 		if (!XDL_ISSPACE(c))
@@ -1005,11 +1005,11 @@ static void xdl_mark_ignorable_lines(xdchange_t *xscr, xdfenv_t *xe, long flags)
 
 		rec = &xe->xdf1.recs[xch->i1];
 		for (i = 0; i < xch->chg1 && ignore; i++)
-			ignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags);
+			ignore = xdl_blankline((const char*) rec[i]->ptr, rec[i]->size, flags);
 
 		rec = &xe->xdf2.recs[xch->i2];
 		for (i = 0; i < xch->chg2 && ignore; i++)
-			ignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags);
+			ignore = xdl_blankline((const char*)rec[i]->ptr, rec[i]->size, flags);
 
 		xch->ignore = ignore;
 	}
@@ -1020,7 +1020,7 @@ static int record_matches_regex(xrecord_t *rec, xpparam_t const *xpp) {
 	size_t i;
 
 	for (i = 0; i < xpp->ignore_regex_nr; i++)
-		if (!regexec_buf(xpp->ignore_regex[i], rec->ptr, rec->size, 1,
+		if (!regexec_buf(xpp->ignore_regex[i], (const char*) rec->ptr, rec->size, 1,
 				 &regmatch, 0))
 			return 1;
 
xdiff/xemit.c +1 -1
index 1d40c9cb4076..bbf7b7f8c862 100644
@@ -24,7 +24,7 @@
 
 static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec) {
 
-	*rec = xdf->recs[ri]->ptr;
+	*rec = (char const*) xdf->recs[ri]->ptr;
 
 	return xdf->recs[ri]->size;
 }
xdiff/xmerge.c +7 -7
index af40c88a5b36..6fa6ea61a208 100644
@@ -101,8 +101,8 @@ static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2,
 	xrecord_t **rec2 = xe2->xdf2.recs + i2;
 
 	for (i = 0; i < line_count; i++) {
-		int result = xdl_recmatch(rec1[i]->ptr, rec1[i]->size,
-			rec2[i]->ptr, rec2[i]->size, flags);
+		int result = xdl_recmatch((const char*) rec1[i]->ptr, rec1[i]->size,
+			(const char*) rec2[i]->ptr, rec2[i]->size, flags);
 		if (!result)
 			return -1;
 	}
@@ -324,8 +324,8 @@ static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1,
 
 static int recmatch(xrecord_t *rec1, xrecord_t *rec2, unsigned long flags)
 {
-	return xdl_recmatch(rec1->ptr, rec1->size,
-			    rec2->ptr, rec2->size, flags);
+	return xdl_recmatch((char const*) rec1->ptr, rec1->size,
+			    (char const*) rec2->ptr, rec2->size, flags);
 }
 
 /*
@@ -383,10 +383,10 @@ static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m,
 		 */
 		t1.ptr = (char *)xe1->xdf2.recs[m->i1]->ptr;
 		t1.size = xe1->xdf2.recs[m->i1 + m->chg1 - 1]->ptr
-			+ xe1->xdf2.recs[m->i1 + m->chg1 - 1]->size - t1.ptr;
+			+ xe1->xdf2.recs[m->i1 + m->chg1 - 1]->size - (u8 const*) t1.ptr;
 		t2.ptr = (char *)xe2->xdf2.recs[m->i2]->ptr;
 		t2.size = xe2->xdf2.recs[m->i2 + m->chg2 - 1]->ptr
-			+ xe2->xdf2.recs[m->i2 + m->chg2 - 1]->size - t2.ptr;
+			+ xe2->xdf2.recs[m->i2 + m->chg2 - 1]->size - (u8 const*) t2.ptr;
 		if (xdl_do_diff(&t1, &t2, xpp, &xe) < 0)
 			return -1;
 		if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 ||
@@ -440,7 +440,7 @@ static int line_contains_alnum(const char *ptr, long size)
 static int lines_contain_alnum(xdfenv_t *xe, int i, int chg)
 {
 	for (; chg; chg--, i++)
-		if (line_contains_alnum(xe->xdf2.recs[i]->ptr,
+		if (line_contains_alnum((char const*) xe->xdf2.recs[i]->ptr,
 				xe->xdf2.recs[i]->size))
 			return 1;
 	return 0;
xdiff/xpatience.c +1 -1
index 77dc411d1937..986a3a3f749a 100644
@@ -121,7 +121,7 @@ static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map,
 		return;
 	map->entries[index].line1 = line;
 	map->entries[index].hash = record->ha;
-	map->entries[index].anchor = is_anchor(xpp, map->env->xdf1.recs[line - 1]->ptr);
+	map->entries[index].anchor = is_anchor(xpp, (const char*) map->env->xdf1.recs[line - 1]->ptr);
 	if (!map->first)
 		map->first = map->entries + index;
 	if (map->last) {
xdiff/xprepare.c +4 -4
index ad356281f939..00cdf7d8a038 100644
@@ -32,7 +32,7 @@
 
 typedef struct s_xdlclass {
 	struct s_xdlclass *next;
-	unsigned long ha;
+	u64 ha;
 	char const *line;
 	long size;
 	long idx;
@@ -96,12 +96,12 @@ static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t
 	char const *line;
 	xdlclass_t *rcrec;
 
-	line = rec->ptr;
+	line = (char const*) rec->ptr;
 	hi = (long) XDL_HASHLONG(rec->ha, cf->hbits);
 	for (rcrec = cf->rchash[hi]; rcrec; rcrec = rcrec->next)
 		if (rcrec->ha == rec->ha &&
 				xdl_recmatch(rcrec->line, rcrec->size,
-					rec->ptr, rec->size, cf->flags))
+					(const char*) rec->ptr, rec->size, cf->flags))
 			break;
 
 	if (!rcrec) {
@@ -159,7 +159,7 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
 				goto abort;
 			if (!(crec = xdl_cha_alloc(&xdf->rcha)))
 				goto abort;
-			crec->ptr = prev;
+			crec->ptr = (u8 const*) prev;
 			crec->size = (long) (cur - prev);
 			crec->ha = hav;
 			recs[nrec++] = crec;
xdiff/xtypes.h +3 -3
index 8b8467360ecf..6e5f67ebf380 100644
@@ -39,9 +39,9 @@ typedef struct s_chastore {
 } chastore_t;
 
 typedef struct s_xrecord {
-	char const *ptr;
-	long size;
-	unsigned long ha;
+	u8 const* ptr;
+	usize size;
+	u64 ha;
 } xrecord_t;
 
 typedef struct s_xdfile {
xdiff/xutils.c +2 -2
index 444a108f87c0..10e4f20b7c31 100644
@@ -418,10 +418,10 @@ int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp,
 
 	subfile1.ptr = (char *)diff_env->xdf1.recs[line1 - 1]->ptr;
 	subfile1.size = diff_env->xdf1.recs[line1 + count1 - 2]->ptr +
-		diff_env->xdf1.recs[line1 + count1 - 2]->size - subfile1.ptr;
+		diff_env->xdf1.recs[line1 + count1 - 2]->size - (u8 const*) subfile1.ptr;
 	subfile2.ptr = (char *)diff_env->xdf2.recs[line2 - 1]->ptr;
 	subfile2.size = diff_env->xdf2.recs[line2 + count2 - 2]->ptr +
-		diff_env->xdf2.recs[line2 + count2 - 2]->size - subfile2.ptr;
+		diff_env->xdf2.recs[line2 + count2 - 2]->size - (u8 const*) subfile2.ptr;
 	if (xdl_do_diff(&subfile1, &subfile2, xpp, &env) < 0)
 		return -1;
 
Signature · 1 line
gitgitgadget
#198 lore raw

PATCH v3 10/15 xdiff: use one definition for freeing xdfile_t

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

Simplify xdl_prepare_ctx() by using xdl_free_ctx() instead of using local variables with hand rolled memory management.

---
 xdiff/xprepare.c | 60 +++++++++++++++++++-----------------------------
 1 file changed, 24 insertions(+), 36 deletions(-)
xdiff/xprepare.c +24 -36
index 00cdf7d8a038..55e1cc308756 100644
@@ -129,86 +129,74 @@ static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t
 }
 
 
+static void xdl_free_ctx(xdfile_t *xdf) {
+	xdl_free(xdf->rindex);
+	xdl_free(xdf->rchg - 1);
+	xdl_free(xdf->ha);
+	xdl_free(xdf->recs);
+	xdl_cha_free(&xdf->rcha);
+}
+
+
 static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
 			   xdlclassifier_t *cf, xdfile_t *xdf) {
-	long nrec, bsize;
+	long bsize;
 	unsigned long hav;
 	char const *blk, *cur, *top, *prev;
 	xrecord_t *crec;
-	xrecord_t **recs;
-	unsigned long *ha;
-	char *rchg;
-	long *rindex;
 
-	ha = NULL;
-	rindex = NULL;
-	rchg = NULL;
-	recs = NULL;
+	xdf->ha = NULL;
+	xdf->rindex = NULL;
+	xdf->rchg = NULL;
+	xdf->recs = NULL;
+	xdf->nrec = 0;
 
 	if (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0)
 		goto abort;
-	if (!XDL_ALLOC_ARRAY(recs, narec))
+	if (!XDL_ALLOC_ARRAY(xdf->recs, narec))
 		goto abort;
 
-	nrec = 0;
 	if ((cur = blk = xdl_mmfile_first(mf, &bsize))) {
 		for (top = blk + bsize; cur < top; ) {
 			prev = cur;
 			hav = xdl_hash_record(&cur, top, xpp->flags);
-			if (XDL_ALLOC_GROW(recs, nrec + 1, narec))
+			if (XDL_ALLOC_GROW(xdf->recs, xdf->nrec + 1, narec))
 				goto abort;
 			if (!(crec = xdl_cha_alloc(&xdf->rcha)))
 				goto abort;
 			crec->ptr = (u8 const*) prev;
 			crec->size = (long) (cur - prev);
 			crec->ha = hav;
-			recs[nrec++] = crec;
+			xdf->recs[xdf->nrec++] = crec;
 			if (xdl_classify_record(pass, cf, crec) < 0)
 				goto abort;
 		}
 	}
 
-	if (!XDL_CALLOC_ARRAY(rchg, nrec + 2))
+	if (!XDL_CALLOC_ARRAY(xdf->rchg, xdf->nrec + 2))
 		goto abort;
 
 	if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) &&
 	    (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF)) {
-		if (!XDL_ALLOC_ARRAY(rindex, nrec + 1))
+		if (!XDL_ALLOC_ARRAY(xdf->rindex, xdf->nrec + 1))
 			goto abort;
-		if (!XDL_ALLOC_ARRAY(ha, nrec + 1))
+		if (!XDL_ALLOC_ARRAY(xdf->ha, xdf->nrec + 1))
 			goto abort;
 	}
 
-	xdf->nrec = nrec;
-	xdf->recs = recs;
-	xdf->rchg = rchg + 1;
-	xdf->rindex = rindex;
+	xdf->rchg += 1;
 	xdf->nreff = 0;
-	xdf->ha = ha;
 	xdf->dstart = 0;
-	xdf->dend = nrec - 1;
+	xdf->dend = xdf->nrec - 1;
 
 	return 0;
 
 abort:
-	xdl_free(ha);
-	xdl_free(rindex);
-	xdl_free(rchg);
-	xdl_free(recs);
-	xdl_cha_free(&xdf->rcha);
+	xdl_free_ctx(xdf);
 	return -1;
 }
 
 
-static void xdl_free_ctx(xdfile_t *xdf) {
-	xdl_free(xdf->rindex);
-	xdl_free(xdf->rchg - 1);
-	xdl_free(xdf->ha);
-	xdl_free(xdf->recs);
-	xdl_cha_free(&xdf->rcha);
-}
-
-
 void xdl_free_env(xdfenv_t *xe) {
 
 	xdl_free_ctx(&xe->xdf2);
Signature · 1 line
gitgitgadget
#199 lore raw

PATCH v3 11/15 xdiff: replace chastore with an ivec in xdfile_t

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

xdfile_t currently uses a chastore which functions as a memory pool and a vector which maps to the alocations created by the chastore. It seems like xrecord_t used to be a linked list until the recs and nrec fields were added. I think that xrecord_t.next was meant to be removed, but was overlooked. This dual data structure setup make the code somewhat confusing.

Additionally the C type chastore_t isn't FFI friendly. While it could be implemented in Rust, since the data structure is confusing anyway, replace it with an ivec whose purpose is to be interoperable. This makes the fields nrec and recs in xdfile_t redundant, which will be removed in the next 2 commits.

---
 xdiff/xprepare.c | 34 +++++++++++++++++-----------------
 xdiff/xtypes.h   |  6 ++++--
 2 files changed, 21 insertions(+), 19 deletions(-)
xdiff/xprepare.c +17 -17
index 55e1cc308756..3b33186c15a3 100644
@@ -130,11 +130,11 @@ static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t
 
 
 static void xdl_free_ctx(xdfile_t *xdf) {
+	ivec_free(&xdf->record);
 	xdl_free(xdf->rindex);
 	xdl_free(xdf->rchg - 1);
 	xdl_free(xdf->ha);
 	xdl_free(xdf->recs);
-	xdl_cha_free(&xdf->rcha);
 }
 
 
@@ -143,35 +143,35 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
 	long bsize;
 	unsigned long hav;
 	char const *blk, *cur, *top, *prev;
-	xrecord_t *crec;
 
 	xdf->ha = NULL;
 	xdf->rindex = NULL;
 	xdf->rchg = NULL;
 	xdf->recs = NULL;
 	xdf->nrec = 0;
-
-	if (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0)
-		goto abort;
-	if (!XDL_ALLOC_ARRAY(xdf->recs, narec))
-		goto abort;
+	IVEC_INIT(xdf->record);
 
 	if ((cur = blk = xdl_mmfile_first(mf, &bsize))) {
 		for (top = blk + bsize; cur < top; ) {
+			xrecord_t crec;
 			prev = cur;
 			hav = xdl_hash_record(&cur, top, xpp->flags);
-			if (XDL_ALLOC_GROW(xdf->recs, xdf->nrec + 1, narec))
-				goto abort;
-			if (!(crec = xdl_cha_alloc(&xdf->rcha)))
-				goto abort;
-			crec->ptr = (u8 const*) prev;
-			crec->size = (long) (cur - prev);
-			crec->ha = hav;
-			xdf->recs[xdf->nrec++] = crec;
-			if (xdl_classify_record(pass, cf, crec) < 0)
-				goto abort;
+			crec.ptr = (u8 const*) prev;
+			crec.size = cur - prev;
+			crec.ha = hav;
+			ivec_push(&xdf->record, &crec);
 		}
 	}
+	ivec_shrink_to_fit(&xdf->record);
+
+	xdf->nrec = (long) xdf->record.length;
+	if (!XDL_ALLOC_ARRAY(xdf->recs, xdf->record.length))
+		goto abort;
+	for (usize i = 0; i < xdf->record.length; i++) {
+		if (xdl_classify_record(pass, cf, &xdf->record.ptr[i]) < 0)
+			goto abort;
+		xdf->recs[i] = &xdf->record.ptr[i];
+	}
 
 	if (!XDL_CALLOC_ARRAY(xdf->rchg, xdf->nrec + 2))
 		goto abort;
xdiff/xtypes.h +4 -2
index 6e5f67ebf380..5028a70b2675 100644
@@ -23,7 +23,7 @@
 #if !defined(XTYPES_H)
 #define XTYPES_H
 
-
+#include "../interop/ivec.h"
 
 typedef struct s_chanode {
 	struct s_chanode *next;
@@ -44,8 +44,10 @@ typedef struct s_xrecord {
 	u64 ha;
 } xrecord_t;
 
+DEFINE_IVEC_TYPE(xrecord_t, xrecord);
+
 typedef struct s_xdfile {
-	chastore_t rcha;
+	struct ivec_xrecord record;
 	long nrec;
 	long dstart, dend;
 	xrecord_t **recs;
Signature · 1 line
gitgitgadget
#200 lore raw

PATCH v3 12/15 xdiff: delete nrec field from xdfile_t

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

Because of the data structure cleanup in the previous commit, the nrec field is no longer necessary. Use record.length in place of nrec.

This commit is best viewed with --color-words.

---
 xdiff/xdiffi.c    | 10 +++++-----
 xdiff/xemit.c     | 20 ++++++++++----------
 xdiff/xmerge.c    | 10 +++++-----
 xdiff/xpatience.c |  2 +-
 xdiff/xprepare.c  | 34 ++++++++++++++++------------------
 xdiff/xtypes.h    |  1 -
 6 files changed, 37 insertions(+), 40 deletions(-)
xdiff/xdiffi.c +5 -5
index 3b364c61f671..bcab2d7ae516 100644
@@ -496,7 +496,7 @@ static void measure_split(const xdfile_t *xdf, long split,
 {
 	long i;
 
-	if (split >= xdf->nrec) {
+	if (split >= (long) xdf->record.length) {
 		m->end_of_file = 1;
 		m->indent = -1;
 	} else {
@@ -519,7 +519,7 @@ static void measure_split(const xdfile_t *xdf, long split,
 
 	m->post_blank = 0;
 	m->post_indent = -1;
-	for (i = split + 1; i < xdf->nrec; i++) {
+	for (i = split + 1; i < (long) xdf->record.length; i++) {
 		m->post_indent = get_indent(xdf->recs[i]);
 		if (m->post_indent != -1)
 			break;
@@ -730,7 +730,7 @@ static void group_init(xdfile_t *xdf, struct xdlgroup *g)
  */
 static inline int group_next(xdfile_t *xdf, struct xdlgroup *g)
 {
-	if (g->end == xdf->nrec)
+	if (g->end == (long) xdf->record.length)
 		return -1;
 
 	g->start = g->end + 1;
@@ -763,7 +763,7 @@ static inline int group_previous(xdfile_t *xdf, struct xdlgroup *g)
  */
 static int group_slide_down(xdfile_t *xdf, struct xdlgroup *g)
 {
-	if (g->end < xdf->nrec &&
+	if (g->end < (long) xdf->record.length &&
 	    recs_match(xdf->recs[g->start], xdf->recs[g->end])) {
 		xdf->rchg[g->start++] = 0;
 		xdf->rchg[g->end++] = 1;
@@ -950,7 +950,7 @@ int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr) {
 	/*
 	 * Trivial. Collects "groups" of changes and creates an edit script.
 	 */
-	for (i1 = xe->xdf1.nrec, i2 = xe->xdf2.nrec; i1 >= 0 || i2 >= 0; i1--, i2--)
+	for (i1 = xe->xdf1.record.length, i2 = xe->xdf2.record.length; i1 >= 0 || i2 >= 0; i1--, i2--)
 		if (rchg1[i1 - 1] || rchg2[i2 - 1]) {
 			for (l1 = i1; rchg1[i1 - 1]; i1--);
 			for (l2 = i2; rchg2[i2 - 1]; i2--);
xdiff/xemit.c +10 -10
index bbf7b7f8c862..11c2823ecab5 100644
@@ -147,7 +147,7 @@ static long get_func_line(xdfenv_t *xe, xdemitconf_t const *xecfg,
 	buf = func_line ? func_line->buf : dummy;
 	size = func_line ? sizeof(func_line->buf) : sizeof(dummy);
 
-	for (l = start; l != limit && 0 <= l && l < xe->xdf1.nrec; l += step) {
+	for (l = start; l != limit && 0 <= l && l < (long) xe->xdf1.record.length; l += step) {
 		long len = match_func_rec(&xe->xdf1, xecfg, l, buf, size);
 		if (len >= 0) {
 			if (func_line)
@@ -191,14 +191,14 @@ pre_context_calculation:
 			long fs1, i1 = xch->i1;
 
 			/* Appended chunk? */
-			if (i1 >= xe->xdf1.nrec) {
+			if (i1 >= (long) xe->xdf1.record.length) {
 				long i2 = xch->i2;
 
 				/*
 				 * We don't need additional context if
 				 * a whole function was added.
 				 */
-				while (i2 < xe->xdf2.nrec) {
+				while (i2 < (long) xe->xdf2.record.length) {
 					if (is_func_rec(&xe->xdf2, xecfg, i2))
 						goto post_context_calculation;
 					i2++;
@@ -208,7 +208,7 @@ pre_context_calculation:
 				 * Otherwise get more context from the
 				 * pre-image.
 				 */
-				i1 = xe->xdf1.nrec - 1;
+				i1 = xe->xdf1.record.length - 1;
 			}
 
 			fs1 = get_func_line(xe, xecfg, NULL, i1, -1);
@@ -240,8 +240,8 @@ pre_context_calculation:
 
  post_context_calculation:
 		lctx = xecfg->ctxlen;
-		lctx = XDL_MIN(lctx, xe->xdf1.nrec - (xche->i1 + xche->chg1));
-		lctx = XDL_MIN(lctx, xe->xdf2.nrec - (xche->i2 + xche->chg2));
+		lctx = XDL_MIN(lctx, (long) xe->xdf1.record.length - (xche->i1 + xche->chg1));
+		lctx = XDL_MIN(lctx, (long) xe->xdf2.record.length - (xche->i2 + xche->chg2));
 
 		e1 = xche->i1 + xche->chg1 + lctx;
 		e2 = xche->i2 + xche->chg2 + lctx;
@@ -249,13 +249,13 @@ pre_context_calculation:
 		if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) {
 			long fe1 = get_func_line(xe, xecfg, NULL,
 						 xche->i1 + xche->chg1,
-						 xe->xdf1.nrec);
+						 xe->xdf1.record.length);
 			while (fe1 > 0 && is_empty_rec(&xe->xdf1, fe1 - 1))
 				fe1--;
 			if (fe1 < 0)
-				fe1 = xe->xdf1.nrec;
+				fe1 = xe->xdf1.record.length;
 			if (fe1 > e1) {
-				e2 = XDL_MIN(e2 + (fe1 - e1), xe->xdf2.nrec);
+				e2 = XDL_MIN(e2 + (fe1 - e1), (long) xe->xdf2.record.length);
 				e1 = fe1;
 			}
 
@@ -266,7 +266,7 @@ pre_context_calculation:
 			 */
 			if (xche->next) {
 				long l = XDL_MIN(xche->next->i1,
-						 xe->xdf1.nrec - 1);
+						 (long) xe->xdf1.record.length - 1);
 				if (l - xecfg->ctxlen <= e1 ||
 				    get_func_line(xe, xecfg, NULL, l, e1) < 0) {
 					xche = xche->next;
xdiff/xmerge.c +5 -5
index 6fa6ea61a208..f48549605d09 100644
@@ -158,11 +158,11 @@ static int is_eol_crlf(xdfile_t *file, int i)
 {
 	long size;
 
-	if (i < file->nrec - 1)
+	if (i < (isize) file->record.length - 1)
 		/* All lines before the last *must* end in LF */
 		return (size = file->recs[i]->size) > 1 &&
 			file->recs[i]->ptr[size - 2] == '\r';
-	if (!file->nrec)
+	if (!file->record.length)
 		/* Cannot determine eol style from empty file */
 		return -1;
 	if ((size = file->recs[i]->size) &&
@@ -317,7 +317,7 @@ static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1,
 			continue;
 		i = m->i1 + m->chg1;
 	}
-	size += xdl_recs_copy(xe1, i, xe1->xdf2.nrec - i, 0, 0,
+	size += xdl_recs_copy(xe1, i, (int) xe1->xdf2.record.length - i, 0, 0,
 			      dest ? dest + size : NULL);
 	return size;
 }
@@ -622,7 +622,7 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1,
 			changes = c;
 		i0 = xscr1->i1;
 		i1 = xscr1->i2;
-		i2 = xscr1->i1 + xe2->xdf2.nrec - xe2->xdf1.nrec;
+		i2 = xscr1->i1 + xe2->xdf2.record.length - xe2->xdf1.record.length;
 		chg0 = xscr1->chg1;
 		chg1 = xscr1->chg2;
 		chg2 = xscr1->chg1;
@@ -637,7 +637,7 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1,
 		if (!changes)
 			changes = c;
 		i0 = xscr2->i1;
-		i1 = xscr2->i1 + xe1->xdf2.nrec - xe1->xdf1.nrec;
+		i1 = xscr2->i1 + xe1->xdf2.record.length - xe1->xdf1.record.length;
 		i2 = xscr2->i2;
 		chg0 = xscr2->chg1;
 		chg1 = xscr2->chg1;
xdiff/xpatience.c +1 -1
index 986a3a3f749a..e1ce9a399fbf 100644
@@ -370,5 +370,5 @@ static int patience_diff(xpparam_t const *xpp, xdfenv_t *env,
 
 int xdl_do_patience_diff(xpparam_t const *xpp, xdfenv_t *env)
 {
-	return patience_diff(xpp, env, 1, env->xdf1.nrec, 1, env->xdf2.nrec);
+	return patience_diff(xpp, env, 1, env->xdf1.record.length, 1, env->xdf2.record.length);
 }
xdiff/xprepare.c +16 -18
index 3b33186c15a3..9b46523afe97 100644
@@ -138,7 +138,7 @@ static void xdl_free_ctx(xdfile_t *xdf) {
 }
 
 
-static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
+static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, xpparam_t const *xpp,
 			   xdlclassifier_t *cf, xdfile_t *xdf) {
 	long bsize;
 	unsigned long hav;
@@ -148,7 +148,6 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
 	xdf->rindex = NULL;
 	xdf->rchg = NULL;
 	xdf->recs = NULL;
-	xdf->nrec = 0;
 	IVEC_INIT(xdf->record);
 
 	if ((cur = blk = xdl_mmfile_first(mf, &bsize))) {
@@ -164,7 +163,6 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
 	}
 	ivec_shrink_to_fit(&xdf->record);
 
-	xdf->nrec = (long) xdf->record.length;
 	if (!XDL_ALLOC_ARRAY(xdf->recs, xdf->record.length))
 		goto abort;
 	for (usize i = 0; i < xdf->record.length; i++) {
@@ -173,21 +171,21 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
 		xdf->recs[i] = &xdf->record.ptr[i];
 	}
 
-	if (!XDL_CALLOC_ARRAY(xdf->rchg, xdf->nrec + 2))
+	if (!XDL_CALLOC_ARRAY(xdf->rchg, xdf->record.length + 2))
 		goto abort;
 
 	if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) &&
 	    (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF)) {
-		if (!XDL_ALLOC_ARRAY(xdf->rindex, xdf->nrec + 1))
+		if (!XDL_ALLOC_ARRAY(xdf->rindex, xdf->record.length + 1))
 			goto abort;
-		if (!XDL_ALLOC_ARRAY(xdf->ha, xdf->nrec + 1))
+		if (!XDL_ALLOC_ARRAY(xdf->ha, xdf->record.length + 1))
 			goto abort;
 	}
 
 	xdf->rchg += 1;
 	xdf->nreff = 0;
 	xdf->dstart = 0;
-	xdf->dend = xdf->nrec - 1;
+	xdf->dend = xdf->record.length - 1;
 
 	return 0;
 
@@ -274,12 +272,12 @@ static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xd
 	char *dis, *dis1, *dis2;
 	int need_min = !!(cf->flags & XDF_NEED_MINIMAL);
 
-	if (!XDL_CALLOC_ARRAY(dis, xdf1->nrec + xdf2->nrec + 2))
+	if (!XDL_CALLOC_ARRAY(dis, xdf1->record.length + xdf2->record.length + 2))
 		return -1;
 	dis1 = dis;
-	dis2 = dis1 + xdf1->nrec + 1;
+	dis2 = dis1 + xdf1->record.length + 1;
 
-	if ((mlim = xdl_bogosqrt(xdf1->nrec)) > XDL_MAX_EQLIMIT)
+	if ((mlim = xdl_bogosqrt(xdf1->record.length)) > XDL_MAX_EQLIMIT)
 		mlim = XDL_MAX_EQLIMIT;
 	for (i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; i <= xdf1->dend; i++, recs++) {
 		rcrec = cf->rcrecs[(*recs)->ha];
@@ -287,7 +285,7 @@ static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xd
 		dis1[i] = (nm == 0) ? 0: (nm >= mlim && !need_min) ? 2: 1;
 	}
 
-	if ((mlim = xdl_bogosqrt(xdf2->nrec)) > XDL_MAX_EQLIMIT)
+	if ((mlim = xdl_bogosqrt(xdf2->record.length)) > XDL_MAX_EQLIMIT)
 		mlim = XDL_MAX_EQLIMIT;
 	for (i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; i <= xdf2->dend; i++, recs++) {
 		rcrec = cf->rcrecs[(*recs)->ha];
@@ -334,21 +332,21 @@ static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2) {
 
 	recs1 = xdf1->recs;
 	recs2 = xdf2->recs;
-	for (i = 0, lim = XDL_MIN(xdf1->nrec, xdf2->nrec); i < lim;
+	for (i = 0, lim = XDL_MIN(xdf1->record.length, xdf2->record.length); i < lim;
 	     i++, recs1++, recs2++)
 		if ((*recs1)->ha != (*recs2)->ha)
 			break;
 
 	xdf1->dstart = xdf2->dstart = i;
 
-	recs1 = xdf1->recs + xdf1->nrec - 1;
-	recs2 = xdf2->recs + xdf2->nrec - 1;
+	recs1 = xdf1->recs + xdf1->record.length - 1;
+	recs2 = xdf2->recs + xdf2->record.length - 1;
 	for (lim -= i, i = 0; i < lim; i++, recs1--, recs2--)
 		if ((*recs1)->ha != (*recs2)->ha)
 			break;
 
-	xdf1->dend = xdf1->nrec - i - 1;
-	xdf2->dend = xdf2->nrec - i - 1;
+	xdf1->dend = xdf1->record.length - i - 1;
+	xdf2->dend = xdf2->record.length - i - 1;
 
 	return 0;
 }
@@ -388,12 +386,12 @@ int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
 	if (xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0)
 		return -1;
 
-	if (xdl_prepare_ctx(1, mf1, enl1, xpp, &cf, &xe->xdf1) < 0) {
+	if (xdl_prepare_ctx(1, mf1, xpp, &cf, &xe->xdf1) < 0) {
 
 		xdl_free_classifier(&cf);
 		return -1;
 	}
-	if (xdl_prepare_ctx(2, mf2, enl2, xpp, &cf, &xe->xdf2) < 0) {
+	if (xdl_prepare_ctx(2, mf2, xpp, &cf, &xe->xdf2) < 0) {
 
 		xdl_free_ctx(&xe->xdf1);
 		xdl_free_classifier(&cf);
xdiff/xtypes.h +0 -1
index 5028a70b2675..c322e62fbf06 100644
@@ -48,7 +48,6 @@ DEFINE_IVEC_TYPE(xrecord_t, xrecord);
 
 typedef struct s_xdfile {
 	struct ivec_xrecord record;
-	long nrec;
 	long dstart, dend;
 	xrecord_t **recs;
 	char *rchg;
Signature · 1 line
gitgitgadget
#201 lore raw

PATCH v3 13/15 xdiff: delete recs field from xdfile_t

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

Because of the change from chastore to ivec a few commits ago, recs now points to record's elements in a 1:1 mapping. Since both recs and record are vectors, this additional mapping is superfluous. Remove recs.

This commit is best viewed with --color-words.

---
 xdiff/xdiffi.c     | 30 ++++++++++++------------
 xdiff/xemit.c      |  4 ++--
 xdiff/xhistogram.c |  2 +-
 xdiff/xmerge.c     | 58 +++++++++++++++++++++++-----------------------
 xdiff/xpatience.c  | 14 +++++------
 xdiff/xprepare.c   | 37 +++++++++++++----------------
 xdiff/xtypes.h     |  1 -
 xdiff/xutils.c     | 12 +++++-----
 8 files changed, 76 insertions(+), 82 deletions(-)
xdiff/xdiffi.c +15 -15
index bcab2d7ae516..ebdb72432261 100644
@@ -501,13 +501,13 @@ static void measure_split(const xdfile_t *xdf, long split,
 		m->indent = -1;
 	} else {
 		m->end_of_file = 0;
-		m->indent = get_indent(xdf->recs[split]);
+		m->indent = get_indent(&xdf->record.ptr[split]);
 	}
 
 	m->pre_blank = 0;
 	m->pre_indent = -1;
 	for (i = split - 1; i >= 0; i--) {
-		m->pre_indent = get_indent(xdf->recs[i]);
+		m->pre_indent = get_indent(&xdf->record.ptr[i]);
 		if (m->pre_indent != -1)
 			break;
 		m->pre_blank += 1;
@@ -520,7 +520,7 @@ static void measure_split(const xdfile_t *xdf, long split,
 	m->post_blank = 0;
 	m->post_indent = -1;
 	for (i = split + 1; i < (long) xdf->record.length; i++) {
-		m->post_indent = get_indent(xdf->recs[i]);
+		m->post_indent = get_indent(&xdf->record.ptr[i]);
 		if (m->post_indent != -1)
 			break;
 		m->post_blank += 1;
@@ -764,7 +764,7 @@ static inline int group_previous(xdfile_t *xdf, struct xdlgroup *g)
 static int group_slide_down(xdfile_t *xdf, struct xdlgroup *g)
 {
 	if (g->end < (long) xdf->record.length &&
-	    recs_match(xdf->recs[g->start], xdf->recs[g->end])) {
+	    recs_match(&xdf->record.ptr[g->start], &xdf->record.ptr[g->end])) {
 		xdf->rchg[g->start++] = 0;
 		xdf->rchg[g->end++] = 1;
 
@@ -785,7 +785,7 @@ static int group_slide_down(xdfile_t *xdf, struct xdlgroup *g)
 static int group_slide_up(xdfile_t *xdf, struct xdlgroup *g)
 {
 	if (g->start > 0 &&
-	    recs_match(xdf->recs[g->start - 1], xdf->recs[g->end - 1])) {
+	    recs_match(&xdf->record.ptr[g->start - 1], &xdf->record.ptr[g->end - 1])) {
 		xdf->rchg[--g->start] = 1;
 		xdf->rchg[--g->end] = 0;
 
@@ -1000,16 +1000,16 @@ static void xdl_mark_ignorable_lines(xdchange_t *xscr, xdfenv_t *xe, long flags)
 
 	for (xch = xscr; xch; xch = xch->next) {
 		int ignore = 1;
-		xrecord_t **rec;
+		xrecord_t *rec;
 		long i;
 
-		rec = &xe->xdf1.recs[xch->i1];
+		rec = &xe->xdf1.record.ptr[xch->i1];
 		for (i = 0; i < xch->chg1 && ignore; i++)
-			ignore = xdl_blankline((const char*) rec[i]->ptr, rec[i]->size, flags);
+			ignore = xdl_blankline((const char*) rec[i].ptr, rec[i].size, flags);
 
-		rec = &xe->xdf2.recs[xch->i2];
+		rec = &xe->xdf2.record.ptr[xch->i2];
 		for (i = 0; i < xch->chg2 && ignore; i++)
-			ignore = xdl_blankline((const char*)rec[i]->ptr, rec[i]->size, flags);
+			ignore = xdl_blankline((const char*)rec[i].ptr, rec[i].size, flags);
 
 		xch->ignore = ignore;
 	}
@@ -1033,7 +1033,7 @@ static void xdl_mark_ignorable_regex(xdchange_t *xscr, const xdfenv_t *xe,
 	xdchange_t *xch;
 
 	for (xch = xscr; xch; xch = xch->next) {
-		xrecord_t **rec;
+		xrecord_t *rec;
 		int ignore = 1;
 		long i;
 
@@ -1043,13 +1043,13 @@ static void xdl_mark_ignorable_regex(xdchange_t *xscr, const xdfenv_t *xe,
 		if (xch->ignore)
 			continue;
 
-		rec = &xe->xdf1.recs[xch->i1];
+		rec = &xe->xdf1.record.ptr[xch->i1];
 		for (i = 0; i < xch->chg1 && ignore; i++)
-			ignore = record_matches_regex(rec[i], xpp);
+			ignore = record_matches_regex(&rec[i], xpp);
 
-		rec = &xe->xdf2.recs[xch->i2];
+		rec = &xe->xdf2.record.ptr[xch->i2];
 		for (i = 0; i < xch->chg2 && ignore; i++)
-			ignore = record_matches_regex(rec[i], xpp);
+			ignore = record_matches_regex(&rec[i], xpp);
 
 		xch->ignore = ignore;
 	}
xdiff/xemit.c +2 -2
index 11c2823ecab5..0c9a12a5e828 100644
@@ -24,9 +24,9 @@
 
 static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec) {
 
-	*rec = (char const*) xdf->recs[ri]->ptr;
+	*rec = (char const*) xdf->record.ptr[ri].ptr;
 
-	return xdf->recs[ri]->size;
+	return xdf->record.ptr[ri].size;
 }
 
 
xdiff/xhistogram.c +1 -1
index 040d81e0bc9f..643d1c8b7071 100644
@@ -86,7 +86,7 @@ struct region {
 	((LINE_MAP(index, ptr))->cnt)
 
 #define REC(env, s, l) \
-	(env->xdf##s.recs[l - 1])
+	(&env->xdf##s.record.ptr[l - 1])
 
 static int cmp_recs(xrecord_t *r1, xrecord_t *r2)
 {
xdiff/xmerge.c +29 -29

125 lines not on this page · raw message

xdiff/xpatience.c +7 -7
index e1ce9a399fbf..31b819ec58f0 100644
@@ -88,9 +88,9 @@ static int is_anchor(xpparam_t const *xpp, const char *line)
 static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map,
 			  int pass)
 {
-	xrecord_t **records = pass == 1 ?
-		map->env->xdf1.recs : map->env->xdf2.recs;
-	xrecord_t *record = records[line - 1];
+	xrecord_t *records = pass == 1 ?
+		map->env->xdf1.record.ptr : map->env->xdf2.record.ptr;
+	xrecord_t *record = &records[line - 1];
 	/*
 	 * After xdl_prepare_env() (or more precisely, due to
 	 * xdl_classify_record()), the "ha" member of the records (AKA lines)
@@ -121,7 +121,7 @@ static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map,
 		return;
 	map->entries[index].line1 = line;
 	map->entries[index].hash = record->ha;
-	map->entries[index].anchor = is_anchor(xpp, (const char*) map->env->xdf1.recs[line - 1]->ptr);
+	map->entries[index].anchor = is_anchor(xpp, (const char*) map->env->xdf1.record.ptr[line - 1].ptr);
 	if (!map->first)
 		map->first = map->entries + index;
 	if (map->last) {
@@ -246,9 +246,9 @@ static int find_longest_common_sequence(struct hashmap *map, struct entry **res)
 
 static int match(struct hashmap *map, int line1, int line2)
 {
-	xrecord_t *record1 = map->env->xdf1.recs[line1 - 1];
-	xrecord_t *record2 = map->env->xdf2.recs[line2 - 1];
-	return record1->ha == record2->ha;
+	u64 mph1 = map->env->xdf1.record.ptr[line1 - 1].ha;
+	u64 mph2 = map->env->xdf2.record.ptr[line2 - 1].ha;
+	return mph1 == mph2;
 }
 
 static int patience_diff(xpparam_t const *xpp, xdfenv_t *env,
xdiff/xprepare.c +16 -21
index 9b46523afe97..93370f1c6db4 100644
@@ -134,7 +134,6 @@ static void xdl_free_ctx(xdfile_t *xdf) {
 	xdl_free(xdf->rindex);
 	xdl_free(xdf->rchg - 1);
 	xdl_free(xdf->ha);
-	xdl_free(xdf->recs);
 }
 
 
@@ -147,7 +146,6 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, xpparam_t const *xpp
 	xdf->ha = NULL;
 	xdf->rindex = NULL;
 	xdf->rchg = NULL;
-	xdf->recs = NULL;
 	IVEC_INIT(xdf->record);
 
 	if ((cur = blk = xdl_mmfile_first(mf, &bsize))) {
@@ -163,12 +161,9 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, xpparam_t const *xpp
 	}
 	ivec_shrink_to_fit(&xdf->record);
 
-	if (!XDL_ALLOC_ARRAY(xdf->recs, xdf->record.length))
-		goto abort;
 	for (usize i = 0; i < xdf->record.length; i++) {
 		if (xdl_classify_record(pass, cf, &xdf->record.ptr[i]) < 0)
 			goto abort;
-		xdf->recs[i] = &xdf->record.ptr[i];
 	}
 
 	if (!XDL_CALLOC_ARRAY(xdf->rchg, xdf->record.length + 2))
@@ -267,7 +262,7 @@ static int xdl_clean_mmatch(char const *dis, long i, long s, long e) {
  */
 static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) {
 	long i, nm, nreff, mlim;
-	xrecord_t **recs;
+	xrecord_t *recs;
 	xdlclass_t *rcrec;
 	char *dis, *dis1, *dis2;
 	int need_min = !!(cf->flags & XDF_NEED_MINIMAL);
@@ -279,38 +274,38 @@ static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xd
 
 	if ((mlim = xdl_bogosqrt(xdf1->record.length)) > XDL_MAX_EQLIMIT)
 		mlim = XDL_MAX_EQLIMIT;
-	for (i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; i <= xdf1->dend; i++, recs++) {
-		rcrec = cf->rcrecs[(*recs)->ha];
+	for (i = xdf1->dstart, recs = &xdf1->record.ptr[xdf1->dstart]; i <= xdf1->dend; i++, recs++) {
+		rcrec = cf->rcrecs[recs->ha];
 		nm = rcrec ? rcrec->len2 : 0;
 		dis1[i] = (nm == 0) ? 0: (nm >= mlim && !need_min) ? 2: 1;
 	}
 
 	if ((mlim = xdl_bogosqrt(xdf2->record.length)) > XDL_MAX_EQLIMIT)
 		mlim = XDL_MAX_EQLIMIT;
-	for (i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; i <= xdf2->dend; i++, recs++) {
-		rcrec = cf->rcrecs[(*recs)->ha];
+	for (i = xdf2->dstart, recs = &xdf2->record.ptr[xdf2->dstart]; i <= xdf2->dend; i++, recs++) {
+		rcrec = cf->rcrecs[recs->ha];
 		nm = rcrec ? rcrec->len1 : 0;
 		dis2[i] = (nm == 0) ? 0: (nm >= mlim && !need_min) ? 2: 1;
 	}
 
-	for (nreff = 0, i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart];
+	for (nreff = 0, i = xdf1->dstart, recs = &xdf1->record.ptr[xdf1->dstart];
 	     i <= xdf1->dend; i++, recs++) {
 		if (dis1[i] == 1 ||
 		    (dis1[i] == 2 && !xdl_clean_mmatch(dis1, i, xdf1->dstart, xdf1->dend))) {
 			xdf1->rindex[nreff] = i;
-			xdf1->ha[nreff] = (*recs)->ha;
+			xdf1->ha[nreff] = recs->ha;
 			nreff++;
 		} else
 			xdf1->rchg[i] = 1;
 	}
 	xdf1->nreff = nreff;
 
-	for (nreff = 0, i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart];
+	for (nreff = 0, i = xdf2->dstart, recs = &xdf2->record.ptr[xdf2->dstart];
 	     i <= xdf2->dend; i++, recs++) {
 		if (dis2[i] == 1 ||
 		    (dis2[i] == 2 && !xdl_clean_mmatch(dis2, i, xdf2->dstart, xdf2->dend))) {
 			xdf2->rindex[nreff] = i;
-			xdf2->ha[nreff] = (*recs)->ha;
+			xdf2->ha[nreff] = recs->ha;
 			nreff++;
 		} else
 			xdf2->rchg[i] = 1;
@@ -328,21 +323,21 @@ static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xd
  */
 static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2) {
 	long i, lim;
-	xrecord_t **recs1, **recs2;
+	xrecord_t *recs1, *recs2;
 
-	recs1 = xdf1->recs;
-	recs2 = xdf2->recs;
+	recs1 = xdf1->record.ptr;
+	recs2 = xdf2->record.ptr;
 	for (i = 0, lim = XDL_MIN(xdf1->record.length, xdf2->record.length); i < lim;
 	     i++, recs1++, recs2++)
-		if ((*recs1)->ha != (*recs2)->ha)
+		if (recs1->ha != recs2->ha)
 			break;
 
 	xdf1->dstart = xdf2->dstart = i;
 
-	recs1 = xdf1->recs + xdf1->record.length - 1;
-	recs2 = xdf2->recs + xdf2->record.length - 1;
+	recs1 = xdf1->record.ptr + xdf1->record.length - 1;
+	recs2 = xdf2->record.ptr + xdf2->record.length - 1;
 	for (lim -= i, i = 0; i < lim; i++, recs1--, recs2--)
-		if ((*recs1)->ha != (*recs2)->ha)
+		if (recs1->ha != recs2->ha)
 			break;
 
 	xdf1->dend = xdf1->record.length - i - 1;
xdiff/xtypes.h +0 -1
index c322e62fbf06..849f218b3277 100644
@@ -49,7 +49,6 @@ DEFINE_IVEC_TYPE(xrecord_t, xrecord);
 typedef struct s_xdfile {
 	struct ivec_xrecord record;
 	long dstart, dend;
-	xrecord_t **recs;
 	char *rchg;
 	long *rindex;
 	long nreff;
xdiff/xutils.c +6 -6
index 10e4f20b7c31..eed88ee6cbe2 100644
@@ -416,12 +416,12 @@ int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp,
 	mmfile_t subfile1, subfile2;
 	xdfenv_t env;
 
-	subfile1.ptr = (char *)diff_env->xdf1.recs[line1 - 1]->ptr;
-	subfile1.size = diff_env->xdf1.recs[line1 + count1 - 2]->ptr +
-		diff_env->xdf1.recs[line1 + count1 - 2]->size - (u8 const*) subfile1.ptr;
-	subfile2.ptr = (char *)diff_env->xdf2.recs[line2 - 1]->ptr;
-	subfile2.size = diff_env->xdf2.recs[line2 + count2 - 2]->ptr +
-		diff_env->xdf2.recs[line2 + count2 - 2]->size - (u8 const*) subfile2.ptr;
+	subfile1.ptr = (char *)diff_env->xdf1.record.ptr[line1 - 1].ptr;
+	subfile1.size = diff_env->xdf1.record.ptr[line1 + count1 - 2].ptr +
+		diff_env->xdf1.record.ptr[line1 + count1 - 2].size - (u8 const*) subfile1.ptr;
+	subfile2.ptr = (char *)diff_env->xdf2.record.ptr[line2 - 1].ptr;
+	subfile2.size = diff_env->xdf2.record.ptr[line2 + count2 - 2].ptr +
+		diff_env->xdf2.record.ptr[line2 + count2 - 2].size - (u8 const*) subfile2.ptr;
 	if (xdl_do_diff(&subfile1, &subfile2, xpp, &env) < 0)
 		return -1;
 
Signature · 1 line
gitgitgadget
#202 lore raw

PATCH v3 14/15 xdiff: make xdfile_t more rust friendly

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

Convert the remaining ambiguous fields in xdfile_t from C types to Rust types for interoperability.

---
 xdiff/xdiffi.c | 16 ++++++++--------
 xdiff/xdiffi.h |  8 ++++----
 xdiff/xtypes.h | 10 +++++-----
 3 files changed, 17 insertions(+), 17 deletions(-)
xdiff/xdiffi.c +8 -8
index ebdb72432261..0509b4875996 100644
@@ -42,8 +42,8 @@ typedef struct s_xdpsplit {
  * using this algorithm, so a little bit of heuristic is needed to cut the
  * search and to return a suboptimal point.
  */
-static long xdl_split(unsigned long const *ha1, long off1, long lim1,
-		      unsigned long const *ha2, long off2, long lim2,
+static long xdl_split(u64 const *ha1, long off1, long lim1,
+		      u64 const *ha2, long off2, long lim2,
 		      long *kvdf, long *kvdb, int need_min, xdpsplit_t *spl,
 		      xdalgoenv_t *xenv) {
 	long dmin = off1 - lim2, dmax = lim1 - off2;
@@ -260,7 +260,7 @@ static long xdl_split(unsigned long const *ha1, long off1, long lim1,
 int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1,
 		 diffdata_t *dd2, long off2, long lim2,
 		 long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv) {
-	unsigned long const *ha1 = dd1->ha, *ha2 = dd2->ha;
+	u64 const *ha1 = dd1->ha, *ha2 = dd2->ha;
 
 	/*
 	 * Shrink the box by walking through each diagonal snake (SW and NE).
@@ -273,14 +273,14 @@ int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1,
 	 * be obviously changed.
 	 */
 	if (off1 == lim1) {
-		char *rchg2 = dd2->rchg;
-		long *rindex2 = dd2->rindex;
+		u8 *rchg2 = dd2->rchg;
+		usize *rindex2 = dd2->rindex;
 
 		for (; off2 < lim2; off2++)
 			rchg2[rindex2[off2]] = 1;
 	} else if (off2 == lim2) {
-		char *rchg1 = dd1->rchg;
-		long *rindex1 = dd1->rindex;
+		u8 *rchg1 = dd1->rchg;
+		usize *rindex1 = dd1->rindex;
 
 		for (; off1 < lim1; off1++)
 			rchg1[rindex1[off1]] = 1;
@@ -944,7 +944,7 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) {
 
 int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr) {
 	xdchange_t *cscr = NULL, *xch;
-	char *rchg1 = xe->xdf1.rchg, *rchg2 = xe->xdf2.rchg;
+	u8 *rchg1 = xe->xdf1.rchg, *rchg2 = xe->xdf2.rchg;
 	long i1, i2, l1, l2;
 
 	/*
xdiff/xdiffi.h +4 -4
index 126c9d8ff4e4..c766ee115c99 100644
@@ -25,10 +25,10 @@
 
 
 typedef struct s_diffdata {
-	long nrec;
-	unsigned long const *ha;
-	long *rindex;
-	char *rchg;
+	usize nrec;
+	u64 const *ha;
+	usize *rindex;
+	u8 *rchg;
 } diffdata_t;
 
 typedef struct s_xdalgoenv {
xdiff/xtypes.h +5 -5
index 849f218b3277..66b3dfae8bdf 100644
@@ -48,11 +48,11 @@ DEFINE_IVEC_TYPE(xrecord_t, xrecord);
 
 typedef struct s_xdfile {
 	struct ivec_xrecord record;
-	long dstart, dend;
-	char *rchg;
-	long *rindex;
-	long nreff;
-	unsigned long *ha;
+	isize dstart, dend;
+	u8 *rchg;
+	usize *rindex;
+	usize nreff;
+	u64 *ha;
 } xdfile_t;
 
 typedef struct s_xdfenv {
Signature · 1 line
gitgitgadget
#203 lore raw

PATCH v3 15/15 xdiff: implement xdl_trim_ends() in Rust

In reply to Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

Replace the C implementation of xdl_trim_ends() with a Rust implementation.

---
 rust/xdiff/src/lib.rs      | 14 ++++++++++++++
 rust/xdiff/src/xprepare.rs | 27 +++++++++++++++++++++++++++
 rust/xdiff/src/xtypes.rs   | 19 +++++++++++++++++++
 xdiff/xprepare.c           | 28 +---------------------------
 4 files changed, 61 insertions(+), 27 deletions(-)
 create mode 100644 rust/xdiff/src/xprepare.rs
 create mode 100644 rust/xdiff/src/xtypes.rs
rust/xdiff/src/lib.rs +14 -0
index 8b137891791f..4cc05a7e6b4b 100644
@@ -1 +1,15 @@
+pub mod xprepare;
+pub mod xtypes;
 
+use crate::xprepare::trim_ends;
+use crate::xtypes::xdfile;
+
+#[no_mangle]
+unsafe extern "C" fn xdl_trim_ends(xdf1: *mut xdfile, xdf2: *mut xdfile) -> i32 {
+    let xdf1 = xdf1.as_mut().expect("null pointer");
+    let xdf2 = xdf2.as_mut().expect("null pointer");
+
+    trim_ends(xdf1, xdf2);
+
+    0
+}
rust/xdiff/src/xprepare.rs new file +27 -0
new file mode 100644
index 000000000000..f64f60c09965
@@ -0,0 +1,27 @@
+use crate::xtypes::xdfile;
+
+///
+/// Early trim initial and terminal matching records.
+///
+pub(crate) fn trim_ends(xdf1: &mut xdfile, xdf2: &mut xdfile) {
+    let mut lim = std::cmp::min(xdf1.record.len(), xdf2.record.len());
+
+    for i in 0..lim {
+        if xdf1.record[i].ha != xdf2.record[i].ha {
+            xdf1.dstart = i as isize;
+            xdf2.dstart = i as isize;
+            lim -= i;
+            break;
+        }
+    }
+
+    for i in 0..lim {
+        let f1i = xdf1.record.len() - 1 - i;
+        let f2i = xdf2.record.len() - 1 - i;
+        if xdf1.record[f1i].ha != xdf2.record[f2i].ha {
+            xdf1.dend = f1i as isize;
+            xdf2.dend = f2i as isize;
+            break;
+        }
+    }
+}
rust/xdiff/src/xtypes.rs new file +19 -0
new file mode 100644
index 000000000000..3d1ce9742f28
@@ -0,0 +1,19 @@
+use interop::ivec::IVec;
+
+#[repr(C)]
+pub(crate) struct xrecord {
+    pub(crate) ptr: *const u8,
+    pub(crate) size: usize,
+    pub(crate) ha: u64,
+}
+
+#[repr(C)]
+pub(crate) struct xdfile {
+    pub(crate) record: IVec<xrecord>,
+    pub(crate) dstart: isize,
+    pub(crate) dend: isize,
+    pub(crate) rchg: *mut u8,
+    pub(crate) rindex: *mut usize,
+    pub(crate) nreff: usize,
+    pub(crate) ha: *mut u64,
+}
xdiff/xprepare.c +1 -27
index 93370f1c6db4..2c7480875f9f 100644
@@ -318,33 +318,7 @@ static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xd
 }
 
 
-/*
- * Early trim initial and terminal matching records.
- */
-static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2) {
-	long i, lim;
-	xrecord_t *recs1, *recs2;
-
-	recs1 = xdf1->record.ptr;
-	recs2 = xdf2->record.ptr;
-	for (i = 0, lim = XDL_MIN(xdf1->record.length, xdf2->record.length); i < lim;
-	     i++, recs1++, recs2++)
-		if (recs1->ha != recs2->ha)
-			break;
-
-	xdf1->dstart = xdf2->dstart = i;
-
-	recs1 = xdf1->record.ptr + xdf1->record.length - 1;
-	recs2 = xdf2->record.ptr + xdf2->record.length - 1;
-	for (lim -= i, i = 0; i < lim; i++, recs1--, recs2--)
-		if (recs1->ha != recs2->ha)
-			break;
-
-	xdf1->dend = xdf1->record.length - i - 1;
-	xdf2->dend = xdf2->record.length - i - 1;
-
-	return 0;
-}
+extern i32 xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2);
 
 
 static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) {
Signature · 1 line
gitgitgadget
#182 lore raw

Re: [PATCH v3 06/15] ivec: create a vector type that is interoperable between C and Rust

In reply to Ezekiel Newren via GitGitGadget
Quoted 4 lines · Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

Trying to use Rust's Vec in C, or git's ALLOC_GROW() macros (via wrapper functions) in Rust is painful because:

nit: s/git's/Git's/

rust/interop/src/ivec.rs

Quoted 4 lines

[snip]

rust/interop/src/ivec.rs +1 -0
[snip]
+        // assert_eq!(vec.capacity, vec.slice.len());

Why are there three commented-out assertions? (all capacity/length)

Quoted 6 lines
rust/interop/src/ivec.rs +5 -0
+        assert_eq!(expected, vec.length);
+        assert!(vec.capacity >= expected);
+        for i in 0..vec.length {
+            assert_eq!(default_value, vec[i]);
+        }
[snip]
Signature · 1 line
Kristoffer Haugsbakk
#183 lore raw

Re: [PATCH v3 06/15] ivec: create a vector type that is interoperable between C and Rust

Quoted 23 lines · Kristoffer Haugsbakk
Quoted 4 lines · Ezekiel Newren via GitGitGadget

From: Ezekiel Newren <[email protected]>

Trying to use Rust's Vec in C, or git's ALLOC_GROW() macros (via wrapper functions) in Rust is painful because:

nit: s/git's/Git's/

rust/interop/src/ivec.rs

Quoted 4 lines

[snip]

rust/interop/src/ivec.rs +1 -0
[snip]
+        // assert_eq!(vec.capacity, vec.slice.len());

Why are there three commented-out assertions? (all capacity/length)

rust/interop/src/ivec.rs line 425

Quoted 6 lines
rust/interop/src/ivec.rs +5 -0
+        assert_eq!(expected, vec.length);
+        assert!(vec.capacity >= expected);
+        for i in 0..vec.length {
+            assert_eq!(default_value, vec[i]);
+        }
[snip]
Quoted 2 lines

--
Kristoffer Haugsbakk

Good catch, I should have removed those commented out lines. Looking back through the code I also missed calling std::ptr::drop_in_place() if the IVec shrinks. I'll apply those changes in the next version.

#127 lore raw

RE: [PATCH v3 02/15] xdiff: introduce rust

In reply to Ezekiel Newren via GitGitGadget
Quoted 416 lines · Ezekiel Newren

From: Ezekiel Newren <[email protected]>

Upcoming patches will simplify xdiff, while also porting parts of it to Rust. In preparation, add some stubs and setup the Rust build. For now, it is easier to let cargo build rust and have make or meson merely link against the static library that cargo builds. In line with ongoing libification efforts, use multiple crates to allow more modularity on the Rust side. xdiff is the crate that this series will focus on, but we also introduce the interop crate for future patch series.

In order to facilitate interoperability between C and Rust, introduce C definitions for Rust primitive types in git-compat-util.h.

---

.gitignore              |  3 +++
Makefile                | 53 ++++++++++++++++++++++++++++----------
build_rust.sh           | 57 +++++++++++++++++++++++++++++++++++++++++
git-compat-util.h       | 17 ++++++++++++
meson.build             | 52 +++++++++++++++++++++++++++++++------
rust/Cargo.toml         |  6 +++++
rust/interop/Cargo.toml | 14 ++++++++++  rust/interop/src/lib.rs |  0
rust/xdiff/Cargo.toml   | 15 +++++++++++
rust/xdiff/src/lib.rs   |  0
10 files changed, 196 insertions(+), 21 deletions(-)  create mode 100755

build_rust.sh create mode 100644 rust/Cargo.toml create mode 100644 rust/interop/Cargo.toml create mode 100644 rust/interop/src/lib.rs create mode 100644 rust/xdiff/Cargo.toml create mode 100644 rust/xdiff/src/lib.rs

.gitignore +0 -0
index 04c444404e4b..ff81e3580c4e 100644
@@ -254,3 +254,6 @@ Release/

/contrib/buildsystems/out
/contrib/libgit-rs/target
/contrib/libgit-sys/target

.gitignore +3 -0
+/.idea/
+/rust/target/
+/rust/Cargo.lock
Makefile +0 -0
index 70d1543b6b86..1ec0c1ee6603 100644
@@ -919,6 +919,29 @@ TEST_SHELL_PATH = $(SHELL_PATH)
 

LIB_FILE = libgit.a
XDIFF_LIB = xdiff/lib.a

Makefile +23 -0
+
+EXTLIBS =
+
+ifeq ($(DEBUG), 1)
+  RUST_BUILD_MODE = debug
+else
+  RUST_BUILD_MODE = release
+endif
+
+RUST_TARGET_DIR = rust/target/$(RUST_BUILD_MODE) RUST_FLAGS_FOR_C =
+-L$(RUST_TARGET_DIR)
+
+.PHONY: compile_rust
+compile_rust:
+	./build_rust.sh . $(RUST_BUILD_MODE) xdiff
+
+EXTLIBS += ./$(RUST_TARGET_DIR)/libxdiff.a
+
+UNAME_S := $(shell uname -s)
+ifeq ($(UNAME_S),Linux)
+  EXTLIBS += -ldl
+endif
+

REFTABLE_LIB = reftable/libreftable.a

GENERATED_H += command-list.h

Makefile +0 -0
@@ -1390,7 +1413,7 @@ UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.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)

Makefile +1 -1
@@ -2541,7 +2564,7 @@ git.sp git.s git.o: EXTRA_CPPFLAGS = \
 	'-DGIT_MAN_PATH="$(mandir_relative_SQ)"' \
 	'-DGIT_INFO_PATH="$(infodir_relative_SQ)"'
 
-git$X: git.o GIT-LDFLAGS $(BUILTIN_OBJS) $(GITLIBS)
+git$X: git.o GIT-LDFLAGS $(BUILTIN_OBJS) $(GITLIBS) compile_rust
 	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
 		$(filter %.o,$^) $(LIBS)
 
@@ -2891,17 +2914,17 @@ headless-git.o: compat/win32/headless.c GIT-

CFLAGS
headless-git$X: headless-git.o git.res GIT-LDFLAGS

	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) $(ALL_LDFLAGS) -mwindows -o $@

$< git.res

Makefile +1 -1
-git-%$X: %.o GIT-LDFLAGS $(GITLIBS)
+git-%$X: %.o GIT-LDFLAGS $(GITLIBS) compile_rust
	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter

%.o,$^) $(LIBS)

-git-imap-send$X: imap-send.o $(IMAP_SEND_BUILDDEPS) GIT-LDFLAGS $(GITLIBS)
+git-imap-send$X: imap-send.o $(IMAP_SEND_BUILDDEPS) GIT-LDFLAGS +$(GITLIBS) compile_rust

	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter
%.o,$^) \
		$(IMAP_SEND_LDFLAGS) $(LIBS)
Makefile +2 -1
-git-http-fetch$X: http.o http-walker.o http-fetch.o GIT-LDFLAGS $(GITLIBS)
+git-http-fetch$X: http.o http-walker.o http-fetch.o GIT-LDFLAGS
+$(GITLIBS) compile_rust
	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter
%.o,$^) \
		$(CURL_LIBCURL) $(LIBS)
Makefile +1 -1
-git-http-push$X: http.o http-push.o GIT-LDFLAGS $(GITLIBS)
+git-http-push$X: http.o http-push.o GIT-LDFLAGS $(GITLIBS) compile_rust
	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter
%.o,$^) \
		$(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIBS)
Makefile +3 -2
@@ -2911,11 +2934,11 @@ $(REMOTE_CURL_ALIASES):
 $(REMOTE_CURL_PRIMARY)
 	ln -s $< $@ 2>/dev/null || \
 	cp $< $@
 
-$(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS
 $(GITLIBS)
+$(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS
+$(GITLIBS) compile_rust
 	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter
 %.o,$^) \
 		$(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIBS)
 
-scalar$X: scalar.o GIT-LDFLAGS $(GITLIBS)
+scalar$X: scalar.o GIT-LDFLAGS $(GITLIBS) compile_rust
	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
		$(filter %.o,$^) $(LIBS)
Makefile +1 -0
@@ -2925,6 +2948,7 @@ $(LIB_FILE): $(LIB_OBJS)
 $(XDIFF_LIB): $(XDIFF_OBJS)
 	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
 
+
 $(REFTABLE_LIB): $(REFTABLE_OBJS)
 	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
 
@@ -3294,7 +3318,7 @@ perf: all
 
 t/helper/test-tool$X: $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))

$(UNIT_TEST_DIR)/test-lib.o

Makefile +2 -1
-t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS)
+t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS)
+compile_rust
	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter

%.o,$^) $(filter %.a,$^) $(LIBS)

check-sha1:: t/helper/test-tool$X

Makefile +4 -1
@@ -3756,7 +3780,10 @@ cocciclean:
 	$(RM) -r .build/contrib/coccinelle
 	$(RM) contrib/coccinelle/*.cocci.patch
 
-clean: profile-clean coverage-clean cocciclean
+rustclean:
+	cd rust && cargo clean
+
+clean: profile-clean coverage-clean cocciclean rustclean
 	$(RM) -r .build $(UNIT_TEST_BIN)
 	$(RM) GIT-TEST-SUITES
 	$(RM) po/git.pot po/git-core.pot
@@ -3911,13 +3938,13 @@ FUZZ_CXXFLAGS ?= $(ALL_CFLAGS)

.PHONY: fuzz-all
fuzz-all: $(FUZZ_PROGRAMS)

-$(FUZZ_PROGRAMS): %: %.o oss-fuzz/dummy-cmd-main.o $(GITLIBS) GIT- LDFLAGS
+$(FUZZ_PROGRAMS): %: %.o oss-fuzz/dummy-cmd-main.o $(GITLIBS) +GIT-LDFLAGS compile_rust

	$(QUIET_LINK)$(FUZZ_CXX) $(FUZZ_CXXFLAGS) -o $@ $(ALL_LDFLAGS) \
		-Wl,--allow-multiple-definition \
		$(filter %.o,$^) $(filter %.a,$^) $(LIBS) $(LIB_FUZZING_ENGINE)

$(UNIT_TEST_PROGS): $(UNIT_TEST_BIN)/%$X: $(UNIT_TEST_DIR)/%.o $(UNIT_TEST_OBJS) \

Makefile +1 -1
-	$(GITLIBS) GIT-LDFLAGS
+	$(GITLIBS) GIT-LDFLAGS compile_rust
	$(call mkdir_p_parent_template)
	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
		$(filter %.o,$^) $(filter %.a,$^) $(LIBS) @@ -3936,7 +3963,7 @@

$(UNIT_TEST_DIR)/clar.suite: $(UNIT_TEST_DIR)/clar-decls.h $(UNIT_TEST_DIR)/gene
$(UNIT_TEST_DIR)/clar/clar.o: $(UNIT_TEST_DIR)/clar.suite $(CLAR_TEST_OBJS): $(UNIT_TEST_DIR)/clar-decls.h $(CLAR_TEST_OBJS): EXTRA_CPPFLAGS = -I$(UNIT_TEST_DIR) -$(CLAR_TEST_PROG): $(UNIT_TEST_DIR)/clar.suite $(CLAR_TEST_OBJS) $(GITLIBS) GIT-LDFLAGS
+$(CLAR_TEST_PROG): $(UNIT_TEST_DIR)/clar.suite $(CLAR_TEST_OBJS) +$(GITLIBS) GIT-LDFLAGS compile_rust

	$(call mkdir_p_parent_template)
	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter

%.o,$^) $(LIBS)

build_rust.sh new file mode 100755 index renamed from build_rust.sh +0 -0

000000000000..192385a1d961

build_rust.sh new file +52 -0
@@ -0,0 +1,57 @@
+#!/bin/sh
+
+
+rustc -vV || exit $?
+cargo --version || exit $?
+
+dir_git_root=${0%/*}
+dir_build=$1
+rust_build_profile=$2
+crate=$3
+
+dir_rust=$dir_git_root/rust
+
+if [ "$dir_git_root" = "" ]; then
+  echo "did not specify the directory for the root of git"
+  exit 1
+fi
+
+if [ "$dir_build" = "" ]; then
+  echo "did not specify the build directory"
+  exit 1
+fi
+
+if [ "$rust_build_profile" = "" ]; then
+  echo "did not specify the rust_build_profile"
+  exit 1
+fi
+
+if [ "$rust_build_profile" = "release" ]; then
+  rust_args="--release"
+  export RUSTFLAGS=''
+elif [ "$rust_build_profile" = "debug" ]; then
+  rust_args=""
+  export RUSTFLAGS='-C debuginfo=2 -C opt-level=1 -C force-frame-pointers=yes'
+else
+  echo "illegal rust_build_profile value $rust_build_profile"
+  exit 1
+fi
+
+cd $dir_rust && cargo clean && pwd && cargo build -p $crate $rust_args;
+cd $dir_git_root
+
+libfile="lib${crate}.a"
+if rustup show active-toolchain | grep windows-msvc; then
+  libfile="${crate}.lib"
+fi
+dst=$dir_build/$libfile
+
+if [ "$dir_git_root" != "$dir_build" ]; then
+  src=$dir_rust/target/$rust_build_profile/$libfile
+  if [ ! -f $src ]; then
+    echo >&2 "::error:: cannot find path of static library $src is not a file or does not

exist"

build_rust.sh new file +6 -0
+    exit 5
+  fi
+
+  rm $dst 2>/dev/null
+  mv $src $dst
+fi
git-compat-util.h index renamed from git-compat-util.h +0 -0

4678e21c4cb8..82dc99764ac0 100644

git-compat-util.h +0 -0
@@ -196,6 +196,23 @@ static inline int is_xplatform_dir_sep(int c)  #include

"compat/msvc.h"
#endif

git-compat-util.h +17 -0
+/* rust types */
+typedef uint8_t   u8;
+typedef uint16_t  u16;
+typedef uint32_t  u32;
+typedef uint64_t  u64;
+
+typedef int8_t    i8;
+typedef int16_t   i16;
+typedef int32_t   i32;
+typedef int64_t   i64;
+
+typedef float     f32;
+typedef double    f64;
+
+typedef size_t    usize;
+typedef ptrdiff_t isize;
+

/* used on Mac OS X */
#ifdef PRECOMPOSE_UNICODE
#include "compat/precompose_utf8.h"

meson.build +34 -0
index 596f5ac7110e..324f968338b9 100644
@@ -267,6 +267,40 @@ version_gen_environment.set('GIT_DATE',
 get_option('build_date'))  version_gen_environment.set('GIT_USER_AGENT',
 get_option('user_agent'))  version_gen_environment.set('GIT_VERSION',
 get_option('version'))
 
+if get_option('optimization') in ['2', '3', 's', 'z']
+  rust_build_profile = 'release'
+else
+  rust_build_profile = 'debug'
+endif
+
+# Run `rustup show active-toolchain` and capture output rustup_out =
+run_command('rustup', 'show', 'active-toolchain',
+                         check: true).stdout().strip()
+
+rust_crates = ['xdiff']
+rust_builds = []
+
+foreach crate : rust_crates
+  if rustup_out.contains('windows-msvc')
+    libfile = crate + '.lib'
+  else
+    libfile = 'lib' + crate + '.a'
+  endif
+
+  rust_builds += custom_target(
+    'rust_build_'+crate,
+    output: libfile,
+    build_by_default: true,
+    build_always_stale: true,
+    command: [
+      meson.project_source_root() / 'build_rust.sh',
+      meson.current_build_dir(), rust_build_profile, crate,
+    ],
+    install: false,
+  )
+endforeach
+
+
 compiler = meson.get_compiler('c')
 

libgit_sources = [

meson.build +0 -0
@@ -1678,14 +1712,16 @@ version_def_h = custom_target(  libgit_sources +=

version_def_h

libgit = declare_dependency(

meson.build +10 -8
-  link_with: static_library('git',
-    sources: libgit_sources,
-    c_args: libgit_c_args + [
-      '-DGIT_VERSION_H="' + version_def_h.full_path() + '"',
-    ],
-    dependencies: libgit_dependencies,
-    include_directories: libgit_include_directories,
-  ),
+  link_with: [
+    static_library('git',
+      sources: libgit_sources,
+      c_args: libgit_c_args + [
+        '-DGIT_VERSION_H="' + version_def_h.full_path() + '"',
+      ],
+      dependencies: libgit_dependencies,
+      include_directories: libgit_include_directories,
+    ),
+  ] + rust_builds,
  compile_args: libgit_c_args,
  dependencies: libgit_dependencies,
  include_directories: libgit_include_directories, diff --git a/rust/Cargo.toml

b/rust/Cargo.toml new file mode 100644 index 000000000000..ed3d79d7f827

rust/Cargo.toml new file +6 -0
@@ -0,0 +1,6 @@
+[workspace]
+members = [
+    "xdiff",
+    "interop",
+]
+resolver = "2"
rust/interop/Cargo.toml new file mode renamed from rust/interop/Cargo.toml +0 -0

100644 index 000000000000..045e3b01cfad

rust/interop/Cargo.toml new file +13 -0
@@ -0,0 +1,14 @@
+[package]
+name = "interop"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+name = "interop"
+path = "src/lib.rs"
+## staticlib to generate xdiff.a for use by gcc ## cdylib (optional) to
+generate xdiff.so for use by gcc ## rlib is required by the rust unit
+tests crate-type = ["staticlib", "rlib"]
+
+[dependencies]
rust/interop/src/lib.rs new file mode 100644 renamed from rust/interop/src/lib.rs +0 -0
index 000000000000..e69de29bb2d1 diff --git a/rust/xdiff/Cargo.toml

b/rust/xdiff/Cargo.toml new file mode 100644 index 000000000000..eb7966aada64

rust/xdiff/Cargo.toml new file +14 -0
@@ -0,0 +1,15 @@
+[package]
+name = "xdiff"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+name = "xdiff"
+path = "src/lib.rs"
+## staticlib to generate xdiff.a for use by gcc ## cdylib (optional) to
+generate xdiff.so for use by gcc ## rlib is required by the rust unit
+tests crate-type = ["staticlib", "rlib"]
+
+[dependencies]
+interop = { path = "../interop" }
rust/xdiff/src/lib.rs new file mode 100644 index renamed from rust/xdiff/src/lib.rs +0 -0
Quoted 1 line

000000000000..e69de29bb2d1

Does this introduce Rust as a mandatory dependency for git? If so, it cuts out numerous platforms.