{"thread":{"id":"64060","subject":"[PATCH 00/15] Introduce rust: In xdiff","startedAt":"2025-08-29T19:42:22Z","lastAt":"2025-09-23T21:04:39Z","messageCount":100,"participants":["Ezekiel Newren via GitGitGadget","brian m. carlson via GitGitGadget","Johannes Schindelin via GitGitGadget","brian m. carlson","Ezekiel Newren","Patrick Steinhardt via GitGitGadget","Patrick Steinhardt","Eric Sunshine","Junio C Hamano","Elijah Newren","D. Ben Knoble","Collin Funk","Ramsay Jones"],"isPatch":true,"patchVersion":1,"patchTotal":15},"messages":[{"id":"525222","messageId":"pull.2043.git.git.1756496539.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":null,"subject":"[PATCH 00/15] Introduce rust: In xdiff","fromName":"Ezekiel Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-08-29T19:42:04Z","receivedAt":"2025-08-29T19:42:22Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"This is a continuation of\nhttps://lore.kernel.org/git/pull.1980.v3.git.git.1755921356.gitgitgadget@gmail.com/,\nbut I am removing the RFC label.\n\nChanges since the last RFC patch series (range-diff added below):\n\n * Addressed feedback:\n   * Use self instead of this\n   * Update ivec to use Git code style\n   * Remove environment variable CI_IS_DOCKER from github workflows\n   * Remove PHONY target interop-objs\n   * Removed left over asserts in Rust and fix near by code\n * Other changes:\n   * Avoid name mangling of ivec if c++ compiler used\n   * Fixed a typo in patch 11 commit message\n\nHigh level overview:\n\n * patch 1: add a policy for using Rust (brian's patch, with a small tweak)\n * patch 2: introduce Rust to the codebase\n * patches 3-5: adapt CI (github workflows) to build Git with Rust\n * patch 6: introduce the ivec type\n * patches 7-14: xdiff code cleanup in preparation for translating to Rust\n * patch 15: translate a C function into Rust and call it from C\n\nBuild results for these changes:\nhttps://github.com/git/git/actions/runs/17330190596\n\nRange-Diff against last RFC version:\n\n 1:  6d065f550f =  1:  6d065f550f doc: add a policy for using Rust\n 2:  0393995125 =  2:  cc9cf87775 xdiff: introduce rust\n 3:  a98d9e4d21 !  3:  6c47401ba0 github workflows: install rust\n    @@ .github/workflows/main.yml: jobs:\n                image: fedora:latest\n              # A RHEL 8 compatible distro.  Supported until 2029-05-31.\n     @@ .github/workflows/main.yml: jobs:\n    -       jobname: ${{matrix.vector.jobname}}\n            CC: ${{matrix.vector.cc}}\n            CI_JOB_IMAGE: ${{matrix.vector.image}}\n    -+      CI_IS_DOCKER: \"true\"\n            CUSTOM_PATH: /custom\n     +      CARGO_HOME: /home/builder/.cargo\n          runs-on: ubuntu-latest\n 4:  0d2b39c3e0 =  4:  8e350700bc win+Meson: do allow linking with the Rust-built xdiff\n 5:  e65488ab99 =  5:  a00cbf7bcb github workflows: upload Cargo.lock\n 6:  db5d22b188 !  6:  922d506ed6 ivec: create a vector type that is interoperable between C and Rust\n    @@ Makefile: reconfigure config.mak.autogen: config.status\n      endif\n      \n     +INTEROP_OBJS += interop/ivec.o\n    -+.PHONY: interop-objs\n    -+interop-objs: $(INTEROP_OBJS)\n     +\n      XDIFF_OBJS += xdiff/xdiffi.o\n      XDIFF_OBJS += xdiff/xemit.o\n    @@ interop/ivec.c (new)\n     @@\n     +#include \"ivec.h\"\n     +\n    -+static void ivec_set_capacity(void* self, usize new_capacity) {\n    -+\tstruct rawivec *this = self;\n    ++#ifdef __cplusplus\n    ++extern \"C\" {\n    ++#endif\n    ++\n    ++static void ivec_set_capacity(void *self_, usize new_capacity)\n    ++{\n    ++\tstruct rawivec *self = self_;\n    ++\n     +\tif (new_capacity == 0)\n    -+\t\tFREE_AND_NULL(this->ptr);\n    ++\t\tFREE_AND_NULL(self->ptr);\n     +\telse\n    -+\t\tthis->ptr = xrealloc(this->ptr, new_capacity * this->element_size);\n    -+\tthis->capacity = new_capacity;\n    ++\t\tself->ptr = xrealloc(self->ptr, new_capacity * self->element_size);\n    ++\tself->capacity = new_capacity;\n     +}\n     +\n    -+void ivec_init(void* self, usize element_size) {\n    -+\tstruct rawivec *this = self;\n    -+\tthis->ptr = NULL;\n    -+\tthis->length = 0;\n    -+\tthis->capacity = 0;\n    -+\tthis->element_size = element_size;\n    ++void ivec_init(void *self_, usize element_size)\n    ++{\n    ++\tstruct rawivec *self = self_;\n    ++\n    ++\tself->ptr = NULL;\n    ++\tself->length = 0;\n    ++\tself->capacity = 0;\n    ++\tself->element_size = element_size;\n     +}\n     +\n     +/*\n    @@ interop/ivec.c (new)\n     + * to the specified capacity, and then calloc self.capacity number of\n     + * elements.\n     + */\n    -+void ivec_zero(void* self, usize capacity) {\n    -+\tstruct rawivec *this = self;\n    -+\tif (this->ptr)\n    -+\t\tFREE_AND_NULL(this->ptr);\n    -+\tthis->capacity = this->length = capacity;\n    -+\tthis->ptr = xcalloc(this->capacity, this->element_size);\n    ++void ivec_zero(void *self_, usize capacity)\n    ++{\n    ++\tstruct rawivec *self = self_;\n    ++\n    ++\tif (self->ptr)\n    ++\t\tFREE_AND_NULL(self->ptr);\n    ++\tself->capacity = self->length = capacity;\n    ++\tself->ptr = xcalloc(self->capacity, self->element_size);\n     +}\n     +\n    -+void ivec_clear(void* self) {\n    -+\tstruct rawivec *this = self;\n    -+\tthis->length = 0;\n    ++void ivec_clear(void *self_)\n    ++{\n    ++\tstruct rawivec *self = self_;\n    ++\n    ++\tself->length = 0;\n     +}\n     +\n    -+void ivec_reserve_exact(void* self, usize additional) {\n    -+\tstruct rawivec *this = self;\n    -+\tusize new_capacity = this->capacity + additional;\n    ++void ivec_reserve_exact(void *self_, usize additional)\n    ++{\n    ++\tstruct rawivec *self = self_;\n    ++\tusize new_capacity = self->capacity + additional;\n    ++\n     +\tivec_set_capacity(self, new_capacity);\n     +}\n     +\n    -+void ivec_reserve(void* self, usize additional) {\n    -+\tstruct rawivec *this = self;\n    ++void ivec_reserve(void *self_, usize additional)\n    ++{\n    ++\tstruct rawivec *self = self_;\n     +\tusize growby = 128;\n    -+\tif (this->capacity > growby) {\n    -+\t\tgrowby = this->capacity;\n    ++\n    ++\tif (self->capacity > growby) {\n    ++\t\tgrowby = self->capacity;\n     +\t}\n     +\tif (additional > growby) {\n     +\t\tgrowby = additional;\n    @@ interop/ivec.c (new)\n     +\tivec_reserve_exact(self, growby);\n     +}\n     +\n    -+void ivec_shrink_to_fit(void* self) {\n    -+\tstruct rawivec *this = self;\n    -+\tivec_set_capacity(self, this->length);\n    ++void ivec_shrink_to_fit(void *self_)\n    ++{\n    ++\tstruct rawivec *self = self_;\n    ++\n    ++\tivec_set_capacity(self_, self->length);\n     +}\n     +\n    -+void ivec_resize(void* self, usize new_length, void* default_value) {\n    -+\tstruct rawivec *this = self;\n    -+\tisize additional = (isize) (new_length - this->capacity);\n    ++void ivec_resize(void *self_, usize new_length, void *default_value)\n    ++{\n    ++\tstruct rawivec *self = self_;\n    ++\tisize additional = (isize) (new_length - self->capacity);\n    ++\n     +\tif (additional > 0) {\n    -+\t\tivec_reserve(self, additional);\n    ++\t\tivec_reserve(self_, additional);\n     +\t}\n     +\n    -+\tfor (usize i = this->length; i < new_length; i++) {\n    -+\t\tvoid* dst = (u8*) this->ptr + (this->length + i) * this->element_size;\n    -+\t\tmemcpy(dst, default_value, this->element_size);\n    ++\tfor (usize i = self->length; i < new_length; i++) {\n    ++\t\tvoid *dst = (u8 *)self->ptr + (self->length + i) * self->element_size;\n    ++\t\tmemcpy(dst, default_value, self->element_size);\n     +\t}\n    -+\tthis->length = new_length;\n    ++\tself->length = new_length;\n     +}\n     +\n    -+void ivec_push(void* self, void* value) {\n    -+\tstruct rawivec *this = self;\n    -+\tu8* dst;\n    ++void ivec_push(void *self_, void *value)\n    ++{\n    ++\tstruct rawivec *self = self_;\n    ++\tu8 *dst;\n     +\n    -+\tif (this->length == this->capacity) {\n    -+\t\tivec_reserve(self, 1);\n    ++\tif (self->length == self->capacity) {\n    ++\t\tivec_reserve(self_, 1);\n     +\t}\n    -+\tdst = (u8*) this->ptr + this->length * this->element_size;\n    -+\tmemcpy(dst, value, this->element_size);\n    -+\tthis->length++;\n    ++\tdst = (u8 *)self->ptr + self->length * self->element_size;\n    ++\tmemcpy(dst, value, self->element_size);\n    ++\tself->length++;\n     +}\n     +\n    -+void ivec_extend_from_slice(void* self, void const* ptr, usize size) {\n    -+\tstruct rawivec *this = self;\n    -+\tu8* dst;\n    ++void ivec_extend_from_slice(void *self_, void const *ptr, usize size)\n    ++{\n    ++\tstruct rawivec *self = self_;\n    ++\tu8 *dst;\n     +\n     +\tif (size == 0)\n     +\t\treturn;\n     +\n    -+\tif (this->length + size > this->capacity) {\n    -+\t\tivec_reserve(self, this->capacity - this->length + size);\n    ++\tif (self->length + size > self->capacity) {\n    ++\t\tivec_reserve(self_, self->capacity - self->length + size);\n     +\t}\n    -+\tdst = (u8*) this->ptr + this->length * this->element_size;\n    -+\tmemcpy(dst, ptr, size * this->element_size);\n    -+\tthis->length += size;\n    ++\tdst = (u8 *)self->ptr + self->length * self->element_size;\n    ++\tmemcpy(dst, ptr, size * self->element_size);\n    ++\tself->length += size;\n     +}\n     +\n    -+bool ivec_equal(void* self, void* other) {\n    -+\tstruct rawivec *lhs = self;\n    ++bool ivec_equal(void *self_, void *other)\n    ++{\n    ++\tstruct rawivec *lhs = self_;\n     +\tstruct rawivec *rhs = other;\n     +\n     +\tif (lhs->element_size != rhs->element_size) {\n    @@ interop/ivec.c (new)\n     +\t\treturn false;\n     +\t}\n     +\n    -+\n     +\tfor (usize i = 0; i < lhs->length; i++) {\n    -+\t\tvoid* left = (u8 *) lhs->ptr + i * lhs->element_size;\n    -+\t\tvoid* right = (u8 *) rhs->ptr + i * rhs->element_size;\n    ++\t\tvoid *left = (u8 *)lhs->ptr + i * lhs->element_size;\n    ++\t\tvoid *right = (u8 *)rhs->ptr + i * rhs->element_size;\n     +\t\tif (memcmp(left, right, lhs->element_size) != 0) {\n     +\t\t\treturn false;\n     +\t\t}\n    @@ interop/ivec.c (new)\n     +}\n     +\n     +\n    -+void ivec_free(void* self) {\n    -+\tstruct rawivec *this = self;\n    -+\tFREE_AND_NULL(this->ptr);\n    -+\tthis->length = 0;\n    -+\tthis->capacity = 0;\n    ++void ivec_free(void *self_)\n    ++{\n    ++\tstruct rawivec *self = self_;\n    ++\n    ++\tFREE_AND_NULL(self->ptr);\n    ++\tself->length = 0;\n    ++\tself->capacity = 0;\n     +\t/* don't modify self->element_size */\n     +}\n     +\n    -+void ivec_move(void* source, void* destination) {\n    -+\tstruct rawivec *this = source;\n    -+\tstruct rawivec *that = destination;\n    ++void ivec_move(void *source, void *destination)\n    ++{\n    ++\tstruct rawivec *src = source;\n    ++\tstruct rawivec *dst = destination;\n     +\n    -+\tif (this->element_size != that->element_size)\n    ++\tif (src->element_size != dst->element_size)\n     +\t\tBUG(\"mismatched element_size\");\n     +\n     +\tivec_free(destination);\n    -+\tthat->ptr = this->ptr;\n    -+\tthat->length = this->length;\n    -+\tthat->capacity = this->capacity;\n    ++\tdst->ptr = src->ptr;\n    ++\tdst->length = src->length;\n    ++\tdst->capacity = src->capacity;\n    ++\n    ++\tsrc->ptr = NULL;\n    ++\tsrc->length = 0;\n    ++\tsrc->capacity = 0;\n    ++}\n     +\n    -+\tthis->ptr = NULL;\n    -+\tthis->length = 0;\n    -+\tthis->capacity = 0;\n    ++#ifdef __cplusplus\n     +}\n    ++#endif\n     \n      ## interop/ivec.h (new) ##\n     @@\n     +#ifndef IVEC_H\n     +#define IVEC_H\n     +\n    -+#include \"../git-compat-util.h\"\n    ++#include <git-compat-util.h>\n    ++\n    ++#ifdef __cplusplus\n    ++extern \"C\" {\n    ++#endif\n     +\n     +struct rawivec {\n    -+\tvoid* ptr;\n    ++\tvoid *ptr;\n     +\tusize length;\n     +\tusize capacity;\n     +\tusize element_size;\n    @@ interop/ivec.h (new)\n     +\n     +#define DEFINE_IVEC_TYPE(type, suffix) \\\n     +struct ivec_##suffix { \\\n    -+\ttype* ptr; \\\n    ++\ttype *ptr; \\\n     +\tsize_t length; \\\n     +\tsize_t capacity; \\\n     +\tsize_t element_size; \\\n    @@ interop/ivec.h (new)\n     +DEFINE_IVEC_TYPE(usize, usize);\n     +DEFINE_IVEC_TYPE(isize, isize);\n     +\n    -+void ivec_init(void* self, usize element_size);\n    -+void ivec_zero(void* self, usize capacity);\n    -+void ivec_clear(void* self);\n    -+void ivec_reserve_exact(void* self, usize additional);\n    -+void ivec_reserve(void* self, usize additional);\n    -+void ivec_shrink_to_fit(void* self);\n    -+void ivec_resize(void* self, usize new_length, void* default_value);\n    -+void ivec_push(void* self, void* value);\n    -+void ivec_extend_from_slice(void* self, void const* ptr, usize size);\n    -+bool ivec_equal(void* self, void* other);\n    -+void ivec_free(void* self);\n    -+void ivec_move(void* source, void* destination);\n    ++void ivec_init(void *self_, usize element_size);\n    ++void ivec_zero(void *self_, usize capacity);\n    ++void ivec_clear(void *self_);\n    ++void ivec_reserve_exact(void *self_, usize additional);\n    ++void ivec_reserve(void *self_, usize additional);\n    ++void ivec_shrink_to_fit(void *self_);\n    ++void ivec_resize(void *self_, usize new_length, void *default_value);\n    ++void ivec_push(void *self_, void *value);\n    ++void ivec_extend_from_slice(void *self_, void const *ptr, usize size);\n    ++bool ivec_equal(void *self_, void *other);\n    ++void ivec_free(void *self_);\n    ++void ivec_move(void *source, void *destination);\n    ++\n    ++#ifdef __cplusplus\n    ++}\n    ++#endif\n     +\n     +#endif //IVEC_H\n     \n    @@ rust/interop/src/ivec.rs (new)\n     +\n     +impl<T> Drop for IVec<T> {\n     +    fn drop(&mut self) {\n    -+        unsafe {\n    -+            self._free();\n    -+        }\n    ++        self._set_capacity(0);\n     +    }\n     +}\n     +\n    @@ rust/interop/src/ivec.rs (new)\n     +        }\n     +    }\n     +\n    -+    fn _zero(&mut self) {\n    -+        self.ptr = std::ptr::null_mut();\n    -+        self.length = 0;\n    -+        self.capacity = 0;\n    -+        // DO NOT MODIFY element_size!!!\n    -+    }\n    -+\n    -+    unsafe fn _free(&mut self) {\n    -+        free(self.ptr as *mut std::ffi::c_void);\n    -+        self._zero();\n    -+    }\n    -+\n     +    fn _set_capacity(&mut self, new_capacity: usize) {\n     +        unsafe {\n     +            if new_capacity == self.capacity {\n     +                return;\n     +            }\n    ++            if new_capacity < self.length {\n    ++                self.truncate(new_capacity);\n    ++            }\n     +            if new_capacity == 0 {\n    -+                self._free();\n    ++                free(self.ptr as *mut c_void);\n    ++                self.ptr = std::ptr::null_mut();\n    ++                self.length = 0;\n    ++                self.capacity = 0;\n    ++                // DO NOT MODIFY element_size!!!\n     +            } else {\n    -+                let t = realloc(\n    -+                    self.ptr as *mut std::ffi::c_void,\n    -+                    new_capacity * size_of::<T>(),\n    -+                );\n    ++                let t = realloc(self.ptr as *mut c_void, new_capacity * size_of::<T>());\n     +                if t.is_null() {\n     +                    panic!(\"out of memory\");\n     +                }\n    @@ rust/interop/src/ivec.rs (new)\n     +            /* capacity does not need to be changed */\n     +        }\n     +\n    ++        /* IVec grows */\n     +        if new_length > self.length {\n     +            let range = self.length..new_length;\n     +            self._buffer_mut()[range].fill(default_value);\n    ++            self.length = new_length;\n     +        }\n     +\n    -+        self.length = new_length;\n    ++        /* IVec shrinks */\n    ++        if new_length < self.length {\n    ++            self.truncate(new_length);\n    ++        }\n     +    }\n     +\n     +    fn _buffer_mut(&mut self) -> &mut [T] {\n    @@ rust/interop/src/ivec.rs (new)\n     +        self._resize(new_length, default_value, true);\n     +    }\n     +\n    ++    pub fn truncate(&mut self, new_length: usize) {\n    ++        if new_length >= self.length {\n    ++            return;\n    ++        }\n    ++\n    ++        if std::mem::needs_drop::<T>() {\n    ++            let range = new_length..self.length;\n    ++            for v in &mut self.as_mut_slice()[range] {\n    ++                unsafe {\n    ++                    std::ptr::drop_in_place(v);\n    ++                }\n    ++            }\n    ++        }\n    ++\n    ++        self.length = new_length;\n    ++    }\n    ++\n     +    pub fn insert(&mut self, index: usize, value: T) {\n     +        if self.length == self.capacity {\n     +            self.reserve(1);\n    @@ rust/interop/src/ivec.rs (new)\n     +#[cfg(test)]\n     +mod tests {\n     +    use crate::ivec::IVec;\n    ++    use std::cell::RefCell;\n    ++    use std::mem::size_of;\n    ++    use std::ops::{Deref, DerefMut};\n     +    use std::panic;\n    ++    use std::rc::Rc;\n    ++\n    ++    struct DropTest {\n    ++        value: Rc<RefCell<u64>>,\n    ++    }\n    ++\n    ++    impl DropTest {\n    ++        fn new(value: Rc<RefCell<u64>>) -> Self {\n    ++            Self { value }\n    ++        }\n    ++    }\n    ++\n    ++    impl Drop for DropTest {\n    ++        fn drop(&mut self) {\n    ++            *self.value.borrow_mut() -= 1;\n    ++        }\n    ++    }\n    ++\n    ++    #[test]\n    ++    fn test_drop_elements() {\n    ++        let counter = Rc::new(RefCell::new(0u64));\n    ++        let size = 5;\n    ++\n    ++        /* drop whole IVec */\n    ++        let mut vec = IVec::new();\n    ++        for _ in 0..size {\n    ++            let tmp = DropTest::new(counter.clone());\n    ++            *tmp.value.borrow_mut() += 1;\n    ++            vec.push(tmp);\n    ++        }\n    ++        let cur = *counter.borrow();\n    ++        assert_eq!(size, cur);\n    ++        drop(vec);\n    ++\n    ++        let cur = *counter.borrow();\n    ++        assert_eq!(0u64, cur);\n    ++\n    ++        /* drop some elements */\n    ++        let mut vec = IVec::new();\n    ++        for i in 0..size {\n    ++            let tmp = DropTest::new(counter.clone());\n    ++            *tmp.value.borrow_mut() += 1;\n    ++            vec.push(tmp);\n    ++        }\n    ++        let cur = *counter.borrow().deref();\n    ++        assert_eq!(size, cur);\n    ++\n    ++        let expected = 2u64;\n    ++        vec.truncate(expected as usize);\n    ++\n    ++        let cur = *counter.borrow();\n    ++        assert_eq!(expected, cur);\n    ++        drop(vec);\n    ++    }\n     +\n     +    #[test]\n     +    fn test_panic_on_out_of_bounds() {\n    @@ rust/interop/src/ivec.rs (new)\n     +        let expected = vec.capacity + 10;\n     +        let default_value = 19;\n     +        vec.resize(expected, default_value);\n    -+        // assert_eq!(vec.capacity, vec.slice.len());\n     +        assert_eq!(expected, vec.length);\n     +        assert!(vec.capacity >= expected);\n     +        for i in 0..vec.length {\n    @@ rust/interop/src/ivec.rs (new)\n     +        }\n     +\n     +        vec.reserve(10);\n    -+        // assert_eq!(vec.capacity, vec.slice.len());\n     +        assert!(vec.capacity > vec.length);\n     +        let length_before = vec.length;\n     +        vec.shrink_to_fit();\n     +        assert_eq!(length_before, vec.length);\n     +        assert_eq!(vec.length, vec.capacity);\n    -+        // assert_eq!(vec.capacity, vec.slice.len());\n     +    }\n     +\n     +    #[test]\n    @@ rust/interop/src/ivec.rs (new)\n     +        assert_eq!(16, vec.element_size);\n     +        assert_eq!(size_of::<usize>() * 4, size_of::<IVec<u128>>());\n     +    }\n    -+\n    -+    #[test]\n    -+    fn test_manual_free() {\n    -+        type TestType = i16;\n    -+        let mut vec = IVec::<TestType>::new();\n    -+\n    -+        unsafe { vec._free() };\n    -+        assert!(vec.ptr.is_null());\n    -+        assert_eq!(0, vec.length);\n    -+        assert_eq!(0, vec.capacity);\n    -+        assert_eq!(size_of::<TestType>(), vec.element_size);\n    -+    }\n     +}\n     \n      ## rust/interop/src/lib.rs ##\n    @@ rust/interop/src/lib.rs\n     +    pub fn realloc(ptr: *mut c_void, size: usize) -> *mut c_void;\n     +    pub fn free(ptr: *mut c_void);\n     +}\n    -\n    - ## rust/xdiff/src/lib.rs ##\n    -@@\n    -+\n 7:  d4bed95463 =  7:  3a6f822e2a xdiff/xprepare: remove superfluous forward declarations\n 8:  7c68ce5349 =  8:  c7cd71dae0 xdiff: delete unnecessary fields from xrecord_t and xdfile_t\n 9:  e516ccc8c0 =  9:  1096c8f0a4 xdiff: make fields of xrecord_t Rust friendly\n10:  21bfb9f088 = 10:  0cd2168512 xdiff: use one definition for freeing xdfile_t\n11:  6ce0e252b3 ! 11:  7ea2dccd71 xdiff: replace chastore with an ivec in xdfile_t\n    @@ Commit message\n         xdiff: replace chastore with an ivec in xdfile_t\n     \n         xdfile_t currently uses a chastore which functions as a memory pool and\n    -    a vector which maps to the alocations created by the chastore. It seems\n    +    a vector which maps to the allocations created by the chastore. It seems\n         like xrecord_t used to be a linked list until the recs and nrec fields\n         were added. I think that xrecord_t.next was meant to be removed, but\n         was overlooked. This dual data structure setup make the code somewhat\n12:  0cfc6cf26b = 12:  d8f561e173 xdiff: delete nrec field from xdfile_t\n13:  cf0387d851 = 13:  15c2854a32 xdiff: delete recs field from xdfile_t\n14:  ea699135f9 = 14:  3dacb2c09d xdiff: make xdfile_t more rust friendly\n15:  b18544b74f ! 15:  9fdd23acf6 xdiff: implement xdl_trim_ends() in Rust\n    @@ rust/xdiff/src/lib.rs\n     @@\n     +pub mod xprepare;\n     +pub mod xtypes;\n    - \n    ++\n     +use crate::xprepare::trim_ends;\n     +use crate::xtypes::xdfile;\n     +\n\n\nEzekiel Newren (13):\n  xdiff: introduce rust\n  github workflows: install rust\n  github workflows: upload Cargo.lock\n  ivec: create a vector type that is interoperable between C and Rust\n  xdiff/xprepare: remove superfluous forward declarations\n  xdiff: delete unnecessary fields from xrecord_t and xdfile_t\n  xdiff: make fields of xrecord_t Rust friendly\n  xdiff: use one definition for freeing xdfile_t\n  xdiff: replace chastore with an ivec in xdfile_t\n  xdiff: delete nrec field from xdfile_t\n  xdiff: delete recs field from xdfile_t\n  xdiff: make xdfile_t more rust friendly\n  xdiff: implement xdl_trim_ends() in Rust\n\nJohannes Schindelin (1):\n  win+Meson: do allow linking with the Rust-built xdiff\n\nbrian m. carlson (1):\n  doc: add a policy for using Rust\n\n .github/workflows/main.yml                    |  88 ++-\n .gitignore                                    |   3 +\n Documentation/Makefile                        |   1 +\n Documentation/technical/platform-support.adoc |   2 +\n Documentation/technical/rust-support.adoc     | 142 +++++\n Makefile                                      |  67 ++-\n build_rust.sh                                 |  57 ++\n ci/install-dependencies.sh                    |  14 +-\n ci/install-rust-toolchain.sh                  |  30 +\n ci/install-rustup.sh                          |  25 +\n ci/lib.sh                                     |   1 +\n ci/make-test-artifacts.sh                     |   9 +\n ci/run-build-and-tests.sh                     |  13 +\n config.mak.uname                              |   4 +\n git-compat-util.h                             |  17 +\n interop/ivec.c                                | 180 ++++++\n interop/ivec.h                                |  60 ++\n meson.build                                   |  54 +-\n rust/Cargo.toml                               |   6 +\n rust/interop/Cargo.toml                       |  14 +\n rust/interop/src/ivec.rs                      | 516 ++++++++++++++++++\n rust/interop/src/lib.rs                       |  10 +\n rust/xdiff/Cargo.toml                         |  15 +\n rust/xdiff/src/lib.rs                         |  15 +\n rust/xdiff/src/xprepare.rs                    |  27 +\n rust/xdiff/src/xtypes.rs                      |  19 +\n xdiff/xdiffi.c                                |  60 +-\n xdiff/xdiffi.h                                |   8 +-\n xdiff/xemit.c                                 |  24 +-\n xdiff/xhistogram.c                            |   2 +-\n xdiff/xmerge.c                                |  72 +--\n xdiff/xpatience.c                             |  16 +-\n xdiff/xprepare.c                              | 271 ++++-----\n xdiff/xtypes.h                                |  27 +-\n xdiff/xutils.c                                |  12 +-\n 35 files changed, 1562 insertions(+), 319 deletions(-)\n create mode 100644 Documentation/technical/rust-support.adoc\n create mode 100755 build_rust.sh\n create mode 100755 ci/install-rust-toolchain.sh\n create mode 100755 ci/install-rustup.sh\n create mode 100644 interop/ivec.c\n create mode 100644 interop/ivec.h\n create mode 100644 rust/Cargo.toml\n create mode 100644 rust/interop/Cargo.toml\n create mode 100644 rust/interop/src/ivec.rs\n create mode 100644 rust/interop/src/lib.rs\n create mode 100644 rust/xdiff/Cargo.toml\n create mode 100644 rust/xdiff/src/lib.rs\n create mode 100644 rust/xdiff/src/xprepare.rs\n create mode 100644 rust/xdiff/src/xtypes.rs\n\n\nbase-commit: 16bd9f20a403117f2e0d9bcda6c6e621d3763e77\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2043%2Fezekielnewren%2Fintroduce_rust-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2043/ezekielnewren/introduce_rust-v1\nPull-Request: https://github.com/git/git/pull/2043\n-- \ngitgitgadget\n"},{"id":"525223","messageId":"6d065f550fe871cf010409f7bd2a63438cf52723.1756496539.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.git.git.1756496539.gitgitgadget@gmail.com","subject":"[PATCH 01/15] doc: add a policy for using Rust","fromName":"brian m. carlson via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-08-29T19:42:05Z","receivedAt":"2025-08-29T19:42:24Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"From: \"brian m. carlson\" <sandals@crustytoothpaste.net>\n\nGit has historically been written primarily in C, with some shell and\nPerl.  However, C is not memory safe, which makes it more likely that\nsecurity vulnerabilities or other bugs will be introduced, and it is\nalso more verbose and less ergonomic than other, more modern languages.\n\nOne of the most common modern compiled languages which is easily\ninteroperable with C is Rust.  It is popular (the most admired language\non the 2024 Stack Overflow Developer Survey), efficient, portable, and\nrobust.\n\nIntroduce a document laying out the incremental introduction of Rust to\nGit and provide a detailed rationale for doing so, including the points\nabove.  Propose a design for this approach that addresses the needs of\ndownstreams and distributors, as well as contributors.\n\nSince we don't want to carry both a C and Rust version of code and want\nto be able to add new features only in Rust, mention that Rust is a\nrequired part of our platform support policy.\n\nIt should be noted that a recent discussion at the Berlin Git Merge\nContributor Summit found widespread support for the addition of Rust to\nGit.  While of course not all contributors were represented, the\nproposal appeared to have the support of a majority of active\ncontributors.\n\nSigned-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n[en: Added some comments about types, and changed the recommondations\n     about cbindgen, bindgen, rustix, libc.]\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n Documentation/Makefile                        |   1 +\n Documentation/technical/platform-support.adoc |   2 +\n Documentation/technical/rust-support.adoc     | 142 ++++++++++++++++++\n 3 files changed, 145 insertions(+)\n create mode 100644 Documentation/technical/rust-support.adoc\n\ndiff --git a/Documentation/Makefile b/Documentation/Makefile\nindex b109d25e9c..066b761c01 100644\n--- a/Documentation/Makefile\n+++ b/Documentation/Makefile\n@@ -127,6 +127,7 @@ TECH_DOCS += technical/parallel-checkout\n TECH_DOCS += technical/partial-clone\n TECH_DOCS += technical/platform-support\n TECH_DOCS += technical/racy-git\n+TECH_DOCS += technical/rust-support\n TECH_DOCS += technical/reftable\n TECH_DOCS += technical/scalar\n TECH_DOCS += technical/send-pack-pipeline\ndiff --git a/Documentation/technical/platform-support.adoc b/Documentation/technical/platform-support.adoc\nindex 0a2fb28d62..dc71672dcb 100644\n--- a/Documentation/technical/platform-support.adoc\n+++ b/Documentation/technical/platform-support.adoc\n@@ -33,6 +33,8 @@ meet the following minimum requirements:\n \n * Has active security support (taking security releases of dependencies, etc)\n \n+* Supports Rust and the toolchain version specified in link:rust-support.adoc[].\n+\n These requirements are a starting point, and not sufficient on their own for the\n Git community to be enthusiastic about supporting your platform. Maintainers of\n platforms which do meet these requirements can follow the steps below to make it\ndiff --git a/Documentation/technical/rust-support.adoc b/Documentation/technical/rust-support.adoc\nnew file mode 100644\nindex 0000000000..57a001fa2d\n--- /dev/null\n+++ b/Documentation/technical/rust-support.adoc\n@@ -0,0 +1,142 @@\n+Usage of Rust in Git\n+====================\n+\n+Objective\n+---------\n+Introduce Rust into Git incrementally to improve security and maintainability.\n+\n+Background\n+----------\n+Git has historically been written primarily in C, with some portions in shell,\n+Perl, or other languages.  At the time it was originally written, this was\n+important for portability and was a logical choice for software development.\n+\n+:0: link:https://security.googleblog.com/2024/09/eliminating-memory-safety-vulnerabilities-Android.html\n+:1: link:https://www.cisa.gov/resources-tools/resources/product-security-bad-practices\n+\n+However, as time has progressed, we've seen an increased concern with memory\n+safety vulnerabilities and the development of newer languages, such as Rust,\n+that substantially limit or eliminate this class of vulnerabilities.\n+Development in a variety of projects has found that memory safety\n+vulnerabilities constitute about 70% of vulnerabilities of software in\n+languages that are not memory safe.  For instance, {0}[one survey of Android]\n+found that memory safety vulnerabilities decreased from 76% to 24% over six\n+years due to an increase in memory safe code.  Similarly, the U.S. government\n+is {1}[proposing to classify development in memory unsafe languages as a\n+Product Security Bad Practice\"].\n+\n+These risks are even more substantial when we consider the fact that Git is a\n+network-facing service.  Many organizations run Git servers internally or use a\n+cloud-based forge, and the risk of accidental exposure or compromise of user\n+data is substantial.  It's important to ensure that Git, whether it's used\n+locally or remotely, is robustly secure.\n+\n+In addition, C is a difficult language to write well and concisely.  While it\n+is of course possible to do anything with C, it lacks built-in support for\n+niceties found in modern languages, such as hash tables, generics, typed\n+errors, and automatic destruction, and most modern language offer shorter, more\n+ergonomic syntax for expressing code.  This is valuable functionality that can\n+allow Git to be developed more rapidly, more easily, by more developers of a\n+variety of levels, and with more confidence in the correctness of the code.\n+\n+For these reasons, adding Rust to Git is a sensible and prudent move that will\n+allow us to improve the quality of the code and potentially attract new developers.\n+\n+Goals\n+-----\n+1. Git continues to build, run, and pass tests on a wide variety of operating\n+   systems and architectures.\n+2. Transition from C to Rust is incremental; that is, code can be ported as it\n+   is convenient and Git does not need to transition all at once.\n+3. Git continues to support older operating systems in conformance with the\n+   platform support policy.\n+\n+Non-Goals\n+---------\n+1. Support for every possible operating system and architecture.  Git already\n+   has a platform support policy which defines what is supported and we already\n+   exclude some operating systems for various reasons (e.g., lacking enough POSIX\n+   tools to pass the test suite).\n+2. Implementing C-only versions of Rust code or compiling a C-only Git.  This\n+   would be difficult to maintain and would not offer the ergonomic benefits we\n+   desire.\n+\n+Design\n+------\n+Git will adopt Rust incrementally.  This transition will start with the\n+creation of a static library that can be linked into the existing Git binaries.\n+At some point, we may wish to expose a dynamic library and compile the Git\n+binaries themselves using Rust.  Using an incremental approach allows us to\n+determine as we go along how to structure our code in the best way for the\n+project and avoids the need to make hard, potentially disruptive, transitions\n+caused by porting a binary wholesale from one language to another that might\n+introduce bugs.\n+\n+Crates like libc or rustix define types like c_long, but in ways that are not\n+safe across platforms.\n+From https://docs.rs/rustix/latest/rustix/ffi/type.c_long.html:\n+\n+    This type will always be i32 or i64.  Most notably, many Linux-based\n+    systems assume an i64, but Windows assumes i32.  The C standard technically\n+    only requires that this type be a signed integer that is at least 32 bits\n+    and at least the size of an int, although in practice, no system would\n+    have a long that is neither an i32 nor i64.\n+\n+Also, note that other locations, such as\n+https://docs.rs/libc/latest/libc/type.c_long.html, just hardcode c_long as i64\n+even though C may mean i32 on some platforms.\n+\n+As such, using the c_long type would give us portability issues, and\n+perpetuate some of the bugs git has faced across platforms.  Avoid using C's\n+types (long, unsigned, char, etc.), and switch to unambiguous types (e.g. i32\n+or i64) before trying to make C and Rust interoperate.\n+\n+Crates like libc and rustix may have also traditionally aided interoperability\n+with older versions of Rust (e.g.  when worrying about stat[64] system calls),\n+but the Rust standard library in newer versions of Rust handle these concerns\n+in a platform agnostic way.  There may arise cases where we need to consider\n+these crates, but for now we omit them.\n+\n+Tools like bindgen and cbindgen create C-styled unsafe Rust code rather than\n+idiomatic Rust; where possible, we prefer to switch to idiomatic Rust.  Any\n+standard C library functions that are needed can be manually wrapped on the\n+Rust side.\n+\n+Rust upstream releases every six weeks and only supports the latest stable\n+release.  While it is nice that upstream is active, we would like our software\n+releases to have a lifespan exceeding six weeks.  To allow compiling our code\n+on a variety of systems, we will support the version of Rust in Debian stable,\n+plus, for a year after a new Debian stable is released, the version in Debian\n+oldstable.\n+\n+This provides an approximately three-year lifespan of support for a Rust\n+release and allows us to support a variety of operating systems and\n+architectures, including those for which Rust upstream does not build binaries.\n+Debian stable is the benchmark distribution used by many Rust projects when\n+determining supported Rust versions, and it is an extremely portable and\n+popular free software operating system that is available to the public at no\n+charge, which makes it a sensible choice for us as well.\n+\n+We may change this policy if the Rust project issues long-term support releases\n+or the Rust community and distributors agree on releases to target as if they\n+were long-term support releases.\n+\n+This version support policy necessitates that we be very careful about the\n+dependencies we include, since many Rust projects support only the latest\n+stable version.  However, we typically have been careful about dependencies in\n+the first place, so this should not be a major departure from existing policy,\n+although it may be a change for some existing Rust developers.\n+\n+We will avoid including the `Cargo.lock` file in the repository and instead\n+specify minimum dependency versions in the `Cargo.toml` file.  We want to allow\n+people to use newer versions of dependencies if necessary to support newer\n+platforms without needing to force upgrades of dependencies on all users, and\n+it provides additional flexibility for distribution maintainers.\n+\n+We do not plan to support beta or nightly versions of the Rust compiler.  These\n+versions may change rapidly and especially parts of the toolchain such as\n+Clippy, the lint tool, can have false positives or add additional warnings with\n+too great of a frequency to be supportable by the project.  However, we do plan\n+to support alternate compilers, such as the rust_codegen_gcc backend and gccrs\n+when they are stable and support our desired release versions.  This will\n+provide greater support for more operating systems and architectures.\n-- \ngitgitgadget\n\n"},{"id":"525224","messageId":"cc9cf877754c050ebb2014ab63f2dce431df646a.1756496539.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.git.git.1756496539.gitgitgadget@gmail.com","subject":"[PATCH 02/15] xdiff: introduce rust","fromName":"Ezekiel Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-08-29T19:42:06Z","receivedAt":"2025-08-29T19:42:25Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"From: Ezekiel Newren <ezekielnewren@gmail.com>\n\nUpcoming patches will simplify xdiff, while also porting parts of it to\nRust. In preparation, add some stubs and setup the Rust build. For now,\nit is easier to let cargo build rust and have make or meson merely link\nagainst the static library that cargo builds. In line with ongoing\nlibification efforts, use multiple crates to allow more modularity on\nthe Rust side. xdiff is the crate that this series will focus on, but\nwe also introduce the interop crate for future patch series.\n\nIn order to facilitate interoperability between C and Rust, introduce C\ndefinitions for Rust primitive types in git-compat-util.h.\n\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n .gitignore              |  3 +++\n Makefile                | 53 ++++++++++++++++++++++++++++----------\n build_rust.sh           | 57 +++++++++++++++++++++++++++++++++++++++++\n git-compat-util.h       | 17 ++++++++++++\n meson.build             | 52 +++++++++++++++++++++++++++++++------\n rust/Cargo.toml         |  6 +++++\n rust/interop/Cargo.toml | 14 ++++++++++\n rust/interop/src/lib.rs |  0\n rust/xdiff/Cargo.toml   | 15 +++++++++++\n rust/xdiff/src/lib.rs   |  0\n 10 files changed, 196 insertions(+), 21 deletions(-)\n create mode 100755 build_rust.sh\n create mode 100644 rust/Cargo.toml\n create mode 100644 rust/interop/Cargo.toml\n create mode 100644 rust/interop/src/lib.rs\n create mode 100644 rust/xdiff/Cargo.toml\n create mode 100644 rust/xdiff/src/lib.rs\n\ndiff --git a/.gitignore b/.gitignore\nindex 04c444404e..ff81e3580c 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -254,3 +254,6 @@ Release/\n /contrib/buildsystems/out\n /contrib/libgit-rs/target\n /contrib/libgit-sys/target\n+/.idea/\n+/rust/target/\n+/rust/Cargo.lock\ndiff --git a/Makefile b/Makefile\nindex 70d1543b6b..1ec0c1ee66 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -919,6 +919,29 @@ TEST_SHELL_PATH = $(SHELL_PATH)\n \n LIB_FILE = libgit.a\n XDIFF_LIB = xdiff/lib.a\n+\n+EXTLIBS =\n+\n+ifeq ($(DEBUG), 1)\n+  RUST_BUILD_MODE = debug\n+else\n+  RUST_BUILD_MODE = release\n+endif\n+\n+RUST_TARGET_DIR = rust/target/$(RUST_BUILD_MODE)\n+RUST_FLAGS_FOR_C = -L$(RUST_TARGET_DIR)\n+\n+.PHONY: compile_rust\n+compile_rust:\n+\t./build_rust.sh . $(RUST_BUILD_MODE) xdiff\n+\n+EXTLIBS += ./$(RUST_TARGET_DIR)/libxdiff.a\n+\n+UNAME_S := $(shell uname -s)\n+ifeq ($(UNAME_S),Linux)\n+  EXTLIBS += -ldl\n+endif\n+\n REFTABLE_LIB = reftable/libreftable.a\n \n GENERATED_H += command-list.h\n@@ -1390,7 +1413,7 @@ UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o\n \n # xdiff and reftable libs may in turn depend on what is in libgit.a\n GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(LIB_FILE)\n-EXTLIBS =\n+\n \n GIT_USER_AGENT = git/$(GIT_VERSION)\n \n@@ -2541,7 +2564,7 @@ git.sp git.s git.o: EXTRA_CPPFLAGS = \\\n \t'-DGIT_MAN_PATH=\"$(mandir_relative_SQ)\"' \\\n \t'-DGIT_INFO_PATH=\"$(infodir_relative_SQ)\"'\n \n-git$X: git.o GIT-LDFLAGS $(BUILTIN_OBJS) $(GITLIBS)\n+git$X: git.o GIT-LDFLAGS $(BUILTIN_OBJS) $(GITLIBS) compile_rust\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \\\n \t\t$(filter %.o,$^) $(LIBS)\n \n@@ -2891,17 +2914,17 @@ headless-git.o: compat/win32/headless.c GIT-CFLAGS\n headless-git$X: headless-git.o git.res GIT-LDFLAGS\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) $(ALL_LDFLAGS) -mwindows -o $@ $< git.res\n \n-git-%$X: %.o GIT-LDFLAGS $(GITLIBS)\n+git-%$X: %.o GIT-LDFLAGS $(GITLIBS) compile_rust\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)\n \n-git-imap-send$X: imap-send.o $(IMAP_SEND_BUILDDEPS) GIT-LDFLAGS $(GITLIBS)\n+git-imap-send$X: imap-send.o $(IMAP_SEND_BUILDDEPS) GIT-LDFLAGS $(GITLIBS) compile_rust\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \\\n \t\t$(IMAP_SEND_LDFLAGS) $(LIBS)\n \n-git-http-fetch$X: http.o http-walker.o http-fetch.o GIT-LDFLAGS $(GITLIBS)\n+git-http-fetch$X: http.o http-walker.o http-fetch.o GIT-LDFLAGS $(GITLIBS) compile_rust\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \\\n \t\t$(CURL_LIBCURL) $(LIBS)\n-git-http-push$X: http.o http-push.o GIT-LDFLAGS $(GITLIBS)\n+git-http-push$X: http.o http-push.o GIT-LDFLAGS $(GITLIBS) compile_rust\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \\\n \t\t$(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIBS)\n \n@@ -2911,11 +2934,11 @@ $(REMOTE_CURL_ALIASES): $(REMOTE_CURL_PRIMARY)\n \tln -s $< $@ 2>/dev/null || \\\n \tcp $< $@\n \n-$(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS $(GITLIBS)\n+$(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS $(GITLIBS) compile_rust\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \\\n \t\t$(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIBS)\n \n-scalar$X: scalar.o GIT-LDFLAGS $(GITLIBS)\n+scalar$X: scalar.o GIT-LDFLAGS $(GITLIBS) compile_rust\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \\\n \t\t$(filter %.o,$^) $(LIBS)\n \n@@ -2925,6 +2948,7 @@ $(LIB_FILE): $(LIB_OBJS)\n $(XDIFF_LIB): $(XDIFF_OBJS)\n \t$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^\n \n+\n $(REFTABLE_LIB): $(REFTABLE_OBJS)\n \t$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^\n \n@@ -3294,7 +3318,7 @@ perf: all\n \n t/helper/test-tool$X: $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS)) $(UNIT_TEST_DIR)/test-lib.o\n \n-t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS)\n+t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS) compile_rust\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS)\n \n check-sha1:: t/helper/test-tool$X\n@@ -3756,7 +3780,10 @@ cocciclean:\n \t$(RM) -r .build/contrib/coccinelle\n \t$(RM) contrib/coccinelle/*.cocci.patch\n \n-clean: profile-clean coverage-clean cocciclean\n+rustclean:\n+\tcd rust && cargo clean\n+\n+clean: profile-clean coverage-clean cocciclean rustclean\n \t$(RM) -r .build $(UNIT_TEST_BIN)\n \t$(RM) GIT-TEST-SUITES\n \t$(RM) po/git.pot po/git-core.pot\n@@ -3911,13 +3938,13 @@ FUZZ_CXXFLAGS ?= $(ALL_CFLAGS)\n .PHONY: fuzz-all\n fuzz-all: $(FUZZ_PROGRAMS)\n \n-$(FUZZ_PROGRAMS): %: %.o oss-fuzz/dummy-cmd-main.o $(GITLIBS) GIT-LDFLAGS\n+$(FUZZ_PROGRAMS): %: %.o oss-fuzz/dummy-cmd-main.o $(GITLIBS) GIT-LDFLAGS compile_rust\n \t$(QUIET_LINK)$(FUZZ_CXX) $(FUZZ_CXXFLAGS) -o $@ $(ALL_LDFLAGS) \\\n \t\t-Wl,--allow-multiple-definition \\\n \t\t$(filter %.o,$^) $(filter %.a,$^) $(LIBS) $(LIB_FUZZING_ENGINE)\n \n $(UNIT_TEST_PROGS): $(UNIT_TEST_BIN)/%$X: $(UNIT_TEST_DIR)/%.o $(UNIT_TEST_OBJS) \\\n-\t$(GITLIBS) GIT-LDFLAGS\n+\t$(GITLIBS) GIT-LDFLAGS compile_rust\n \t$(call mkdir_p_parent_template)\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \\\n \t\t$(filter %.o,$^) $(filter %.a,$^) $(LIBS)\n@@ -3936,7 +3963,7 @@ $(UNIT_TEST_DIR)/clar.suite: $(UNIT_TEST_DIR)/clar-decls.h $(UNIT_TEST_DIR)/gene\n $(UNIT_TEST_DIR)/clar/clar.o: $(UNIT_TEST_DIR)/clar.suite\n $(CLAR_TEST_OBJS): $(UNIT_TEST_DIR)/clar-decls.h\n $(CLAR_TEST_OBJS): EXTRA_CPPFLAGS = -I$(UNIT_TEST_DIR)\n-$(CLAR_TEST_PROG): $(UNIT_TEST_DIR)/clar.suite $(CLAR_TEST_OBJS) $(GITLIBS) GIT-LDFLAGS\n+$(CLAR_TEST_PROG): $(UNIT_TEST_DIR)/clar.suite $(CLAR_TEST_OBJS) $(GITLIBS) GIT-LDFLAGS compile_rust\n \t$(call mkdir_p_parent_template)\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)\n \ndiff --git a/build_rust.sh b/build_rust.sh\nnew file mode 100755\nindex 0000000000..192385a1d9\n--- /dev/null\n+++ b/build_rust.sh\n@@ -0,0 +1,57 @@\n+#!/bin/sh\n+\n+\n+rustc -vV || exit $?\n+cargo --version || exit $?\n+\n+dir_git_root=${0%/*}\n+dir_build=$1\n+rust_build_profile=$2\n+crate=$3\n+\n+dir_rust=$dir_git_root/rust\n+\n+if [ \"$dir_git_root\" = \"\" ]; then\n+  echo \"did not specify the directory for the root of git\"\n+  exit 1\n+fi\n+\n+if [ \"$dir_build\" = \"\" ]; then\n+  echo \"did not specify the build directory\"\n+  exit 1\n+fi\n+\n+if [ \"$rust_build_profile\" = \"\" ]; then\n+  echo \"did not specify the rust_build_profile\"\n+  exit 1\n+fi\n+\n+if [ \"$rust_build_profile\" = \"release\" ]; then\n+  rust_args=\"--release\"\n+  export RUSTFLAGS=''\n+elif [ \"$rust_build_profile\" = \"debug\" ]; then\n+  rust_args=\"\"\n+  export RUSTFLAGS='-C debuginfo=2 -C opt-level=1 -C force-frame-pointers=yes'\n+else\n+  echo \"illegal rust_build_profile value $rust_build_profile\"\n+  exit 1\n+fi\n+\n+cd $dir_rust && cargo clean && pwd && cargo build -p $crate $rust_args; cd $dir_git_root\n+\n+libfile=\"lib${crate}.a\"\n+if rustup show active-toolchain | grep windows-msvc; then\n+  libfile=\"${crate}.lib\"\n+fi\n+dst=$dir_build/$libfile\n+\n+if [ \"$dir_git_root\" != \"$dir_build\" ]; then\n+  src=$dir_rust/target/$rust_build_profile/$libfile\n+  if [ ! -f $src ]; then\n+    echo >&2 \"::error:: cannot find path of static library $src is not a file or does not exist\"\n+    exit 5\n+  fi\n+\n+  rm $dst 2>/dev/null\n+  mv $src $dst\n+fi\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex 4678e21c4c..82dc99764a 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -196,6 +196,23 @@ static inline int is_xplatform_dir_sep(int c)\n #include \"compat/msvc.h\"\n #endif\n \n+/* rust types */\n+typedef uint8_t   u8;\n+typedef uint16_t  u16;\n+typedef uint32_t  u32;\n+typedef uint64_t  u64;\n+\n+typedef int8_t    i8;\n+typedef int16_t   i16;\n+typedef int32_t   i32;\n+typedef int64_t   i64;\n+\n+typedef float     f32;\n+typedef double    f64;\n+\n+typedef size_t    usize;\n+typedef ptrdiff_t isize;\n+\n /* used on Mac OS X */\n #ifdef PRECOMPOSE_UNICODE\n #include \"compat/precompose_utf8.h\"\ndiff --git a/meson.build b/meson.build\nindex 596f5ac711..324f968338 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -267,6 +267,40 @@ version_gen_environment.set('GIT_DATE', get_option('build_date'))\n version_gen_environment.set('GIT_USER_AGENT', get_option('user_agent'))\n version_gen_environment.set('GIT_VERSION', get_option('version'))\n \n+if get_option('optimization') in ['2', '3', 's', 'z']\n+  rust_build_profile = 'release'\n+else\n+  rust_build_profile = 'debug'\n+endif\n+\n+# Run `rustup show active-toolchain` and capture output\n+rustup_out = run_command('rustup', 'show', 'active-toolchain',\n+                         check: true).stdout().strip()\n+\n+rust_crates = ['xdiff']\n+rust_builds = []\n+\n+foreach crate : rust_crates\n+  if rustup_out.contains('windows-msvc')\n+    libfile = crate + '.lib'\n+  else\n+    libfile = 'lib' + crate + '.a'\n+  endif\n+\n+  rust_builds += custom_target(\n+    'rust_build_'+crate,\n+    output: libfile,\n+    build_by_default: true,\n+    build_always_stale: true,\n+    command: [\n+      meson.project_source_root() / 'build_rust.sh',\n+      meson.current_build_dir(), rust_build_profile, crate,\n+    ],\n+    install: false,\n+  )\n+endforeach\n+\n+\n compiler = meson.get_compiler('c')\n \n libgit_sources = [\n@@ -1678,14 +1712,16 @@ version_def_h = custom_target(\n libgit_sources += version_def_h\n \n libgit = declare_dependency(\n-  link_with: static_library('git',\n-    sources: libgit_sources,\n-    c_args: libgit_c_args + [\n-      '-DGIT_VERSION_H=\"' + version_def_h.full_path() + '\"',\n-    ],\n-    dependencies: libgit_dependencies,\n-    include_directories: libgit_include_directories,\n-  ),\n+  link_with: [\n+    static_library('git',\n+      sources: libgit_sources,\n+      c_args: libgit_c_args + [\n+        '-DGIT_VERSION_H=\"' + version_def_h.full_path() + '\"',\n+      ],\n+      dependencies: libgit_dependencies,\n+      include_directories: libgit_include_directories,\n+    ),\n+  ] + rust_builds,\n   compile_args: libgit_c_args,\n   dependencies: libgit_dependencies,\n   include_directories: libgit_include_directories,\ndiff --git a/rust/Cargo.toml b/rust/Cargo.toml\nnew file mode 100644\nindex 0000000000..ed3d79d7f8\n--- /dev/null\n+++ b/rust/Cargo.toml\n@@ -0,0 +1,6 @@\n+[workspace]\n+members = [\n+    \"xdiff\",\n+    \"interop\",\n+]\n+resolver = \"2\"\ndiff --git a/rust/interop/Cargo.toml b/rust/interop/Cargo.toml\nnew file mode 100644\nindex 0000000000..045e3b01cf\n--- /dev/null\n+++ b/rust/interop/Cargo.toml\n@@ -0,0 +1,14 @@\n+[package]\n+name = \"interop\"\n+version = \"0.1.0\"\n+edition = \"2021\"\n+\n+[lib]\n+name = \"interop\"\n+path = \"src/lib.rs\"\n+## staticlib to generate xdiff.a for use by gcc\n+## cdylib (optional) to generate xdiff.so for use by gcc\n+## rlib is required by the rust unit tests\n+crate-type = [\"staticlib\", \"rlib\"]\n+\n+[dependencies]\ndiff --git a/rust/interop/src/lib.rs b/rust/interop/src/lib.rs\nnew file mode 100644\nindex 0000000000..e69de29bb2\ndiff --git a/rust/xdiff/Cargo.toml b/rust/xdiff/Cargo.toml\nnew file mode 100644\nindex 0000000000..eb7966aada\n--- /dev/null\n+++ b/rust/xdiff/Cargo.toml\n@@ -0,0 +1,15 @@\n+[package]\n+name = \"xdiff\"\n+version = \"0.1.0\"\n+edition = \"2021\"\n+\n+[lib]\n+name = \"xdiff\"\n+path = \"src/lib.rs\"\n+## staticlib to generate xdiff.a for use by gcc\n+## cdylib (optional) to generate xdiff.so for use by gcc\n+## rlib is required by the rust unit tests\n+crate-type = [\"staticlib\", \"rlib\"]\n+\n+[dependencies]\n+interop = { path = \"../interop\" }\ndiff --git a/rust/xdiff/src/lib.rs b/rust/xdiff/src/lib.rs\nnew file mode 100644\nindex 0000000000..e69de29bb2\n-- \ngitgitgadget\n\n"},{"id":"525225","messageId":"6c47401ba00d7eabce4a74a47a495c9b23f52ee7.1756496539.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.git.git.1756496539.gitgitgadget@gmail.com","subject":"[PATCH 03/15] github workflows: install rust","fromName":"Ezekiel Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-08-29T19:42:07Z","receivedAt":"2025-08-29T19:42:26Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"From: Ezekiel Newren <ezekielnewren@gmail.com>\n\nPrefer using actions-rs/toolchain@v1 where possible to install rustup,\nbut for docker targets use a script to install rustup. Consolidate the\nRust toolchain definitions in main.yaml. Use install-rust-toolchain.sh\nto ensure the correct toolchain is used. Five overrides are used in\nmain.yaml:\n\n  * On Windows: Rust didn't resolve the bcrypt library on Windows\n    correctly until version 1.78.0. Also since rustup mis-identifies\n    the Rust toolchain, the Rust target triple must be set to\n    x86_64-pc-windows-gnu for make (win build), and\n    x86_64-pc-windows-msvc for meson (win+Meson build).\n  * On musl: libc differences, such as ftruncate64 vs ftruncate, were\n    not accounted for until Rust version 1.72.0. No older version of\n    Rust will work on musl for our needs.\n  * In a 32-bit docker container running on a 64-bit host, we need to\n    override the Rust target triple. This is because rustup asks the\n    kernel for the bitness of the system and it says 64, even though\n    the container is 32-bit. This also allows us to remove the\n    BITNESS environment variable in ci/lib.sh.\n\nThe logic for selecting library names was initially provided in a patch\nfrom Johannes, but was reworked and squashed into this commit.\n\nHelped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n .github/workflows/main.yml   | 60 +++++++++++++++++++++++++++++++++++-\n ci/install-dependencies.sh   | 14 ++++-----\n ci/install-rust-toolchain.sh | 30 ++++++++++++++++++\n ci/install-rustup.sh         | 25 +++++++++++++++\n ci/lib.sh                    |  1 +\n ci/make-test-artifacts.sh    |  9 ++++++\n ci/run-build-and-tests.sh    | 13 ++++++++\n 7 files changed, 144 insertions(+), 8 deletions(-)\n create mode 100755 ci/install-rust-toolchain.sh\n create mode 100755 ci/install-rustup.sh\n\ndiff --git a/.github/workflows/main.yml b/.github/workflows/main.yml\nindex 7dbf9f7f12..ac1d583ab2 100644\n--- a/.github/workflows/main.yml\n+++ b/.github/workflows/main.yml\n@@ -26,6 +26,13 @@ jobs:\n     outputs:\n       enabled: ${{ steps.check-ref.outputs.enabled }}${{ steps.skip-if-redundant.outputs.enabled }}\n       skip_concurrent: ${{ steps.check-ref.outputs.skip_concurrent }}\n+      rust_version_minimum: 1.61.0\n+      rust_version_windows: 1.78.0\n+      rust_version_musl: 1.72.0\n+      ## the rust target is inferred by rustup unless specified\n+      rust_target_windows_make: x86_64-pc-windows-gnu\n+      rust_target_windows_meson: x86_64-pc-windows-msvc\n+      rust_target_32bit_linux: i686-unknown-linux-gnu\n     steps:\n       - name: try to clone ci-config branch\n         run: |\n@@ -108,12 +115,26 @@ jobs:\n     needs: ci-config\n     if: needs.ci-config.outputs.enabled == 'yes'\n     runs-on: windows-latest\n+    env:\n+      CARGO_HOME: \"/c/Users/runneradmin/.cargo\"\n     concurrency:\n       group: windows-build-${{ github.ref }}\n       cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }}\n     steps:\n     - uses: actions/checkout@v4\n     - uses: git-for-windows/setup-git-for-windows-sdk@v1\n+    - name: Install rustup via github actions\n+      uses: actions-rs/toolchain@v1\n+      with:\n+        toolchain: stable\n+        profile: minimal\n+        override: false\n+    - name: Install Rust toolchain\n+      shell: bash\n+      env:\n+        RUST_VERSION: ${{ needs.ci-config.outputs.rust_version_windows }}\n+        RUST_TARGET: ${{ needs.ci-config.outputs.rust_target_windows_make }}\n+      run: ci/install-rust-toolchain.sh\n     - name: build\n       shell: bash\n       env:\n@@ -254,12 +275,26 @@ jobs:\n     needs: ci-config\n     if: needs.ci-config.outputs.enabled == 'yes'\n     runs-on: windows-latest\n+    env:\n+      CARGO_HOME: \"/c/Users/runneradmin/.cargo\"\n     concurrency:\n       group: windows-meson-build-${{ github.ref }}\n       cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }}\n     steps:\n     - uses: actions/checkout@v4\n     - uses: actions/setup-python@v5\n+    - name: Install rustup via github actions\n+      uses: actions-rs/toolchain@v1\n+      with:\n+        toolchain: stable\n+        profile: minimal\n+        override: false\n+    - name: Install Rust toolchain\n+      shell: bash\n+      env:\n+        RUST_VERSION: ${{ needs.ci-config.outputs.rust_version_windows }}\n+        RUST_TARGET: ${{ needs.ci-config.outputs.rust_target_windows_meson }}\n+      run: ci/install-rust-toolchain.sh\n     - name: Set up dependencies\n       shell: pwsh\n       run: pip install meson ninja\n@@ -329,11 +364,24 @@ jobs:\n       jobname: ${{matrix.vector.jobname}}\n       CI_JOB_IMAGE: ${{matrix.vector.pool}}\n       TEST_OUTPUT_DIRECTORY: ${{github.workspace}}/t\n+      CARGO_HOME: \"/Users/runner/.cargo\"\n     runs-on: ${{matrix.vector.pool}}\n     steps:\n     - uses: actions/checkout@v4\n     - run: ci/install-dependencies.sh\n-    - run: ci/run-build-and-tests.sh\n+    - name: Install rustup via github actions\n+      uses: actions-rs/toolchain@v1\n+      with:\n+        toolchain: stable\n+        profile: minimal\n+        override: false\n+    - name: Install Rust toolchain\n+      shell: bash\n+      env:\n+        RUST_VERSION: ${{ needs.ci-config.outputs.rust_version_minimum }}\n+      run: ci/install-rust-toolchain.sh\n+    - name: Run build and tests\n+      run: ci/run-build-and-tests.sh\n     - name: print test failures\n       if: failure() && env.FAILED_TEST_ARTIFACTS != ''\n       run: ci/print-test-failures.sh\n@@ -393,9 +441,11 @@ jobs:\n           cc: gcc\n         - jobname: linux-musl-meson\n           image: alpine:latest\n+          rust_version_override: ${{ needs.ci-config.outputs.rust_version_musl }}\n         # Supported until 2025-04-02.\n         - jobname: linux32\n           image: i386/ubuntu:focal\n+          rust_target_override: ${{ needs.ci-config.outputs.rust_target_32bit_linux }}\n         - jobname: pedantic\n           image: fedora:latest\n         # A RHEL 8 compatible distro.  Supported until 2029-05-31.\n@@ -409,6 +459,7 @@ jobs:\n       CC: ${{matrix.vector.cc}}\n       CI_JOB_IMAGE: ${{matrix.vector.image}}\n       CUSTOM_PATH: /custom\n+      CARGO_HOME: /home/builder/.cargo\n     runs-on: ubuntu-latest\n     container: ${{matrix.vector.image}}\n     steps:\n@@ -433,6 +484,13 @@ jobs:\n     - run: ci/install-dependencies.sh\n     - run: useradd builder --create-home\n     - run: chown -R builder .\n+    - name: Install rustup via script\n+      run: sudo --preserve-env --set-home --user=builder ci/install-rustup.sh\n+    - name: Install Rust toolchain\n+      env:\n+        RUST_VERSION: ${{ matrix.vector.rust_version_override || needs.ci-config.outputs.rust_version_minimum }}\n+        RUST_TARGET: ${{ matrix.vector.rust_target_override || '' }}\n+      run: sudo --preserve-env --set-home --user=builder ci/install-rust-toolchain.sh\n     - run: sudo --preserve-env --set-home --user=builder ci/run-build-and-tests.sh\n     - name: print test failures\n       if: failure() && env.FAILED_TEST_ARTIFACTS != ''\ndiff --git a/ci/install-dependencies.sh b/ci/install-dependencies.sh\nindex d061a47293..7801075821 100755\n--- a/ci/install-dependencies.sh\n+++ b/ci/install-dependencies.sh\n@@ -24,14 +24,14 @@ fi\n \n case \"$distro\" in\n alpine-*)\n-\tapk add --update shadow sudo meson ninja-build gcc libc-dev curl-dev openssl-dev expat-dev gettext \\\n+\tapk add --update shadow sudo meson ninja-build gcc libc-dev curl curl-dev openssl-dev expat-dev gettext \\\n \t\tzlib-ng-dev pcre2-dev python3 musl-libintl perl-utils ncurses \\\n \t\tapache2 apache2-http2 apache2-proxy apache2-ssl apache2-webdav apr-util-dbd_sqlite3 \\\n \t\tbash cvs gnupg perl-cgi perl-dbd-sqlite perl-io-tty >/dev/null\n \t;;\n fedora-*|almalinux-*)\n \tdnf -yq update >/dev/null &&\n-\tdnf -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\n+\tdnf -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\n \t;;\n ubuntu-*|i386/ubuntu-*|debian-*)\n \t# Required so that apt doesn't wait for user input on certain packages.\n@@ -55,8 +55,8 @@ ubuntu-*|i386/ubuntu-*|debian-*)\n \tsudo apt-get -q update\n \tsudo apt-get -q -y install \\\n \t\t$LANGUAGES apache2 cvs cvsps git gnupg $SVN \\\n-\t\tmake libssl-dev libcurl4-openssl-dev libexpat-dev wget sudo default-jre \\\n-\t\ttcl tk gettext zlib1g-dev perl-modules liberror-perl libauthen-sasl-perl \\\n+\t\tmake libssl-dev curl libcurl4-openssl-dev libexpat-dev wget sudo default-jre \\\n+\t\ttcl tk gettext zlib1g zlib1g-dev perl-modules liberror-perl libauthen-sasl-perl \\\n \t\tlibemail-valid-perl libio-pty-perl libio-socket-ssl-perl libnet-smtp-ssl-perl libdbd-sqlite3-perl libcgi-pm-perl \\\n \t\tlibsecret-1-dev libpcre2-dev meson ninja-build pkg-config \\\n \t\t${CC_PACKAGE:-${CC:-gcc}} $PYTHON_PACKAGE\n@@ -121,13 +121,13 @@ ClangFormat)\n \t;;\n StaticAnalysis)\n \tsudo apt-get -q update\n-\tsudo apt-get -q -y install coccinelle libcurl4-openssl-dev libssl-dev \\\n+\tsudo apt-get -q -y install coccinelle curl libcurl4-openssl-dev libssl-dev \\\n \t\tlibexpat-dev gettext make\n \t;;\n sparse)\n \tsudo apt-get -q update -q\n-\tsudo apt-get -q -y install libssl-dev libcurl4-openssl-dev \\\n-\t\tlibexpat-dev gettext zlib1g-dev sparse\n+\tsudo apt-get -q -y install libssl-dev curl libcurl4-openssl-dev \\\n+\t\tlibexpat-dev gettext zlib1g zlib1g-dev sparse\n \t;;\n Documentation)\n \tsudo apt-get -q update\ndiff --git a/ci/install-rust-toolchain.sh b/ci/install-rust-toolchain.sh\nnew file mode 100755\nindex 0000000000..06a29c4cfa\n--- /dev/null\n+++ b/ci/install-rust-toolchain.sh\n@@ -0,0 +1,30 @@\n+#!/bin/sh\n+\n+if [ \"$CARGO_HOME\" = \"\" ]; then\n+  echo >&2 \"::error:: CARGO_HOME is not set\"\n+  exit 2\n+fi\n+export PATH=\"$CARGO_HOME/bin:$PATH\"\n+rustup -vV || exit $?\n+\n+## Enforce the correct Rust toolchain\n+rustup override unset || true\n+\n+## install a specific version of rust\n+if [ \"$RUST_TARGET\" != \"\" ]; then\n+  rustup default --force-non-host \"$RUST_VERSION-$RUST_TARGET\" || exit $?\n+else\n+  rustup default \"$RUST_VERSION\" || exit $?\n+fi\n+\n+rustc -vV || exit $?\n+\n+RE_RUST_TARGET=\"$RUST_TARGET\"\n+if [ \"$RUST_TARGET\" = \"\" ]; then\n+  RE_RUST_TARGET=\"[^ ]+\"\n+fi\n+\n+if ! rustup show active-toolchain | grep -E \"^$RUST_VERSION-$RE_RUST_TARGET \\(default\\)$\"; then\n+  echo >&2 \"::error:: wrong Rust toolchain, active-toolchain: $(rustup show active-toolchain)\"\n+  exit 3\n+fi\ndiff --git a/ci/install-rustup.sh b/ci/install-rustup.sh\nnew file mode 100755\nindex 0000000000..0036231aee\n--- /dev/null\n+++ b/ci/install-rustup.sh\n@@ -0,0 +1,25 @@\n+#!/bin/sh\n+\n+## github workflows actions-rs/toolchain@v1 doesn't work for docker\n+## targets. This script should only be used if the ci pipeline\n+## doesn't support installing rust on a particular target.\n+\n+if [ \"$(id -u)\" -eq 0 ]; then\n+  echo >&2 \"::warning:: installing rust as root\"\n+fi\n+\n+if [ \"$CARGO_HOME\" = \"\" ]; then\n+  echo >&2 \"::error:: CARGO_HOME is not set\"\n+  exit 2\n+fi\n+\n+export RUSTUP_HOME=$CARGO_HOME\n+\n+## install rustup\n+curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --default-toolchain none -y\n+if [ ! -f $CARGO_HOME/env ]; then\n+  echo \"PATH=$CARGO_HOME/bin:\\$PATH\" > $CARGO_HOME/env\n+fi\n+. $CARGO_HOME/env\n+\n+rustup -vV\ndiff --git a/ci/lib.sh b/ci/lib.sh\nindex f561884d40..a7992b22fd 100755\n--- a/ci/lib.sh\n+++ b/ci/lib.sh\n@@ -1,5 +1,6 @@\n # Library of functions shared by all CI scripts\n \n+\n if test true = \"$GITHUB_ACTIONS\"\n then\n \tbegin_group () {\ndiff --git a/ci/make-test-artifacts.sh b/ci/make-test-artifacts.sh\nindex 74141af0cc..e37ed7030c 100755\n--- a/ci/make-test-artifacts.sh\n+++ b/ci/make-test-artifacts.sh\n@@ -7,6 +7,15 @@ mkdir -p \"$1\" # in case ci/lib.sh decides to quit early\n \n . ${0%/*}/lib.sh\n \n+## ensure rustup is in the PATH variable\n+if [ \"$CARGO_HOME\" = \"\" ]; then\n+  echo >&2 \"::error:: CARGO_HOME is not set\"\n+  exit 2\n+fi\n+export PATH=\"$CARGO_HOME/bin:$PATH\"\n+\n+rustc -vV\n+\n group Build make artifacts-tar ARTIFACTS_DIRECTORY=\"$1\"\n \n check_unignored_build_artifacts\ndiff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh\nindex 01823fd0f1..22b61e2812 100755\n--- a/ci/run-build-and-tests.sh\n+++ b/ci/run-build-and-tests.sh\n@@ -5,6 +5,15 @@\n \n . ${0%/*}/lib.sh\n \n+## ensure rustup is in the PATH variable\n+if [ \"$CARGO_HOME\" = \"\" ]; then\n+  echo >&2 \"::error:: CARGO_HOME is not set\"\n+  exit 2\n+fi\n+. $CARGO_HOME/env\n+\n+rustc -vV || exit $?\n+\n run_tests=t\n \n case \"$jobname\" in\n@@ -72,5 +81,9 @@ case \"$jobname\" in\n \t;;\n esac\n \n+if [ -d \"$CARGO_HOME\" ]; then\n+  rm -rf $CARGO_HOME\n+fi\n+\n check_unignored_build_artifacts\n save_good_tree\n-- \ngitgitgadget\n\n"},{"id":"525226","messageId":"8e350700bcdd0c2e020e24c202335aab4fffe286.1756496539.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.git.git.1756496539.gitgitgadget@gmail.com","subject":"[PATCH 04/15] win+Meson: do allow linking with the Rust-built xdiff","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-08-29T19:42:08Z","receivedAt":"2025-08-29T19:42:28Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWhen linking against the Rust-built `xdiff`, there is now a new required\ndependency: Without _also_ linking to the system library `userenv`, the\ncompile would fail with this error message:\n\n  xdiff.lib(std-c85e9beb7923f636.std.df32d1bc89881d89-cgu.0.rcgu.o) :\n  error LNK2019: unresolved external symbol __imp_GetUserProfileDirectoryW\n  referenced in function _ZN3std3env8home_dir17hfd1c3b6676cd78f6E\n\nTherefore, just like we do in case of Makefile-based builds on Windows,\nwe now also link to that library when building with Meson.\n\nNote that if we only have Rust depend upon libuserenv then at link time\nGCC would complain about:\n\n  undefined reference to `GetUserProfileDirectoryW'\n\nApparently there is _some_ closure that gets compiled in that requires\nthis function, and that in turn forces Git to link to libuserenv.\n\nThis is a new requirement, and therefore has not been made part of the\n\"minimal Git for Windows SDK\".\n\nIn the near future, I intend to include it, but for now let's just\nensure that the file is added manually if it is missing.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n[en: Squashed a few of Johannes's patches, and moved lib userenv\n handling from an earlier patch]\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n .github/workflows/main.yml | 8 ++++++++\n config.mak.uname           | 4 ++++\n meson.build                | 1 +\n 3 files changed, 13 insertions(+)\n\ndiff --git a/.github/workflows/main.yml b/.github/workflows/main.yml\nindex ac1d583ab2..ff2f90d122 100644\n--- a/.github/workflows/main.yml\n+++ b/.github/workflows/main.yml\n@@ -123,6 +123,14 @@ jobs:\n     steps:\n     - uses: actions/checkout@v4\n     - uses: git-for-windows/setup-git-for-windows-sdk@v1\n+    - name: ensure that libuserenv.a is present\n+      shell: bash\n+      run: |\n+        cd /mingw64/lib && {\n+          test -f libuserenv.a ||\n+          /c/Program\\ Files/Git/mingw64/bin/curl -Lo libuserenv.a \\\n+            https://github.com/git-for-windows/git-sdk-64/raw/HEAD/mingw64/lib/libuserenv.a\n+        }\n     - name: Install rustup via github actions\n       uses: actions-rs/toolchain@v1\n       with:\ndiff --git a/config.mak.uname b/config.mak.uname\nindex 3e26bb074a..6805e3778a 100644\n--- a/config.mak.uname\n+++ b/config.mak.uname\n@@ -740,6 +740,10 @@ ifeq ($(uname_S),MINGW)\n \t\tCOMPAT_CFLAGS += -D_USE_32BIT_TIME_T\n \t\tBASIC_LDFLAGS += -Wl,--large-address-aware\n         endif\n+\n+\t# Unfortunately now needed because of Rust\n+\tEXTLIBS += -luserenv\n+\n \tCC = gcc\n \tCOMPAT_CFLAGS += -D__USE_MINGW_ANSI_STDIO=0 -DDETECT_MSYS_TTY \\\n \t\t-fstack-protector-strong\ndiff --git a/meson.build b/meson.build\nindex 324f968338..5aa9901bfc 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -1267,6 +1267,7 @@ elif host_machine.system() == 'windows'\n   ]\n \n   libgit_dependencies += compiler.find_library('ntdll')\n+  libgit_dependencies += compiler.find_library('userenv')\n   libgit_include_directories += 'compat/win32'\n   if compiler.get_id() == 'msvc'\n     libgit_include_directories += 'compat/vcbuild/include'\n-- \ngitgitgadget\n\n"},{"id":"525227","messageId":"a00cbf7bcbc0c0f2a30597bafdf26ec0366117fc.1756496539.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.git.git.1756496539.gitgitgadget@gmail.com","subject":"[PATCH 05/15] github workflows: upload Cargo.lock","fromName":"Ezekiel Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-08-29T19:42:09Z","receivedAt":"2025-08-29T19:42:29Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"From: Ezekiel Newren <ezekielnewren@gmail.com>\n\nMake each ci workflow upload its Cargo.lock file as a build artifact so\nthat we can audit build dependencies.\n\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n .github/workflows/main.yml | 20 ++++++++++++++++++++\n 1 file changed, 20 insertions(+)\n\ndiff --git a/.github/workflows/main.yml b/.github/workflows/main.yml\nindex ff2f90d122..cdd57b4ee1 100644\n--- a/.github/workflows/main.yml\n+++ b/.github/workflows/main.yml\n@@ -156,6 +156,11 @@ jobs:\n       with:\n         name: windows-artifacts\n         path: artifacts\n+    - name: upload Cargo.lock\n+      uses: actions/upload-artifact@v4\n+      with:\n+        name: cargo-lock-windows\n+        path: rust/Cargo.lock\n   windows-test:\n     name: win test\n     runs-on: windows-latest\n@@ -317,6 +322,11 @@ jobs:\n       with:\n         name: windows-meson-artifacts\n         path: build\n+    - name: Upload Cargo.lock\n+      uses: actions/upload-artifact@v4\n+      with:\n+        name: cargo-lock-windows-meson\n+        path: rust/Cargo.lock\n   windows-meson-test:\n     name: win+Meson test\n     runs-on: windows-latest\n@@ -399,6 +409,11 @@ jobs:\n       with:\n         name: failed-tests-${{matrix.vector.jobname}}\n         path: ${{env.FAILED_TEST_ARTIFACTS}}\n+    - name: Upload Cargo.lock\n+      uses: actions/upload-artifact@v4\n+      with:\n+        name: cargo-lock-${{matrix.vector.jobname}}\n+        path: rust/Cargo.lock\n   fuzz-smoke-test:\n     name: fuzz smoke test\n     needs: ci-config\n@@ -509,6 +524,11 @@ jobs:\n       with:\n         name: failed-tests-${{matrix.vector.jobname}}\n         path: ${{env.FAILED_TEST_ARTIFACTS}}\n+    - name: Upload Cargo.lock\n+      uses: actions/upload-artifact@v4\n+      with:\n+        name: cargo-lock-${{matrix.vector.jobname}}\n+        path: rust/Cargo.lock\n   static-analysis:\n     needs: ci-config\n     if: needs.ci-config.outputs.enabled == 'yes'\n-- \ngitgitgadget\n\n"},{"id":"525228","messageId":"922d506ed67635b699be4adb18a0081a6544e0e1.1756496539.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.git.git.1756496539.gitgitgadget@gmail.com","subject":"[PATCH 06/15] ivec: create a vector type that is interoperable between C and Rust","fromName":"Ezekiel Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-08-29T19:42:10Z","receivedAt":"2025-08-29T19:42:30Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"From: Ezekiel Newren <ezekielnewren@gmail.com>\n\nTrying to use Rust's Vec in C, or git's ALLOC_GROW() macros (via\nwrapper functions) in Rust is painful because:\n\n  * C doing vector things the Rust way would require wrapper functions,\n    and Rust doing vector things the C way would require wrapper\n    functions, so ivec was created to ensure a consistent contract\n    between the 2 languages for how to manipulate a vector.\n  * Currently, Rust defines its own 'Vec' type that is generic, but its\n    memory allocator and struct layout weren't designed for\n    interoperability with C (or any language for that matter), meaning\n    that the C side cannot push to or expand a 'Vec' without defining\n    wrapper functions in Rust that C can call. Without special care,\n    the two languages might use different allocators (malloc/free on\n    the C side, and possibly something else in Rust), which would make\n    it difficult for a function in one language to free elements\n    allocated by a call from a function in the other language.\n  * Similarly, git defines ALLOC_GROW() and related macros in\n    git-compat-util.h. While we could add functions allowing Rust to\n    invoke something similar to those macros, passing three variables\n    (pointer, length, allocated_size) instead of a single variable\n    (vector) across the language boundary requires more cognitive\n    overhead for readers to keep track of and makes it easier to make\n    mistakes. Further, for low-level components that we want to\n    eventually convert to pure Rust, such triplets would feel very out\n    of place.\n\nTo address these issue, introduce a new type, ivec -- short for\ninteroperable vector. (We refer to it as 'ivec' generally, though on\nthe Rust side the struct is called IVec to match Rust style.)  This new\ntype is specifically designed for FFI purposes, so that both languages\nhandle the vector in the same way, though it could be used on either\nside independently. This type is designed such that it can easily be\nreplaced by a standard Rust 'Vec' once interoperability is no longer a\nconcern.\n\nOne particular item to note is that Git's macros to handle vec\noperations infer the amount that a vec needs to grow from the size of\na pointer, but that makes it somewhat specific to the macros used in C.\nTo avoid defining every ivec function as a macro I opted to also\ninclude an element_size field that allows concrete functions like\npush() to know how much to grow the memory. This element_size also\nhelps in verifying that the ivec is correct when passing from C to\nRust.\n\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n Makefile                 |  14 +-\n interop/ivec.c           | 180 ++++++++++++++\n interop/ivec.h           |  60 +++++\n meson.build              |   1 +\n rust/interop/src/ivec.rs | 516 +++++++++++++++++++++++++++++++++++++++\n rust/interop/src/lib.rs  |  10 +\n 6 files changed, 778 insertions(+), 3 deletions(-)\n create mode 100644 interop/ivec.c\n create mode 100644 interop/ivec.h\n create mode 100644 rust/interop/src/ivec.rs\n\ndiff --git a/Makefile b/Makefile\nindex 1ec0c1ee66..46e940c96d 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -672,6 +672,7 @@ BUILTIN_OBJS =\n BUILT_INS =\n COMPAT_CFLAGS =\n COMPAT_OBJS =\n+INTEROP_OBJS =\n XDIFF_OBJS =\n GENERATED_H =\n EXTRA_CPPFLAGS =\n@@ -918,6 +919,7 @@ export PYTHON_PATH\n TEST_SHELL_PATH = $(SHELL_PATH)\n \n LIB_FILE = libgit.a\n+INTEROP_LIB = interop/lib.a\n XDIFF_LIB = xdiff/lib.a\n \n EXTLIBS =\n@@ -1412,7 +1414,7 @@ UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o\n UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o\n \n # xdiff and reftable libs may in turn depend on what is in libgit.a\n-GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(LIB_FILE)\n+GITLIBS = common-main.o $(LIB_FILE) $(INTEROP_LIB) $(XDIFF_LIB) $(REFTABLE_LIB) $(LIB_FILE)\n \n \n GIT_USER_AGENT = git/$(GIT_VERSION)\n@@ -2747,6 +2749,8 @@ reconfigure config.mak.autogen: config.status\n .PHONY: reconfigure # This is a convenience target.\n endif\n \n+INTEROP_OBJS += interop/ivec.o\n+\n XDIFF_OBJS += xdiff/xdiffi.o\n XDIFF_OBJS += xdiff/xemit.o\n XDIFF_OBJS += xdiff/xhistogram.o\n@@ -2791,6 +2795,7 @@ OBJECTS += $(GIT_OBJS)\n OBJECTS += $(SCALAR_OBJS)\n OBJECTS += $(PROGRAM_OBJS)\n OBJECTS += $(TEST_OBJS)\n+OBJECTS += $(INTEROP_OBJS)\n OBJECTS += $(XDIFF_OBJS)\n OBJECTS += $(FUZZ_OBJS)\n OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS)\n@@ -2945,7 +2950,10 @@ scalar$X: scalar.o GIT-LDFLAGS $(GITLIBS) compile_rust\n $(LIB_FILE): $(LIB_OBJS)\n \t$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^\n \n-$(XDIFF_LIB): $(XDIFF_OBJS)\n+$(INTEROP_LIB): $(INTEROP_OBJS)\n+\t$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^\n+\n+$(XDIFF_LIB): $(INTEROP_OBJS) $(XDIFF_OBJS)\n \t$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^\n \n \n@@ -3790,7 +3798,7 @@ clean: profile-clean coverage-clean cocciclean rustclean\n \t$(RM) git.rc git.res\n \t$(RM) $(OBJECTS)\n \t$(RM) headless-git.o\n-\t$(RM) $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB)\n+\t$(RM) $(LIB_FILE) $(INTEROP_LIB) $(XDIFF_LIB) $(REFTABLE_LIB)\n \t$(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS)\n \t$(RM) $(TEST_PROGRAMS)\n \t$(RM) $(FUZZ_PROGRAMS)\ndiff --git a/interop/ivec.c b/interop/ivec.c\nnew file mode 100644\nindex 0000000000..1c4bf20707\n--- /dev/null\n+++ b/interop/ivec.c\n@@ -0,0 +1,180 @@\n+#include \"ivec.h\"\n+\n+#ifdef __cplusplus\n+extern \"C\" {\n+#endif\n+\n+static void ivec_set_capacity(void *self_, usize new_capacity)\n+{\n+\tstruct rawivec *self = self_;\n+\n+\tif (new_capacity == 0)\n+\t\tFREE_AND_NULL(self->ptr);\n+\telse\n+\t\tself->ptr = xrealloc(self->ptr, new_capacity * self->element_size);\n+\tself->capacity = new_capacity;\n+}\n+\n+void ivec_init(void *self_, usize element_size)\n+{\n+\tstruct rawivec *self = self_;\n+\n+\tself->ptr = NULL;\n+\tself->length = 0;\n+\tself->capacity = 0;\n+\tself->element_size = element_size;\n+}\n+\n+/*\n+ * MUST CALL IVEC_INIT() FIRST!!!\n+ * This function will free the ivec, set self.capacity and self.length\n+ * to the specified capacity, and then calloc self.capacity number of\n+ * elements.\n+ */\n+void ivec_zero(void *self_, usize capacity)\n+{\n+\tstruct rawivec *self = self_;\n+\n+\tif (self->ptr)\n+\t\tFREE_AND_NULL(self->ptr);\n+\tself->capacity = self->length = capacity;\n+\tself->ptr = xcalloc(self->capacity, self->element_size);\n+}\n+\n+void ivec_clear(void *self_)\n+{\n+\tstruct rawivec *self = self_;\n+\n+\tself->length = 0;\n+}\n+\n+void ivec_reserve_exact(void *self_, usize additional)\n+{\n+\tstruct rawivec *self = self_;\n+\tusize new_capacity = self->capacity + additional;\n+\n+\tivec_set_capacity(self, new_capacity);\n+}\n+\n+void ivec_reserve(void *self_, usize additional)\n+{\n+\tstruct rawivec *self = self_;\n+\tusize growby = 128;\n+\n+\tif (self->capacity > growby) {\n+\t\tgrowby = self->capacity;\n+\t}\n+\tif (additional > growby) {\n+\t\tgrowby = additional;\n+\t}\n+\tivec_reserve_exact(self, growby);\n+}\n+\n+void ivec_shrink_to_fit(void *self_)\n+{\n+\tstruct rawivec *self = self_;\n+\n+\tivec_set_capacity(self_, self->length);\n+}\n+\n+void ivec_resize(void *self_, usize new_length, void *default_value)\n+{\n+\tstruct rawivec *self = self_;\n+\tisize additional = (isize) (new_length - self->capacity);\n+\n+\tif (additional > 0) {\n+\t\tivec_reserve(self_, additional);\n+\t}\n+\n+\tfor (usize i = self->length; i < new_length; i++) {\n+\t\tvoid *dst = (u8 *)self->ptr + (self->length + i) * self->element_size;\n+\t\tmemcpy(dst, default_value, self->element_size);\n+\t}\n+\tself->length = new_length;\n+}\n+\n+void ivec_push(void *self_, void *value)\n+{\n+\tstruct rawivec *self = self_;\n+\tu8 *dst;\n+\n+\tif (self->length == self->capacity) {\n+\t\tivec_reserve(self_, 1);\n+\t}\n+\tdst = (u8 *)self->ptr + self->length * self->element_size;\n+\tmemcpy(dst, value, self->element_size);\n+\tself->length++;\n+}\n+\n+void ivec_extend_from_slice(void *self_, void const *ptr, usize size)\n+{\n+\tstruct rawivec *self = self_;\n+\tu8 *dst;\n+\n+\tif (size == 0)\n+\t\treturn;\n+\n+\tif (self->length + size > self->capacity) {\n+\t\tivec_reserve(self_, self->capacity - self->length + size);\n+\t}\n+\tdst = (u8 *)self->ptr + self->length * self->element_size;\n+\tmemcpy(dst, ptr, size * self->element_size);\n+\tself->length += size;\n+}\n+\n+bool ivec_equal(void *self_, void *other)\n+{\n+\tstruct rawivec *lhs = self_;\n+\tstruct rawivec *rhs = other;\n+\n+\tif (lhs->element_size != rhs->element_size) {\n+\t\treturn false;\n+\t}\n+\n+\tif (lhs->length != rhs->length) {\n+\t\treturn false;\n+\t}\n+\n+\tfor (usize i = 0; i < lhs->length; i++) {\n+\t\tvoid *left = (u8 *)lhs->ptr + i * lhs->element_size;\n+\t\tvoid *right = (u8 *)rhs->ptr + i * rhs->element_size;\n+\t\tif (memcmp(left, right, lhs->element_size) != 0) {\n+\t\t\treturn false;\n+\t\t}\n+\t}\n+\n+\treturn true;\n+}\n+\n+\n+void ivec_free(void *self_)\n+{\n+\tstruct rawivec *self = self_;\n+\n+\tFREE_AND_NULL(self->ptr);\n+\tself->length = 0;\n+\tself->capacity = 0;\n+\t/* don't modify self->element_size */\n+}\n+\n+void ivec_move(void *source, void *destination)\n+{\n+\tstruct rawivec *src = source;\n+\tstruct rawivec *dst = destination;\n+\n+\tif (src->element_size != dst->element_size)\n+\t\tBUG(\"mismatched element_size\");\n+\n+\tivec_free(destination);\n+\tdst->ptr = src->ptr;\n+\tdst->length = src->length;\n+\tdst->capacity = src->capacity;\n+\n+\tsrc->ptr = NULL;\n+\tsrc->length = 0;\n+\tsrc->capacity = 0;\n+}\n+\n+#ifdef __cplusplus\n+}\n+#endif\ndiff --git a/interop/ivec.h b/interop/ivec.h\nnew file mode 100644\nindex 0000000000..ed3afff944\n--- /dev/null\n+++ b/interop/ivec.h\n@@ -0,0 +1,60 @@\n+#ifndef IVEC_H\n+#define IVEC_H\n+\n+#include <git-compat-util.h>\n+\n+#ifdef __cplusplus\n+extern \"C\" {\n+#endif\n+\n+struct rawivec {\n+\tvoid *ptr;\n+\tusize length;\n+\tusize capacity;\n+\tusize element_size;\n+};\n+\n+#define DEFINE_IVEC_TYPE(type, suffix) \\\n+struct ivec_##suffix { \\\n+\ttype *ptr; \\\n+\tsize_t length; \\\n+\tsize_t capacity; \\\n+\tsize_t element_size; \\\n+}\n+\n+#define IVEC_INIT(variable) ivec_init(&(variable), sizeof(*(variable).ptr))\n+\n+DEFINE_IVEC_TYPE(u8, u8);\n+DEFINE_IVEC_TYPE(u16, u16);\n+DEFINE_IVEC_TYPE(u32, u32);\n+DEFINE_IVEC_TYPE(u64, u64);\n+\n+DEFINE_IVEC_TYPE(i8, i8);\n+DEFINE_IVEC_TYPE(i16, i16);\n+DEFINE_IVEC_TYPE(i32, i32);\n+DEFINE_IVEC_TYPE(i64, i64);\n+\n+DEFINE_IVEC_TYPE(f32, f32);\n+DEFINE_IVEC_TYPE(f64, f64);\n+\n+DEFINE_IVEC_TYPE(usize, usize);\n+DEFINE_IVEC_TYPE(isize, isize);\n+\n+void ivec_init(void *self_, usize element_size);\n+void ivec_zero(void *self_, usize capacity);\n+void ivec_clear(void *self_);\n+void ivec_reserve_exact(void *self_, usize additional);\n+void ivec_reserve(void *self_, usize additional);\n+void ivec_shrink_to_fit(void *self_);\n+void ivec_resize(void *self_, usize new_length, void *default_value);\n+void ivec_push(void *self_, void *value);\n+void ivec_extend_from_slice(void *self_, void const *ptr, usize size);\n+bool ivec_equal(void *self_, void *other);\n+void ivec_free(void *self_);\n+void ivec_move(void *source, void *destination);\n+\n+#ifdef __cplusplus\n+}\n+#endif\n+\n+#endif //IVEC_H\ndiff --git a/meson.build b/meson.build\nindex 5aa9901bfc..fc7c133f79 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -395,6 +395,7 @@ libgit_sources = [\n   'hex-ll.c',\n   'hook.c',\n   'ident.c',\n+  'interop/ivec.c',\n   'json-writer.c',\n   'kwset.c',\n   'levenshtein.c',\ndiff --git a/rust/interop/src/ivec.rs b/rust/interop/src/ivec.rs\nnew file mode 100644\nindex 0000000000..ba9393a82b\n--- /dev/null\n+++ b/rust/interop/src/ivec.rs\n@@ -0,0 +1,516 @@\n+use crate::*;\n+use core::mem::{align_of, size_of};\n+use std::fmt::{Debug, Formatter};\n+use std::ops::{Index, IndexMut};\n+\n+#[repr(C)]\n+pub struct IVec<T> {\n+    ptr: *mut T,\n+    length: usize,\n+    capacity: usize,\n+    element_size: usize,\n+}\n+\n+impl<T> Default for IVec<T> {\n+    fn default() -> Self {\n+        Self::new()\n+    }\n+}\n+\n+impl<T> Drop for IVec<T> {\n+    fn drop(&mut self) {\n+        self._set_capacity(0);\n+    }\n+}\n+\n+impl<T: Clone> Clone for IVec<T> {\n+    fn clone(&self) -> Self {\n+        let mut copy = Self::new();\n+        copy.reserve_exact(self.len());\n+        for i in 0..self.len() {\n+            copy.push(self[i].clone());\n+        }\n+\n+        copy\n+    }\n+}\n+\n+impl<T: PartialEq> PartialEq for IVec<T> {\n+    fn eq(&self, other: &Self) -> bool {\n+        if self.len() != other.len() {\n+            return false;\n+        }\n+\n+        let lhs = self.as_slice();\n+        let rhs = &other.as_slice()[..lhs.len()];\n+        for i in 0..lhs.len() {\n+            if lhs[i] != rhs[i] {\n+                return false;\n+            }\n+        }\n+\n+        true\n+    }\n+}\n+\n+impl<T: PartialEq> Eq for IVec<T> {}\n+\n+/*\n+ * constructors\n+ */\n+impl<T> IVec<T> {\n+    pub fn new() -> Self {\n+        Self {\n+            ptr: std::ptr::null_mut(),\n+            length: 0,\n+            capacity: 0,\n+            element_size: size_of::<T>(),\n+        }\n+    }\n+\n+    /// uses calloc to create the IVec, it's unsafe because\n+    /// zeroed memory may not be a valid default value\n+    pub unsafe fn zero(capacity: usize) -> Self {\n+        Self {\n+            ptr: calloc(capacity, size_of::<T>()) as *mut T,\n+            length: capacity,\n+            capacity,\n+            element_size: size_of::<T>(),\n+        }\n+    }\n+\n+    pub fn with_capacity(capacity: usize) -> Self {\n+        let mut vec = Self::new();\n+        vec._set_capacity(capacity);\n+        vec\n+    }\n+\n+    pub fn with_capacity_and_default(capacity: usize, default_value: T) -> Self\n+    where\n+        T: Copy,\n+    {\n+        let mut vec = Self::new();\n+        vec._set_capacity(capacity);\n+        vec._buffer_mut().fill(default_value);\n+        vec\n+    }\n+\n+    pub unsafe fn from_raw_mut<'a>(raw: *mut Self) -> &'a mut Self {\n+        let vec = raw.as_mut().expect(\"null pointer\");\n+        #[cfg(debug_assertions)]\n+        vec.test_invariants();\n+        vec\n+    }\n+\n+    pub unsafe fn from_raw<'a>(raw: *const Self) -> &'a Self {\n+        let vec = &*raw.as_ref().expect(\"null pointer\");\n+        #[cfg(debug_assertions)]\n+        vec.test_invariants();\n+        vec\n+    }\n+}\n+\n+/*\n+ * private methods\n+ */\n+impl<T> IVec<T> {\n+    pub fn test_invariants(&self) {\n+        if !self.ptr.is_null() && (self.ptr as usize) % align_of::<T>() != 0 {\n+            panic!(\n+                \"misaligned pointer: expected {:x}, got {:x}\",\n+                align_of::<T>(),\n+                self.ptr as usize\n+            );\n+        }\n+        if self.ptr.is_null() && (self.length > 0 || self.capacity > 0) {\n+            panic!(\"ptr is null, but length or capacity is > 0\");\n+        }\n+        if !self.ptr.is_null() && self.capacity == 0 {\n+            panic!(\"ptr ISN'T null, but capacity == 0\");\n+        }\n+        if self.element_size != size_of::<T>() {\n+            panic!(\n+                \"incorrect element size, should be: {}, but was: {}\",\n+                size_of::<T>(),\n+                self.element_size\n+            );\n+        }\n+        if self.length > self.capacity {\n+            panic!(\"length: {} > capacity: {}\", self.length, self.capacity);\n+        }\n+        if self.capacity > usize::MAX / size_of::<T>() {\n+            panic!(\n+                \"Capacity {} is too large, potential overflow detected\",\n+                self.capacity\n+            );\n+        }\n+    }\n+\n+    fn _set_capacity(&mut self, new_capacity: usize) {\n+        unsafe {\n+            if new_capacity == self.capacity {\n+                return;\n+            }\n+            if new_capacity < self.length {\n+                self.truncate(new_capacity);\n+            }\n+            if new_capacity == 0 {\n+                free(self.ptr as *mut c_void);\n+                self.ptr = std::ptr::null_mut();\n+                self.length = 0;\n+                self.capacity = 0;\n+                // DO NOT MODIFY element_size!!!\n+            } else {\n+                let t = realloc(self.ptr as *mut c_void, new_capacity * size_of::<T>());\n+                if t.is_null() {\n+                    panic!(\"out of memory\");\n+                }\n+                self.ptr = t as *mut T;\n+            }\n+            self.capacity = new_capacity;\n+        }\n+    }\n+\n+    fn _resize(&mut self, new_length: usize, default_value: T, exact: bool)\n+    where\n+        T: Copy,\n+    {\n+        if exact {\n+            self._set_capacity(new_length);\n+        } else if new_length > self.capacity {\n+            self.reserve(new_length - self.capacity);\n+        } else {\n+            /* capacity does not need to be changed */\n+        }\n+\n+        /* IVec grows */\n+        if new_length > self.length {\n+            let range = self.length..new_length;\n+            self._buffer_mut()[range].fill(default_value);\n+            self.length = new_length;\n+        }\n+\n+        /* IVec shrinks */\n+        if new_length < self.length {\n+            self.truncate(new_length);\n+        }\n+    }\n+\n+    fn _buffer_mut(&mut self) -> &mut [T] {\n+        if self.ptr.is_null() {\n+            &mut []\n+        } else {\n+            unsafe { std::slice::from_raw_parts_mut(self.ptr, self.capacity) }\n+        }\n+    }\n+\n+    fn _buffer(&self) -> &[T] {\n+        if self.ptr.is_null() {\n+            &[]\n+        } else {\n+            unsafe { std::slice::from_raw_parts(self.ptr, self.capacity) }\n+        }\n+    }\n+}\n+\n+/*\n+ * methods\n+ */\n+impl<T> IVec<T> {\n+    pub fn len(&self) -> usize {\n+        self.length\n+    }\n+\n+    pub unsafe fn set_len(&mut self, new_length: usize) {\n+        self.length = new_length;\n+    }\n+\n+    pub fn capacity(&self) -> usize {\n+        self.capacity\n+    }\n+\n+    pub fn reserve_exact(&mut self, additional: usize) {\n+        self._set_capacity(self.capacity + additional);\n+    }\n+\n+    pub fn reserve(&mut self, additional: usize) {\n+        let growby = std::cmp::max(128, self.capacity);\n+        self.reserve_exact(std::cmp::max(additional, growby));\n+    }\n+\n+    pub fn shrink_to_fit(&mut self) {\n+        self._set_capacity(self.length);\n+    }\n+\n+    pub fn resize(&mut self, new_length: usize, default_value: T)\n+    where\n+        T: Copy,\n+    {\n+        self._resize(new_length, default_value, false);\n+    }\n+\n+    pub fn resize_exact(&mut self, new_length: usize, default_value: T)\n+    where\n+        T: Copy,\n+    {\n+        self._resize(new_length, default_value, true);\n+    }\n+\n+    pub fn truncate(&mut self, new_length: usize) {\n+        if new_length >= self.length {\n+            return;\n+        }\n+\n+        if std::mem::needs_drop::<T>() {\n+            let range = new_length..self.length;\n+            for v in &mut self.as_mut_slice()[range] {\n+                unsafe {\n+                    std::ptr::drop_in_place(v);\n+                }\n+            }\n+        }\n+\n+        self.length = new_length;\n+    }\n+\n+    pub fn insert(&mut self, index: usize, value: T) {\n+        if self.length == self.capacity {\n+            self.reserve(1);\n+        }\n+\n+        unsafe {\n+            let src = &self._buffer()[index] as *const T;\n+            let dst = src.add(1) as *mut T;\n+            let len = self.length - index;\n+            std::ptr::copy(src, dst, len);\n+            std::ptr::write(self.ptr.add(index), value);\n+        }\n+    }\n+\n+    pub fn push(&mut self, value: T) {\n+        if self.length == self.capacity {\n+            self.reserve(1);\n+        }\n+\n+        let i = self.length;\n+        unsafe {\n+            std::ptr::write(self.ptr.add(i), value);\n+        }\n+        self.length += 1;\n+    }\n+\n+    pub fn extend_from_slice(&mut self, slice: &[T])\n+    where\n+        T: Clone,\n+    {\n+        for v in slice {\n+            self.push(v.clone());\n+        }\n+    }\n+\n+    pub fn clear(&mut self) {\n+        self.length = 0;\n+    }\n+\n+    pub fn as_ptr(&self) -> *const T {\n+        self.ptr\n+    }\n+\n+    pub fn as_mut_ptr(&self) -> *mut T {\n+        self.ptr\n+    }\n+\n+    pub fn as_slice(&self) -> &[T] {\n+        &self._buffer()[0..self.length]\n+    }\n+\n+    pub fn as_mut_slice(&mut self) -> &mut [T] {\n+        let range = 0..self.length;\n+        &mut self._buffer_mut()[range]\n+    }\n+}\n+\n+impl<T> Extend<T> for IVec<T> {\n+    fn extend<IT: IntoIterator<Item = T>>(&mut self, iter: IT) {\n+        for v in iter {\n+            self.push(v);\n+        }\n+    }\n+}\n+\n+impl<T> Index<usize> for IVec<T> {\n+    type Output = T;\n+\n+    fn index(&self, index: usize) -> &Self::Output {\n+        &self.as_slice()[index]\n+    }\n+}\n+\n+impl<T> IndexMut<usize> for IVec<T> {\n+    fn index_mut(&mut self, index: usize) -> &mut Self::Output {\n+        &mut self.as_mut_slice()[index]\n+    }\n+}\n+\n+impl<T: Debug> Debug for IVec<T> {\n+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {\n+        writeln!(\n+            f,\n+            \"ptr: {}, capacity: {}, len: {}, element_size: {}, content: {:?}\",\n+            self.ptr as usize,\n+            self.capacity,\n+            self.length,\n+            self.element_size,\n+            self.as_slice()\n+        )\n+    }\n+}\n+\n+impl std::fmt::Write for IVec<u8> {\n+    fn write_str(&mut self, s: &str) -> std::fmt::Result {\n+        Ok(self.extend_from_slice(s.as_bytes()))\n+    }\n+}\n+\n+impl std::io::Write for IVec<u8> {\n+    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {\n+        self.extend_from_slice(buf);\n+        Ok(buf.len())\n+    }\n+\n+    fn flush(&mut self) -> std::io::Result<()> {\n+        Ok(())\n+    }\n+}\n+\n+#[cfg(test)]\n+mod tests {\n+    use crate::ivec::IVec;\n+    use std::cell::RefCell;\n+    use std::mem::size_of;\n+    use std::ops::{Deref, DerefMut};\n+    use std::panic;\n+    use std::rc::Rc;\n+\n+    struct DropTest {\n+        value: Rc<RefCell<u64>>,\n+    }\n+\n+    impl DropTest {\n+        fn new(value: Rc<RefCell<u64>>) -> Self {\n+            Self { value }\n+        }\n+    }\n+\n+    impl Drop for DropTest {\n+        fn drop(&mut self) {\n+            *self.value.borrow_mut() -= 1;\n+        }\n+    }\n+\n+    #[test]\n+    fn test_drop_elements() {\n+        let counter = Rc::new(RefCell::new(0u64));\n+        let size = 5;\n+\n+        /* drop whole IVec */\n+        let mut vec = IVec::new();\n+        for _ in 0..size {\n+            let tmp = DropTest::new(counter.clone());\n+            *tmp.value.borrow_mut() += 1;\n+            vec.push(tmp);\n+        }\n+        let cur = *counter.borrow();\n+        assert_eq!(size, cur);\n+        drop(vec);\n+\n+        let cur = *counter.borrow();\n+        assert_eq!(0u64, cur);\n+\n+        /* drop some elements */\n+        let mut vec = IVec::new();\n+        for i in 0..size {\n+            let tmp = DropTest::new(counter.clone());\n+            *tmp.value.borrow_mut() += 1;\n+            vec.push(tmp);\n+        }\n+        let cur = *counter.borrow().deref();\n+        assert_eq!(size, cur);\n+\n+        let expected = 2u64;\n+        vec.truncate(expected as usize);\n+\n+        let cur = *counter.borrow();\n+        assert_eq!(expected, cur);\n+        drop(vec);\n+    }\n+\n+    #[test]\n+    fn test_panic_on_out_of_bounds() {\n+        type TestType = i16;\n+        let result = panic::catch_unwind(|| {\n+            let mut v = IVec::<TestType>::with_capacity(1_000_000);\n+            v[0] = 55;\n+        });\n+\n+        match result {\n+            Ok(_) => assert!(false, \"index was out of bounds, but no panic was triggered\"),\n+            Err(_) => assert!(true),\n+        }\n+    }\n+\n+    #[test]\n+    fn test_push_clear_resize_then_shrink_to_fit() {\n+        let mut vec = IVec::<u64>::new();\n+        let mut monotonic = 1;\n+\n+        vec.reserve_exact(1);\n+        assert_eq!(1, vec.capacity);\n+\n+        // test push\n+        for _ in 0..10 {\n+            vec.push(monotonic);\n+            assert_eq!(monotonic as usize, vec.length);\n+            assert_eq!(monotonic, vec[(monotonic - 1) as usize]);\n+            assert!(vec.capacity >= vec.length);\n+            monotonic += 1;\n+        }\n+\n+        // test clear\n+        let expected = vec.capacity;\n+        vec.clear();\n+        assert_eq!(0, vec.length);\n+        assert_eq!(expected, vec.capacity);\n+\n+        // test resize\n+        let expected = vec.capacity + 10;\n+        let default_value = 19;\n+        vec.resize(expected, default_value);\n+        assert_eq!(expected, vec.length);\n+        assert!(vec.capacity >= expected);\n+        for i in 0..vec.length {\n+            assert_eq!(default_value, vec[i]);\n+        }\n+\n+        vec.reserve(10);\n+        assert!(vec.capacity > vec.length);\n+        let length_before = vec.length;\n+        vec.shrink_to_fit();\n+        assert_eq!(length_before, vec.length);\n+        assert_eq!(vec.length, vec.capacity);\n+    }\n+\n+    #[test]\n+    fn test_struct_size() {\n+        let vec = IVec::<i16>::new();\n+\n+        assert_eq!(2, vec.element_size);\n+        assert_eq!(size_of::<usize>() * 4, size_of::<IVec<i16>>());\n+\n+        drop(vec);\n+\n+        let vec = IVec::<u128>::new();\n+        assert_eq!(16, vec.element_size);\n+        assert_eq!(size_of::<usize>() * 4, size_of::<IVec<u128>>());\n+    }\n+}\ndiff --git a/rust/interop/src/lib.rs b/rust/interop/src/lib.rs\nindex e69de29bb2..4850f66e5b 100644\n--- a/rust/interop/src/lib.rs\n+++ b/rust/interop/src/lib.rs\n@@ -0,0 +1,10 @@\n+pub mod ivec;\n+\n+use std::ffi::c_void;\n+\n+extern \"C\" {\n+    pub fn malloc(size: usize) -> *mut c_void;\n+    pub fn calloc(nmemb: usize, size: usize) -> *mut c_void;\n+    pub fn realloc(ptr: *mut c_void, size: usize) -> *mut c_void;\n+    pub fn free(ptr: *mut c_void);\n+}\n-- \ngitgitgadget\n\n"},{"id":"525229","messageId":"3a6f822e2adbdd896732dc8e0f622a862ee462dd.1756496539.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.git.git.1756496539.gitgitgadget@gmail.com","subject":"[PATCH 07/15] xdiff/xprepare: remove superfluous forward declarations","fromName":"Ezekiel Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-08-29T19:42:11Z","receivedAt":"2025-08-29T19:42:32Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"From: Ezekiel Newren <ezekielnewren@gmail.com>\n\nMove xdl_prepare_env() later in the file to avoid the need\nfor forward declarations.\n\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n xdiff/xprepare.c | 116 ++++++++++++++++++++---------------------------\n 1 file changed, 50 insertions(+), 66 deletions(-)\n\ndiff --git a/xdiff/xprepare.c b/xdiff/xprepare.c\nindex e1d4017b2d..a45c5ee208 100644\n--- a/xdiff/xprepare.c\n+++ b/xdiff/xprepare.c\n@@ -53,21 +53,6 @@ typedef struct s_xdlclassifier {\n \n \n \n-static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags);\n-static void xdl_free_classifier(xdlclassifier_t *cf);\n-static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash,\n-\t\t\t       unsigned int hbits, xrecord_t *rec);\n-static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,\n-\t\t\t   xdlclassifier_t *cf, xdfile_t *xdf);\n-static void xdl_free_ctx(xdfile_t *xdf);\n-static int xdl_clean_mmatch(char const *dis, long i, long s, long e);\n-static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2);\n-static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2);\n-static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2);\n-\n-\n-\n-\n static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags) {\n \tcf->flags = flags;\n \n@@ -242,57 +227,6 @@ static void xdl_free_ctx(xdfile_t *xdf) {\n }\n \n \n-int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,\n-\t\t    xdfenv_t *xe) {\n-\tlong enl1, enl2, sample;\n-\txdlclassifier_t cf;\n-\n-\tmemset(&cf, 0, sizeof(cf));\n-\n-\t/*\n-\t * For histogram diff, we can afford a smaller sample size and\n-\t * thus a poorer estimate of the number of lines, as the hash\n-\t * table (rhash) won't be filled up/grown. The number of lines\n-\t * (nrecs) will be updated correctly anyway by\n-\t * xdl_prepare_ctx().\n-\t */\n-\tsample = (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF\n-\t\t  ? XDL_GUESS_NLINES2 : XDL_GUESS_NLINES1);\n-\n-\tenl1 = xdl_guess_lines(mf1, sample) + 1;\n-\tenl2 = xdl_guess_lines(mf2, sample) + 1;\n-\n-\tif (xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0)\n-\t\treturn -1;\n-\n-\tif (xdl_prepare_ctx(1, mf1, enl1, xpp, &cf, &xe->xdf1) < 0) {\n-\n-\t\txdl_free_classifier(&cf);\n-\t\treturn -1;\n-\t}\n-\tif (xdl_prepare_ctx(2, mf2, enl2, xpp, &cf, &xe->xdf2) < 0) {\n-\n-\t\txdl_free_ctx(&xe->xdf1);\n-\t\txdl_free_classifier(&cf);\n-\t\treturn -1;\n-\t}\n-\n-\tif ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) &&\n-\t    (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF) &&\n-\t    xdl_optimize_ctxs(&cf, &xe->xdf1, &xe->xdf2) < 0) {\n-\n-\t\txdl_free_ctx(&xe->xdf2);\n-\t\txdl_free_ctx(&xe->xdf1);\n-\t\txdl_free_classifier(&cf);\n-\t\treturn -1;\n-\t}\n-\n-\txdl_free_classifier(&cf);\n-\n-\treturn 0;\n-}\n-\n-\n void xdl_free_env(xdfenv_t *xe) {\n \n \txdl_free_ctx(&xe->xdf2);\n@@ -460,3 +394,53 @@ static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2\n \n \treturn 0;\n }\n+\n+int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,\n+\t\t    xdfenv_t *xe) {\n+\tlong enl1, enl2, sample;\n+\txdlclassifier_t cf;\n+\n+\tmemset(&cf, 0, sizeof(cf));\n+\n+\t/*\n+\t * For histogram diff, we can afford a smaller sample size and\n+\t * thus a poorer estimate of the number of lines, as the hash\n+\t * table (rhash) won't be filled up/grown. The number of lines\n+\t * (nrecs) will be updated correctly anyway by\n+\t * xdl_prepare_ctx().\n+\t */\n+\tsample = (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF\n+\t\t  ? XDL_GUESS_NLINES2 : XDL_GUESS_NLINES1);\n+\n+\tenl1 = xdl_guess_lines(mf1, sample) + 1;\n+\tenl2 = xdl_guess_lines(mf2, sample) + 1;\n+\n+\tif (xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0)\n+\t\treturn -1;\n+\n+\tif (xdl_prepare_ctx(1, mf1, enl1, xpp, &cf, &xe->xdf1) < 0) {\n+\n+\t\txdl_free_classifier(&cf);\n+\t\treturn -1;\n+\t}\n+\tif (xdl_prepare_ctx(2, mf2, enl2, xpp, &cf, &xe->xdf2) < 0) {\n+\n+\t\txdl_free_ctx(&xe->xdf1);\n+\t\txdl_free_classifier(&cf);\n+\t\treturn -1;\n+\t}\n+\n+\tif ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) &&\n+\t    (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF) &&\n+\t    xdl_optimize_ctxs(&cf, &xe->xdf1, &xe->xdf2) < 0) {\n+\n+\t\txdl_free_ctx(&xe->xdf2);\n+\t\txdl_free_ctx(&xe->xdf1);\n+\t\txdl_free_classifier(&cf);\n+\t\treturn -1;\n+\t    }\n+\n+\txdl_free_classifier(&cf);\n+\n+\treturn 0;\n+}\n-- \ngitgitgadget\n\n"},{"id":"525230","messageId":"c7cd71dae0081369bc9f94f64b04099d40e991a0.1756496539.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.git.git.1756496539.gitgitgadget@gmail.com","subject":"[PATCH 08/15] xdiff: delete unnecessary fields from xrecord_t and xdfile_t","fromName":"Ezekiel Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-08-29T19:42:12Z","receivedAt":"2025-08-29T19:42:33Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"From: Ezekiel Newren <ezekielnewren@gmail.com>\n\nxrecord_t.next, xdfile_t.hbits, xdfile_t.rhash are initialized,\nbut never used for anything by the code. Remove them.\n\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n xdiff/xprepare.c | 24 +++---------------------\n xdiff/xtypes.h   |  3 ---\n 2 files changed, 3 insertions(+), 24 deletions(-)\n\ndiff --git a/xdiff/xprepare.c b/xdiff/xprepare.c\nindex a45c5ee208..ad356281f9 100644\n--- a/xdiff/xprepare.c\n+++ b/xdiff/xprepare.c\n@@ -91,8 +91,7 @@ static void xdl_free_classifier(xdlclassifier_t *cf) {\n }\n \n \n-static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash,\n-\t\t\t       unsigned int hbits, xrecord_t *rec) {\n+static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t *rec) {\n \tlong hi;\n \tchar const *line;\n \txdlclass_t *rcrec;\n@@ -126,23 +125,17 @@ static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t\n \n \trec->ha = (unsigned long) rcrec->idx;\n \n-\thi = (long) XDL_HASHLONG(rec->ha, hbits);\n-\trec->next = rhash[hi];\n-\trhash[hi] = rec;\n-\n \treturn 0;\n }\n \n \n static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,\n \t\t\t   xdlclassifier_t *cf, xdfile_t *xdf) {\n-\tunsigned int hbits;\n-\tlong nrec, hsize, bsize;\n+\tlong nrec, bsize;\n \tunsigned long hav;\n \tchar const *blk, *cur, *top, *prev;\n \txrecord_t *crec;\n \txrecord_t **recs;\n-\txrecord_t **rhash;\n \tunsigned long *ha;\n \tchar *rchg;\n \tlong *rindex;\n@@ -150,7 +143,6 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_\n \tha = NULL;\n \trindex = NULL;\n \trchg = NULL;\n-\trhash = NULL;\n \trecs = NULL;\n \n \tif (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0)\n@@ -158,11 +150,6 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_\n \tif (!XDL_ALLOC_ARRAY(recs, narec))\n \t\tgoto abort;\n \n-\thbits = xdl_hashbits((unsigned int) narec);\n-\thsize = 1 << hbits;\n-\tif (!XDL_CALLOC_ARRAY(rhash, hsize))\n-\t\tgoto abort;\n-\n \tnrec = 0;\n \tif ((cur = blk = xdl_mmfile_first(mf, &bsize))) {\n \t\tfor (top = blk + bsize; cur < top; ) {\n@@ -176,7 +163,7 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_\n \t\t\tcrec->size = (long) (cur - prev);\n \t\t\tcrec->ha = hav;\n \t\t\trecs[nrec++] = crec;\n-\t\t\tif (xdl_classify_record(pass, cf, rhash, hbits, crec) < 0)\n+\t\t\tif (xdl_classify_record(pass, cf, crec) < 0)\n \t\t\t\tgoto abort;\n \t\t}\n \t}\n@@ -194,8 +181,6 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_\n \n \txdf->nrec = nrec;\n \txdf->recs = recs;\n-\txdf->hbits = hbits;\n-\txdf->rhash = rhash;\n \txdf->rchg = rchg + 1;\n \txdf->rindex = rindex;\n \txdf->nreff = 0;\n@@ -209,7 +194,6 @@ abort:\n \txdl_free(ha);\n \txdl_free(rindex);\n \txdl_free(rchg);\n-\txdl_free(rhash);\n \txdl_free(recs);\n \txdl_cha_free(&xdf->rcha);\n \treturn -1;\n@@ -217,8 +201,6 @@ abort:\n \n \n static void xdl_free_ctx(xdfile_t *xdf) {\n-\n-\txdl_free(xdf->rhash);\n \txdl_free(xdf->rindex);\n \txdl_free(xdf->rchg - 1);\n \txdl_free(xdf->ha);\ndiff --git a/xdiff/xtypes.h b/xdiff/xtypes.h\nindex 8442bd436e..8b8467360e 100644\n--- a/xdiff/xtypes.h\n+++ b/xdiff/xtypes.h\n@@ -39,7 +39,6 @@ typedef struct s_chastore {\n } chastore_t;\n \n typedef struct s_xrecord {\n-\tstruct s_xrecord *next;\n \tchar const *ptr;\n \tlong size;\n \tunsigned long ha;\n@@ -48,8 +47,6 @@ typedef struct s_xrecord {\n typedef struct s_xdfile {\n \tchastore_t rcha;\n \tlong nrec;\n-\tunsigned int hbits;\n-\txrecord_t **rhash;\n \tlong dstart, dend;\n \txrecord_t **recs;\n \tchar *rchg;\n-- \ngitgitgadget\n\n"},{"id":"525231","messageId":"1096c8f0a400b7dd86d28b740a5d94bdea9bb60d.1756496539.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.git.git.1756496539.gitgitgadget@gmail.com","subject":"[PATCH 09/15] xdiff: make fields of xrecord_t Rust friendly","fromName":"Ezekiel Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-08-29T19:42:13Z","receivedAt":"2025-08-29T19:42:35Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"From: Ezekiel Newren <ezekielnewren@gmail.com>\n\nA few commits ago, we added definitions for Rust primitive types,\nto facilitate interoperability between C and Rust. Switch a\nfew variables to use these types. Which, for now, will\nrequire adding some casts.\n\nAlso change xdlclass_t::ha to be u64 to match xrecord_t::ha, as\npointed out by Johannes.\n\nHelped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n xdiff/xdiffi.c    |  8 ++++----\n xdiff/xemit.c     |  2 +-\n xdiff/xmerge.c    | 14 +++++++-------\n xdiff/xpatience.c |  2 +-\n xdiff/xprepare.c  |  8 ++++----\n xdiff/xtypes.h    |  6 +++---\n xdiff/xutils.c    |  4 ++--\n 7 files changed, 22 insertions(+), 22 deletions(-)\n\ndiff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c\nindex 5a96e36dfb..3b364c61f6 100644\n--- a/xdiff/xdiffi.c\n+++ b/xdiff/xdiffi.c\n@@ -418,7 +418,7 @@ static int get_indent(xrecord_t *rec)\n \tlong i;\n \tint ret = 0;\n \n-\tfor (i = 0; i < rec->size; i++) {\n+\tfor (i = 0; i < (long) rec->size; i++) {\n \t\tchar c = rec->ptr[i];\n \n \t\tif (!XDL_ISSPACE(c))\n@@ -1005,11 +1005,11 @@ static void xdl_mark_ignorable_lines(xdchange_t *xscr, xdfenv_t *xe, long flags)\n \n \t\trec = &xe->xdf1.recs[xch->i1];\n \t\tfor (i = 0; i < xch->chg1 && ignore; i++)\n-\t\t\tignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags);\n+\t\t\tignore = xdl_blankline((const char*) rec[i]->ptr, rec[i]->size, flags);\n \n \t\trec = &xe->xdf2.recs[xch->i2];\n \t\tfor (i = 0; i < xch->chg2 && ignore; i++)\n-\t\t\tignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags);\n+\t\t\tignore = xdl_blankline((const char*)rec[i]->ptr, rec[i]->size, flags);\n \n \t\txch->ignore = ignore;\n \t}\n@@ -1020,7 +1020,7 @@ static int record_matches_regex(xrecord_t *rec, xpparam_t const *xpp) {\n \tsize_t i;\n \n \tfor (i = 0; i < xpp->ignore_regex_nr; i++)\n-\t\tif (!regexec_buf(xpp->ignore_regex[i], rec->ptr, rec->size, 1,\n+\t\tif (!regexec_buf(xpp->ignore_regex[i], (const char*) rec->ptr, rec->size, 1,\n \t\t\t\t &regmatch, 0))\n \t\t\treturn 1;\n \ndiff --git a/xdiff/xemit.c b/xdiff/xemit.c\nindex 1d40c9cb40..bbf7b7f8c8 100644\n--- a/xdiff/xemit.c\n+++ b/xdiff/xemit.c\n@@ -24,7 +24,7 @@\n \n static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec) {\n \n-\t*rec = xdf->recs[ri]->ptr;\n+\t*rec = (char const*) xdf->recs[ri]->ptr;\n \n \treturn xdf->recs[ri]->size;\n }\ndiff --git a/xdiff/xmerge.c b/xdiff/xmerge.c\nindex af40c88a5b..6fa6ea61a2 100644\n--- a/xdiff/xmerge.c\n+++ b/xdiff/xmerge.c\n@@ -101,8 +101,8 @@ static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2,\n \txrecord_t **rec2 = xe2->xdf2.recs + i2;\n \n \tfor (i = 0; i < line_count; i++) {\n-\t\tint result = xdl_recmatch(rec1[i]->ptr, rec1[i]->size,\n-\t\t\trec2[i]->ptr, rec2[i]->size, flags);\n+\t\tint result = xdl_recmatch((const char*) rec1[i]->ptr, rec1[i]->size,\n+\t\t\t(const char*) rec2[i]->ptr, rec2[i]->size, flags);\n \t\tif (!result)\n \t\t\treturn -1;\n \t}\n@@ -324,8 +324,8 @@ static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1,\n \n static int recmatch(xrecord_t *rec1, xrecord_t *rec2, unsigned long flags)\n {\n-\treturn xdl_recmatch(rec1->ptr, rec1->size,\n-\t\t\t    rec2->ptr, rec2->size, flags);\n+\treturn xdl_recmatch((char const*) rec1->ptr, rec1->size,\n+\t\t\t    (char const*) rec2->ptr, rec2->size, flags);\n }\n \n /*\n@@ -383,10 +383,10 @@ static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m,\n \t\t */\n \t\tt1.ptr = (char *)xe1->xdf2.recs[m->i1]->ptr;\n \t\tt1.size = xe1->xdf2.recs[m->i1 + m->chg1 - 1]->ptr\n-\t\t\t+ xe1->xdf2.recs[m->i1 + m->chg1 - 1]->size - t1.ptr;\n+\t\t\t+ xe1->xdf2.recs[m->i1 + m->chg1 - 1]->size - (u8 const*) t1.ptr;\n \t\tt2.ptr = (char *)xe2->xdf2.recs[m->i2]->ptr;\n \t\tt2.size = xe2->xdf2.recs[m->i2 + m->chg2 - 1]->ptr\n-\t\t\t+ xe2->xdf2.recs[m->i2 + m->chg2 - 1]->size - t2.ptr;\n+\t\t\t+ xe2->xdf2.recs[m->i2 + m->chg2 - 1]->size - (u8 const*) t2.ptr;\n \t\tif (xdl_do_diff(&t1, &t2, xpp, &xe) < 0)\n \t\t\treturn -1;\n \t\tif (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 ||\n@@ -440,7 +440,7 @@ static int line_contains_alnum(const char *ptr, long size)\n static int lines_contain_alnum(xdfenv_t *xe, int i, int chg)\n {\n \tfor (; chg; chg--, i++)\n-\t\tif (line_contains_alnum(xe->xdf2.recs[i]->ptr,\n+\t\tif (line_contains_alnum((char const*) xe->xdf2.recs[i]->ptr,\n \t\t\t\txe->xdf2.recs[i]->size))\n \t\t\treturn 1;\n \treturn 0;\ndiff --git a/xdiff/xpatience.c b/xdiff/xpatience.c\nindex 77dc411d19..986a3a3f74 100644\n--- a/xdiff/xpatience.c\n+++ b/xdiff/xpatience.c\n@@ -121,7 +121,7 @@ static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map,\n \t\treturn;\n \tmap->entries[index].line1 = line;\n \tmap->entries[index].hash = record->ha;\n-\tmap->entries[index].anchor = is_anchor(xpp, map->env->xdf1.recs[line - 1]->ptr);\n+\tmap->entries[index].anchor = is_anchor(xpp, (const char*) map->env->xdf1.recs[line - 1]->ptr);\n \tif (!map->first)\n \t\tmap->first = map->entries + index;\n \tif (map->last) {\ndiff --git a/xdiff/xprepare.c b/xdiff/xprepare.c\nindex ad356281f9..00cdf7d8a0 100644\n--- a/xdiff/xprepare.c\n+++ b/xdiff/xprepare.c\n@@ -32,7 +32,7 @@\n \n typedef struct s_xdlclass {\n \tstruct s_xdlclass *next;\n-\tunsigned long ha;\n+\tu64 ha;\n \tchar const *line;\n \tlong size;\n \tlong idx;\n@@ -96,12 +96,12 @@ static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t\n \tchar const *line;\n \txdlclass_t *rcrec;\n \n-\tline = rec->ptr;\n+\tline = (char const*) rec->ptr;\n \thi = (long) XDL_HASHLONG(rec->ha, cf->hbits);\n \tfor (rcrec = cf->rchash[hi]; rcrec; rcrec = rcrec->next)\n \t\tif (rcrec->ha == rec->ha &&\n \t\t\t\txdl_recmatch(rcrec->line, rcrec->size,\n-\t\t\t\t\trec->ptr, rec->size, cf->flags))\n+\t\t\t\t\t(const char*) rec->ptr, rec->size, cf->flags))\n \t\t\tbreak;\n \n \tif (!rcrec) {\n@@ -159,7 +159,7 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_\n \t\t\t\tgoto abort;\n \t\t\tif (!(crec = xdl_cha_alloc(&xdf->rcha)))\n \t\t\t\tgoto abort;\n-\t\t\tcrec->ptr = prev;\n+\t\t\tcrec->ptr = (u8 const*) prev;\n \t\t\tcrec->size = (long) (cur - prev);\n \t\t\tcrec->ha = hav;\n \t\t\trecs[nrec++] = crec;\ndiff --git a/xdiff/xtypes.h b/xdiff/xtypes.h\nindex 8b8467360e..6e5f67ebf3 100644\n--- a/xdiff/xtypes.h\n+++ b/xdiff/xtypes.h\n@@ -39,9 +39,9 @@ typedef struct s_chastore {\n } chastore_t;\n \n typedef struct s_xrecord {\n-\tchar const *ptr;\n-\tlong size;\n-\tunsigned long ha;\n+\tu8 const* ptr;\n+\tusize size;\n+\tu64 ha;\n } xrecord_t;\n \n typedef struct s_xdfile {\ndiff --git a/xdiff/xutils.c b/xdiff/xutils.c\nindex 444a108f87..10e4f20b7c 100644\n--- a/xdiff/xutils.c\n+++ b/xdiff/xutils.c\n@@ -418,10 +418,10 @@ int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp,\n \n \tsubfile1.ptr = (char *)diff_env->xdf1.recs[line1 - 1]->ptr;\n \tsubfile1.size = diff_env->xdf1.recs[line1 + count1 - 2]->ptr +\n-\t\tdiff_env->xdf1.recs[line1 + count1 - 2]->size - subfile1.ptr;\n+\t\tdiff_env->xdf1.recs[line1 + count1 - 2]->size - (u8 const*) subfile1.ptr;\n \tsubfile2.ptr = (char *)diff_env->xdf2.recs[line2 - 1]->ptr;\n \tsubfile2.size = diff_env->xdf2.recs[line2 + count2 - 2]->ptr +\n-\t\tdiff_env->xdf2.recs[line2 + count2 - 2]->size - subfile2.ptr;\n+\t\tdiff_env->xdf2.recs[line2 + count2 - 2]->size - (u8 const*) subfile2.ptr;\n \tif (xdl_do_diff(&subfile1, &subfile2, xpp, &env) < 0)\n \t\treturn -1;\n \n-- \ngitgitgadget\n\n"},{"id":"525232","messageId":"0cd2168512465f1d131e8bddccb4be584f34b6c2.1756496539.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.git.git.1756496539.gitgitgadget@gmail.com","subject":"[PATCH 10/15] xdiff: use one definition for freeing xdfile_t","fromName":"Ezekiel Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-08-29T19:42:14Z","receivedAt":"2025-08-29T19:42:36Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"From: Ezekiel Newren <ezekielnewren@gmail.com>\n\nSimplify xdl_prepare_ctx() by using xdl_free_ctx() instead of using\nlocal variables with hand rolled memory management.\n\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n xdiff/xprepare.c | 60 +++++++++++++++++++-----------------------------\n 1 file changed, 24 insertions(+), 36 deletions(-)\n\ndiff --git a/xdiff/xprepare.c b/xdiff/xprepare.c\nindex 00cdf7d8a0..55e1cc3087 100644\n--- a/xdiff/xprepare.c\n+++ b/xdiff/xprepare.c\n@@ -129,86 +129,74 @@ static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t\n }\n \n \n+static void xdl_free_ctx(xdfile_t *xdf) {\n+\txdl_free(xdf->rindex);\n+\txdl_free(xdf->rchg - 1);\n+\txdl_free(xdf->ha);\n+\txdl_free(xdf->recs);\n+\txdl_cha_free(&xdf->rcha);\n+}\n+\n+\n static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,\n \t\t\t   xdlclassifier_t *cf, xdfile_t *xdf) {\n-\tlong nrec, bsize;\n+\tlong bsize;\n \tunsigned long hav;\n \tchar const *blk, *cur, *top, *prev;\n \txrecord_t *crec;\n-\txrecord_t **recs;\n-\tunsigned long *ha;\n-\tchar *rchg;\n-\tlong *rindex;\n \n-\tha = NULL;\n-\trindex = NULL;\n-\trchg = NULL;\n-\trecs = NULL;\n+\txdf->ha = NULL;\n+\txdf->rindex = NULL;\n+\txdf->rchg = NULL;\n+\txdf->recs = NULL;\n+\txdf->nrec = 0;\n \n \tif (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0)\n \t\tgoto abort;\n-\tif (!XDL_ALLOC_ARRAY(recs, narec))\n+\tif (!XDL_ALLOC_ARRAY(xdf->recs, narec))\n \t\tgoto abort;\n \n-\tnrec = 0;\n \tif ((cur = blk = xdl_mmfile_first(mf, &bsize))) {\n \t\tfor (top = blk + bsize; cur < top; ) {\n \t\t\tprev = cur;\n \t\t\thav = xdl_hash_record(&cur, top, xpp->flags);\n-\t\t\tif (XDL_ALLOC_GROW(recs, nrec + 1, narec))\n+\t\t\tif (XDL_ALLOC_GROW(xdf->recs, xdf->nrec + 1, narec))\n \t\t\t\tgoto abort;\n \t\t\tif (!(crec = xdl_cha_alloc(&xdf->rcha)))\n \t\t\t\tgoto abort;\n \t\t\tcrec->ptr = (u8 const*) prev;\n \t\t\tcrec->size = (long) (cur - prev);\n \t\t\tcrec->ha = hav;\n-\t\t\trecs[nrec++] = crec;\n+\t\t\txdf->recs[xdf->nrec++] = crec;\n \t\t\tif (xdl_classify_record(pass, cf, crec) < 0)\n \t\t\t\tgoto abort;\n \t\t}\n \t}\n \n-\tif (!XDL_CALLOC_ARRAY(rchg, nrec + 2))\n+\tif (!XDL_CALLOC_ARRAY(xdf->rchg, xdf->nrec + 2))\n \t\tgoto abort;\n \n \tif ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) &&\n \t    (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF)) {\n-\t\tif (!XDL_ALLOC_ARRAY(rindex, nrec + 1))\n+\t\tif (!XDL_ALLOC_ARRAY(xdf->rindex, xdf->nrec + 1))\n \t\t\tgoto abort;\n-\t\tif (!XDL_ALLOC_ARRAY(ha, nrec + 1))\n+\t\tif (!XDL_ALLOC_ARRAY(xdf->ha, xdf->nrec + 1))\n \t\t\tgoto abort;\n \t}\n \n-\txdf->nrec = nrec;\n-\txdf->recs = recs;\n-\txdf->rchg = rchg + 1;\n-\txdf->rindex = rindex;\n+\txdf->rchg += 1;\n \txdf->nreff = 0;\n-\txdf->ha = ha;\n \txdf->dstart = 0;\n-\txdf->dend = nrec - 1;\n+\txdf->dend = xdf->nrec - 1;\n \n \treturn 0;\n \n abort:\n-\txdl_free(ha);\n-\txdl_free(rindex);\n-\txdl_free(rchg);\n-\txdl_free(recs);\n-\txdl_cha_free(&xdf->rcha);\n+\txdl_free_ctx(xdf);\n \treturn -1;\n }\n \n \n-static void xdl_free_ctx(xdfile_t *xdf) {\n-\txdl_free(xdf->rindex);\n-\txdl_free(xdf->rchg - 1);\n-\txdl_free(xdf->ha);\n-\txdl_free(xdf->recs);\n-\txdl_cha_free(&xdf->rcha);\n-}\n-\n-\n void xdl_free_env(xdfenv_t *xe) {\n \n \txdl_free_ctx(&xe->xdf2);\n-- \ngitgitgadget\n\n"},{"id":"525233","messageId":"7ea2dccd71fc502f20614ce217fc9885d1b17413.1756496539.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.git.git.1756496539.gitgitgadget@gmail.com","subject":"[PATCH 11/15] xdiff: replace chastore with an ivec in xdfile_t","fromName":"Ezekiel Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-08-29T19:42:15Z","receivedAt":"2025-08-29T19:42:37Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"From: Ezekiel Newren <ezekielnewren@gmail.com>\n\nxdfile_t currently uses a chastore which functions as a memory pool and\na vector which maps to the allocations created by the chastore. It seems\nlike xrecord_t used to be a linked list until the recs and nrec fields\nwere added. I think that xrecord_t.next was meant to be removed, but\nwas overlooked. This dual data structure setup make the code somewhat\nconfusing.\n\nAdditionally the C type chastore_t isn't FFI friendly. While it could\nbe implemented in Rust, since the data structure is confusing anyway,\nreplace it with an ivec whose purpose is to be interoperable. This\nmakes the fields nrec and recs in xdfile_t redundant, which will be\nremoved in the next 2 commits.\n\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n xdiff/xprepare.c | 34 +++++++++++++++++-----------------\n xdiff/xtypes.h   |  6 ++++--\n 2 files changed, 21 insertions(+), 19 deletions(-)\n\ndiff --git a/xdiff/xprepare.c b/xdiff/xprepare.c\nindex 55e1cc3087..3b33186c15 100644\n--- a/xdiff/xprepare.c\n+++ b/xdiff/xprepare.c\n@@ -130,11 +130,11 @@ static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t\n \n \n static void xdl_free_ctx(xdfile_t *xdf) {\n+\tivec_free(&xdf->record);\n \txdl_free(xdf->rindex);\n \txdl_free(xdf->rchg - 1);\n \txdl_free(xdf->ha);\n \txdl_free(xdf->recs);\n-\txdl_cha_free(&xdf->rcha);\n }\n \n \n@@ -143,35 +143,35 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_\n \tlong bsize;\n \tunsigned long hav;\n \tchar const *blk, *cur, *top, *prev;\n-\txrecord_t *crec;\n \n \txdf->ha = NULL;\n \txdf->rindex = NULL;\n \txdf->rchg = NULL;\n \txdf->recs = NULL;\n \txdf->nrec = 0;\n-\n-\tif (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0)\n-\t\tgoto abort;\n-\tif (!XDL_ALLOC_ARRAY(xdf->recs, narec))\n-\t\tgoto abort;\n+\tIVEC_INIT(xdf->record);\n \n \tif ((cur = blk = xdl_mmfile_first(mf, &bsize))) {\n \t\tfor (top = blk + bsize; cur < top; ) {\n+\t\t\txrecord_t crec;\n \t\t\tprev = cur;\n \t\t\thav = xdl_hash_record(&cur, top, xpp->flags);\n-\t\t\tif (XDL_ALLOC_GROW(xdf->recs, xdf->nrec + 1, narec))\n-\t\t\t\tgoto abort;\n-\t\t\tif (!(crec = xdl_cha_alloc(&xdf->rcha)))\n-\t\t\t\tgoto abort;\n-\t\t\tcrec->ptr = (u8 const*) prev;\n-\t\t\tcrec->size = (long) (cur - prev);\n-\t\t\tcrec->ha = hav;\n-\t\t\txdf->recs[xdf->nrec++] = crec;\n-\t\t\tif (xdl_classify_record(pass, cf, crec) < 0)\n-\t\t\t\tgoto abort;\n+\t\t\tcrec.ptr = (u8 const*) prev;\n+\t\t\tcrec.size = cur - prev;\n+\t\t\tcrec.ha = hav;\n+\t\t\tivec_push(&xdf->record, &crec);\n \t\t}\n \t}\n+\tivec_shrink_to_fit(&xdf->record);\n+\n+\txdf->nrec = (long) xdf->record.length;\n+\tif (!XDL_ALLOC_ARRAY(xdf->recs, xdf->record.length))\n+\t\tgoto abort;\n+\tfor (usize i = 0; i < xdf->record.length; i++) {\n+\t\tif (xdl_classify_record(pass, cf, &xdf->record.ptr[i]) < 0)\n+\t\t\tgoto abort;\n+\t\txdf->recs[i] = &xdf->record.ptr[i];\n+\t}\n \n \tif (!XDL_CALLOC_ARRAY(xdf->rchg, xdf->nrec + 2))\n \t\tgoto abort;\ndiff --git a/xdiff/xtypes.h b/xdiff/xtypes.h\nindex 6e5f67ebf3..5028a70b26 100644\n--- a/xdiff/xtypes.h\n+++ b/xdiff/xtypes.h\n@@ -23,7 +23,7 @@\n #if !defined(XTYPES_H)\n #define XTYPES_H\n \n-\n+#include \"../interop/ivec.h\"\n \n typedef struct s_chanode {\n \tstruct s_chanode *next;\n@@ -44,8 +44,10 @@ typedef struct s_xrecord {\n \tu64 ha;\n } xrecord_t;\n \n+DEFINE_IVEC_TYPE(xrecord_t, xrecord);\n+\n typedef struct s_xdfile {\n-\tchastore_t rcha;\n+\tstruct ivec_xrecord record;\n \tlong nrec;\n \tlong dstart, dend;\n \txrecord_t **recs;\n-- \ngitgitgadget\n\n"},{"id":"525234","messageId":"d8f561e1730c6a4274bd13f23fd96cd9468a07c6.1756496539.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.git.git.1756496539.gitgitgadget@gmail.com","subject":"[PATCH 12/15] xdiff: delete nrec field from xdfile_t","fromName":"Ezekiel Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-08-29T19:42:16Z","receivedAt":"2025-08-29T19:42:39Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"From: Ezekiel Newren <ezekielnewren@gmail.com>\n\nBecause of the data structure cleanup in the previous commit, the nrec\nfield is no longer necessary. Use record.length in place of nrec.\n\nThis commit is best viewed with --color-words.\n\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n xdiff/xdiffi.c    | 10 +++++-----\n xdiff/xemit.c     | 20 ++++++++++----------\n xdiff/xmerge.c    | 10 +++++-----\n xdiff/xpatience.c |  2 +-\n xdiff/xprepare.c  | 34 ++++++++++++++++------------------\n xdiff/xtypes.h    |  1 -\n 6 files changed, 37 insertions(+), 40 deletions(-)\n\ndiff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c\nindex 3b364c61f6..bcab2d7ae5 100644\n--- a/xdiff/xdiffi.c\n+++ b/xdiff/xdiffi.c\n@@ -496,7 +496,7 @@ static void measure_split(const xdfile_t *xdf, long split,\n {\n \tlong i;\n \n-\tif (split >= xdf->nrec) {\n+\tif (split >= (long) xdf->record.length) {\n \t\tm->end_of_file = 1;\n \t\tm->indent = -1;\n \t} else {\n@@ -519,7 +519,7 @@ static void measure_split(const xdfile_t *xdf, long split,\n \n \tm->post_blank = 0;\n \tm->post_indent = -1;\n-\tfor (i = split + 1; i < xdf->nrec; i++) {\n+\tfor (i = split + 1; i < (long) xdf->record.length; i++) {\n \t\tm->post_indent = get_indent(xdf->recs[i]);\n \t\tif (m->post_indent != -1)\n \t\t\tbreak;\n@@ -730,7 +730,7 @@ static void group_init(xdfile_t *xdf, struct xdlgroup *g)\n  */\n static inline int group_next(xdfile_t *xdf, struct xdlgroup *g)\n {\n-\tif (g->end == xdf->nrec)\n+\tif (g->end == (long) xdf->record.length)\n \t\treturn -1;\n \n \tg->start = g->end + 1;\n@@ -763,7 +763,7 @@ static inline int group_previous(xdfile_t *xdf, struct xdlgroup *g)\n  */\n static int group_slide_down(xdfile_t *xdf, struct xdlgroup *g)\n {\n-\tif (g->end < xdf->nrec &&\n+\tif (g->end < (long) xdf->record.length &&\n \t    recs_match(xdf->recs[g->start], xdf->recs[g->end])) {\n \t\txdf->rchg[g->start++] = 0;\n \t\txdf->rchg[g->end++] = 1;\n@@ -950,7 +950,7 @@ int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr) {\n \t/*\n \t * Trivial. Collects \"groups\" of changes and creates an edit script.\n \t */\n-\tfor (i1 = xe->xdf1.nrec, i2 = xe->xdf2.nrec; i1 >= 0 || i2 >= 0; i1--, i2--)\n+\tfor (i1 = xe->xdf1.record.length, i2 = xe->xdf2.record.length; i1 >= 0 || i2 >= 0; i1--, i2--)\n \t\tif (rchg1[i1 - 1] || rchg2[i2 - 1]) {\n \t\t\tfor (l1 = i1; rchg1[i1 - 1]; i1--);\n \t\t\tfor (l2 = i2; rchg2[i2 - 1]; i2--);\ndiff --git a/xdiff/xemit.c b/xdiff/xemit.c\nindex bbf7b7f8c8..11c2823eca 100644\n--- a/xdiff/xemit.c\n+++ b/xdiff/xemit.c\n@@ -147,7 +147,7 @@ static long get_func_line(xdfenv_t *xe, xdemitconf_t const *xecfg,\n \tbuf = func_line ? func_line->buf : dummy;\n \tsize = func_line ? sizeof(func_line->buf) : sizeof(dummy);\n \n-\tfor (l = start; l != limit && 0 <= l && l < xe->xdf1.nrec; l += step) {\n+\tfor (l = start; l != limit && 0 <= l && l < (long) xe->xdf1.record.length; l += step) {\n \t\tlong len = match_func_rec(&xe->xdf1, xecfg, l, buf, size);\n \t\tif (len >= 0) {\n \t\t\tif (func_line)\n@@ -191,14 +191,14 @@ pre_context_calculation:\n \t\t\tlong fs1, i1 = xch->i1;\n \n \t\t\t/* Appended chunk? */\n-\t\t\tif (i1 >= xe->xdf1.nrec) {\n+\t\t\tif (i1 >= (long) xe->xdf1.record.length) {\n \t\t\t\tlong i2 = xch->i2;\n \n \t\t\t\t/*\n \t\t\t\t * We don't need additional context if\n \t\t\t\t * a whole function was added.\n \t\t\t\t */\n-\t\t\t\twhile (i2 < xe->xdf2.nrec) {\n+\t\t\t\twhile (i2 < (long) xe->xdf2.record.length) {\n \t\t\t\t\tif (is_func_rec(&xe->xdf2, xecfg, i2))\n \t\t\t\t\t\tgoto post_context_calculation;\n \t\t\t\t\ti2++;\n@@ -208,7 +208,7 @@ pre_context_calculation:\n \t\t\t\t * Otherwise get more context from the\n \t\t\t\t * pre-image.\n \t\t\t\t */\n-\t\t\t\ti1 = xe->xdf1.nrec - 1;\n+\t\t\t\ti1 = xe->xdf1.record.length - 1;\n \t\t\t}\n \n \t\t\tfs1 = get_func_line(xe, xecfg, NULL, i1, -1);\n@@ -240,8 +240,8 @@ pre_context_calculation:\n \n  post_context_calculation:\n \t\tlctx = xecfg->ctxlen;\n-\t\tlctx = XDL_MIN(lctx, xe->xdf1.nrec - (xche->i1 + xche->chg1));\n-\t\tlctx = XDL_MIN(lctx, xe->xdf2.nrec - (xche->i2 + xche->chg2));\n+\t\tlctx = XDL_MIN(lctx, (long) xe->xdf1.record.length - (xche->i1 + xche->chg1));\n+\t\tlctx = XDL_MIN(lctx, (long) xe->xdf2.record.length - (xche->i2 + xche->chg2));\n \n \t\te1 = xche->i1 + xche->chg1 + lctx;\n \t\te2 = xche->i2 + xche->chg2 + lctx;\n@@ -249,13 +249,13 @@ pre_context_calculation:\n \t\tif (xecfg->flags & XDL_EMIT_FUNCCONTEXT) {\n \t\t\tlong fe1 = get_func_line(xe, xecfg, NULL,\n \t\t\t\t\t\t xche->i1 + xche->chg1,\n-\t\t\t\t\t\t xe->xdf1.nrec);\n+\t\t\t\t\t\t xe->xdf1.record.length);\n \t\t\twhile (fe1 > 0 && is_empty_rec(&xe->xdf1, fe1 - 1))\n \t\t\t\tfe1--;\n \t\t\tif (fe1 < 0)\n-\t\t\t\tfe1 = xe->xdf1.nrec;\n+\t\t\t\tfe1 = xe->xdf1.record.length;\n \t\t\tif (fe1 > e1) {\n-\t\t\t\te2 = XDL_MIN(e2 + (fe1 - e1), xe->xdf2.nrec);\n+\t\t\t\te2 = XDL_MIN(e2 + (fe1 - e1), (long) xe->xdf2.record.length);\n \t\t\t\te1 = fe1;\n \t\t\t}\n \n@@ -266,7 +266,7 @@ pre_context_calculation:\n \t\t\t */\n \t\t\tif (xche->next) {\n \t\t\t\tlong l = XDL_MIN(xche->next->i1,\n-\t\t\t\t\t\t xe->xdf1.nrec - 1);\n+\t\t\t\t\t\t (long) xe->xdf1.record.length - 1);\n \t\t\t\tif (l - xecfg->ctxlen <= e1 ||\n \t\t\t\t    get_func_line(xe, xecfg, NULL, l, e1) < 0) {\n \t\t\t\t\txche = xche->next;\ndiff --git a/xdiff/xmerge.c b/xdiff/xmerge.c\nindex 6fa6ea61a2..f48549605d 100644\n--- a/xdiff/xmerge.c\n+++ b/xdiff/xmerge.c\n@@ -158,11 +158,11 @@ static int is_eol_crlf(xdfile_t *file, int i)\n {\n \tlong size;\n \n-\tif (i < file->nrec - 1)\n+\tif (i < (isize) file->record.length - 1)\n \t\t/* All lines before the last *must* end in LF */\n \t\treturn (size = file->recs[i]->size) > 1 &&\n \t\t\tfile->recs[i]->ptr[size - 2] == '\\r';\n-\tif (!file->nrec)\n+\tif (!file->record.length)\n \t\t/* Cannot determine eol style from empty file */\n \t\treturn -1;\n \tif ((size = file->recs[i]->size) &&\n@@ -317,7 +317,7 @@ static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1,\n \t\t\tcontinue;\n \t\ti = m->i1 + m->chg1;\n \t}\n-\tsize += xdl_recs_copy(xe1, i, xe1->xdf2.nrec - i, 0, 0,\n+\tsize += xdl_recs_copy(xe1, i, (int) xe1->xdf2.record.length - i, 0, 0,\n \t\t\t      dest ? dest + size : NULL);\n \treturn size;\n }\n@@ -622,7 +622,7 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1,\n \t\t\tchanges = c;\n \t\ti0 = xscr1->i1;\n \t\ti1 = xscr1->i2;\n-\t\ti2 = xscr1->i1 + xe2->xdf2.nrec - xe2->xdf1.nrec;\n+\t\ti2 = xscr1->i1 + xe2->xdf2.record.length - xe2->xdf1.record.length;\n \t\tchg0 = xscr1->chg1;\n \t\tchg1 = xscr1->chg2;\n \t\tchg2 = xscr1->chg1;\n@@ -637,7 +637,7 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1,\n \t\tif (!changes)\n \t\t\tchanges = c;\n \t\ti0 = xscr2->i1;\n-\t\ti1 = xscr2->i1 + xe1->xdf2.nrec - xe1->xdf1.nrec;\n+\t\ti1 = xscr2->i1 + xe1->xdf2.record.length - xe1->xdf1.record.length;\n \t\ti2 = xscr2->i2;\n \t\tchg0 = xscr2->chg1;\n \t\tchg1 = xscr2->chg1;\ndiff --git a/xdiff/xpatience.c b/xdiff/xpatience.c\nindex 986a3a3f74..e1ce9a399f 100644\n--- a/xdiff/xpatience.c\n+++ b/xdiff/xpatience.c\n@@ -370,5 +370,5 @@ static int patience_diff(xpparam_t const *xpp, xdfenv_t *env,\n \n int xdl_do_patience_diff(xpparam_t const *xpp, xdfenv_t *env)\n {\n-\treturn patience_diff(xpp, env, 1, env->xdf1.nrec, 1, env->xdf2.nrec);\n+\treturn patience_diff(xpp, env, 1, env->xdf1.record.length, 1, env->xdf2.record.length);\n }\ndiff --git a/xdiff/xprepare.c b/xdiff/xprepare.c\nindex 3b33186c15..9b46523afe 100644\n--- a/xdiff/xprepare.c\n+++ b/xdiff/xprepare.c\n@@ -138,7 +138,7 @@ static void xdl_free_ctx(xdfile_t *xdf) {\n }\n \n \n-static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,\n+static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, xpparam_t const *xpp,\n \t\t\t   xdlclassifier_t *cf, xdfile_t *xdf) {\n \tlong bsize;\n \tunsigned long hav;\n@@ -148,7 +148,6 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_\n \txdf->rindex = NULL;\n \txdf->rchg = NULL;\n \txdf->recs = NULL;\n-\txdf->nrec = 0;\n \tIVEC_INIT(xdf->record);\n \n \tif ((cur = blk = xdl_mmfile_first(mf, &bsize))) {\n@@ -164,7 +163,6 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_\n \t}\n \tivec_shrink_to_fit(&xdf->record);\n \n-\txdf->nrec = (long) xdf->record.length;\n \tif (!XDL_ALLOC_ARRAY(xdf->recs, xdf->record.length))\n \t\tgoto abort;\n \tfor (usize i = 0; i < xdf->record.length; i++) {\n@@ -173,21 +171,21 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_\n \t\txdf->recs[i] = &xdf->record.ptr[i];\n \t}\n \n-\tif (!XDL_CALLOC_ARRAY(xdf->rchg, xdf->nrec + 2))\n+\tif (!XDL_CALLOC_ARRAY(xdf->rchg, xdf->record.length + 2))\n \t\tgoto abort;\n \n \tif ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) &&\n \t    (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF)) {\n-\t\tif (!XDL_ALLOC_ARRAY(xdf->rindex, xdf->nrec + 1))\n+\t\tif (!XDL_ALLOC_ARRAY(xdf->rindex, xdf->record.length + 1))\n \t\t\tgoto abort;\n-\t\tif (!XDL_ALLOC_ARRAY(xdf->ha, xdf->nrec + 1))\n+\t\tif (!XDL_ALLOC_ARRAY(xdf->ha, xdf->record.length + 1))\n \t\t\tgoto abort;\n \t}\n \n \txdf->rchg += 1;\n \txdf->nreff = 0;\n \txdf->dstart = 0;\n-\txdf->dend = xdf->nrec - 1;\n+\txdf->dend = xdf->record.length - 1;\n \n \treturn 0;\n \n@@ -274,12 +272,12 @@ static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xd\n \tchar *dis, *dis1, *dis2;\n \tint need_min = !!(cf->flags & XDF_NEED_MINIMAL);\n \n-\tif (!XDL_CALLOC_ARRAY(dis, xdf1->nrec + xdf2->nrec + 2))\n+\tif (!XDL_CALLOC_ARRAY(dis, xdf1->record.length + xdf2->record.length + 2))\n \t\treturn -1;\n \tdis1 = dis;\n-\tdis2 = dis1 + xdf1->nrec + 1;\n+\tdis2 = dis1 + xdf1->record.length + 1;\n \n-\tif ((mlim = xdl_bogosqrt(xdf1->nrec)) > XDL_MAX_EQLIMIT)\n+\tif ((mlim = xdl_bogosqrt(xdf1->record.length)) > XDL_MAX_EQLIMIT)\n \t\tmlim = XDL_MAX_EQLIMIT;\n \tfor (i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; i <= xdf1->dend; i++, recs++) {\n \t\trcrec = cf->rcrecs[(*recs)->ha];\n@@ -287,7 +285,7 @@ static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xd\n \t\tdis1[i] = (nm == 0) ? 0: (nm >= mlim && !need_min) ? 2: 1;\n \t}\n \n-\tif ((mlim = xdl_bogosqrt(xdf2->nrec)) > XDL_MAX_EQLIMIT)\n+\tif ((mlim = xdl_bogosqrt(xdf2->record.length)) > XDL_MAX_EQLIMIT)\n \t\tmlim = XDL_MAX_EQLIMIT;\n \tfor (i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; i <= xdf2->dend; i++, recs++) {\n \t\trcrec = cf->rcrecs[(*recs)->ha];\n@@ -334,21 +332,21 @@ static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2) {\n \n \trecs1 = xdf1->recs;\n \trecs2 = xdf2->recs;\n-\tfor (i = 0, lim = XDL_MIN(xdf1->nrec, xdf2->nrec); i < lim;\n+\tfor (i = 0, lim = XDL_MIN(xdf1->record.length, xdf2->record.length); i < lim;\n \t     i++, recs1++, recs2++)\n \t\tif ((*recs1)->ha != (*recs2)->ha)\n \t\t\tbreak;\n \n \txdf1->dstart = xdf2->dstart = i;\n \n-\trecs1 = xdf1->recs + xdf1->nrec - 1;\n-\trecs2 = xdf2->recs + xdf2->nrec - 1;\n+\trecs1 = xdf1->recs + xdf1->record.length - 1;\n+\trecs2 = xdf2->recs + xdf2->record.length - 1;\n \tfor (lim -= i, i = 0; i < lim; i++, recs1--, recs2--)\n \t\tif ((*recs1)->ha != (*recs2)->ha)\n \t\t\tbreak;\n \n-\txdf1->dend = xdf1->nrec - i - 1;\n-\txdf2->dend = xdf2->nrec - i - 1;\n+\txdf1->dend = xdf1->record.length - i - 1;\n+\txdf2->dend = xdf2->record.length - i - 1;\n \n \treturn 0;\n }\n@@ -388,12 +386,12 @@ int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,\n \tif (xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0)\n \t\treturn -1;\n \n-\tif (xdl_prepare_ctx(1, mf1, enl1, xpp, &cf, &xe->xdf1) < 0) {\n+\tif (xdl_prepare_ctx(1, mf1, xpp, &cf, &xe->xdf1) < 0) {\n \n \t\txdl_free_classifier(&cf);\n \t\treturn -1;\n \t}\n-\tif (xdl_prepare_ctx(2, mf2, enl2, xpp, &cf, &xe->xdf2) < 0) {\n+\tif (xdl_prepare_ctx(2, mf2, xpp, &cf, &xe->xdf2) < 0) {\n \n \t\txdl_free_ctx(&xe->xdf1);\n \t\txdl_free_classifier(&cf);\ndiff --git a/xdiff/xtypes.h b/xdiff/xtypes.h\nindex 5028a70b26..c322e62fbf 100644\n--- a/xdiff/xtypes.h\n+++ b/xdiff/xtypes.h\n@@ -48,7 +48,6 @@ DEFINE_IVEC_TYPE(xrecord_t, xrecord);\n \n typedef struct s_xdfile {\n \tstruct ivec_xrecord record;\n-\tlong nrec;\n \tlong dstart, dend;\n \txrecord_t **recs;\n \tchar *rchg;\n-- \ngitgitgadget\n\n"},{"id":"525235","messageId":"15c2854a327c2bab8ddee067a2763b3a7488280e.1756496539.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.git.git.1756496539.gitgitgadget@gmail.com","subject":"[PATCH 13/15] xdiff: delete recs field from xdfile_t","fromName":"Ezekiel Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-08-29T19:42:17Z","receivedAt":"2025-08-29T19:42:40Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"From: Ezekiel Newren <ezekielnewren@gmail.com>\n\nBecause of the change from chastore to ivec a few commits ago,\nrecs now points to record's elements in a 1:1 mapping.\nSince both recs and record are vectors, this additional mapping is\nsuperfluous. Remove recs.\n\nThis commit is best viewed with --color-words.\n\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n xdiff/xdiffi.c     | 30 ++++++++++++------------\n xdiff/xemit.c      |  4 ++--\n xdiff/xhistogram.c |  2 +-\n xdiff/xmerge.c     | 58 +++++++++++++++++++++++-----------------------\n xdiff/xpatience.c  | 14 +++++------\n xdiff/xprepare.c   | 37 +++++++++++++----------------\n xdiff/xtypes.h     |  1 -\n xdiff/xutils.c     | 12 +++++-----\n 8 files changed, 76 insertions(+), 82 deletions(-)\n\ndiff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c\nindex bcab2d7ae5..ebdb724322 100644\n--- a/xdiff/xdiffi.c\n+++ b/xdiff/xdiffi.c\n@@ -501,13 +501,13 @@ static void measure_split(const xdfile_t *xdf, long split,\n \t\tm->indent = -1;\n \t} else {\n \t\tm->end_of_file = 0;\n-\t\tm->indent = get_indent(xdf->recs[split]);\n+\t\tm->indent = get_indent(&xdf->record.ptr[split]);\n \t}\n \n \tm->pre_blank = 0;\n \tm->pre_indent = -1;\n \tfor (i = split - 1; i >= 0; i--) {\n-\t\tm->pre_indent = get_indent(xdf->recs[i]);\n+\t\tm->pre_indent = get_indent(&xdf->record.ptr[i]);\n \t\tif (m->pre_indent != -1)\n \t\t\tbreak;\n \t\tm->pre_blank += 1;\n@@ -520,7 +520,7 @@ static void measure_split(const xdfile_t *xdf, long split,\n \tm->post_blank = 0;\n \tm->post_indent = -1;\n \tfor (i = split + 1; i < (long) xdf->record.length; i++) {\n-\t\tm->post_indent = get_indent(xdf->recs[i]);\n+\t\tm->post_indent = get_indent(&xdf->record.ptr[i]);\n \t\tif (m->post_indent != -1)\n \t\t\tbreak;\n \t\tm->post_blank += 1;\n@@ -764,7 +764,7 @@ static inline int group_previous(xdfile_t *xdf, struct xdlgroup *g)\n static int group_slide_down(xdfile_t *xdf, struct xdlgroup *g)\n {\n \tif (g->end < (long) xdf->record.length &&\n-\t    recs_match(xdf->recs[g->start], xdf->recs[g->end])) {\n+\t    recs_match(&xdf->record.ptr[g->start], &xdf->record.ptr[g->end])) {\n \t\txdf->rchg[g->start++] = 0;\n \t\txdf->rchg[g->end++] = 1;\n \n@@ -785,7 +785,7 @@ static int group_slide_down(xdfile_t *xdf, struct xdlgroup *g)\n static int group_slide_up(xdfile_t *xdf, struct xdlgroup *g)\n {\n \tif (g->start > 0 &&\n-\t    recs_match(xdf->recs[g->start - 1], xdf->recs[g->end - 1])) {\n+\t    recs_match(&xdf->record.ptr[g->start - 1], &xdf->record.ptr[g->end - 1])) {\n \t\txdf->rchg[--g->start] = 1;\n \t\txdf->rchg[--g->end] = 0;\n \n@@ -1000,16 +1000,16 @@ static void xdl_mark_ignorable_lines(xdchange_t *xscr, xdfenv_t *xe, long flags)\n \n \tfor (xch = xscr; xch; xch = xch->next) {\n \t\tint ignore = 1;\n-\t\txrecord_t **rec;\n+\t\txrecord_t *rec;\n \t\tlong i;\n \n-\t\trec = &xe->xdf1.recs[xch->i1];\n+\t\trec = &xe->xdf1.record.ptr[xch->i1];\n \t\tfor (i = 0; i < xch->chg1 && ignore; i++)\n-\t\t\tignore = xdl_blankline((const char*) rec[i]->ptr, rec[i]->size, flags);\n+\t\t\tignore = xdl_blankline((const char*) rec[i].ptr, rec[i].size, flags);\n \n-\t\trec = &xe->xdf2.recs[xch->i2];\n+\t\trec = &xe->xdf2.record.ptr[xch->i2];\n \t\tfor (i = 0; i < xch->chg2 && ignore; i++)\n-\t\t\tignore = xdl_blankline((const char*)rec[i]->ptr, rec[i]->size, flags);\n+\t\t\tignore = xdl_blankline((const char*)rec[i].ptr, rec[i].size, flags);\n \n \t\txch->ignore = ignore;\n \t}\n@@ -1033,7 +1033,7 @@ static void xdl_mark_ignorable_regex(xdchange_t *xscr, const xdfenv_t *xe,\n \txdchange_t *xch;\n \n \tfor (xch = xscr; xch; xch = xch->next) {\n-\t\txrecord_t **rec;\n+\t\txrecord_t *rec;\n \t\tint ignore = 1;\n \t\tlong i;\n \n@@ -1043,13 +1043,13 @@ static void xdl_mark_ignorable_regex(xdchange_t *xscr, const xdfenv_t *xe,\n \t\tif (xch->ignore)\n \t\t\tcontinue;\n \n-\t\trec = &xe->xdf1.recs[xch->i1];\n+\t\trec = &xe->xdf1.record.ptr[xch->i1];\n \t\tfor (i = 0; i < xch->chg1 && ignore; i++)\n-\t\t\tignore = record_matches_regex(rec[i], xpp);\n+\t\t\tignore = record_matches_regex(&rec[i], xpp);\n \n-\t\trec = &xe->xdf2.recs[xch->i2];\n+\t\trec = &xe->xdf2.record.ptr[xch->i2];\n \t\tfor (i = 0; i < xch->chg2 && ignore; i++)\n-\t\t\tignore = record_matches_regex(rec[i], xpp);\n+\t\t\tignore = record_matches_regex(&rec[i], xpp);\n \n \t\txch->ignore = ignore;\n \t}\ndiff --git a/xdiff/xemit.c b/xdiff/xemit.c\nindex 11c2823eca..0c9a12a5e8 100644\n--- a/xdiff/xemit.c\n+++ b/xdiff/xemit.c\n@@ -24,9 +24,9 @@\n \n static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec) {\n \n-\t*rec = (char const*) xdf->recs[ri]->ptr;\n+\t*rec = (char const*) xdf->record.ptr[ri].ptr;\n \n-\treturn xdf->recs[ri]->size;\n+\treturn xdf->record.ptr[ri].size;\n }\n \n \ndiff --git a/xdiff/xhistogram.c b/xdiff/xhistogram.c\nindex 040d81e0bc..643d1c8b70 100644\n--- a/xdiff/xhistogram.c\n+++ b/xdiff/xhistogram.c\n@@ -86,7 +86,7 @@ struct region {\n \t((LINE_MAP(index, ptr))->cnt)\n \n #define REC(env, s, l) \\\n-\t(env->xdf##s.recs[l - 1])\n+\t(&env->xdf##s.record.ptr[l - 1])\n \n static int cmp_recs(xrecord_t *r1, xrecord_t *r2)\n {\ndiff --git a/xdiff/xmerge.c b/xdiff/xmerge.c\nindex f48549605d..0a3e0f28ab 100644\n--- a/xdiff/xmerge.c\n+++ b/xdiff/xmerge.c\n@@ -97,12 +97,12 @@ static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2,\n \t\tint line_count, long flags)\n {\n \tint i;\n-\txrecord_t **rec1 = xe1->xdf2.recs + i1;\n-\txrecord_t **rec2 = xe2->xdf2.recs + i2;\n+\txrecord_t *rec1 = xe1->xdf2.record.ptr + i1;\n+\txrecord_t *rec2 = xe2->xdf2.record.ptr + i2;\n \n \tfor (i = 0; i < line_count; i++) {\n-\t\tint result = xdl_recmatch((const char*) rec1[i]->ptr, rec1[i]->size,\n-\t\t\t(const char*) rec2[i]->ptr, rec2[i]->size, flags);\n+\t\tint result = xdl_recmatch((const char*) rec1[i].ptr, rec1[i].size,\n+\t\t\t(const char*) rec2[i].ptr, rec2[i].size, flags);\n \t\tif (!result)\n \t\t\treturn -1;\n \t}\n@@ -111,20 +111,20 @@ static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2,\n \n static int xdl_recs_copy_0(int use_orig, xdfenv_t *xe, int i, int count, int needs_cr, int add_nl, char *dest)\n {\n-\txrecord_t **recs;\n+\txrecord_t *recs;\n \tint size = 0;\n \n-\trecs = (use_orig ? xe->xdf1.recs : xe->xdf2.recs) + i;\n+\trecs = (use_orig ? xe->xdf1.record.ptr : xe->xdf2.record.ptr) + i;\n \n \tif (count < 1)\n \t\treturn 0;\n \n-\tfor (i = 0; i < count; size += recs[i++]->size)\n+\tfor (i = 0; i < count; size += recs[i++].size)\n \t\tif (dest)\n-\t\t\tmemcpy(dest + size, recs[i]->ptr, recs[i]->size);\n+\t\t\tmemcpy(dest + size, recs[i].ptr, recs[i].size);\n \tif (add_nl) {\n-\t\ti = recs[count - 1]->size;\n-\t\tif (i == 0 || recs[count - 1]->ptr[i - 1] != '\\n') {\n+\t\ti = recs[count - 1].size;\n+\t\tif (i == 0 || recs[count - 1].ptr[i - 1] != '\\n') {\n \t\t\tif (needs_cr) {\n \t\t\t\tif (dest)\n \t\t\t\t\tdest[size] = '\\r';\n@@ -160,22 +160,22 @@ static int is_eol_crlf(xdfile_t *file, int i)\n \n \tif (i < (isize) file->record.length - 1)\n \t\t/* All lines before the last *must* end in LF */\n-\t\treturn (size = file->recs[i]->size) > 1 &&\n-\t\t\tfile->recs[i]->ptr[size - 2] == '\\r';\n+\t\treturn (size = file->record.ptr[i].size) > 1 &&\n+\t\t\tfile->record.ptr[i].ptr[size - 2] == '\\r';\n \tif (!file->record.length)\n \t\t/* Cannot determine eol style from empty file */\n \t\treturn -1;\n-\tif ((size = file->recs[i]->size) &&\n-\t\t\tfile->recs[i]->ptr[size - 1] == '\\n')\n+\tif ((size = file->record.ptr[i].size) &&\n+\t\t\tfile->record.ptr[i].ptr[size - 1] == '\\n')\n \t\t/* Last line; ends in LF; Is it CR/LF? */\n \t\treturn size > 1 &&\n-\t\t\tfile->recs[i]->ptr[size - 2] == '\\r';\n+\t\t\tfile->record.ptr[i].ptr[size - 2] == '\\r';\n \tif (!i)\n \t\t/* The only line has no eol */\n \t\treturn -1;\n \t/* Determine eol from second-to-last line */\n-\treturn (size = file->recs[i - 1]->size) > 1 &&\n-\t\tfile->recs[i - 1]->ptr[size - 2] == '\\r';\n+\treturn (size = file->record.ptr[i - 1].size) > 1 &&\n+\t\tfile->record.ptr[i - 1].ptr[size - 2] == '\\r';\n }\n \n static int is_cr_needed(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m)\n@@ -334,22 +334,22 @@ static int recmatch(xrecord_t *rec1, xrecord_t *rec2, unsigned long flags)\n static void xdl_refine_zdiff3_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m,\n \t\txpparam_t const *xpp)\n {\n-\txrecord_t **rec1 = xe1->xdf2.recs, **rec2 = xe2->xdf2.recs;\n+\txrecord_t *rec1 = xe1->xdf2.record.ptr, *rec2 = xe2->xdf2.record.ptr;\n \tfor (; m; m = m->next) {\n \t\t/* let's handle just the conflicts */\n \t\tif (m->mode)\n \t\t\tcontinue;\n \n \t\twhile(m->chg1 && m->chg2 &&\n-\t\t      recmatch(rec1[m->i1], rec2[m->i2], xpp->flags)) {\n+\t\t      recmatch(&rec1[m->i1], &rec2[m->i2], xpp->flags)) {\n \t\t\tm->chg1--;\n \t\t\tm->chg2--;\n \t\t\tm->i1++;\n \t\t\tm->i2++;\n \t\t}\n \t\twhile (m->chg1 && m->chg2 &&\n-\t\t       recmatch(rec1[m->i1 + m->chg1 - 1],\n-\t\t\t\trec2[m->i2 + m->chg2 - 1], xpp->flags)) {\n+\t\t       recmatch(&rec1[m->i1 + m->chg1 - 1],\n+\t\t\t\t&rec2[m->i2 + m->chg2 - 1], xpp->flags)) {\n \t\t\tm->chg1--;\n \t\t\tm->chg2--;\n \t\t}\n@@ -381,12 +381,12 @@ static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m,\n \t\t * This probably does not work outside git, since\n \t\t * we have a very simple mmfile structure.\n \t\t */\n-\t\tt1.ptr = (char *)xe1->xdf2.recs[m->i1]->ptr;\n-\t\tt1.size = xe1->xdf2.recs[m->i1 + m->chg1 - 1]->ptr\n-\t\t\t+ xe1->xdf2.recs[m->i1 + m->chg1 - 1]->size - (u8 const*) t1.ptr;\n-\t\tt2.ptr = (char *)xe2->xdf2.recs[m->i2]->ptr;\n-\t\tt2.size = xe2->xdf2.recs[m->i2 + m->chg2 - 1]->ptr\n-\t\t\t+ xe2->xdf2.recs[m->i2 + m->chg2 - 1]->size - (u8 const*) t2.ptr;\n+\t\tt1.ptr = (char *)xe1->xdf2.record.ptr[m->i1].ptr;\n+\t\tt1.size = xe1->xdf2.record.ptr[m->i1 + m->chg1 - 1].ptr\n+\t\t\t+ xe1->xdf2.record.ptr[m->i1 + m->chg1 - 1].size - (u8 const*) t1.ptr;\n+\t\tt2.ptr = (char *)xe2->xdf2.record.ptr[m->i2].ptr;\n+\t\tt2.size = xe2->xdf2.record.ptr[m->i2 + m->chg2 - 1].ptr\n+\t\t\t+ xe2->xdf2.record.ptr[m->i2 + m->chg2 - 1].size - (u8 const*) t2.ptr;\n \t\tif (xdl_do_diff(&t1, &t2, xpp, &xe) < 0)\n \t\t\treturn -1;\n \t\tif (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 ||\n@@ -440,8 +440,8 @@ static int line_contains_alnum(const char *ptr, long size)\n static int lines_contain_alnum(xdfenv_t *xe, int i, int chg)\n {\n \tfor (; chg; chg--, i++)\n-\t\tif (line_contains_alnum((char const*) xe->xdf2.recs[i]->ptr,\n-\t\t\t\txe->xdf2.recs[i]->size))\n+\t\tif (line_contains_alnum((char const*) xe->xdf2.record.ptr[i].ptr,\n+\t\t\t\txe->xdf2.record.ptr[i].size))\n \t\t\treturn 1;\n \treturn 0;\n }\ndiff --git a/xdiff/xpatience.c b/xdiff/xpatience.c\nindex e1ce9a399f..31b819ec58 100644\n--- a/xdiff/xpatience.c\n+++ b/xdiff/xpatience.c\n@@ -88,9 +88,9 @@ static int is_anchor(xpparam_t const *xpp, const char *line)\n static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map,\n \t\t\t  int pass)\n {\n-\txrecord_t **records = pass == 1 ?\n-\t\tmap->env->xdf1.recs : map->env->xdf2.recs;\n-\txrecord_t *record = records[line - 1];\n+\txrecord_t *records = pass == 1 ?\n+\t\tmap->env->xdf1.record.ptr : map->env->xdf2.record.ptr;\n+\txrecord_t *record = &records[line - 1];\n \t/*\n \t * After xdl_prepare_env() (or more precisely, due to\n \t * xdl_classify_record()), the \"ha\" member of the records (AKA lines)\n@@ -121,7 +121,7 @@ static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map,\n \t\treturn;\n \tmap->entries[index].line1 = line;\n \tmap->entries[index].hash = record->ha;\n-\tmap->entries[index].anchor = is_anchor(xpp, (const char*) map->env->xdf1.recs[line - 1]->ptr);\n+\tmap->entries[index].anchor = is_anchor(xpp, (const char*) map->env->xdf1.record.ptr[line - 1].ptr);\n \tif (!map->first)\n \t\tmap->first = map->entries + index;\n \tif (map->last) {\n@@ -246,9 +246,9 @@ static int find_longest_common_sequence(struct hashmap *map, struct entry **res)\n \n static int match(struct hashmap *map, int line1, int line2)\n {\n-\txrecord_t *record1 = map->env->xdf1.recs[line1 - 1];\n-\txrecord_t *record2 = map->env->xdf2.recs[line2 - 1];\n-\treturn record1->ha == record2->ha;\n+\tu64 mph1 = map->env->xdf1.record.ptr[line1 - 1].ha;\n+\tu64 mph2 = map->env->xdf2.record.ptr[line2 - 1].ha;\n+\treturn mph1 == mph2;\n }\n \n static int patience_diff(xpparam_t const *xpp, xdfenv_t *env,\ndiff --git a/xdiff/xprepare.c b/xdiff/xprepare.c\nindex 9b46523afe..93370f1c6d 100644\n--- a/xdiff/xprepare.c\n+++ b/xdiff/xprepare.c\n@@ -134,7 +134,6 @@ static void xdl_free_ctx(xdfile_t *xdf) {\n \txdl_free(xdf->rindex);\n \txdl_free(xdf->rchg - 1);\n \txdl_free(xdf->ha);\n-\txdl_free(xdf->recs);\n }\n \n \n@@ -147,7 +146,6 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, xpparam_t const *xpp\n \txdf->ha = NULL;\n \txdf->rindex = NULL;\n \txdf->rchg = NULL;\n-\txdf->recs = NULL;\n \tIVEC_INIT(xdf->record);\n \n \tif ((cur = blk = xdl_mmfile_first(mf, &bsize))) {\n@@ -163,12 +161,9 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, xpparam_t const *xpp\n \t}\n \tivec_shrink_to_fit(&xdf->record);\n \n-\tif (!XDL_ALLOC_ARRAY(xdf->recs, xdf->record.length))\n-\t\tgoto abort;\n \tfor (usize i = 0; i < xdf->record.length; i++) {\n \t\tif (xdl_classify_record(pass, cf, &xdf->record.ptr[i]) < 0)\n \t\t\tgoto abort;\n-\t\txdf->recs[i] = &xdf->record.ptr[i];\n \t}\n \n \tif (!XDL_CALLOC_ARRAY(xdf->rchg, xdf->record.length + 2))\n@@ -267,7 +262,7 @@ static int xdl_clean_mmatch(char const *dis, long i, long s, long e) {\n  */\n static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) {\n \tlong i, nm, nreff, mlim;\n-\txrecord_t **recs;\n+\txrecord_t *recs;\n \txdlclass_t *rcrec;\n \tchar *dis, *dis1, *dis2;\n \tint need_min = !!(cf->flags & XDF_NEED_MINIMAL);\n@@ -279,38 +274,38 @@ static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xd\n \n \tif ((mlim = xdl_bogosqrt(xdf1->record.length)) > XDL_MAX_EQLIMIT)\n \t\tmlim = XDL_MAX_EQLIMIT;\n-\tfor (i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; i <= xdf1->dend; i++, recs++) {\n-\t\trcrec = cf->rcrecs[(*recs)->ha];\n+\tfor (i = xdf1->dstart, recs = &xdf1->record.ptr[xdf1->dstart]; i <= xdf1->dend; i++, recs++) {\n+\t\trcrec = cf->rcrecs[recs->ha];\n \t\tnm = rcrec ? rcrec->len2 : 0;\n \t\tdis1[i] = (nm == 0) ? 0: (nm >= mlim && !need_min) ? 2: 1;\n \t}\n \n \tif ((mlim = xdl_bogosqrt(xdf2->record.length)) > XDL_MAX_EQLIMIT)\n \t\tmlim = XDL_MAX_EQLIMIT;\n-\tfor (i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; i <= xdf2->dend; i++, recs++) {\n-\t\trcrec = cf->rcrecs[(*recs)->ha];\n+\tfor (i = xdf2->dstart, recs = &xdf2->record.ptr[xdf2->dstart]; i <= xdf2->dend; i++, recs++) {\n+\t\trcrec = cf->rcrecs[recs->ha];\n \t\tnm = rcrec ? rcrec->len1 : 0;\n \t\tdis2[i] = (nm == 0) ? 0: (nm >= mlim && !need_min) ? 2: 1;\n \t}\n \n-\tfor (nreff = 0, i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart];\n+\tfor (nreff = 0, i = xdf1->dstart, recs = &xdf1->record.ptr[xdf1->dstart];\n \t     i <= xdf1->dend; i++, recs++) {\n \t\tif (dis1[i] == 1 ||\n \t\t    (dis1[i] == 2 && !xdl_clean_mmatch(dis1, i, xdf1->dstart, xdf1->dend))) {\n \t\t\txdf1->rindex[nreff] = i;\n-\t\t\txdf1->ha[nreff] = (*recs)->ha;\n+\t\t\txdf1->ha[nreff] = recs->ha;\n \t\t\tnreff++;\n \t\t} else\n \t\t\txdf1->rchg[i] = 1;\n \t}\n \txdf1->nreff = nreff;\n \n-\tfor (nreff = 0, i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart];\n+\tfor (nreff = 0, i = xdf2->dstart, recs = &xdf2->record.ptr[xdf2->dstart];\n \t     i <= xdf2->dend; i++, recs++) {\n \t\tif (dis2[i] == 1 ||\n \t\t    (dis2[i] == 2 && !xdl_clean_mmatch(dis2, i, xdf2->dstart, xdf2->dend))) {\n \t\t\txdf2->rindex[nreff] = i;\n-\t\t\txdf2->ha[nreff] = (*recs)->ha;\n+\t\t\txdf2->ha[nreff] = recs->ha;\n \t\t\tnreff++;\n \t\t} else\n \t\t\txdf2->rchg[i] = 1;\n@@ -328,21 +323,21 @@ static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xd\n  */\n static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2) {\n \tlong i, lim;\n-\txrecord_t **recs1, **recs2;\n+\txrecord_t *recs1, *recs2;\n \n-\trecs1 = xdf1->recs;\n-\trecs2 = xdf2->recs;\n+\trecs1 = xdf1->record.ptr;\n+\trecs2 = xdf2->record.ptr;\n \tfor (i = 0, lim = XDL_MIN(xdf1->record.length, xdf2->record.length); i < lim;\n \t     i++, recs1++, recs2++)\n-\t\tif ((*recs1)->ha != (*recs2)->ha)\n+\t\tif (recs1->ha != recs2->ha)\n \t\t\tbreak;\n \n \txdf1->dstart = xdf2->dstart = i;\n \n-\trecs1 = xdf1->recs + xdf1->record.length - 1;\n-\trecs2 = xdf2->recs + xdf2->record.length - 1;\n+\trecs1 = xdf1->record.ptr + xdf1->record.length - 1;\n+\trecs2 = xdf2->record.ptr + xdf2->record.length - 1;\n \tfor (lim -= i, i = 0; i < lim; i++, recs1--, recs2--)\n-\t\tif ((*recs1)->ha != (*recs2)->ha)\n+\t\tif (recs1->ha != recs2->ha)\n \t\t\tbreak;\n \n \txdf1->dend = xdf1->record.length - i - 1;\ndiff --git a/xdiff/xtypes.h b/xdiff/xtypes.h\nindex c322e62fbf..849f218b32 100644\n--- a/xdiff/xtypes.h\n+++ b/xdiff/xtypes.h\n@@ -49,7 +49,6 @@ DEFINE_IVEC_TYPE(xrecord_t, xrecord);\n typedef struct s_xdfile {\n \tstruct ivec_xrecord record;\n \tlong dstart, dend;\n-\txrecord_t **recs;\n \tchar *rchg;\n \tlong *rindex;\n \tlong nreff;\ndiff --git a/xdiff/xutils.c b/xdiff/xutils.c\nindex 10e4f20b7c..eed88ee6cb 100644\n--- a/xdiff/xutils.c\n+++ b/xdiff/xutils.c\n@@ -416,12 +416,12 @@ int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp,\n \tmmfile_t subfile1, subfile2;\n \txdfenv_t env;\n \n-\tsubfile1.ptr = (char *)diff_env->xdf1.recs[line1 - 1]->ptr;\n-\tsubfile1.size = diff_env->xdf1.recs[line1 + count1 - 2]->ptr +\n-\t\tdiff_env->xdf1.recs[line1 + count1 - 2]->size - (u8 const*) subfile1.ptr;\n-\tsubfile2.ptr = (char *)diff_env->xdf2.recs[line2 - 1]->ptr;\n-\tsubfile2.size = diff_env->xdf2.recs[line2 + count2 - 2]->ptr +\n-\t\tdiff_env->xdf2.recs[line2 + count2 - 2]->size - (u8 const*) subfile2.ptr;\n+\tsubfile1.ptr = (char *)diff_env->xdf1.record.ptr[line1 - 1].ptr;\n+\tsubfile1.size = diff_env->xdf1.record.ptr[line1 + count1 - 2].ptr +\n+\t\tdiff_env->xdf1.record.ptr[line1 + count1 - 2].size - (u8 const*) subfile1.ptr;\n+\tsubfile2.ptr = (char *)diff_env->xdf2.record.ptr[line2 - 1].ptr;\n+\tsubfile2.size = diff_env->xdf2.record.ptr[line2 + count2 - 2].ptr +\n+\t\tdiff_env->xdf2.record.ptr[line2 + count2 - 2].size - (u8 const*) subfile2.ptr;\n \tif (xdl_do_diff(&subfile1, &subfile2, xpp, &env) < 0)\n \t\treturn -1;\n \n-- \ngitgitgadget\n\n"},{"id":"525236","messageId":"3dacb2c09dd6f2b718033a181a53479f52863917.1756496539.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.git.git.1756496539.gitgitgadget@gmail.com","subject":"[PATCH 14/15] xdiff: make xdfile_t more rust friendly","fromName":"Ezekiel Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-08-29T19:42:18Z","receivedAt":"2025-08-29T19:42:41Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"From: Ezekiel Newren <ezekielnewren@gmail.com>\n\nConvert the remaining ambiguous fields in xdfile_t from C types to Rust\ntypes for interoperability.\n\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n xdiff/xdiffi.c | 16 ++++++++--------\n xdiff/xdiffi.h |  8 ++++----\n xdiff/xtypes.h | 10 +++++-----\n 3 files changed, 17 insertions(+), 17 deletions(-)\n\ndiff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c\nindex ebdb724322..0509b48759 100644\n--- a/xdiff/xdiffi.c\n+++ b/xdiff/xdiffi.c\n@@ -42,8 +42,8 @@ typedef struct s_xdpsplit {\n  * using this algorithm, so a little bit of heuristic is needed to cut the\n  * search and to return a suboptimal point.\n  */\n-static long xdl_split(unsigned long const *ha1, long off1, long lim1,\n-\t\t      unsigned long const *ha2, long off2, long lim2,\n+static long xdl_split(u64 const *ha1, long off1, long lim1,\n+\t\t      u64 const *ha2, long off2, long lim2,\n \t\t      long *kvdf, long *kvdb, int need_min, xdpsplit_t *spl,\n \t\t      xdalgoenv_t *xenv) {\n \tlong dmin = off1 - lim2, dmax = lim1 - off2;\n@@ -260,7 +260,7 @@ static long xdl_split(unsigned long const *ha1, long off1, long lim1,\n int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1,\n \t\t diffdata_t *dd2, long off2, long lim2,\n \t\t long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv) {\n-\tunsigned long const *ha1 = dd1->ha, *ha2 = dd2->ha;\n+\tu64 const *ha1 = dd1->ha, *ha2 = dd2->ha;\n \n \t/*\n \t * Shrink the box by walking through each diagonal snake (SW and NE).\n@@ -273,14 +273,14 @@ int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1,\n \t * be obviously changed.\n \t */\n \tif (off1 == lim1) {\n-\t\tchar *rchg2 = dd2->rchg;\n-\t\tlong *rindex2 = dd2->rindex;\n+\t\tu8 *rchg2 = dd2->rchg;\n+\t\tusize *rindex2 = dd2->rindex;\n \n \t\tfor (; off2 < lim2; off2++)\n \t\t\trchg2[rindex2[off2]] = 1;\n \t} else if (off2 == lim2) {\n-\t\tchar *rchg1 = dd1->rchg;\n-\t\tlong *rindex1 = dd1->rindex;\n+\t\tu8 *rchg1 = dd1->rchg;\n+\t\tusize *rindex1 = dd1->rindex;\n \n \t\tfor (; off1 < lim1; off1++)\n \t\t\trchg1[rindex1[off1]] = 1;\n@@ -944,7 +944,7 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) {\n \n int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr) {\n \txdchange_t *cscr = NULL, *xch;\n-\tchar *rchg1 = xe->xdf1.rchg, *rchg2 = xe->xdf2.rchg;\n+\tu8 *rchg1 = xe->xdf1.rchg, *rchg2 = xe->xdf2.rchg;\n \tlong i1, i2, l1, l2;\n \n \t/*\ndiff --git a/xdiff/xdiffi.h b/xdiff/xdiffi.h\nindex 126c9d8ff4..c766ee115c 100644\n--- a/xdiff/xdiffi.h\n+++ b/xdiff/xdiffi.h\n@@ -25,10 +25,10 @@\n \n \n typedef struct s_diffdata {\n-\tlong nrec;\n-\tunsigned long const *ha;\n-\tlong *rindex;\n-\tchar *rchg;\n+\tusize nrec;\n+\tu64 const *ha;\n+\tusize *rindex;\n+\tu8 *rchg;\n } diffdata_t;\n \n typedef struct s_xdalgoenv {\ndiff --git a/xdiff/xtypes.h b/xdiff/xtypes.h\nindex 849f218b32..66b3dfae8b 100644\n--- a/xdiff/xtypes.h\n+++ b/xdiff/xtypes.h\n@@ -48,11 +48,11 @@ DEFINE_IVEC_TYPE(xrecord_t, xrecord);\n \n typedef struct s_xdfile {\n \tstruct ivec_xrecord record;\n-\tlong dstart, dend;\n-\tchar *rchg;\n-\tlong *rindex;\n-\tlong nreff;\n-\tunsigned long *ha;\n+\tisize dstart, dend;\n+\tu8 *rchg;\n+\tusize *rindex;\n+\tusize nreff;\n+\tu64 *ha;\n } xdfile_t;\n \n typedef struct s_xdfenv {\n-- \ngitgitgadget\n\n"},{"id":"525237","messageId":"9fdd23acf6613c6f07c8928755ce18b87321911e.1756496539.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.git.git.1756496539.gitgitgadget@gmail.com","subject":"[PATCH 15/15] xdiff: implement xdl_trim_ends() in Rust","fromName":"Ezekiel Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-08-29T19:42:19Z","receivedAt":"2025-08-29T19:42:43Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"From: Ezekiel Newren <ezekielnewren@gmail.com>\n\nReplace the C implementation of xdl_trim_ends() with a Rust\nimplementation.\n\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n rust/xdiff/src/lib.rs      | 15 +++++++++++++++\n rust/xdiff/src/xprepare.rs | 27 +++++++++++++++++++++++++++\n rust/xdiff/src/xtypes.rs   | 19 +++++++++++++++++++\n xdiff/xprepare.c           | 28 +---------------------------\n 4 files changed, 62 insertions(+), 27 deletions(-)\n create mode 100644 rust/xdiff/src/xprepare.rs\n create mode 100644 rust/xdiff/src/xtypes.rs\n\ndiff --git a/rust/xdiff/src/lib.rs b/rust/xdiff/src/lib.rs\nindex e69de29bb2..4cc05a7e6b 100644\n--- a/rust/xdiff/src/lib.rs\n+++ b/rust/xdiff/src/lib.rs\n@@ -0,0 +1,15 @@\n+pub mod xprepare;\n+pub mod xtypes;\n+\n+use crate::xprepare::trim_ends;\n+use crate::xtypes::xdfile;\n+\n+#[no_mangle]\n+unsafe extern \"C\" fn xdl_trim_ends(xdf1: *mut xdfile, xdf2: *mut xdfile) -> i32 {\n+    let xdf1 = xdf1.as_mut().expect(\"null pointer\");\n+    let xdf2 = xdf2.as_mut().expect(\"null pointer\");\n+\n+    trim_ends(xdf1, xdf2);\n+\n+    0\n+}\ndiff --git a/rust/xdiff/src/xprepare.rs b/rust/xdiff/src/xprepare.rs\nnew file mode 100644\nindex 0000000000..f64f60c099\n--- /dev/null\n+++ b/rust/xdiff/src/xprepare.rs\n@@ -0,0 +1,27 @@\n+use crate::xtypes::xdfile;\n+\n+///\n+/// Early trim initial and terminal matching records.\n+///\n+pub(crate) fn trim_ends(xdf1: &mut xdfile, xdf2: &mut xdfile) {\n+    let mut lim = std::cmp::min(xdf1.record.len(), xdf2.record.len());\n+\n+    for i in 0..lim {\n+        if xdf1.record[i].ha != xdf2.record[i].ha {\n+            xdf1.dstart = i as isize;\n+            xdf2.dstart = i as isize;\n+            lim -= i;\n+            break;\n+        }\n+    }\n+\n+    for i in 0..lim {\n+        let f1i = xdf1.record.len() - 1 - i;\n+        let f2i = xdf2.record.len() - 1 - i;\n+        if xdf1.record[f1i].ha != xdf2.record[f2i].ha {\n+            xdf1.dend = f1i as isize;\n+            xdf2.dend = f2i as isize;\n+            break;\n+        }\n+    }\n+}\ndiff --git a/rust/xdiff/src/xtypes.rs b/rust/xdiff/src/xtypes.rs\nnew file mode 100644\nindex 0000000000..3d1ce9742f\n--- /dev/null\n+++ b/rust/xdiff/src/xtypes.rs\n@@ -0,0 +1,19 @@\n+use interop::ivec::IVec;\n+\n+#[repr(C)]\n+pub(crate) struct xrecord {\n+    pub(crate) ptr: *const u8,\n+    pub(crate) size: usize,\n+    pub(crate) ha: u64,\n+}\n+\n+#[repr(C)]\n+pub(crate) struct xdfile {\n+    pub(crate) record: IVec<xrecord>,\n+    pub(crate) dstart: isize,\n+    pub(crate) dend: isize,\n+    pub(crate) rchg: *mut u8,\n+    pub(crate) rindex: *mut usize,\n+    pub(crate) nreff: usize,\n+    pub(crate) ha: *mut u64,\n+}\ndiff --git a/xdiff/xprepare.c b/xdiff/xprepare.c\nindex 93370f1c6d..2c7480875f 100644\n--- a/xdiff/xprepare.c\n+++ b/xdiff/xprepare.c\n@@ -318,33 +318,7 @@ static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xd\n }\n \n \n-/*\n- * Early trim initial and terminal matching records.\n- */\n-static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2) {\n-\tlong i, lim;\n-\txrecord_t *recs1, *recs2;\n-\n-\trecs1 = xdf1->record.ptr;\n-\trecs2 = xdf2->record.ptr;\n-\tfor (i = 0, lim = XDL_MIN(xdf1->record.length, xdf2->record.length); i < lim;\n-\t     i++, recs1++, recs2++)\n-\t\tif (recs1->ha != recs2->ha)\n-\t\t\tbreak;\n-\n-\txdf1->dstart = xdf2->dstart = i;\n-\n-\trecs1 = xdf1->record.ptr + xdf1->record.length - 1;\n-\trecs2 = xdf2->record.ptr + xdf2->record.length - 1;\n-\tfor (lim -= i, i = 0; i < lim; i++, recs1--, recs2--)\n-\t\tif (recs1->ha != recs2->ha)\n-\t\t\tbreak;\n-\n-\txdf1->dend = xdf1->record.length - i - 1;\n-\txdf2->dend = xdf2->record.length - i - 1;\n-\n-\treturn 0;\n-}\n+extern i32 xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2);\n \n \n static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) {\n-- \ngitgitgadget\n"},{"id":"525240","messageId":"aLIG5bLOisbxfn1y@fruit.crustytoothpaste.net","threadId":"64060","inReplyTo":"6d065f550fe871cf010409f7bd2a63438cf52723.1756496539.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 01/15] doc: add a policy for using Rust","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2025-08-29T20:00:37Z","receivedAt":"2025-08-29T20:00:40Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2025-08-29 at 19:42:05, brian m. carlson via GitGitGadget wrote:\n> +Crates like libc or rustix define types like c_long, but in ways that are not\n> +safe across platforms.\n> +From https://docs.rs/rustix/latest/rustix/ffi/type.c_long.html:\n> +\n> +    This type will always be i32 or i64.  Most notably, many Linux-based\n> +    systems assume an i64, but Windows assumes i32.  The C standard technically\n> +    only requires that this type be a signed integer that is at least 32 bits\n> +    and at least the size of an int, although in practice, no system would\n> +    have a long that is neither an i32 nor i64.\n> +\n> +Also, note that other locations, such as\n> +https://docs.rs/libc/latest/libc/type.c_long.html, just hardcode c_long as i64\n> +even though C may mean i32 on some platforms.\n> +\n> +As such, using the c_long type would give us portability issues, and\n> +perpetuate some of the bugs git has faced across platforms.  Avoid using C's\n> +types (long, unsigned, char, etc.), and switch to unambiguous types (e.g. i32\n> +or i64) before trying to make C and Rust interoperate.\n\nThis makes sense.  I agree fixed-size types are better and less brittle.\n\n> +Crates like libc and rustix may have also traditionally aided interoperability\n> +with older versions of Rust (e.g.  when worrying about stat[64] system calls),\n> +but the Rust standard library in newer versions of Rust handle these concerns\n> +in a platform agnostic way.  There may arise cases where we need to consider\n> +these crates, but for now we omit them.\n\nI'm fine with omitting them for now.  However, we may very well need\nthem in the future.\n\n> +Tools like bindgen and cbindgen create C-styled unsafe Rust code rather than\n> +idiomatic Rust; where possible, we prefer to switch to idiomatic Rust.  Any\n> +standard C library functions that are needed can be manually wrapped on the\n> +Rust side.\n\nI agree that we want to use idiomatic Rust whenever possible.  However,\nI don't want to define structures and function definitions in both\nlanguages and rely on people keeping them in sync, since that's a great\nway to create brittle, broken code.  Very notably, I have seen these\nkinds of misalignments break only on big-endian architectures, which\nmost of us do not use, so we can really end up causing problems that are\nvery subtle this way.\n\nI would prefer we wrote these functions with cbindgen to avoid this.  If\nwe define structures only in Rust and never ever use C structures, then\nwe can avoid bindgen.\n-- \nbrian m. carlson (they/them)\nToronto, Ontario, CA\n"},{"id":"525242","messageId":"CAH=ZcbBJV0oT1Ht6qVKmq7xG8ki9Enutq+VNes4n9+2r+0nrvA@mail.gmail.com","threadId":"64060","inReplyTo":"aLIG5bLOisbxfn1y@fruit.crustytoothpaste.net","subject":"Re: [PATCH 01/15] doc: add a policy for using Rust","fromName":"Ezekiel Newren","fromEmail":"ezekielnewren@gmail.com","sentAt":"2025-08-29T20:11:46Z","receivedAt":"2025-08-29T20:12:00Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"On Fri, Aug 29, 2025 at 2:00 PM brian m. carlson\n<sandals@crustytoothpaste.net> wrote:\n> > +Tools like bindgen and cbindgen create C-styled unsafe Rust code rather than\n> > +idiomatic Rust; where possible, we prefer to switch to idiomatic Rust.  Any\n> > +standard C library functions that are needed can be manually wrapped on the\n> > +Rust side.\n>\n> I agree that we want to use idiomatic Rust whenever possible.  However,\n> I don't want to define structures and function definitions in both\n> languages and rely on people keeping them in sync, since that's a great\n> way to create brittle, broken code.  Very notably, I have seen these\n> kinds of misalignments break only on big-endian architectures, which\n> most of us do not use, so we can really end up causing problems that are\n> very subtle this way.\n>\n> I would prefer we wrote these functions with cbindgen to avoid this.  If\n> we define structures only in Rust and never ever use C structures, then\n> we can avoid bindgen.\n\nCould you create a patch with your preferred wording about bindgen and\ncbindgen for me? I'd prefer to introduce bindgen/cbindgen in a\ndifferent patch series because this one is already doing a lot just to\nmake Rust exist in Git.\n"},{"id":"525352","messageId":"aLcdwiRpD1Yo7SKy@fruit.crustytoothpaste.net","threadId":"64060","inReplyTo":"CAH=ZcbBJV0oT1Ht6qVKmq7xG8ki9Enutq+VNes4n9+2r+0nrvA@mail.gmail.com","subject":"Re: [PATCH 01/15] doc: add a policy for using Rust","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2025-09-02T16:39:30Z","receivedAt":"2025-09-02T16:39:38Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2025-08-29 at 20:11:46, Ezekiel Newren wrote:\n> Could you create a patch with your preferred wording about bindgen and\n> cbindgen for me? I'd prefer to introduce bindgen/cbindgen in a\n> different patch series because this one is already doing a lot just to\n> make Rust exist in Git.\n\nI think it's fine to introduce it in a different series.  I'll plan to\ndo that myself if it doesn't get done sooner.\n\nMy change to the text is the following:\n\nTools like bindgen and cbindgen create C-styled unsafe Rust code rather than\nidiomatic Rust; where possible, we prefer to switch to idiomatic Rust.\nHowever, we may use bindgen and cbindgen to share existing Git types as an\ninterim step.\n\nYou can find the patch at https://github.com/bk2204/git.git in the\n`rust-policy` branch.\n-- \nbrian m. carlson (they/them)\nToronto, Ontario, CA\n"},{"id":"525366","messageId":"CAH=ZcbCgYEVrhejh66H4xqpuXyZj9jh+JqfH3mYik206-fVTSg@mail.gmail.com","threadId":"64060","inReplyTo":"aLcdwiRpD1Yo7SKy@fruit.crustytoothpaste.net","subject":"Re: [PATCH 01/15] doc: add a policy for using Rust","fromName":"Ezekiel Newren","fromEmail":"ezekielnewren@gmail.com","sentAt":"2025-09-02T18:39:39Z","receivedAt":"2025-09-02T18:39:52Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"On Tue, Sep 2, 2025 at 10:39 AM brian m. carlson\n<sandals@crustytoothpaste.net> wrote:\n> I think it's fine to introduce it in a different series.  I'll plan to\n> do that myself if it doesn't get done sooner.\n\nActually now that I'm knee deep in adding cbindgen to Rust it'll make\nless sense to add it later. I'm currently working on refactoring my\nentire patch series to include cbindgen from the beginning. I haven't\nlooked into cbindgen until now because I wanted to understand at a\ndeep level how C <-> Rust ffi worked rather than using an automagical\ntool like cbindgen. I now think cbindgen should be part of the\nintroduction of Rust.\n"},{"id":"525571","messageId":"CAH=ZcbBzqHfikO79JyFRvWVvh+vFQtXmhXViJWuW1yq3Xa04bQ@mail.gmail.com","threadId":"64060","inReplyTo":"CAH=ZcbCgYEVrhejh66H4xqpuXyZj9jh+JqfH3mYik206-fVTSg@mail.gmail.com","subject":"Re: [PATCH 01/15] doc: add a policy for using Rust","fromName":"Ezekiel Newren","fromEmail":"ezekielnewren@gmail.com","sentAt":"2025-09-04T22:55:58Z","receivedAt":"2025-09-04T22:56:11Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"On Tue, Sep 2, 2025 at 12:39 PM Ezekiel Newren <ezekielnewren@gmail.com> wrote:\n> Actually now that I'm knee deep in adding cbindgen to Rust it'll make\n> less sense to add it later. I'm currently working on refactoring my\n> entire patch series to include cbindgen from the beginning. I haven't\n> looked into cbindgen until now because I wanted to understand at a\n> deep level how C <-> Rust ffi worked rather than using an automagical\n> tool like cbindgen. I now think cbindgen should be part of the\n> introduction of Rust.\n\nI've been able to get cbindgen to work with 1.63.0, but I'm not ready\nto release my next patch series yet.\n"},{"id":"526519","messageId":"pull.2043.v2.git.git.1758071798.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.git.git.1756496539.gitgitgadget@gmail.com","subject":"[PATCH v2 00/18] Introduce rust: In xdiff","fromName":"Ezekiel Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-09-17T01:16:20Z","receivedAt":"2025-09-17T01:16:41Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"This is a continuation of\nhttps://lore.kernel.org/git/pull.1980.v3.git.git.1755921356.gitgitgadget@gmail.com/,\nbut I am removing the RFC label.\n\nSuggestions on changes that I could make to this series is appreciated.\n\nChanges in v2: High level overview:\n\n * patch 1: cleanup: rename variables that collide with Rust primitive type\n   names\n * patches 2-4: Makefile now produces libgit.a correctly\n * patches 5,6: Documentation from Brian and Patrick\n * patches 7,8: Introduce Rust\n * patches 9-11: github workflows\n * patches 12-14: introduce crates build-helper, and cbindgen\n * patches 15-18: varint test balloon\n\nI would like feed back in two categories: big changes, and little changes.\nIs my patch series even going in the right direction, and what are the\nlittle details that I've missed? I know Brian asked for cbindgen and it made\nthe series several commits longer, but I think it's a great idea to have. I\nam not happy with every choice that I made, but I currently don't see a\nbetter way or at least an easier alternative to my approach in bringing Rust\nto Git.\n\nBuild results for these changes:\nhttps://github.com/git/git/actions/runs/17783386212?pr=2043. Some of these\nare failing.\n\nChanges in v1: Changes since the last RFC patch series (range-diff added\nbelow):\n\n * Addressed feedback:\n   * Use self instead of this\n   * Update ivec to use Git code style\n   * Remove environment variable CI_IS_DOCKER from github workflows\n   * Remove PHONY target interop-objs\n   * Removed left over asserts in Rust and fix near by code\n * Other changes:\n   * Avoid name mangling of ivec if c++ compiler used\n   * Fixed a typo in patch 11 commit message\n\nHigh level overview:\n\n * patch 1: add a policy for using Rust (brian's patch, with a small tweak)\n * patch 2: introduce Rust to the codebase\n * patches 3-5: adapt CI (github workflows) to build Git with Rust\n * patch 6: introduce the ivec type\n * patches 7-14: xdiff code cleanup in preparation for translating to Rust\n * patch 15: translate a C function into Rust and call it from C\n\nEzekiel Newren (12):\n  cleanup: rename variables that collide with Rust primitive type names\n  make: add -fPIE flag\n  make: merge xdiff lib into libgit.a\n  make: merge reftable lib into libgit.a\n  build: introduce rust\n  github workflows: install rust\n  github workflows: upload Cargo.lock\n  build: new crate, build-helper\n  build-helper: link against libgit.a and any other required C libraries\n  build-helper: cbindgen, let crates generate a header file\n  build: new crate, misc\n  misc: use BuildHelper\n\nJohannes Schindelin (1):\n  win+Meson: do allow linking with the Rust-built xdiff\n\nPatrick Steinhardt (4):\n  BreakingChanges: announce Rust becoming mandatory\n  help: report on whether or not Rust is enabled\n  varint: use explicit width for integers\n  misc::varint: reimplement as test balloon for Rust\n\nbrian m. carlson (1):\n  doc: add a policy for using Rust\n\n .github/workflows/main.yml                    |  82 +++++++++-\n .gitignore                                    |   4 +\n Documentation/BreakingChanges.adoc            |  35 +++++\n Documentation/Makefile                        |   1 +\n Documentation/technical/platform-support.adoc |   2 +\n Documentation/technical/rust-support.adoc     | 142 +++++++++++++++++\n Makefile                                      | 148 +++++++++++-------\n build_rust.sh                                 |  60 +++++++\n ci/install-dependencies.sh                    |  14 +-\n ci/install-rust-toolchain.sh                  |  30 ++++\n ci/install-rustup.sh                          |  25 +++\n ci/lib.sh                                     |   1 +\n ci/make-test-artifacts.sh                     |   9 ++\n ci/run-build-and-tests.sh                     |  13 ++\n config.mak.uname                              |   4 +\n dir.c                                         |  18 ++-\n help.c                                        |   6 +\n meson.build                                   |  79 ++++++++--\n meson_options.txt                             |   2 +\n odb.c                                         |   8 +-\n read-cache.c                                  |   6 +-\n reftable/record.c                             |   6 +-\n rust/Cargo.toml                               |   6 +\n rust/build-helper/Cargo.toml                  |  11 ++\n rust/build-helper/src/lib.rs                  | 126 +++++++++++++++\n rust/cbindgen-template.toml                   |  16 ++\n rust/misc/Cargo.toml                          |  17 ++\n rust/misc/build.rs                            |   6 +\n rust/misc/src/lib.rs                          |   1 +\n rust/misc/src/varint.rs                       |  92 +++++++++++\n t/helper/test-parse-options.c                 |  12 +-\n varint.c                                      |   6 +-\n varint.h                                      |   4 +-\n xdiff/xtypes.h                                |   2 +-\n xdiff/xutils.c                                |   8 +-\n xdiff/xutils.h                                |   2 +-\n 36 files changed, 899 insertions(+), 105 deletions(-)\n create mode 100644 Documentation/technical/rust-support.adoc\n create mode 100755 build_rust.sh\n create mode 100755 ci/install-rust-toolchain.sh\n create mode 100755 ci/install-rustup.sh\n create mode 100644 rust/Cargo.toml\n create mode 100644 rust/build-helper/Cargo.toml\n create mode 100644 rust/build-helper/src/lib.rs\n create mode 100644 rust/cbindgen-template.toml\n create mode 100644 rust/misc/Cargo.toml\n create mode 100644 rust/misc/build.rs\n create mode 100644 rust/misc/src/lib.rs\n create mode 100644 rust/misc/src/varint.rs\n\n\nbase-commit: c44beea485f0f2feaf460e2ac87fdd5608d63cf0\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2043%2Fezekielnewren%2Fintroduce_rust-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2043/ezekielnewren/introduce_rust-v2\nPull-Request: https://github.com/git/git/pull/2043\n\nRange-diff vs v1:\n\n  -:  ---------- >  1:  5f77f1bd5d cleanup: rename variables that collide with Rust primitive type names\n  -:  ---------- >  2:  7082e32c59 make: add -fPIE flag\n  -:  ---------- >  3:  7fe85f0eac make: merge xdiff lib into libgit.a\n 10:  0cd2168512 !  4:  3061cc46c1 xdiff: use one definition for freeing xdfile_t\n     @@ Metadata\n      Author: Ezekiel Newren <ezekielnewren@gmail.com>\n      \n       ## Commit message ##\n     -    xdiff: use one definition for freeing xdfile_t\n     -\n     -    Simplify xdl_prepare_ctx() by using xdl_free_ctx() instead of using\n     -    local variables with hand rolled memory management.\n     +    make: merge reftable lib into libgit.a\n      \n          Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n      \n     - ## xdiff/xprepare.c ##\n     -@@ xdiff/xprepare.c: static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t\n     - }\n     - \n     + ## Makefile ##\n     +@@ Makefile: export PYTHON_PATH\n     + TEST_SHELL_PATH = $(SHELL_PATH)\n       \n     -+static void xdl_free_ctx(xdfile_t *xdf) {\n     -+\txdl_free(xdf->rindex);\n     -+\txdl_free(xdf->rchg - 1);\n     -+\txdl_free(xdf->ha);\n     -+\txdl_free(xdf->recs);\n     -+\txdl_cha_free(&xdf->rcha);\n     -+}\n     -+\n     -+\n     - static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,\n     - \t\t\t   xdlclassifier_t *cf, xdfile_t *xdf) {\n     --\tlong nrec, bsize;\n     -+\tlong bsize;\n     - \tunsigned long hav;\n     - \tchar const *blk, *cur, *top, *prev;\n     - \txrecord_t *crec;\n     --\txrecord_t **recs;\n     --\tunsigned long *ha;\n     --\tchar *rchg;\n     --\tlong *rindex;\n     + LIB_FILE = libgit.a\n     +-REFTABLE_LIB = reftable/libreftable.a\n       \n     --\tha = NULL;\n     --\trindex = NULL;\n     --\trchg = NULL;\n     --\trecs = NULL;\n     -+\txdf->ha = NULL;\n     -+\txdf->rindex = NULL;\n     -+\txdf->rchg = NULL;\n     -+\txdf->recs = NULL;\n     -+\txdf->nrec = 0;\n     + GENERATED_H += command-list.h\n     + GENERATED_H += config-list.h\n     +@@ Makefile: LIB_OBJS += rebase.o\n     + LIB_OBJS += ref-filter.o\n     + LIB_OBJS += reflog-walk.o\n     + LIB_OBJS += reflog.o\n     ++LIB_OBJS += reftable/basics.o\n     ++LIB_OBJS += reftable/error.o\n     ++LIB_OBJS += reftable/block.o\n     ++LIB_OBJS += reftable/blocksource.o\n     ++LIB_OBJS += reftable/iter.o\n     ++LIB_OBJS += reftable/merged.o\n     ++LIB_OBJS += reftable/pq.o\n     ++LIB_OBJS += reftable/record.o\n     ++LIB_OBJS += reftable/stack.o\n     ++LIB_OBJS += reftable/system.o\n     ++LIB_OBJS += reftable/table.o\n     ++LIB_OBJS += reftable/tree.o\n     ++LIB_OBJS += reftable/writer.o\n     + LIB_OBJS += refs.o\n     + LIB_OBJS += refs/debug.o\n     + LIB_OBJS += refs/files-backend.o\n     +@@ Makefile: CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o\n       \n     - \tif (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0)\n     - \t\tgoto abort;\n     --\tif (!XDL_ALLOC_ARRAY(recs, narec))\n     -+\tif (!XDL_ALLOC_ARRAY(xdf->recs, narec))\n     - \t\tgoto abort;\n     + UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o\n       \n     --\tnrec = 0;\n     - \tif ((cur = blk = xdl_mmfile_first(mf, &bsize))) {\n     - \t\tfor (top = blk + bsize; cur < top; ) {\n     - \t\t\tprev = cur;\n     - \t\t\thav = xdl_hash_record(&cur, top, xpp->flags);\n     --\t\t\tif (XDL_ALLOC_GROW(recs, nrec + 1, narec))\n     -+\t\t\tif (XDL_ALLOC_GROW(xdf->recs, xdf->nrec + 1, narec))\n     - \t\t\t\tgoto abort;\n     - \t\t\tif (!(crec = xdl_cha_alloc(&xdf->rcha)))\n     - \t\t\t\tgoto abort;\n     - \t\t\tcrec->ptr = (u8 const*) prev;\n     - \t\t\tcrec->size = (long) (cur - prev);\n     - \t\t\tcrec->ha = hav;\n     --\t\t\trecs[nrec++] = crec;\n     -+\t\t\txdf->recs[xdf->nrec++] = crec;\n     - \t\t\tif (xdl_classify_record(pass, cf, crec) < 0)\n     - \t\t\t\tgoto abort;\n     - \t\t}\n     - \t}\n     +-# reftable lib may depend on what is in libgit.a\n     +-GITLIBS = common-main.o $(LIB_FILE) $(REFTABLE_LIB) $(LIB_FILE)\n     ++GITLIBS = common-main.o $(LIB_FILE)\n     + EXTLIBS =\n       \n     --\tif (!XDL_CALLOC_ARRAY(rchg, nrec + 2))\n     -+\tif (!XDL_CALLOC_ARRAY(xdf->rchg, xdf->nrec + 2))\n     - \t\tgoto abort;\n     + GIT_USER_AGENT = git/$(GIT_VERSION)\n     +@@ Makefile: reconfigure config.mak.autogen: config.status\n     + .PHONY: reconfigure # This is a convenience target.\n     + endif\n       \n     - \tif ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) &&\n     - \t    (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF)) {\n     --\t\tif (!XDL_ALLOC_ARRAY(rindex, nrec + 1))\n     -+\t\tif (!XDL_ALLOC_ARRAY(xdf->rindex, xdf->nrec + 1))\n     - \t\t\tgoto abort;\n     --\t\tif (!XDL_ALLOC_ARRAY(ha, nrec + 1))\n     -+\t\tif (!XDL_ALLOC_ARRAY(xdf->ha, xdf->nrec + 1))\n     - \t\t\tgoto abort;\n     - \t}\n     +-REFTABLE_OBJS += reftable/basics.o\n     +-REFTABLE_OBJS += reftable/error.o\n     +-REFTABLE_OBJS += reftable/block.o\n     +-REFTABLE_OBJS += reftable/blocksource.o\n     +-REFTABLE_OBJS += reftable/iter.o\n     +-REFTABLE_OBJS += reftable/merged.o\n     +-REFTABLE_OBJS += reftable/pq.o\n     +-REFTABLE_OBJS += reftable/record.o\n     +-REFTABLE_OBJS += reftable/stack.o\n     +-REFTABLE_OBJS += reftable/system.o\n     +-REFTABLE_OBJS += reftable/table.o\n     +-REFTABLE_OBJS += reftable/tree.o\n     +-REFTABLE_OBJS += reftable/writer.o\n     +-\n     + TEST_OBJS := $(patsubst %$X,%.o,$(TEST_PROGRAMS)) $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))\n       \n     --\txdf->nrec = nrec;\n     --\txdf->recs = recs;\n     --\txdf->rchg = rchg + 1;\n     --\txdf->rindex = rindex;\n     -+\txdf->rchg += 1;\n     - \txdf->nreff = 0;\n     --\txdf->ha = ha;\n     - \txdf->dstart = 0;\n     --\txdf->dend = nrec - 1;\n     -+\txdf->dend = xdf->nrec - 1;\n     + .PHONY: test-objs\n     +@@ Makefile: OBJECTS += $(SCALAR_OBJS)\n     + OBJECTS += $(PROGRAM_OBJS)\n     + OBJECTS += $(TEST_OBJS)\n     + OBJECTS += $(FUZZ_OBJS)\n     +-OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS)\n     ++OBJECTS += $(REFTABLE_TEST_OBJS)\n     + OBJECTS += $(UNIT_TEST_OBJS)\n     + OBJECTS += $(CLAR_TEST_OBJS)\n     + OBJECTS += $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))\n     +@@ Makefile: scalar$X: scalar.o GIT-LDFLAGS $(GITLIBS)\n     + $(LIB_FILE): $(LIB_OBJS)\n     + \t$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^\n       \n     - \treturn 0;\n     +-$(REFTABLE_LIB): $(REFTABLE_OBJS)\n     +-\t$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^\n     +-\n     + export DEFAULT_EDITOR DEFAULT_PAGER\n       \n     - abort:\n     --\txdl_free(ha);\n     --\txdl_free(rindex);\n     --\txdl_free(rchg);\n     --\txdl_free(recs);\n     --\txdl_cha_free(&xdf->rcha);\n     -+\txdl_free_ctx(xdf);\n     - \treturn -1;\n     - }\n     + Documentation/GIT-EXCLUDED-PROGRAMS: FORCE\n     +@@ Makefile: clean: profile-clean coverage-clean cocciclean\n     + \t$(RM) git.rc git.res\n     + \t$(RM) $(OBJECTS)\n     + \t$(RM) headless-git.o\n     +-\t$(RM) $(LIB_FILE) $(REFTABLE_LIB)\n     ++\t$(RM) $(LIB_FILE)\n     + \t$(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS)\n     + \t$(RM) $(TEST_PROGRAMS)\n     + \t$(RM) $(FUZZ_PROGRAMS)\n     +@@ Makefile: endif\n       \n     + LIBGIT_PUB_OBJS += contrib/libgit-sys/public_symbol_export.o\n     + LIBGIT_PUB_OBJS += libgit.a\n     +-LIBGIT_PUB_OBJS += reftable/libreftable.a\n       \n     --static void xdl_free_ctx(xdfile_t *xdf) {\n     --\txdl_free(xdf->rindex);\n     --\txdl_free(xdf->rchg - 1);\n     --\txdl_free(xdf->ha);\n     --\txdl_free(xdf->recs);\n     --\txdl_cha_free(&xdf->rcha);\n     --}\n     --\n     --\n     - void xdl_free_env(xdfenv_t *xe) {\n     + LIBGIT_PARTIAL_EXPORT = contrib/libgit-sys/partial_symbol_export.o\n       \n     - \txdl_free_ctx(&xe->xdf2);\n  1:  6d065f550f !  5:  0fc89c3ec3 doc: add a policy for using Rust\n     @@ Documentation/technical/rust-support.adoc (new)\n      +these crates, but for now we omit them.\n      +\n      +Tools like bindgen and cbindgen create C-styled unsafe Rust code rather than\n     -+idiomatic Rust; where possible, we prefer to switch to idiomatic Rust.  Any\n     -+standard C library functions that are needed can be manually wrapped on the\n     -+Rust side.\n     ++idiomatic Rust; where possible, we prefer to switch to idiomatic Rust.\n     ++However, we may use bindgen and cbindgen to share existing Git types as an\n     ++interim step.\n      +\n      +Rust upstream releases every six weeks and only supports the latest stable\n      +release.  While it is nice that upstream is active, we would like our software\n  -:  ---------- >  6:  8e030170dd BreakingChanges: announce Rust becoming mandatory\n  2:  cc9cf87775 !  7:  6032a8740c xdiff: introduce rust\n     @@ Metadata\n      Author: Ezekiel Newren <ezekielnewren@gmail.com>\n      \n       ## Commit message ##\n     -    xdiff: introduce rust\n     +    build: introduce rust\n      \n          Upcoming patches will simplify xdiff, while also porting parts of it to\n          Rust. In preparation, add some stubs and setup the Rust build. For now,\n          it is easier to let cargo build rust and have make or meson merely link\n          against the static library that cargo builds. In line with ongoing\n          libification efforts, use multiple crates to allow more modularity on\n     -    the Rust side. xdiff is the crate that this series will focus on, but\n     -    we also introduce the interop crate for future patch series.\n     +    the Rust side. The xdiff and ivec crates are the focal point for this\n     +    series.\n      \n     -    In order to facilitate interoperability between C and Rust, introduce C\n     -    definitions for Rust primitive types in git-compat-util.h.\n     +    cbindgen will be used to generate header files for each Rust crate for\n     +    C interoperability.\n      \n          Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n      \n     @@ .gitignore: Release/\n       /contrib/buildsystems/out\n       /contrib/libgit-rs/target\n       /contrib/libgit-sys/target\n     -+/.idea/\n     ++.idea/\n      +/rust/target/\n      +/rust/Cargo.lock\n      \n       ## Makefile ##\n     +@@ Makefile: include shared.mak\n     + # Define LIBPCREDIR=/foo/bar if your PCRE header and library files are\n     + # in /foo/bar/include and /foo/bar/lib directories.\n     + #\n     ++# == Optional Rust support ==\n     ++#\n     ++# Define WITH_RUST=false if you don't want to include features and subsystems\n     ++# written in Rust into Git. For now, Rust is still an optional feature of the\n     ++# build process. With Git 3.0 though, Rust will be mandatory.\n     ++#\n     ++# Building Rust code requires Cargo.\n     ++#\n     + # == SHA-1 and SHA-256 defines ==\n     + #\n     + # === SHA-1 backend ===\n      @@ Makefile: TEST_SHELL_PATH = $(SHELL_PATH)\n       \n       LIB_FILE = libgit.a\n     - XDIFF_LIB = xdiff/lib.a\n     -+\n     + \n      +EXTLIBS =\n      +\n     ++GIT_BUILD_DIR := $(CURDIR)\n     ++export GIT_BUILD_DIR\n     ++\n     ++RUST_CRATES :=\n     ++.PHONY: compile_rust rustclean\n     ++\n     ++WITH_RUST ?= true\n     ++ifeq ($(WITH_RUST),true)\n     ++\n      +ifeq ($(DEBUG), 1)\n     -+  RUST_BUILD_MODE = debug\n     ++\tRUST_BUILD_MODE := debug\n      +else\n     -+  RUST_BUILD_MODE = release\n     ++\tRUST_BUILD_MODE := release\n      +endif\n      +\n     -+RUST_TARGET_DIR = rust/target/$(RUST_BUILD_MODE)\n     -+RUST_FLAGS_FOR_C = -L$(RUST_TARGET_DIR)\n     ++RUST_TARGET_DIR := $(GIT_BUILD_DIR)/$(RUST_BUILD_MODE)\n      +\n     -+.PHONY: compile_rust\n     -+compile_rust:\n     -+\t./build_rust.sh . $(RUST_BUILD_MODE) xdiff\n     ++RUST_LIBS := $(foreach c,$(RUST_CRATES),$(GIT_BUILD_DIR)/lib$(c).a)\n     ++\n     ++EXTLIBS += $(RUST_LIBS)\n      +\n     -+EXTLIBS += ./$(RUST_TARGET_DIR)/libxdiff.a\n     ++compile_rust: $(RUST_LIBS)\n     ++rustclean:\n     ++\t$(RM) $(RUST_LIBS)\n     ++\tcd rust && cargo clean\n     ++\n     ++$(GIT_BUILD_DIR)/lib%.a:\n     ++\t./build_rust.sh $(GIT_BUILD_DIR) $(RUST_BUILD_MODE) $*\n      +\n      +UNAME_S := $(shell uname -s)\n      +ifeq ($(UNAME_S),Linux)\n     -+  EXTLIBS += -ldl\n     ++\tEXTLIBS += -ldl\n     ++endif\n     ++\n     ++else ifeq ($(WITH_RUST),false)\n     ++compile_rust:\n     ++\t:\n     ++rustclean:\n     ++\t:\n     ++else\n     ++$(error 'WITH_RUST' must be true or false)\n      +endif\n      +\n     - REFTABLE_LIB = reftable/libreftable.a\n     - \n       GENERATED_H += command-list.h\n     -@@ Makefile: UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o\n     + GENERATED_H += config-list.h\n     + GENERATED_H += hook-list.h\n     +@@ Makefile: CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o\n     + UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o\n       \n     - # xdiff and reftable libs may in turn depend on what is in libgit.a\n     - GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(LIB_FILE)\n     + GITLIBS = common-main.o $(LIB_FILE)\n      -EXTLIBS =\n     -+\n       \n       GIT_USER_AGENT = git/$(GIT_VERSION)\n       \n     +@@ Makefile: CC_LD_DYNPATH = -Wl,-rpath,\n     + BASIC_CFLAGS = -I. -fPIE\n     + BASIC_LDFLAGS =\n     + \n     ++ifeq ($(WITH_RUST),true)\n     ++BASIC_CFLAGS += -DWITH_RUST\n     ++endif\n     + # library flags\n     + ARFLAGS = rcs\n     + PTHREAD_CFLAGS =\n      @@ Makefile: git.sp git.s git.o: EXTRA_CPPFLAGS = \\\n       \t'-DGIT_MAN_PATH=\"$(mandir_relative_SQ)\"' \\\n       \t'-DGIT_INFO_PATH=\"$(infodir_relative_SQ)\"'\n     @@ Makefile: $(REMOTE_CURL_ALIASES): $(REMOTE_CURL_PRIMARY)\n       \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \\\n       \t\t$(filter %.o,$^) $(LIBS)\n       \n     -@@ Makefile: $(LIB_FILE): $(LIB_OBJS)\n     - $(XDIFF_LIB): $(XDIFF_OBJS)\n     - \t$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^\n     - \n     -+\n     - $(REFTABLE_LIB): $(REFTABLE_OBJS)\n     - \t$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^\n     - \n      @@ Makefile: perf: all\n       \n       t/helper/test-tool$X: $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS)) $(UNIT_TEST_DIR)/test-lib.o\n     @@ Makefile: cocciclean:\n       \t$(RM) contrib/coccinelle/*.cocci.patch\n       \n      -clean: profile-clean coverage-clean cocciclean\n     -+rustclean:\n     -+\tcd rust && cargo clean\n     -+\n      +clean: profile-clean coverage-clean cocciclean rustclean\n       \t$(RM) -r .build $(UNIT_TEST_BIN)\n       \t$(RM) GIT-TEST-SUITES\n     @@ build_rust.sh (new)\n      @@\n      +#!/bin/sh\n      +\n     -+\n      +rustc -vV || exit $?\n      +cargo --version || exit $?\n      +\n     @@ build_rust.sh (new)\n      +  exit 1\n      +fi\n      +\n     -+cd $dir_rust && cargo clean && pwd && cargo build -p $crate $rust_args; cd $dir_git_root\n     -+\n      +libfile=\"lib${crate}.a\"\n      +if rustup show active-toolchain | grep windows-msvc; then\n      +  libfile=\"${crate}.lib\"\n     ++  PATH=\"$(echo $PATH | tr ':' '\\n' | grep -Ev \"^(/mingw64/bin|/usr/bin)$\" | paste -sd: -):/mingw64/bin:/usr/bin\"\n      +fi\n     -+dst=$dir_build/$libfile\n      +\n     -+if [ \"$dir_git_root\" != \"$dir_build\" ]; then\n     -+  src=$dir_rust/target/$rust_build_profile/$libfile\n     -+  if [ ! -f $src ]; then\n     -+    echo >&2 \"::error:: cannot find path of static library $src is not a file or does not exist\"\n     -+    exit 5\n     -+  fi\n     ++CARGO_TARGET_DIR=$dir_git_root/.build/rust/$crate\n     ++export CARGO_TARGET_DIR\n     ++\n     ++cd $dir_rust && cargo clean && pwd && USE_LINKING=\"false\" cargo build -p $crate $rust_args; cd $dir_git_root\n     ++\n     ++src=$CARGO_TARGET_DIR/$rust_build_profile/$libfile\n     ++dst=$dir_build/$libfile\n      +\n     -+  rm $dst 2>/dev/null\n     -+  mv $src $dst\n     ++if [ ! -f $src ]; then\n     ++  echo >&2 \"::error:: cannot find path of static library $src is not a file or does not exist\"\n     ++  exit 5\n      +fi\n     -\n     - ## git-compat-util.h ##\n     -@@ git-compat-util.h: static inline int is_xplatform_dir_sep(int c)\n     - #include \"compat/msvc.h\"\n     - #endif\n     - \n     -+/* rust types */\n     -+typedef uint8_t   u8;\n     -+typedef uint16_t  u16;\n     -+typedef uint32_t  u32;\n     -+typedef uint64_t  u64;\n     -+\n     -+typedef int8_t    i8;\n     -+typedef int16_t   i16;\n     -+typedef int32_t   i32;\n     -+typedef int64_t   i64;\n     -+\n     -+typedef float     f32;\n     -+typedef double    f64;\n     -+\n     -+typedef size_t    usize;\n     -+typedef ptrdiff_t isize;\n     -+\n     - /* used on Mac OS X */\n     - #ifdef PRECOMPOSE_UNICODE\n     - #include \"compat/precompose_utf8.h\"\n     ++\n     ++rm $dst 2>/dev/null\n     ++echo mv $src $dst\n     ++mv $src $dst\n      \n       ## meson.build ##\n      @@ meson.build: version_gen_environment.set('GIT_DATE', get_option('build_date'))\n       version_gen_environment.set('GIT_USER_AGENT', get_option('user_agent'))\n       version_gen_environment.set('GIT_VERSION', get_option('version'))\n       \n     -+if get_option('optimization') in ['2', '3', 's', 'z']\n     -+  rust_build_profile = 'release'\n     -+else\n     -+  rust_build_profile = 'debug'\n     -+endif\n     ++rust_crates = []\n      +\n     -+# Run `rustup show active-toolchain` and capture output\n     -+rustup_out = run_command('rustup', 'show', 'active-toolchain',\n     -+                         check: true).stdout().strip()\n     -+\n     -+rust_crates = ['xdiff']\n      +rust_builds = []\n     ++with_rust = get_option('with_rust')\n     ++if with_rust\n     ++  rustc = find_program('rustc', required : false)\n     ++  cargo = find_program('cargo', required : false)\n      +\n     -+foreach crate : rust_crates\n     -+  if rustup_out.contains('windows-msvc')\n     -+    libfile = crate + '.lib'\n     ++  if not rustc.found() or not cargo.found()\n     ++    error('Rust toolchain not found. Reconfigure with -Dwith_rust=false')\n     ++  endif\n     ++\n     ++  if get_option('optimization') in ['2', '3', 's', 'z']\n     ++    rust_build_profile = 'release'\n      +  else\n     -+    libfile = 'lib' + crate + '.a'\n     ++    rust_build_profile = 'debug'\n      +  endif\n      +\n     -+  rust_builds += custom_target(\n     -+    'rust_build_'+crate,\n     -+    output: libfile,\n     -+    build_by_default: true,\n     -+    build_always_stale: true,\n     -+    command: [\n     -+      meson.project_source_root() / 'build_rust.sh',\n     -+      meson.current_build_dir(), rust_build_profile, crate,\n     -+    ],\n     -+    install: false,\n     -+  )\n     -+endforeach\n     ++  # Run `rustup show active-toolchain` and capture output\n     ++  rustup_out = run_command('rustup', 'show', 'active-toolchain',\n     ++                           check: true).stdout().strip()\n     ++\n     ++  rust_environment = script_environment\n     ++\n     ++  foreach crate : rust_crates\n     ++    if rustup_out.contains('windows-msvc')\n     ++      libfile = crate + '.lib'\n     ++    else\n     ++      libfile = 'lib' + crate + '.a'\n     ++    endif\n     ++\n     ++    rust_builds += custom_target(\n     ++      'rust_build_'+crate,\n     ++      output: libfile,\n     ++      build_by_default: true,\n     ++      build_always_stale: true,\n     ++      command: [\n     ++        meson.project_source_root() / 'build_rust.sh',\n     ++        meson.current_build_dir(), rust_build_profile, crate,\n     ++      ],\n     ++      install: false,\n     ++      env: rust_environment,\n     ++    )\n     ++  endforeach\n     ++else\n     ++  message('Rust components disabled (-Dwith_rust=false)')\n     ++endif\n      +\n      +\n       compiler = meson.get_compiler('c')\n       \n       libgit_sources = [\n     +@@ meson.build: libgit_c_args = [\n     +   '-DSHELL_PATH=\"' + fs.as_posix(target_shell.full_path()) + '\"',\n     + ]\n     + \n     ++if with_rust\n     ++  libgit_c_args += '-DWITH_RUST'\n     ++endif\n     ++\n     + system_attributes = get_option('gitattributes')\n     + if system_attributes != ''\n     +   libgit_c_args += '-DETC_GITATTRIBUTES=\"' + system_attributes + '\"'\n     +@@ meson.build: elif host_machine.system() == 'windows'\n     +   ]\n     + \n     +   libgit_dependencies += compiler.find_library('ntdll')\n     ++  libgit_dependencies += compiler.find_library('userenv')\n     +   libgit_include_directories += 'compat/win32'\n     +   if compiler.get_id() == 'msvc'\n     +     libgit_include_directories += 'compat/vcbuild/include'\n      @@ meson.build: version_def_h = custom_target(\n       libgit_sources += version_def_h\n       \n     @@ meson.build: version_def_h = custom_target(\n         dependencies: libgit_dependencies,\n         include_directories: libgit_include_directories,\n      \n     + ## meson_options.txt ##\n     +@@ meson_options.txt: option('zlib_backend', type: 'combo', choices: ['auto', 'zlib', 'zlib-ng'], valu\n     +   description: 'The backend used for compressing objects and other data.')\n     + \n     + # Build tweaks.\n     ++option('with_rust', type: 'boolean', value: true,\n     ++       description: 'Enable building with Rust, true by default.')\n     + option('breaking_changes', type: 'boolean', value: false,\n     +   description: 'Enable upcoming breaking changes.')\n     + option('macos_use_homebrew_gettext', type: 'boolean', value: true,\n     +\n       ## rust/Cargo.toml (new) ##\n      @@\n      +[workspace]\n     -+members = [\n     -+    \"xdiff\",\n     -+    \"interop\",\n     -+]\n     ++members = []\n      +resolver = \"2\"\n     -\n     - ## rust/interop/Cargo.toml (new) ##\n     -@@\n     -+[package]\n     -+name = \"interop\"\n     -+version = \"0.1.0\"\n     -+edition = \"2021\"\n     -+\n     -+[lib]\n     -+name = \"interop\"\n     -+path = \"src/lib.rs\"\n     -+## staticlib to generate xdiff.a for use by gcc\n     -+## cdylib (optional) to generate xdiff.so for use by gcc\n     -+## rlib is required by the rust unit tests\n     -+crate-type = [\"staticlib\", \"rlib\"]\n     -+\n     -+[dependencies]\n     -\n     - ## rust/interop/src/lib.rs (new) ##\n     -\n     - ## rust/xdiff/Cargo.toml (new) ##\n     -@@\n     -+[package]\n     -+name = \"xdiff\"\n     -+version = \"0.1.0\"\n     -+edition = \"2021\"\n     -+\n     -+[lib]\n     -+name = \"xdiff\"\n     -+path = \"src/lib.rs\"\n     -+## staticlib to generate xdiff.a for use by gcc\n     -+## cdylib (optional) to generate xdiff.so for use by gcc\n     -+## rlib is required by the rust unit tests\n     -+crate-type = [\"staticlib\", \"rlib\"]\n     -+\n     -+[dependencies]\n     -+interop = { path = \"../interop\" }\n     -\n     - ## rust/xdiff/src/lib.rs (new) ##\n  -:  ---------- >  8:  cd04ed5aa7 help: report on whether or not Rust is enabled\n  3:  6c47401ba0 !  9:  fcdfc55fb7 github workflows: install rust\n     @@ Commit message\n              the Rust toolchain, the Rust target triple must be set to\n              x86_64-pc-windows-gnu for make (win build), and\n              x86_64-pc-windows-msvc for meson (win+Meson build).\n     +      * MSVC builds: Rearrange PATH to look in /mingw64/bin and /usr/bin\n     +        last.\n            * On musl: libc differences, such as ftruncate64 vs ftruncate, were\n              not accounted for until Rust version 1.72.0. No older version of\n              Rust will work on musl for our needs.\n     @@ .github/workflows/main.yml: jobs:\n           outputs:\n             enabled: ${{ steps.check-ref.outputs.enabled }}${{ steps.skip-if-redundant.outputs.enabled }}\n             skip_concurrent: ${{ steps.check-ref.outputs.skip_concurrent }}\n     -+      rust_version_minimum: 1.61.0\n     ++      rust_version_minimum: 1.63.0\n      +      rust_version_windows: 1.78.0\n      +      rust_version_musl: 1.72.0\n      +      ## the rust target is inferred by rustup unless specified\n     @@ .github/workflows/main.yml: jobs:\n           steps:\n           - uses: actions/checkout@v4\n           - uses: git-for-windows/setup-git-for-windows-sdk@v1\n     ++      with:\n     ++        flavor: full\n      +    - name: Install rustup via github actions\n      +      uses: actions-rs/toolchain@v1\n      +      with:\n  4:  8e350700bc ! 10:  6eeef4081c win+Meson: do allow linking with the Rust-built xdiff\n     @@ Commit message\n           handling from an earlier patch]\n          Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n      \n     - ## .github/workflows/main.yml ##\n     -@@ .github/workflows/main.yml: jobs:\n     -     steps:\n     -     - uses: actions/checkout@v4\n     -     - uses: git-for-windows/setup-git-for-windows-sdk@v1\n     -+    - name: ensure that libuserenv.a is present\n     -+      shell: bash\n     -+      run: |\n     -+        cd /mingw64/lib && {\n     -+          test -f libuserenv.a ||\n     -+          /c/Program\\ Files/Git/mingw64/bin/curl -Lo libuserenv.a \\\n     -+            https://github.com/git-for-windows/git-sdk-64/raw/HEAD/mingw64/lib/libuserenv.a\n     -+        }\n     -     - name: Install rustup via github actions\n     -       uses: actions-rs/toolchain@v1\n     -       with:\n     -\n       ## config.mak.uname ##\n      @@ config.mak.uname: ifeq ($(uname_S),MINGW)\n       \t\tCOMPAT_CFLAGS += -D_USE_32BIT_TIME_T\n     @@ config.mak.uname: ifeq ($(uname_S),MINGW)\n       \tCC = gcc\n       \tCOMPAT_CFLAGS += -D__USE_MINGW_ANSI_STDIO=0 -DDETECT_MSYS_TTY \\\n       \t\t-fstack-protector-strong\n     -\n     - ## meson.build ##\n     -@@ meson.build: elif host_machine.system() == 'windows'\n     -   ]\n     - \n     -   libgit_dependencies += compiler.find_library('ntdll')\n     -+  libgit_dependencies += compiler.find_library('userenv')\n     -   libgit_include_directories += 'compat/win32'\n     -   if compiler.get_id() == 'msvc'\n     -     libgit_include_directories += 'compat/vcbuild/include'\n  5:  a00cbf7bcb = 11:  1e13d6c45f github workflows: upload Cargo.lock\n  6:  922d506ed6 <  -:  ---------- ivec: create a vector type that is interoperable between C and Rust\n  -:  ---------- > 12:  1c923a71f2 build: new crate, build-helper\n  7:  3a6f822e2a ! 13:  6a27e07e63 xdiff/xprepare: remove superfluous forward declarations\n     @@ Metadata\n      Author: Ezekiel Newren <ezekielnewren@gmail.com>\n      \n       ## Commit message ##\n     -    xdiff/xprepare: remove superfluous forward declarations\n     +    build-helper: link against libgit.a and any other required C libraries\n      \n     -    Move xdl_prepare_env() later in the file to avoid the need\n     -    for forward declarations.\n     +    Don't link against the C libraries when building with Make or Meson.\n     +    Run cargo tests like this:\n     +    cd rust && cargo clean && USE_LINKING=true cargo test\n      \n          Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n      \n     - ## xdiff/xprepare.c ##\n     -@@ xdiff/xprepare.c: typedef struct s_xdlclassifier {\n     + ## rust/build-helper/Cargo.toml ##\n     +@@ rust/build-helper/Cargo.toml: version = \"0.1.0\"\n     + edition = \"2021\"\n       \n     - \n     - \n     --static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags);\n     --static void xdl_free_classifier(xdlclassifier_t *cf);\n     --static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash,\n     --\t\t\t       unsigned int hbits, xrecord_t *rec);\n     --static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,\n     --\t\t\t   xdlclassifier_t *cf, xdfile_t *xdf);\n     --static void xdl_free_ctx(xdfile_t *xdf);\n     --static int xdl_clean_mmatch(char const *dis, long i, long s, long e);\n     --static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2);\n     --static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2);\n     --static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2);\n     --\n     --\n     --\n     --\n     - static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags) {\n     - \tcf->flags = flags;\n     - \n     -@@ xdiff/xprepare.c: static void xdl_free_ctx(xdfile_t *xdf) {\n     - }\n     - \n     - \n     --int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,\n     --\t\t    xdfenv_t *xe) {\n     --\tlong enl1, enl2, sample;\n     --\txdlclassifier_t cf;\n     --\n     --\tmemset(&cf, 0, sizeof(cf));\n     --\n     --\t/*\n     --\t * For histogram diff, we can afford a smaller sample size and\n     --\t * thus a poorer estimate of the number of lines, as the hash\n     --\t * table (rhash) won't be filled up/grown. The number of lines\n     --\t * (nrecs) will be updated correctly anyway by\n     --\t * xdl_prepare_ctx().\n     --\t */\n     --\tsample = (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF\n     --\t\t  ? XDL_GUESS_NLINES2 : XDL_GUESS_NLINES1);\n     --\n     --\tenl1 = xdl_guess_lines(mf1, sample) + 1;\n     --\tenl2 = xdl_guess_lines(mf2, sample) + 1;\n     --\n     --\tif (xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0)\n     --\t\treturn -1;\n     --\n     --\tif (xdl_prepare_ctx(1, mf1, enl1, xpp, &cf, &xe->xdf1) < 0) {\n     --\n     --\t\txdl_free_classifier(&cf);\n     --\t\treturn -1;\n     --\t}\n     --\tif (xdl_prepare_ctx(2, mf2, enl2, xpp, &cf, &xe->xdf2) < 0) {\n     --\n     --\t\txdl_free_ctx(&xe->xdf1);\n     --\t\txdl_free_classifier(&cf);\n     --\t\treturn -1;\n     --\t}\n     + [dependencies]\n      -\n     --\tif ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) &&\n     --\t    (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF) &&\n     --\t    xdl_optimize_ctxs(&cf, &xe->xdf1, &xe->xdf2) < 0) {\n     --\n     --\t\txdl_free_ctx(&xe->xdf2);\n     --\t\txdl_free_ctx(&xe->xdf1);\n     --\t\txdl_free_classifier(&cf);\n     --\t\treturn -1;\n     --\t}\n     --\n     --\txdl_free_classifier(&cf);\n     --\n     --\treturn 0;\n     --}\n     --\n     --\n     - void xdl_free_env(xdfenv_t *xe) {\n     - \n     - \txdl_free_ctx(&xe->xdf2);\n     -@@ xdiff/xprepare.c: static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2\n     - \n     - \treturn 0;\n     - }\n     +\n     + ## rust/build-helper/src/lib.rs ##\n     +@@\n     ++use std::collections::HashMap;\n     ++use std::path::PathBuf;\n      +\n     -+int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,\n     -+\t\t    xdfenv_t *xe) {\n     -+\tlong enl1, enl2, sample;\n     -+\txdlclassifier_t cf;\n      +\n     -+\tmemset(&cf, 0, sizeof(cf));\n     ++fn parse_bool_from_str(value: &str) -> bool {\n     ++    match value {\n     ++        \"1\" | \"true\"  | \"yes\" | \"on\"  => true,\n     ++        \"0\" | \"false\" | \"no\"  | \"off\" => false,\n     ++        _ => false\n     ++    }\n     ++}\n     ++\n     ++fn parse_bool_from_option(value: Option<&String>, default: bool) -> bool {\n     ++    match value {\n     ++        Some(v) => {\n     ++            parse_bool_from_str(v.as_str())\n     ++        },\n     ++        None => default,\n     ++    }\n     ++}\n      +\n     -+\t/*\n     -+\t * For histogram diff, we can afford a smaller sample size and\n     -+\t * thus a poorer estimate of the number of lines, as the hash\n     -+\t * table (rhash) won't be filled up/grown. The number of lines\n     -+\t * (nrecs) will be updated correctly anyway by\n     -+\t * xdl_prepare_ctx().\n     -+\t */\n     -+\tsample = (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF\n     -+\t\t  ? XDL_GUESS_NLINES2 : XDL_GUESS_NLINES1);\n     ++/// To build without linking against C libraries run `USE_LINKING=false cargo build`\n     ++/// To run tests set GIT_BUILD_DIR and run `USE_LINKING=true cargo test`\n     ++pub struct BuildHelper {\n     ++    crate_env: HashMap<String, String>,\n     ++}\n      +\n     -+\tenl1 = xdl_guess_lines(mf1, sample) + 1;\n     -+\tenl2 = xdl_guess_lines(mf2, sample) + 1;\n      +\n     -+\tif (xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0)\n     -+\t\treturn -1;\n     ++impl BuildHelper {\n     ++    pub fn new(crate_env: HashMap<String, String>) -> Self {\n     ++        let it = Self {crate_env};\n      +\n     -+\tif (xdl_prepare_ctx(1, mf1, enl1, xpp, &cf, &xe->xdf1) < 0) {\n     ++        let dir_crate = it.dir_crate();\n     ++        let dir_workspace = dir_crate.parent().unwrap();\n     ++        let dir_git = dir_workspace.parent().unwrap();\n     ++        let dir_interop = dir_git.join(\"interop\");\n     ++        if !dir_interop.exists() {\n     ++            std::fs::create_dir(dir_interop.clone()).unwrap();\n     ++        }\n      +\n     -+\t\txdl_free_classifier(&cf);\n     -+\t\treturn -1;\n     -+\t}\n     -+\tif (xdl_prepare_ctx(2, mf2, enl2, xpp, &cf, &xe->xdf2) < 0) {\n     ++        it\n     ++    }\n      +\n     -+\t\txdl_free_ctx(&xe->xdf1);\n     -+\t\txdl_free_classifier(&cf);\n     -+\t\treturn -1;\n     -+\t}\n     ++    pub fn crate_name(&self) -> String {\n     ++        self.crate_env[\"CARGO_PKG_NAME\"].clone()\n     ++    }\n      +\n     -+\tif ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) &&\n     -+\t    (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF) &&\n     -+\t    xdl_optimize_ctxs(&cf, &xe->xdf1, &xe->xdf2) < 0) {\n     ++    pub fn dir_crate(&self) -> PathBuf {\n     ++        PathBuf::from(self.crate_env[\"CARGO_MANIFEST_DIR\"].clone())\n     ++    }\n      +\n     -+\t\txdl_free_ctx(&xe->xdf2);\n     -+\t\txdl_free_ctx(&xe->xdf1);\n     -+\t\txdl_free_classifier(&cf);\n     -+\t\treturn -1;\n     -+\t    }\n     ++    pub fn build(self) {\n     ++        let use_linking = parse_bool_from_option(self.crate_env.get(\"USE_LINKING\"), self.crate_env.get(\"CARGO_TARGET_DIR\").is_none());\n     ++        let dir_crate = self.dir_crate();\n     ++        let dir_git = dir_crate.parent().unwrap().parent().unwrap();\n      +\n     -+\txdl_free_classifier(&cf);\n     ++        println!(\"cargo:rerun-if-changed={}\", dir_git.display());\n      +\n     -+\treturn 0;\n     ++        if use_linking {\n     ++            if let Some(git_build_dir) = self.crate_env.get(\"GIT_BUILD_DIR\") {\n     ++                let mut path_git_build_dir = PathBuf::from(git_build_dir);\n     ++                path_git_build_dir = path_git_build_dir.canonicalize().unwrap();\n     ++                if !path_git_build_dir.is_dir() {\n     ++                    panic!(\"'GIT_BUILD_DIR' is not a directory: {}\", path_git_build_dir.display());\n     ++                }\n     ++                println!(\"cargo:rustc-link-search=native={}\", git_build_dir);\n     ++            } else {\n     ++                panic!(\"environment variable 'GIT_BUILD_DIR' is not set\");\n     ++            }\n     ++\n     ++            println!(\"cargo:rustc-link-lib=static=git\");\n     ++            println!(\"cargo:rustc-link-lib=pcre2-8\");\n     ++            if self.crate_env.get(\"ZLIB_NG\").is_some() {\n     ++                println!(\"cargo:rustc-link-lib=z-ng\");\n     ++            } else {\n     ++                println!(\"cargo:rustc-link-lib=z\");\n     ++            }\n     ++        } else {\n     ++            println!(\"cargo:warning={} is not linking against C objects, `USE_LINKING=true cargo test`\", self.crate_env[\"CARGO_PKG_NAME\"]);\n     ++        }\n     ++    }\n      +}\n     ++\n     ++\n  8:  c7cd71dae0 <  -:  ---------- xdiff: delete unnecessary fields from xrecord_t and xdfile_t\n  9:  1096c8f0a4 <  -:  ---------- xdiff: make fields of xrecord_t Rust friendly\n 11:  7ea2dccd71 <  -:  ---------- xdiff: replace chastore with an ivec in xdfile_t\n 12:  d8f561e173 <  -:  ---------- xdiff: delete nrec field from xdfile_t\n 13:  15c2854a32 <  -:  ---------- xdiff: delete recs field from xdfile_t\n 14:  3dacb2c09d <  -:  ---------- xdiff: make xdfile_t more rust friendly\n 15:  9fdd23acf6 <  -:  ---------- xdiff: implement xdl_trim_ends() in Rust\n  -:  ---------- > 14:  fa33440568 build-helper: cbindgen, let crates generate a header file\n  -:  ---------- > 15:  df837fdb4d varint: use explicit width for integers\n  -:  ---------- > 16:  17143ced6f build: new crate, misc\n  -:  ---------- > 17:  89bb7c3613 misc: use BuildHelper\n  -:  ---------- > 18:  3c521fa2e4 misc::varint: reimplement as test balloon for Rust\n\n-- \ngitgitgadget\n"},{"id":"526518","messageId":"5f77f1bd5d986dc1f8d123919af24dd219e323e8.1758071798.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.v2.git.git.1758071798.gitgitgadget@gmail.com","subject":"[PATCH v2 01/18] cleanup: rename variables that collide with Rust primitive type names","fromName":"Ezekiel Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-09-17T01:16:21Z","receivedAt":"2025-09-17T01:16:42Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"From: Ezekiel Newren <ezekielnewren@gmail.com>\n\nUse a regex to find and rename variables that collide with Rust\nprimitive integer and float type names:\n\n    git grep -n -E -e '\\<([ui](8|16|32|64|size)|(f(32|64)))\\>'\n\nMatches were reviewed and renamed. The remaining matches don't count\nbecause:\n- Rust source files:\n  contrib/libgit-rs/src/config.rs\n  contrib/libgit-sys/src/lib.rs\n  t/t4018/rust-impl\n  t/t4018/rust-trait\n- Intentional references:\n  t/helper/test-parse-options.c (prints Rust int names)\n  t/t0040-parse-options.sh (tests the above)\n\nView with --color-words to highlight the variable renames.\n\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n odb.c                         |  8 ++++----\n reftable/record.c             |  6 +++---\n t/helper/test-parse-options.c | 12 ++++++------\n xdiff/xtypes.h                |  2 +-\n xdiff/xutils.c                |  8 ++++----\n xdiff/xutils.h                |  2 +-\n 6 files changed, 19 insertions(+), 19 deletions(-)\n\ndiff --git a/odb.c b/odb.c\nindex 2a92a018c4..f0255b4b36 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -913,7 +913,7 @@ void *odb_read_object_peeled(struct object_database *odb,\n {\n \tenum object_type type;\n \tvoid *buffer;\n-\tunsigned long isize;\n+\tunsigned long isize_;\n \tstruct object_id actual_oid;\n \n \toidcpy(&actual_oid, oid);\n@@ -921,11 +921,11 @@ void *odb_read_object_peeled(struct object_database *odb,\n \t\tint ref_length = -1;\n \t\tconst char *ref_type = NULL;\n \n-\t\tbuffer = odb_read_object(odb, &actual_oid, &type, &isize);\n+\t\tbuffer = odb_read_object(odb, &actual_oid, &type, &isize_);\n \t\tif (!buffer)\n \t\t\treturn NULL;\n \t\tif (type == required_type) {\n-\t\t\t*size = isize;\n+\t\t\t*size = isize_;\n \t\t\tif (actual_oid_return)\n \t\t\t\toidcpy(actual_oid_return, &actual_oid);\n \t\t\treturn buffer;\n@@ -941,7 +941,7 @@ void *odb_read_object_peeled(struct object_database *odb,\n \t\t}\n \t\tref_length = strlen(ref_type);\n \n-\t\tif (ref_length + odb->repo->hash_algo->hexsz > isize ||\n+\t\tif (ref_length + odb->repo->hash_algo->hexsz > isize_ ||\n \t\t    memcmp(buffer, ref_type, ref_length) ||\n \t\t    get_oid_hex_algop((char *) buffer + ref_length, &actual_oid,\n \t\t\t\t      odb->repo->hash_algo)) {\ndiff --git a/reftable/record.c b/reftable/record.c\nindex fcd387ba5d..e8fc854561 100644\n--- a/reftable/record.c\n+++ b/reftable/record.c\n@@ -675,7 +675,7 @@ static int reftable_log_record_key(const void *r, struct reftable_buf *dest)\n \tconst struct reftable_log_record *rec =\n \t\t(const struct reftable_log_record *)r;\n \tint len = strlen(rec->refname), err;\n-\tuint8_t i64[8];\n+\tuint8_t i64_[8];\n \tuint64_t ts = 0;\n \n \treftable_buf_reset(dest);\n@@ -684,9 +684,9 @@ static int reftable_log_record_key(const void *r, struct reftable_buf *dest)\n \t\treturn err;\n \n \tts = (~ts) - rec->update_index;\n-\treftable_put_be64(&i64[0], ts);\n+\treftable_put_be64(&i64_[0], ts);\n \n-\terr = reftable_buf_add(dest, i64, sizeof(i64));\n+\terr = reftable_buf_add(dest, i64_, sizeof(i64_));\n \tif (err < 0)\n \t\treturn err;\n \ndiff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c\nindex 68579d83f3..aa1cb693a2 100644\n--- a/t/helper/test-parse-options.c\n+++ b/t/helper/test-parse-options.c\n@@ -120,8 +120,8 @@ int cmd__parse_options(int argc, const char **argv)\n \t};\n \tstruct string_list expect = STRING_LIST_INIT_NODUP;\n \tstruct string_list list = STRING_LIST_INIT_NODUP;\n-\tuint16_t u16 = 0;\n-\tint16_t i16 = 0;\n+\tuint16_t u16_ = 0;\n+\tint16_t i16_ = 0;\n \n \tstruct option options[] = {\n \t\tOPT_BOOL(0, \"yes\", &boolean, \"get a boolean\"),\n@@ -142,10 +142,10 @@ int cmd__parse_options(int argc, const char **argv)\n \t\tOPT_NEGBIT(0, \"neg-or4\", &boolean, \"same as --no-or4\", 4),\n \t\tOPT_GROUP(\"\"),\n \t\tOPT_INTEGER('i', \"integer\", &integer, \"get a integer\"),\n-\t\tOPT_INTEGER(0, \"i16\", &i16, \"get a 16 bit integer\"),\n+\t\tOPT_INTEGER(0, \"i16\", &i16_, \"get a 16 bit integer\"),\n \t\tOPT_INTEGER('j', NULL, &integer, \"get a integer, too\"),\n \t\tOPT_UNSIGNED('u', \"unsigned\", &unsigned_integer, \"get an unsigned integer\"),\n-\t\tOPT_UNSIGNED(0, \"u16\", &u16, \"get a 16 bit unsigned integer\"),\n+\t\tOPT_UNSIGNED(0, \"u16\", &u16_, \"get a 16 bit unsigned integer\"),\n \t\tOPT_SET_INT(0, \"set23\", &integer, \"set integer to 23\", 23),\n \t\tOPT_CMDMODE(0, \"mode1\", &integer, \"set integer to 1 (cmdmode option)\", 1),\n \t\tOPT_CMDMODE(0, \"mode2\", &integer, \"set integer to 2 (cmdmode option)\", 2),\n@@ -225,9 +225,9 @@ int cmd__parse_options(int argc, const char **argv)\n \t}\n \tshow(&expect, &ret, \"boolean: %d\", boolean);\n \tshow(&expect, &ret, \"integer: %d\", integer);\n-\tshow(&expect, &ret, \"i16: %\"PRIdMAX, (intmax_t) i16);\n+\tshow(&expect, &ret, \"i16: %\"PRIdMAX, (intmax_t) i16_);\n \tshow(&expect, &ret, \"unsigned: %lu\", unsigned_integer);\n-\tshow(&expect, &ret, \"u16: %\"PRIuMAX, (uintmax_t) u16);\n+\tshow(&expect, &ret, \"u16: %\"PRIuMAX, (uintmax_t) u16_);\n \tshow(&expect, &ret, \"timestamp: %\"PRItime, timestamp);\n \tshow(&expect, &ret, \"string: %s\", string ? string : \"(not set)\");\n \tshow(&expect, &ret, \"abbrev: %d\", abbrev);\ndiff --git a/xdiff/xtypes.h b/xdiff/xtypes.h\nindex 8442bd436e..ec1dc7548a 100644\n--- a/xdiff/xtypes.h\n+++ b/xdiff/xtypes.h\n@@ -32,7 +32,7 @@ typedef struct s_chanode {\n \n typedef struct s_chastore {\n \tchanode_t *head, *tail;\n-\tlong isize, nsize;\n+\tlong isize_, nsize;\n \tchanode_t *ancur;\n \tchanode_t *sncur;\n \tlong scurr;\ndiff --git a/xdiff/xutils.c b/xdiff/xutils.c\nindex 444a108f87..20dee53dbd 100644\n--- a/xdiff/xutils.c\n+++ b/xdiff/xutils.c\n@@ -71,11 +71,11 @@ long xdl_mmfile_size(mmfile_t *mmf)\n }\n \n \n-int xdl_cha_init(chastore_t *cha, long isize, long icount) {\n+int xdl_cha_init(chastore_t *cha, long isize_, long icount) {\n \n \tcha->head = cha->tail = NULL;\n-\tcha->isize = isize;\n-\tcha->nsize = icount * isize;\n+\tcha->isize_ = isize_;\n+\tcha->nsize = icount * isize_;\n \tcha->ancur = cha->sncur = NULL;\n \tcha->scurr = 0;\n \n@@ -113,7 +113,7 @@ void *xdl_cha_alloc(chastore_t *cha) {\n \t}\n \n \tdata = (char *) ancur + sizeof(chanode_t) + ancur->icurr;\n-\tancur->icurr += cha->isize;\n+\tancur->icurr += cha->isize_;\n \n \treturn data;\n }\ndiff --git a/xdiff/xutils.h b/xdiff/xutils.h\nindex fd0bba94e8..b531d2ebff 100644\n--- a/xdiff/xutils.h\n+++ b/xdiff/xutils.h\n@@ -28,7 +28,7 @@\n long xdl_bogosqrt(long n);\n int xdl_emit_diffrec(char const *rec, long size, char const *pre, long psize,\n \t\t     xdemitcb_t *ecb);\n-int xdl_cha_init(chastore_t *cha, long isize, long icount);\n+int xdl_cha_init(chastore_t *cha, long isize_, long icount);\n void xdl_cha_free(chastore_t *cha);\n void *xdl_cha_alloc(chastore_t *cha);\n long xdl_guess_lines(mmfile_t *mf, long sample);\n-- \ngitgitgadget\n\n"},{"id":"526520","messageId":"7082e32c5975d2aaa277eddae7497eba0f2131e0.1758071798.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.v2.git.git.1758071798.gitgitgadget@gmail.com","subject":"[PATCH v2 02/18] make: add -fPIE flag","fromName":"Ezekiel Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-09-17T01:16:22Z","receivedAt":"2025-09-17T01:16:43Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"From: Ezekiel Newren <ezekielnewren@gmail.com>\n\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n Makefile | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/Makefile b/Makefile\nindex e11340c1ae..d3e034f3be 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1402,7 +1402,7 @@ endif\n CFLAGS = -g -O2 -Wall\n LDFLAGS =\n CC_LD_DYNPATH = -Wl,-rpath,\n-BASIC_CFLAGS = -I.\n+BASIC_CFLAGS = -I. -fPIE\n BASIC_LDFLAGS =\n \n # library flags\n-- \ngitgitgadget\n\n"},{"id":"526521","messageId":"7fe85f0eaca88fd3084bccff6754c57a919e7d4f.1758071798.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.v2.git.git.1758071798.gitgitgadget@gmail.com","subject":"[PATCH v2 03/18] make: merge xdiff lib into libgit.a","fromName":"Ezekiel Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-09-17T01:16:23Z","receivedAt":"2025-09-17T01:16:44Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"From: Ezekiel Newren <ezekielnewren@gmail.com>\n\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n Makefile | 30 ++++++++++--------------------\n 1 file changed, 10 insertions(+), 20 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex d3e034f3be..94f38eb4c9 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -670,7 +670,6 @@ BUILTIN_OBJS =\n BUILT_INS =\n COMPAT_CFLAGS =\n COMPAT_OBJS =\n-XDIFF_OBJS =\n GENERATED_H =\n EXTRA_CPPFLAGS =\n FUZZ_OBJS =\n@@ -916,7 +915,6 @@ export PYTHON_PATH\n TEST_SHELL_PATH = $(SHELL_PATH)\n \n LIB_FILE = libgit.a\n-XDIFF_LIB = xdiff/lib.a\n REFTABLE_LIB = reftable/libreftable.a\n \n GENERATED_H += command-list.h\n@@ -1207,6 +1205,13 @@ LIB_OBJS += write-or-die.o\n LIB_OBJS += ws.o\n LIB_OBJS += wt-status.o\n LIB_OBJS += xdiff-interface.o\n+LIB_OBJS += xdiff/xdiffi.o\n+LIB_OBJS += xdiff/xemit.o\n+LIB_OBJS += xdiff/xhistogram.o\n+LIB_OBJS += xdiff/xmerge.o\n+LIB_OBJS += xdiff/xpatience.o\n+LIB_OBJS += xdiff/xprepare.o\n+LIB_OBJS += xdiff/xutils.o\n \n BUILTIN_OBJS += builtin/add.o\n BUILTIN_OBJS += builtin/am.o\n@@ -1385,8 +1390,8 @@ CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o\n \n UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o\n \n-# xdiff and reftable libs may in turn depend on what is in libgit.a\n-GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(LIB_FILE)\n+# reftable lib may depend on what is in libgit.a\n+GITLIBS = common-main.o $(LIB_FILE) $(REFTABLE_LIB) $(LIB_FILE)\n EXTLIBS =\n \n GIT_USER_AGENT = git/$(GIT_VERSION)\n@@ -2718,16 +2723,6 @@ reconfigure config.mak.autogen: config.status\n .PHONY: reconfigure # This is a convenience target.\n endif\n \n-XDIFF_OBJS += xdiff/xdiffi.o\n-XDIFF_OBJS += xdiff/xemit.o\n-XDIFF_OBJS += xdiff/xhistogram.o\n-XDIFF_OBJS += xdiff/xmerge.o\n-XDIFF_OBJS += xdiff/xpatience.o\n-XDIFF_OBJS += xdiff/xprepare.o\n-XDIFF_OBJS += xdiff/xutils.o\n-.PHONY: xdiff-objs\n-xdiff-objs: $(XDIFF_OBJS)\n-\n REFTABLE_OBJS += reftable/basics.o\n REFTABLE_OBJS += reftable/error.o\n REFTABLE_OBJS += reftable/block.o\n@@ -2762,7 +2757,6 @@ OBJECTS += $(GIT_OBJS)\n OBJECTS += $(SCALAR_OBJS)\n OBJECTS += $(PROGRAM_OBJS)\n OBJECTS += $(TEST_OBJS)\n-OBJECTS += $(XDIFF_OBJS)\n OBJECTS += $(FUZZ_OBJS)\n OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS)\n OBJECTS += $(UNIT_TEST_OBJS)\n@@ -2916,9 +2910,6 @@ scalar$X: scalar.o GIT-LDFLAGS $(GITLIBS)\n $(LIB_FILE): $(LIB_OBJS)\n \t$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^\n \n-$(XDIFF_LIB): $(XDIFF_OBJS)\n-\t$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^\n-\n $(REFTABLE_LIB): $(REFTABLE_OBJS)\n \t$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^\n \n@@ -3760,7 +3751,7 @@ clean: profile-clean coverage-clean cocciclean\n \t$(RM) git.rc git.res\n \t$(RM) $(OBJECTS)\n \t$(RM) headless-git.o\n-\t$(RM) $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB)\n+\t$(RM) $(LIB_FILE) $(REFTABLE_LIB)\n \t$(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS)\n \t$(RM) $(TEST_PROGRAMS)\n \t$(RM) $(FUZZ_PROGRAMS)\n@@ -3955,7 +3946,6 @@ endif\n LIBGIT_PUB_OBJS += contrib/libgit-sys/public_symbol_export.o\n LIBGIT_PUB_OBJS += libgit.a\n LIBGIT_PUB_OBJS += reftable/libreftable.a\n-LIBGIT_PUB_OBJS += xdiff/lib.a\n \n LIBGIT_PARTIAL_EXPORT = contrib/libgit-sys/partial_symbol_export.o\n \n-- \ngitgitgadget\n\n"},{"id":"526522","messageId":"3061cc46c1910bdc4f66855db7a07d313b37fb27.1758071798.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.v2.git.git.1758071798.gitgitgadget@gmail.com","subject":"[PATCH v2 04/18] make: merge reftable lib into libgit.a","fromName":"Ezekiel Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-09-17T01:16:24Z","receivedAt":"2025-09-17T01:16:45Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"From: Ezekiel Newren <ezekielnewren@gmail.com>\n\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n Makefile | 39 ++++++++++++++++-----------------------\n 1 file changed, 16 insertions(+), 23 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex 94f38eb4c9..ffb898b611 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -915,7 +915,6 @@ export PYTHON_PATH\n TEST_SHELL_PATH = $(SHELL_PATH)\n \n LIB_FILE = libgit.a\n-REFTABLE_LIB = reftable/libreftable.a\n \n GENERATED_H += command-list.h\n GENERATED_H += config-list.h\n@@ -1125,6 +1124,19 @@ LIB_OBJS += rebase.o\n LIB_OBJS += ref-filter.o\n LIB_OBJS += reflog-walk.o\n LIB_OBJS += reflog.o\n+LIB_OBJS += reftable/basics.o\n+LIB_OBJS += reftable/error.o\n+LIB_OBJS += reftable/block.o\n+LIB_OBJS += reftable/blocksource.o\n+LIB_OBJS += reftable/iter.o\n+LIB_OBJS += reftable/merged.o\n+LIB_OBJS += reftable/pq.o\n+LIB_OBJS += reftable/record.o\n+LIB_OBJS += reftable/stack.o\n+LIB_OBJS += reftable/system.o\n+LIB_OBJS += reftable/table.o\n+LIB_OBJS += reftable/tree.o\n+LIB_OBJS += reftable/writer.o\n LIB_OBJS += refs.o\n LIB_OBJS += refs/debug.o\n LIB_OBJS += refs/files-backend.o\n@@ -1390,8 +1402,7 @@ CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o\n \n UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o\n \n-# reftable lib may depend on what is in libgit.a\n-GITLIBS = common-main.o $(LIB_FILE) $(REFTABLE_LIB) $(LIB_FILE)\n+GITLIBS = common-main.o $(LIB_FILE)\n EXTLIBS =\n \n GIT_USER_AGENT = git/$(GIT_VERSION)\n@@ -2723,20 +2734,6 @@ reconfigure config.mak.autogen: config.status\n .PHONY: reconfigure # This is a convenience target.\n endif\n \n-REFTABLE_OBJS += reftable/basics.o\n-REFTABLE_OBJS += reftable/error.o\n-REFTABLE_OBJS += reftable/block.o\n-REFTABLE_OBJS += reftable/blocksource.o\n-REFTABLE_OBJS += reftable/iter.o\n-REFTABLE_OBJS += reftable/merged.o\n-REFTABLE_OBJS += reftable/pq.o\n-REFTABLE_OBJS += reftable/record.o\n-REFTABLE_OBJS += reftable/stack.o\n-REFTABLE_OBJS += reftable/system.o\n-REFTABLE_OBJS += reftable/table.o\n-REFTABLE_OBJS += reftable/tree.o\n-REFTABLE_OBJS += reftable/writer.o\n-\n TEST_OBJS := $(patsubst %$X,%.o,$(TEST_PROGRAMS)) $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))\n \n .PHONY: test-objs\n@@ -2758,7 +2755,7 @@ OBJECTS += $(SCALAR_OBJS)\n OBJECTS += $(PROGRAM_OBJS)\n OBJECTS += $(TEST_OBJS)\n OBJECTS += $(FUZZ_OBJS)\n-OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS)\n+OBJECTS += $(REFTABLE_TEST_OBJS)\n OBJECTS += $(UNIT_TEST_OBJS)\n OBJECTS += $(CLAR_TEST_OBJS)\n OBJECTS += $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))\n@@ -2910,9 +2907,6 @@ scalar$X: scalar.o GIT-LDFLAGS $(GITLIBS)\n $(LIB_FILE): $(LIB_OBJS)\n \t$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^\n \n-$(REFTABLE_LIB): $(REFTABLE_OBJS)\n-\t$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^\n-\n export DEFAULT_EDITOR DEFAULT_PAGER\n \n Documentation/GIT-EXCLUDED-PROGRAMS: FORCE\n@@ -3751,7 +3745,7 @@ clean: profile-clean coverage-clean cocciclean\n \t$(RM) git.rc git.res\n \t$(RM) $(OBJECTS)\n \t$(RM) headless-git.o\n-\t$(RM) $(LIB_FILE) $(REFTABLE_LIB)\n+\t$(RM) $(LIB_FILE)\n \t$(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS)\n \t$(RM) $(TEST_PROGRAMS)\n \t$(RM) $(FUZZ_PROGRAMS)\n@@ -3945,7 +3939,6 @@ endif\n \n LIBGIT_PUB_OBJS += contrib/libgit-sys/public_symbol_export.o\n LIBGIT_PUB_OBJS += libgit.a\n-LIBGIT_PUB_OBJS += reftable/libreftable.a\n \n LIBGIT_PARTIAL_EXPORT = contrib/libgit-sys/partial_symbol_export.o\n \n-- \ngitgitgadget\n\n"},{"id":"526523","messageId":"0fc89c3ec300008c27e877006d7236b39bb95868.1758071798.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.v2.git.git.1758071798.gitgitgadget@gmail.com","subject":"[PATCH v2 05/18] doc: add a policy for using Rust","fromName":"brian m. carlson via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-09-17T01:16:25Z","receivedAt":"2025-09-17T01:16:47Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"From: \"brian m. carlson\" <sandals@crustytoothpaste.net>\n\nGit has historically been written primarily in C, with some shell and\nPerl.  However, C is not memory safe, which makes it more likely that\nsecurity vulnerabilities or other bugs will be introduced, and it is\nalso more verbose and less ergonomic than other, more modern languages.\n\nOne of the most common modern compiled languages which is easily\ninteroperable with C is Rust.  It is popular (the most admired language\non the 2024 Stack Overflow Developer Survey), efficient, portable, and\nrobust.\n\nIntroduce a document laying out the incremental introduction of Rust to\nGit and provide a detailed rationale for doing so, including the points\nabove.  Propose a design for this approach that addresses the needs of\ndownstreams and distributors, as well as contributors.\n\nSince we don't want to carry both a C and Rust version of code and want\nto be able to add new features only in Rust, mention that Rust is a\nrequired part of our platform support policy.\n\nIt should be noted that a recent discussion at the Berlin Git Merge\nContributor Summit found widespread support for the addition of Rust to\nGit.  While of course not all contributors were represented, the\nproposal appeared to have the support of a majority of active\ncontributors.\n\nSigned-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n[en: Added some comments about types, and changed the recommondations\n     about cbindgen, bindgen, rustix, libc.]\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n Documentation/Makefile                        |   1 +\n Documentation/technical/platform-support.adoc |   2 +\n Documentation/technical/rust-support.adoc     | 142 ++++++++++++++++++\n 3 files changed, 145 insertions(+)\n create mode 100644 Documentation/technical/rust-support.adoc\n\ndiff --git a/Documentation/Makefile b/Documentation/Makefile\nindex df2ce187eb..5e7821db3c 100644\n--- a/Documentation/Makefile\n+++ b/Documentation/Makefile\n@@ -127,6 +127,7 @@ TECH_DOCS += technical/parallel-checkout\n TECH_DOCS += technical/partial-clone\n TECH_DOCS += technical/platform-support\n TECH_DOCS += technical/racy-git\n+TECH_DOCS += technical/rust-support\n TECH_DOCS += technical/reftable\n TECH_DOCS += technical/scalar\n TECH_DOCS += technical/send-pack-pipeline\ndiff --git a/Documentation/technical/platform-support.adoc b/Documentation/technical/platform-support.adoc\nindex 0a2fb28d62..dc71672dcb 100644\n--- a/Documentation/technical/platform-support.adoc\n+++ b/Documentation/technical/platform-support.adoc\n@@ -33,6 +33,8 @@ meet the following minimum requirements:\n \n * Has active security support (taking security releases of dependencies, etc)\n \n+* Supports Rust and the toolchain version specified in link:rust-support.adoc[].\n+\n These requirements are a starting point, and not sufficient on their own for the\n Git community to be enthusiastic about supporting your platform. Maintainers of\n platforms which do meet these requirements can follow the steps below to make it\ndiff --git a/Documentation/technical/rust-support.adoc b/Documentation/technical/rust-support.adoc\nnew file mode 100644\nindex 0000000000..5b856bdee6\n--- /dev/null\n+++ b/Documentation/technical/rust-support.adoc\n@@ -0,0 +1,142 @@\n+Usage of Rust in Git\n+====================\n+\n+Objective\n+---------\n+Introduce Rust into Git incrementally to improve security and maintainability.\n+\n+Background\n+----------\n+Git has historically been written primarily in C, with some portions in shell,\n+Perl, or other languages.  At the time it was originally written, this was\n+important for portability and was a logical choice for software development.\n+\n+:0: link:https://security.googleblog.com/2024/09/eliminating-memory-safety-vulnerabilities-Android.html\n+:1: link:https://www.cisa.gov/resources-tools/resources/product-security-bad-practices\n+\n+However, as time has progressed, we've seen an increased concern with memory\n+safety vulnerabilities and the development of newer languages, such as Rust,\n+that substantially limit or eliminate this class of vulnerabilities.\n+Development in a variety of projects has found that memory safety\n+vulnerabilities constitute about 70% of vulnerabilities of software in\n+languages that are not memory safe.  For instance, {0}[one survey of Android]\n+found that memory safety vulnerabilities decreased from 76% to 24% over six\n+years due to an increase in memory safe code.  Similarly, the U.S. government\n+is {1}[proposing to classify development in memory unsafe languages as a\n+Product Security Bad Practice\"].\n+\n+These risks are even more substantial when we consider the fact that Git is a\n+network-facing service.  Many organizations run Git servers internally or use a\n+cloud-based forge, and the risk of accidental exposure or compromise of user\n+data is substantial.  It's important to ensure that Git, whether it's used\n+locally or remotely, is robustly secure.\n+\n+In addition, C is a difficult language to write well and concisely.  While it\n+is of course possible to do anything with C, it lacks built-in support for\n+niceties found in modern languages, such as hash tables, generics, typed\n+errors, and automatic destruction, and most modern language offer shorter, more\n+ergonomic syntax for expressing code.  This is valuable functionality that can\n+allow Git to be developed more rapidly, more easily, by more developers of a\n+variety of levels, and with more confidence in the correctness of the code.\n+\n+For these reasons, adding Rust to Git is a sensible and prudent move that will\n+allow us to improve the quality of the code and potentially attract new developers.\n+\n+Goals\n+-----\n+1. Git continues to build, run, and pass tests on a wide variety of operating\n+   systems and architectures.\n+2. Transition from C to Rust is incremental; that is, code can be ported as it\n+   is convenient and Git does not need to transition all at once.\n+3. Git continues to support older operating systems in conformance with the\n+   platform support policy.\n+\n+Non-Goals\n+---------\n+1. Support for every possible operating system and architecture.  Git already\n+   has a platform support policy which defines what is supported and we already\n+   exclude some operating systems for various reasons (e.g., lacking enough POSIX\n+   tools to pass the test suite).\n+2. Implementing C-only versions of Rust code or compiling a C-only Git.  This\n+   would be difficult to maintain and would not offer the ergonomic benefits we\n+   desire.\n+\n+Design\n+------\n+Git will adopt Rust incrementally.  This transition will start with the\n+creation of a static library that can be linked into the existing Git binaries.\n+At some point, we may wish to expose a dynamic library and compile the Git\n+binaries themselves using Rust.  Using an incremental approach allows us to\n+determine as we go along how to structure our code in the best way for the\n+project and avoids the need to make hard, potentially disruptive, transitions\n+caused by porting a binary wholesale from one language to another that might\n+introduce bugs.\n+\n+Crates like libc or rustix define types like c_long, but in ways that are not\n+safe across platforms.\n+From https://docs.rs/rustix/latest/rustix/ffi/type.c_long.html:\n+\n+    This type will always be i32 or i64.  Most notably, many Linux-based\n+    systems assume an i64, but Windows assumes i32.  The C standard technically\n+    only requires that this type be a signed integer that is at least 32 bits\n+    and at least the size of an int, although in practice, no system would\n+    have a long that is neither an i32 nor i64.\n+\n+Also, note that other locations, such as\n+https://docs.rs/libc/latest/libc/type.c_long.html, just hardcode c_long as i64\n+even though C may mean i32 on some platforms.\n+\n+As such, using the c_long type would give us portability issues, and\n+perpetuate some of the bugs git has faced across platforms.  Avoid using C's\n+types (long, unsigned, char, etc.), and switch to unambiguous types (e.g. i32\n+or i64) before trying to make C and Rust interoperate.\n+\n+Crates like libc and rustix may have also traditionally aided interoperability\n+with older versions of Rust (e.g.  when worrying about stat[64] system calls),\n+but the Rust standard library in newer versions of Rust handle these concerns\n+in a platform agnostic way.  There may arise cases where we need to consider\n+these crates, but for now we omit them.\n+\n+Tools like bindgen and cbindgen create C-styled unsafe Rust code rather than\n+idiomatic Rust; where possible, we prefer to switch to idiomatic Rust.\n+However, we may use bindgen and cbindgen to share existing Git types as an\n+interim step.\n+\n+Rust upstream releases every six weeks and only supports the latest stable\n+release.  While it is nice that upstream is active, we would like our software\n+releases to have a lifespan exceeding six weeks.  To allow compiling our code\n+on a variety of systems, we will support the version of Rust in Debian stable,\n+plus, for a year after a new Debian stable is released, the version in Debian\n+oldstable.\n+\n+This provides an approximately three-year lifespan of support for a Rust\n+release and allows us to support a variety of operating systems and\n+architectures, including those for which Rust upstream does not build binaries.\n+Debian stable is the benchmark distribution used by many Rust projects when\n+determining supported Rust versions, and it is an extremely portable and\n+popular free software operating system that is available to the public at no\n+charge, which makes it a sensible choice for us as well.\n+\n+We may change this policy if the Rust project issues long-term support releases\n+or the Rust community and distributors agree on releases to target as if they\n+were long-term support releases.\n+\n+This version support policy necessitates that we be very careful about the\n+dependencies we include, since many Rust projects support only the latest\n+stable version.  However, we typically have been careful about dependencies in\n+the first place, so this should not be a major departure from existing policy,\n+although it may be a change for some existing Rust developers.\n+\n+We will avoid including the `Cargo.lock` file in the repository and instead\n+specify minimum dependency versions in the `Cargo.toml` file.  We want to allow\n+people to use newer versions of dependencies if necessary to support newer\n+platforms without needing to force upgrades of dependencies on all users, and\n+it provides additional flexibility for distribution maintainers.\n+\n+We do not plan to support beta or nightly versions of the Rust compiler.  These\n+versions may change rapidly and especially parts of the toolchain such as\n+Clippy, the lint tool, can have false positives or add additional warnings with\n+too great of a frequency to be supportable by the project.  However, we do plan\n+to support alternate compilers, such as the rust_codegen_gcc backend and gccrs\n+when they are stable and support our desired release versions.  This will\n+provide greater support for more operating systems and architectures.\n-- \ngitgitgadget\n\n"},{"id":"526524","messageId":"8e030170ddc3e6307760fa12387b8b4310ae5e26.1758071798.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.v2.git.git.1758071798.gitgitgadget@gmail.com","subject":"[PATCH v2 06/18] BreakingChanges: announce Rust becoming mandatory","fromName":"Patrick Steinhardt via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-09-17T01:16:26Z","receivedAt":"2025-09-17T01:16:48Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"From: Patrick Steinhardt <ps@pks.im>\n\nOver the last couple of years the appetite for bringing Rust into the\ncodebase has grown significantly across the developer base. Introducing\nRust is a major change though and has ramifications for the whole\necosystem:\n\n  - Some platforms have a Rust toolchain available, but have not yet\n    integrated it into their build infrastructure.\n\n  - Some platforms don't have any support for Rust at all.\n\n  - Some platforms may have to figure out how to fit Rust into their\n    bootstrapping sequence.\n\nDue to this, and given that Git is a critical piece of infrastructure\nfor the whole industry, we cannot just introduce such a heavyweight\ndependency without doing our due diligence.\n\nInstead, preceding commits have introduced a test balloon into our build\ninfrastructure that convert one tiny subsystem to use Rust. For now,\nusing Rust to build that subsystem is entirely optional -- if no Rust\nsupport is available, we continue to use the C implementation. This test\nballoon has the intention to give distributions time and let them ease\ninto our adoption of Rust.\n\nHaving multiple implementations of the same subsystem is not sustainable\nthough, and the plan is to eventually be able to use Rust freely all\nacross our codebase. As such, there is the intent to make Rust become a\nmandatory part of our build process.\n\nAdd an announcement to our breaking changes that Rust will become\nmandatory in Git 3.0. A (very careful and non-binding) estimate might be\nthat this major release might be released in the second half of next\nyear, which should give distributors enough time to prepare for the\nchange.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n Documentation/BreakingChanges.adoc | 35 ++++++++++++++++++++++++++++++\n 1 file changed, 35 insertions(+)\n\ndiff --git a/Documentation/BreakingChanges.adoc b/Documentation/BreakingChanges.adoc\nindex f8d2eba061..56bbd5699e 100644\n--- a/Documentation/BreakingChanges.adoc\n+++ b/Documentation/BreakingChanges.adoc\n@@ -165,6 +165,41 @@ A prerequisite for this change is that the ecosystem is ready to support the\n \"reftable\" format. Most importantly, alternative implementations of Git like\n JGit, libgit2 and Gitoxide need to support it.\n \n+* Git will require Rust as a mandatory part of the build process. While Git\n+  already started to adopt Rust in Git 2.49, all parts written in Rust are\n+  optional for the time being. This includes:\n++\n+  ** Subsystems that have an alternative implementation in Rust to test\n+     interoperability between our C and Rust codebase.\n+  ** Newly written features that are not mission critical for a fully functional\n+     Git client.\n++\n+These changes are meant as test balloons to allow distributors of Git to prepare\n+for Rust becoming a mandatory part of the build process. There will be multiple\n+milestones for the introduction of Rust:\n++\n+--\n+1. In Git 2.52, both build systems will default-enable support for Rust.\n+   Consequently, builds will break by default if Rust is not available on the\n+   build host. The use of Rust can still be explicitly disabled via build\n+   flags.\n+2. In Git 3.0, the build options will be removed and support for Rust is\n+   mandatory.\n+--\n++\n+Disable building with Rust:\n+Meson: `meson configure -Dwith_rust=false`.\n+Makefile: `make WITH_RUST=false`,\n++\n+The Git project will declare the last version before Git 3.0 to be a long-term\n+support release. This long-term release will receive important bug fixes for at\n+least four release cycles and security fixes for six release cycles. The Git\n+project will hand over maintainership of the long-term release to distributors\n+in case they need to extend the life of that long-term release even further. In\n+that case, the backporting process will be handled by these distributors, but\n+the backported patches will be reviewed on the mailing list and pulled in by the\n+Git maintainer.\n+\n === Removals\n \n * Support for grafting commits has long been superseded by git-replace(1).\n-- \ngitgitgadget\n\n"},{"id":"526525","messageId":"6032a8740c0ba72420f42c3d8d801e1bdeec12d0.1758071798.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.v2.git.git.1758071798.gitgitgadget@gmail.com","subject":"[PATCH v2 07/18] build: introduce rust","fromName":"Ezekiel Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-09-17T01:16:27Z","receivedAt":"2025-09-17T01:16:49Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"From: Ezekiel Newren <ezekielnewren@gmail.com>\n\nUpcoming patches will simplify xdiff, while also porting parts of it to\nRust. In preparation, add some stubs and setup the Rust build. For now,\nit is easier to let cargo build rust and have make or meson merely link\nagainst the static library that cargo builds. In line with ongoing\nlibification efforts, use multiple crates to allow more modularity on\nthe Rust side. The xdiff and ivec crates are the focal point for this\nseries.\n\ncbindgen will be used to generate header files for each Rust crate for\nC interoperability.\n\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n .gitignore        |  3 ++\n Makefile          | 81 +++++++++++++++++++++++++++++++++++++++--------\n build_rust.sh     | 60 +++++++++++++++++++++++++++++++++++\n meson.build       | 72 ++++++++++++++++++++++++++++++++++++-----\n meson_options.txt |  2 ++\n rust/Cargo.toml   |  3 ++\n 6 files changed, 200 insertions(+), 21 deletions(-)\n create mode 100755 build_rust.sh\n create mode 100644 rust/Cargo.toml\n\ndiff --git a/.gitignore b/.gitignore\nindex 04c444404e..24292eb473 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -254,3 +254,6 @@ Release/\n /contrib/buildsystems/out\n /contrib/libgit-rs/target\n /contrib/libgit-sys/target\n+.idea/\n+/rust/target/\n+/rust/Cargo.lock\ndiff --git a/Makefile b/Makefile\nindex ffb898b611..ef55a8183c 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -483,6 +483,14 @@ include shared.mak\n # Define LIBPCREDIR=/foo/bar if your PCRE header and library files are\n # in /foo/bar/include and /foo/bar/lib directories.\n #\n+# == Optional Rust support ==\n+#\n+# Define WITH_RUST=false if you don't want to include features and subsystems\n+# written in Rust into Git. For now, Rust is still an optional feature of the\n+# build process. With Git 3.0 though, Rust will be mandatory.\n+#\n+# Building Rust code requires Cargo.\n+#\n # == SHA-1 and SHA-256 defines ==\n #\n # === SHA-1 backend ===\n@@ -916,6 +924,51 @@ TEST_SHELL_PATH = $(SHELL_PATH)\n \n LIB_FILE = libgit.a\n \n+EXTLIBS =\n+\n+GIT_BUILD_DIR := $(CURDIR)\n+export GIT_BUILD_DIR\n+\n+RUST_CRATES :=\n+.PHONY: compile_rust rustclean\n+\n+WITH_RUST ?= true\n+ifeq ($(WITH_RUST),true)\n+\n+ifeq ($(DEBUG), 1)\n+\tRUST_BUILD_MODE := debug\n+else\n+\tRUST_BUILD_MODE := release\n+endif\n+\n+RUST_TARGET_DIR := $(GIT_BUILD_DIR)/$(RUST_BUILD_MODE)\n+\n+RUST_LIBS := $(foreach c,$(RUST_CRATES),$(GIT_BUILD_DIR)/lib$(c).a)\n+\n+EXTLIBS += $(RUST_LIBS)\n+\n+compile_rust: $(RUST_LIBS)\n+rustclean:\n+\t$(RM) $(RUST_LIBS)\n+\tcd rust && cargo clean\n+\n+$(GIT_BUILD_DIR)/lib%.a:\n+\t./build_rust.sh $(GIT_BUILD_DIR) $(RUST_BUILD_MODE) $*\n+\n+UNAME_S := $(shell uname -s)\n+ifeq ($(UNAME_S),Linux)\n+\tEXTLIBS += -ldl\n+endif\n+\n+else ifeq ($(WITH_RUST),false)\n+compile_rust:\n+\t:\n+rustclean:\n+\t:\n+else\n+$(error 'WITH_RUST' must be true or false)\n+endif\n+\n GENERATED_H += command-list.h\n GENERATED_H += config-list.h\n GENERATED_H += hook-list.h\n@@ -1403,7 +1456,6 @@ CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o\n UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o\n \n GITLIBS = common-main.o $(LIB_FILE)\n-EXTLIBS =\n \n GIT_USER_AGENT = git/$(GIT_VERSION)\n \n@@ -1421,6 +1473,9 @@ CC_LD_DYNPATH = -Wl,-rpath,\n BASIC_CFLAGS = -I. -fPIE\n BASIC_LDFLAGS =\n \n+ifeq ($(WITH_RUST),true)\n+BASIC_CFLAGS += -DWITH_RUST\n+endif\n # library flags\n ARFLAGS = rcs\n PTHREAD_CFLAGS =\n@@ -2551,7 +2606,7 @@ git.sp git.s git.o: EXTRA_CPPFLAGS = \\\n \t'-DGIT_MAN_PATH=\"$(mandir_relative_SQ)\"' \\\n \t'-DGIT_INFO_PATH=\"$(infodir_relative_SQ)\"'\n \n-git$X: git.o GIT-LDFLAGS $(BUILTIN_OBJS) $(GITLIBS)\n+git$X: git.o GIT-LDFLAGS $(BUILTIN_OBJS) $(GITLIBS) compile_rust\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \\\n \t\t$(filter %.o,$^) $(LIBS)\n \n@@ -2876,17 +2931,17 @@ headless-git.o: compat/win32/headless.c GIT-CFLAGS\n headless-git$X: headless-git.o git.res GIT-LDFLAGS\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) $(ALL_LDFLAGS) -mwindows -o $@ $< git.res\n \n-git-%$X: %.o GIT-LDFLAGS $(GITLIBS)\n+git-%$X: %.o GIT-LDFLAGS $(GITLIBS) compile_rust\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)\n \n-git-imap-send$X: imap-send.o $(IMAP_SEND_BUILDDEPS) GIT-LDFLAGS $(GITLIBS)\n+git-imap-send$X: imap-send.o $(IMAP_SEND_BUILDDEPS) GIT-LDFLAGS $(GITLIBS) compile_rust\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \\\n \t\t$(IMAP_SEND_LDFLAGS) $(LIBS)\n \n-git-http-fetch$X: http.o http-walker.o http-fetch.o GIT-LDFLAGS $(GITLIBS)\n+git-http-fetch$X: http.o http-walker.o http-fetch.o GIT-LDFLAGS $(GITLIBS) compile_rust\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \\\n \t\t$(CURL_LIBCURL) $(LIBS)\n-git-http-push$X: http.o http-push.o GIT-LDFLAGS $(GITLIBS)\n+git-http-push$X: http.o http-push.o GIT-LDFLAGS $(GITLIBS) compile_rust\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \\\n \t\t$(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIBS)\n \n@@ -2896,11 +2951,11 @@ $(REMOTE_CURL_ALIASES): $(REMOTE_CURL_PRIMARY)\n \tln -s $< $@ 2>/dev/null || \\\n \tcp $< $@\n \n-$(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS $(GITLIBS)\n+$(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS $(GITLIBS) compile_rust\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \\\n \t\t$(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIBS)\n \n-scalar$X: scalar.o GIT-LDFLAGS $(GITLIBS)\n+scalar$X: scalar.o GIT-LDFLAGS $(GITLIBS) compile_rust\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \\\n \t\t$(filter %.o,$^) $(LIBS)\n \n@@ -3273,7 +3328,7 @@ perf: all\n \n t/helper/test-tool$X: $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS)) $(UNIT_TEST_DIR)/test-lib.o\n \n-t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS)\n+t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS) compile_rust\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS)\n \n check-sha1:: t/helper/test-tool$X\n@@ -3738,7 +3793,7 @@ cocciclean:\n \t$(RM) -r .build/contrib/coccinelle\n \t$(RM) contrib/coccinelle/*.cocci.patch\n \n-clean: profile-clean coverage-clean cocciclean\n+clean: profile-clean coverage-clean cocciclean rustclean\n \t$(RM) -r .build $(UNIT_TEST_BIN)\n \t$(RM) GIT-TEST-SUITES\n \t$(RM) po/git.pot po/git-core.pot\n@@ -3893,13 +3948,13 @@ FUZZ_CXXFLAGS ?= $(ALL_CFLAGS)\n .PHONY: fuzz-all\n fuzz-all: $(FUZZ_PROGRAMS)\n \n-$(FUZZ_PROGRAMS): %: %.o oss-fuzz/dummy-cmd-main.o $(GITLIBS) GIT-LDFLAGS\n+$(FUZZ_PROGRAMS): %: %.o oss-fuzz/dummy-cmd-main.o $(GITLIBS) GIT-LDFLAGS compile_rust\n \t$(QUIET_LINK)$(FUZZ_CXX) $(FUZZ_CXXFLAGS) -o $@ $(ALL_LDFLAGS) \\\n \t\t-Wl,--allow-multiple-definition \\\n \t\t$(filter %.o,$^) $(filter %.a,$^) $(LIBS) $(LIB_FUZZING_ENGINE)\n \n $(UNIT_TEST_PROGS): $(UNIT_TEST_BIN)/%$X: $(UNIT_TEST_DIR)/%.o $(UNIT_TEST_OBJS) \\\n-\t$(GITLIBS) GIT-LDFLAGS\n+\t$(GITLIBS) GIT-LDFLAGS compile_rust\n \t$(call mkdir_p_parent_template)\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \\\n \t\t$(filter %.o,$^) $(filter %.a,$^) $(LIBS)\n@@ -3918,7 +3973,7 @@ $(UNIT_TEST_DIR)/clar.suite: $(UNIT_TEST_DIR)/clar-decls.h $(UNIT_TEST_DIR)/gene\n $(UNIT_TEST_DIR)/clar/clar.o: $(UNIT_TEST_DIR)/clar.suite\n $(CLAR_TEST_OBJS): $(UNIT_TEST_DIR)/clar-decls.h\n $(CLAR_TEST_OBJS): EXTRA_CPPFLAGS = -I$(UNIT_TEST_DIR)\n-$(CLAR_TEST_PROG): $(UNIT_TEST_DIR)/clar.suite $(CLAR_TEST_OBJS) $(GITLIBS) GIT-LDFLAGS\n+$(CLAR_TEST_PROG): $(UNIT_TEST_DIR)/clar.suite $(CLAR_TEST_OBJS) $(GITLIBS) GIT-LDFLAGS compile_rust\n \t$(call mkdir_p_parent_template)\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)\n \ndiff --git a/build_rust.sh b/build_rust.sh\nnew file mode 100755\nindex 0000000000..b020f2d37a\n--- /dev/null\n+++ b/build_rust.sh\n@@ -0,0 +1,60 @@\n+#!/bin/sh\n+\n+rustc -vV || exit $?\n+cargo --version || exit $?\n+\n+dir_git_root=${0%/*}\n+dir_build=$1\n+rust_build_profile=$2\n+crate=$3\n+\n+dir_rust=$dir_git_root/rust\n+\n+if [ \"$dir_git_root\" = \"\" ]; then\n+  echo \"did not specify the directory for the root of git\"\n+  exit 1\n+fi\n+\n+if [ \"$dir_build\" = \"\" ]; then\n+  echo \"did not specify the build directory\"\n+  exit 1\n+fi\n+\n+if [ \"$rust_build_profile\" = \"\" ]; then\n+  echo \"did not specify the rust_build_profile\"\n+  exit 1\n+fi\n+\n+if [ \"$rust_build_profile\" = \"release\" ]; then\n+  rust_args=\"--release\"\n+  export RUSTFLAGS=''\n+elif [ \"$rust_build_profile\" = \"debug\" ]; then\n+  rust_args=\"\"\n+  export RUSTFLAGS='-C debuginfo=2 -C opt-level=1 -C force-frame-pointers=yes'\n+else\n+  echo \"illegal rust_build_profile value $rust_build_profile\"\n+  exit 1\n+fi\n+\n+libfile=\"lib${crate}.a\"\n+if rustup show active-toolchain | grep windows-msvc; then\n+  libfile=\"${crate}.lib\"\n+  PATH=\"$(echo $PATH | tr ':' '\\n' | grep -Ev \"^(/mingw64/bin|/usr/bin)$\" | paste -sd: -):/mingw64/bin:/usr/bin\"\n+fi\n+\n+CARGO_TARGET_DIR=$dir_git_root/.build/rust/$crate\n+export CARGO_TARGET_DIR\n+\n+cd $dir_rust && cargo clean && pwd && USE_LINKING=\"false\" cargo build -p $crate $rust_args; cd $dir_git_root\n+\n+src=$CARGO_TARGET_DIR/$rust_build_profile/$libfile\n+dst=$dir_build/$libfile\n+\n+if [ ! -f $src ]; then\n+  echo >&2 \"::error:: cannot find path of static library $src is not a file or does not exist\"\n+  exit 5\n+fi\n+\n+rm $dst 2>/dev/null\n+echo mv $src $dst\n+mv $src $dst\ndiff --git a/meson.build b/meson.build\nindex 5dd299b496..cbe1afac83 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -267,6 +267,55 @@ version_gen_environment.set('GIT_DATE', get_option('build_date'))\n version_gen_environment.set('GIT_USER_AGENT', get_option('user_agent'))\n version_gen_environment.set('GIT_VERSION', get_option('version'))\n \n+rust_crates = []\n+\n+rust_builds = []\n+with_rust = get_option('with_rust')\n+if with_rust\n+  rustc = find_program('rustc', required : false)\n+  cargo = find_program('cargo', required : false)\n+\n+  if not rustc.found() or not cargo.found()\n+    error('Rust toolchain not found. Reconfigure with -Dwith_rust=false')\n+  endif\n+\n+  if get_option('optimization') in ['2', '3', 's', 'z']\n+    rust_build_profile = 'release'\n+  else\n+    rust_build_profile = 'debug'\n+  endif\n+\n+  # Run `rustup show active-toolchain` and capture output\n+  rustup_out = run_command('rustup', 'show', 'active-toolchain',\n+                           check: true).stdout().strip()\n+\n+  rust_environment = script_environment\n+\n+  foreach crate : rust_crates\n+    if rustup_out.contains('windows-msvc')\n+      libfile = crate + '.lib'\n+    else\n+      libfile = 'lib' + crate + '.a'\n+    endif\n+\n+    rust_builds += custom_target(\n+      'rust_build_'+crate,\n+      output: libfile,\n+      build_by_default: true,\n+      build_always_stale: true,\n+      command: [\n+        meson.project_source_root() / 'build_rust.sh',\n+        meson.current_build_dir(), rust_build_profile, crate,\n+      ],\n+      install: false,\n+      env: rust_environment,\n+    )\n+  endforeach\n+else\n+  message('Rust components disabled (-Dwith_rust=false)')\n+endif\n+\n+\n compiler = meson.get_compiler('c')\n \n libgit_sources = [\n@@ -774,6 +823,10 @@ libgit_c_args = [\n   '-DSHELL_PATH=\"' + fs.as_posix(target_shell.full_path()) + '\"',\n ]\n \n+if with_rust\n+  libgit_c_args += '-DWITH_RUST'\n+endif\n+\n system_attributes = get_option('gitattributes')\n if system_attributes != ''\n   libgit_c_args += '-DETC_GITATTRIBUTES=\"' + system_attributes + '\"'\n@@ -1266,6 +1319,7 @@ elif host_machine.system() == 'windows'\n   ]\n \n   libgit_dependencies += compiler.find_library('ntdll')\n+  libgit_dependencies += compiler.find_library('userenv')\n   libgit_include_directories += 'compat/win32'\n   if compiler.get_id() == 'msvc'\n     libgit_include_directories += 'compat/vcbuild/include'\n@@ -1702,14 +1756,16 @@ version_def_h = custom_target(\n libgit_sources += version_def_h\n \n libgit = declare_dependency(\n-  link_with: static_library('git',\n-    sources: libgit_sources,\n-    c_args: libgit_c_args + [\n-      '-DGIT_VERSION_H=\"' + version_def_h.full_path() + '\"',\n-    ],\n-    dependencies: libgit_dependencies,\n-    include_directories: libgit_include_directories,\n-  ),\n+  link_with: [\n+    static_library('git',\n+      sources: libgit_sources,\n+      c_args: libgit_c_args + [\n+        '-DGIT_VERSION_H=\"' + version_def_h.full_path() + '\"',\n+      ],\n+      dependencies: libgit_dependencies,\n+      include_directories: libgit_include_directories,\n+    ),\n+  ] + rust_builds,\n   compile_args: libgit_c_args,\n   dependencies: libgit_dependencies,\n   include_directories: libgit_include_directories,\ndiff --git a/meson_options.txt b/meson_options.txt\nindex 1668f260a1..362e928c79 100644\n--- a/meson_options.txt\n+++ b/meson_options.txt\n@@ -69,6 +69,8 @@ option('zlib_backend', type: 'combo', choices: ['auto', 'zlib', 'zlib-ng'], valu\n   description: 'The backend used for compressing objects and other data.')\n \n # Build tweaks.\n+option('with_rust', type: 'boolean', value: true,\n+       description: 'Enable building with Rust, true by default.')\n option('breaking_changes', type: 'boolean', value: false,\n   description: 'Enable upcoming breaking changes.')\n option('macos_use_homebrew_gettext', type: 'boolean', value: true,\ndiff --git a/rust/Cargo.toml b/rust/Cargo.toml\nnew file mode 100644\nindex 0000000000..9bc2f769fb\n--- /dev/null\n+++ b/rust/Cargo.toml\n@@ -0,0 +1,3 @@\n+[workspace]\n+members = []\n+resolver = \"2\"\n-- \ngitgitgadget\n\n"},{"id":"526526","messageId":"cd04ed5aa707e12df51e34c65f5e5d7361179d58.1758071798.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.v2.git.git.1758071798.gitgitgadget@gmail.com","subject":"[PATCH v2 08/18] help: report on whether or not Rust is enabled","fromName":"Patrick Steinhardt via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-09-17T01:16:28Z","receivedAt":"2025-09-17T01:16:50Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"From: Patrick Steinhardt <ps@pks.im>\n\nWe're about to introduce support for Rust into the core of Git, where\nsome (trivial) subsystems are converted to Rust. These subsystems will\nalso retain a C implementation though as Rust is not yet mandatory.\nConsequently, it now becomes possible for a Git version to have bugs\nthat are specific to whether or not it is built with Rust support\noverall.\n\nExpose information about whether or not Git was built with Rust via our\nbuild info. This means that both `git version --build-options`, but also\n`git bugreport` will now expose that bit of information. Hopefully, this\nshould make it easier for us to discover any Rust-specific issues.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n help.c | 6 ++++++\n 1 file changed, 6 insertions(+)\n\ndiff --git a/help.c b/help.c\nindex bb20498cfd..5854dd4a7e 100644\n--- a/help.c\n+++ b/help.c\n@@ -791,6 +791,12 @@ void get_version_info(struct strbuf *buf, int show_build_options)\n \t\tstrbuf_addf(buf, \"shell-path: %s\\n\", SHELL_PATH);\n \t\t/* NEEDSWORK: also save and output GIT-BUILD_OPTIONS? */\n \n+#if defined WITH_RUST\n+\t\tstrbuf_addstr(buf, \"rust: enabled\\n\");\n+#else\n+\t\tstrbuf_addstr(buf, \"rust: disabled\\n\");\n+#endif\n+\n \t\tif (fsmonitor_ipc__is_supported())\n \t\t\tstrbuf_addstr(buf, \"feature: fsmonitor--daemon\\n\");\n #if defined LIBCURL_VERSION\n-- \ngitgitgadget\n\n"},{"id":"526527","messageId":"fcdfc55fb7d7da7d65405486f5eec10e5892a028.1758071798.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.v2.git.git.1758071798.gitgitgadget@gmail.com","subject":"[PATCH v2 09/18] github workflows: install rust","fromName":"Ezekiel Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-09-17T01:16:29Z","receivedAt":"2025-09-17T01:16:52Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"From: Ezekiel Newren <ezekielnewren@gmail.com>\n\nPrefer using actions-rs/toolchain@v1 where possible to install rustup,\nbut for docker targets use a script to install rustup. Consolidate the\nRust toolchain definitions in main.yaml. Use install-rust-toolchain.sh\nto ensure the correct toolchain is used. Five overrides are used in\nmain.yaml:\n\n  * On Windows: Rust didn't resolve the bcrypt library on Windows\n    correctly until version 1.78.0. Also since rustup mis-identifies\n    the Rust toolchain, the Rust target triple must be set to\n    x86_64-pc-windows-gnu for make (win build), and\n    x86_64-pc-windows-msvc for meson (win+Meson build).\n  * MSVC builds: Rearrange PATH to look in /mingw64/bin and /usr/bin\n    last.\n  * On musl: libc differences, such as ftruncate64 vs ftruncate, were\n    not accounted for until Rust version 1.72.0. No older version of\n    Rust will work on musl for our needs.\n  * In a 32-bit docker container running on a 64-bit host, we need to\n    override the Rust target triple. This is because rustup asks the\n    kernel for the bitness of the system and it says 64, even though\n    the container is 32-bit. This also allows us to remove the\n    BITNESS environment variable in ci/lib.sh.\n\nThe logic for selecting library names was initially provided in a patch\nfrom Johannes, but was reworked and squashed into this commit.\n\nHelped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n .github/workflows/main.yml   | 62 +++++++++++++++++++++++++++++++++++-\n ci/install-dependencies.sh   | 14 ++++----\n ci/install-rust-toolchain.sh | 30 +++++++++++++++++\n ci/install-rustup.sh         | 25 +++++++++++++++\n ci/lib.sh                    |  1 +\n ci/make-test-artifacts.sh    |  9 ++++++\n ci/run-build-and-tests.sh    | 13 ++++++++\n 7 files changed, 146 insertions(+), 8 deletions(-)\n create mode 100755 ci/install-rust-toolchain.sh\n create mode 100755 ci/install-rustup.sh\n\ndiff --git a/.github/workflows/main.yml b/.github/workflows/main.yml\nindex d122e79415..e986684a2f 100644\n--- a/.github/workflows/main.yml\n+++ b/.github/workflows/main.yml\n@@ -26,6 +26,13 @@ jobs:\n     outputs:\n       enabled: ${{ steps.check-ref.outputs.enabled }}${{ steps.skip-if-redundant.outputs.enabled }}\n       skip_concurrent: ${{ steps.check-ref.outputs.skip_concurrent }}\n+      rust_version_minimum: 1.63.0\n+      rust_version_windows: 1.78.0\n+      rust_version_musl: 1.72.0\n+      ## the rust target is inferred by rustup unless specified\n+      rust_target_windows_make: x86_64-pc-windows-gnu\n+      rust_target_windows_meson: x86_64-pc-windows-msvc\n+      rust_target_32bit_linux: i686-unknown-linux-gnu\n     steps:\n       - name: try to clone ci-config branch\n         run: |\n@@ -108,12 +115,28 @@ jobs:\n     needs: ci-config\n     if: needs.ci-config.outputs.enabled == 'yes'\n     runs-on: windows-latest\n+    env:\n+      CARGO_HOME: \"/c/Users/runneradmin/.cargo\"\n     concurrency:\n       group: windows-build-${{ github.ref }}\n       cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }}\n     steps:\n     - uses: actions/checkout@v4\n     - uses: git-for-windows/setup-git-for-windows-sdk@v1\n+      with:\n+        flavor: full\n+    - name: Install rustup via github actions\n+      uses: actions-rs/toolchain@v1\n+      with:\n+        toolchain: stable\n+        profile: minimal\n+        override: false\n+    - name: Install Rust toolchain\n+      shell: bash\n+      env:\n+        RUST_VERSION: ${{ needs.ci-config.outputs.rust_version_windows }}\n+        RUST_TARGET: ${{ needs.ci-config.outputs.rust_target_windows_make }}\n+      run: ci/install-rust-toolchain.sh\n     - name: build\n       shell: bash\n       env:\n@@ -254,12 +277,26 @@ jobs:\n     needs: ci-config\n     if: needs.ci-config.outputs.enabled == 'yes'\n     runs-on: windows-latest\n+    env:\n+      CARGO_HOME: \"/c/Users/runneradmin/.cargo\"\n     concurrency:\n       group: windows-meson-build-${{ github.ref }}\n       cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }}\n     steps:\n     - uses: actions/checkout@v4\n     - uses: actions/setup-python@v5\n+    - name: Install rustup via github actions\n+      uses: actions-rs/toolchain@v1\n+      with:\n+        toolchain: stable\n+        profile: minimal\n+        override: false\n+    - name: Install Rust toolchain\n+      shell: bash\n+      env:\n+        RUST_VERSION: ${{ needs.ci-config.outputs.rust_version_windows }}\n+        RUST_TARGET: ${{ needs.ci-config.outputs.rust_target_windows_meson }}\n+      run: ci/install-rust-toolchain.sh\n     - name: Set up dependencies\n       shell: pwsh\n       run: pip install meson ninja\n@@ -329,11 +366,24 @@ jobs:\n       jobname: ${{matrix.vector.jobname}}\n       CI_JOB_IMAGE: ${{matrix.vector.pool}}\n       TEST_OUTPUT_DIRECTORY: ${{github.workspace}}/t\n+      CARGO_HOME: \"/Users/runner/.cargo\"\n     runs-on: ${{matrix.vector.pool}}\n     steps:\n     - uses: actions/checkout@v4\n     - run: ci/install-dependencies.sh\n-    - run: ci/run-build-and-tests.sh\n+    - name: Install rustup via github actions\n+      uses: actions-rs/toolchain@v1\n+      with:\n+        toolchain: stable\n+        profile: minimal\n+        override: false\n+    - name: Install Rust toolchain\n+      shell: bash\n+      env:\n+        RUST_VERSION: ${{ needs.ci-config.outputs.rust_version_minimum }}\n+      run: ci/install-rust-toolchain.sh\n+    - name: Run build and tests\n+      run: ci/run-build-and-tests.sh\n     - name: print test failures\n       if: failure() && env.FAILED_TEST_ARTIFACTS != ''\n       run: ci/print-test-failures.sh\n@@ -393,9 +443,11 @@ jobs:\n           cc: gcc\n         - jobname: linux-musl-meson\n           image: alpine:latest\n+          rust_version_override: ${{ needs.ci-config.outputs.rust_version_musl }}\n         # Supported until 2025-04-02.\n         - jobname: linux32\n           image: i386/ubuntu:focal\n+          rust_target_override: ${{ needs.ci-config.outputs.rust_target_32bit_linux }}\n         - jobname: pedantic\n           image: fedora:latest\n         # A RHEL 8 compatible distro.  Supported until 2029-05-31.\n@@ -409,6 +461,7 @@ jobs:\n       CC: ${{matrix.vector.cc}}\n       CI_JOB_IMAGE: ${{matrix.vector.image}}\n       CUSTOM_PATH: /custom\n+      CARGO_HOME: /home/builder/.cargo\n     runs-on: ubuntu-latest\n     container: ${{matrix.vector.image}}\n     steps:\n@@ -433,6 +486,13 @@ jobs:\n     - run: ci/install-dependencies.sh\n     - run: useradd builder --create-home\n     - run: chown -R builder .\n+    - name: Install rustup via script\n+      run: sudo --preserve-env --set-home --user=builder ci/install-rustup.sh\n+    - name: Install Rust toolchain\n+      env:\n+        RUST_VERSION: ${{ matrix.vector.rust_version_override || needs.ci-config.outputs.rust_version_minimum }}\n+        RUST_TARGET: ${{ matrix.vector.rust_target_override || '' }}\n+      run: sudo --preserve-env --set-home --user=builder ci/install-rust-toolchain.sh\n     - run: sudo --preserve-env --set-home --user=builder ci/run-build-and-tests.sh\n     - name: print test failures\n       if: failure() && env.FAILED_TEST_ARTIFACTS != ''\ndiff --git a/ci/install-dependencies.sh b/ci/install-dependencies.sh\nindex d061a47293..7801075821 100755\n--- a/ci/install-dependencies.sh\n+++ b/ci/install-dependencies.sh\n@@ -24,14 +24,14 @@ fi\n \n case \"$distro\" in\n alpine-*)\n-\tapk add --update shadow sudo meson ninja-build gcc libc-dev curl-dev openssl-dev expat-dev gettext \\\n+\tapk add --update shadow sudo meson ninja-build gcc libc-dev curl curl-dev openssl-dev expat-dev gettext \\\n \t\tzlib-ng-dev pcre2-dev python3 musl-libintl perl-utils ncurses \\\n \t\tapache2 apache2-http2 apache2-proxy apache2-ssl apache2-webdav apr-util-dbd_sqlite3 \\\n \t\tbash cvs gnupg perl-cgi perl-dbd-sqlite perl-io-tty >/dev/null\n \t;;\n fedora-*|almalinux-*)\n \tdnf -yq update >/dev/null &&\n-\tdnf -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\n+\tdnf -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\n \t;;\n ubuntu-*|i386/ubuntu-*|debian-*)\n \t# Required so that apt doesn't wait for user input on certain packages.\n@@ -55,8 +55,8 @@ ubuntu-*|i386/ubuntu-*|debian-*)\n \tsudo apt-get -q update\n \tsudo apt-get -q -y install \\\n \t\t$LANGUAGES apache2 cvs cvsps git gnupg $SVN \\\n-\t\tmake libssl-dev libcurl4-openssl-dev libexpat-dev wget sudo default-jre \\\n-\t\ttcl tk gettext zlib1g-dev perl-modules liberror-perl libauthen-sasl-perl \\\n+\t\tmake libssl-dev curl libcurl4-openssl-dev libexpat-dev wget sudo default-jre \\\n+\t\ttcl tk gettext zlib1g zlib1g-dev perl-modules liberror-perl libauthen-sasl-perl \\\n \t\tlibemail-valid-perl libio-pty-perl libio-socket-ssl-perl libnet-smtp-ssl-perl libdbd-sqlite3-perl libcgi-pm-perl \\\n \t\tlibsecret-1-dev libpcre2-dev meson ninja-build pkg-config \\\n \t\t${CC_PACKAGE:-${CC:-gcc}} $PYTHON_PACKAGE\n@@ -121,13 +121,13 @@ ClangFormat)\n \t;;\n StaticAnalysis)\n \tsudo apt-get -q update\n-\tsudo apt-get -q -y install coccinelle libcurl4-openssl-dev libssl-dev \\\n+\tsudo apt-get -q -y install coccinelle curl libcurl4-openssl-dev libssl-dev \\\n \t\tlibexpat-dev gettext make\n \t;;\n sparse)\n \tsudo apt-get -q update -q\n-\tsudo apt-get -q -y install libssl-dev libcurl4-openssl-dev \\\n-\t\tlibexpat-dev gettext zlib1g-dev sparse\n+\tsudo apt-get -q -y install libssl-dev curl libcurl4-openssl-dev \\\n+\t\tlibexpat-dev gettext zlib1g zlib1g-dev sparse\n \t;;\n Documentation)\n \tsudo apt-get -q update\ndiff --git a/ci/install-rust-toolchain.sh b/ci/install-rust-toolchain.sh\nnew file mode 100755\nindex 0000000000..06a29c4cfa\n--- /dev/null\n+++ b/ci/install-rust-toolchain.sh\n@@ -0,0 +1,30 @@\n+#!/bin/sh\n+\n+if [ \"$CARGO_HOME\" = \"\" ]; then\n+  echo >&2 \"::error:: CARGO_HOME is not set\"\n+  exit 2\n+fi\n+export PATH=\"$CARGO_HOME/bin:$PATH\"\n+rustup -vV || exit $?\n+\n+## Enforce the correct Rust toolchain\n+rustup override unset || true\n+\n+## install a specific version of rust\n+if [ \"$RUST_TARGET\" != \"\" ]; then\n+  rustup default --force-non-host \"$RUST_VERSION-$RUST_TARGET\" || exit $?\n+else\n+  rustup default \"$RUST_VERSION\" || exit $?\n+fi\n+\n+rustc -vV || exit $?\n+\n+RE_RUST_TARGET=\"$RUST_TARGET\"\n+if [ \"$RUST_TARGET\" = \"\" ]; then\n+  RE_RUST_TARGET=\"[^ ]+\"\n+fi\n+\n+if ! rustup show active-toolchain | grep -E \"^$RUST_VERSION-$RE_RUST_TARGET \\(default\\)$\"; then\n+  echo >&2 \"::error:: wrong Rust toolchain, active-toolchain: $(rustup show active-toolchain)\"\n+  exit 3\n+fi\ndiff --git a/ci/install-rustup.sh b/ci/install-rustup.sh\nnew file mode 100755\nindex 0000000000..0036231aee\n--- /dev/null\n+++ b/ci/install-rustup.sh\n@@ -0,0 +1,25 @@\n+#!/bin/sh\n+\n+## github workflows actions-rs/toolchain@v1 doesn't work for docker\n+## targets. This script should only be used if the ci pipeline\n+## doesn't support installing rust on a particular target.\n+\n+if [ \"$(id -u)\" -eq 0 ]; then\n+  echo >&2 \"::warning:: installing rust as root\"\n+fi\n+\n+if [ \"$CARGO_HOME\" = \"\" ]; then\n+  echo >&2 \"::error:: CARGO_HOME is not set\"\n+  exit 2\n+fi\n+\n+export RUSTUP_HOME=$CARGO_HOME\n+\n+## install rustup\n+curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --default-toolchain none -y\n+if [ ! -f $CARGO_HOME/env ]; then\n+  echo \"PATH=$CARGO_HOME/bin:\\$PATH\" > $CARGO_HOME/env\n+fi\n+. $CARGO_HOME/env\n+\n+rustup -vV\ndiff --git a/ci/lib.sh b/ci/lib.sh\nindex f561884d40..a7992b22fd 100755\n--- a/ci/lib.sh\n+++ b/ci/lib.sh\n@@ -1,5 +1,6 @@\n # Library of functions shared by all CI scripts\n \n+\n if test true = \"$GITHUB_ACTIONS\"\n then\n \tbegin_group () {\ndiff --git a/ci/make-test-artifacts.sh b/ci/make-test-artifacts.sh\nindex 74141af0cc..e37ed7030c 100755\n--- a/ci/make-test-artifacts.sh\n+++ b/ci/make-test-artifacts.sh\n@@ -7,6 +7,15 @@ mkdir -p \"$1\" # in case ci/lib.sh decides to quit early\n \n . ${0%/*}/lib.sh\n \n+## ensure rustup is in the PATH variable\n+if [ \"$CARGO_HOME\" = \"\" ]; then\n+  echo >&2 \"::error:: CARGO_HOME is not set\"\n+  exit 2\n+fi\n+export PATH=\"$CARGO_HOME/bin:$PATH\"\n+\n+rustc -vV\n+\n group Build make artifacts-tar ARTIFACTS_DIRECTORY=\"$1\"\n \n check_unignored_build_artifacts\ndiff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh\nindex 01823fd0f1..22b61e2812 100755\n--- a/ci/run-build-and-tests.sh\n+++ b/ci/run-build-and-tests.sh\n@@ -5,6 +5,15 @@\n \n . ${0%/*}/lib.sh\n \n+## ensure rustup is in the PATH variable\n+if [ \"$CARGO_HOME\" = \"\" ]; then\n+  echo >&2 \"::error:: CARGO_HOME is not set\"\n+  exit 2\n+fi\n+. $CARGO_HOME/env\n+\n+rustc -vV || exit $?\n+\n run_tests=t\n \n case \"$jobname\" in\n@@ -72,5 +81,9 @@ case \"$jobname\" in\n \t;;\n esac\n \n+if [ -d \"$CARGO_HOME\" ]; then\n+  rm -rf $CARGO_HOME\n+fi\n+\n check_unignored_build_artifacts\n save_good_tree\n-- \ngitgitgadget\n\n"},{"id":"526528","messageId":"6eeef4081cee269d5cf04df7628e693edd4537c1.1758071798.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.v2.git.git.1758071798.gitgitgadget@gmail.com","subject":"[PATCH v2 10/18] win+Meson: do allow linking with the Rust-built xdiff","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-09-17T01:16:30Z","receivedAt":"2025-09-17T01:16:53Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWhen linking against the Rust-built `xdiff`, there is now a new required\ndependency: Without _also_ linking to the system library `userenv`, the\ncompile would fail with this error message:\n\n  xdiff.lib(std-c85e9beb7923f636.std.df32d1bc89881d89-cgu.0.rcgu.o) :\n  error LNK2019: unresolved external symbol __imp_GetUserProfileDirectoryW\n  referenced in function _ZN3std3env8home_dir17hfd1c3b6676cd78f6E\n\nTherefore, just like we do in case of Makefile-based builds on Windows,\nwe now also link to that library when building with Meson.\n\nNote that if we only have Rust depend upon libuserenv then at link time\nGCC would complain about:\n\n  undefined reference to `GetUserProfileDirectoryW'\n\nApparently there is _some_ closure that gets compiled in that requires\nthis function, and that in turn forces Git to link to libuserenv.\n\nThis is a new requirement, and therefore has not been made part of the\n\"minimal Git for Windows SDK\".\n\nIn the near future, I intend to include it, but for now let's just\nensure that the file is added manually if it is missing.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n[en: Squashed a few of Johannes's patches, and moved lib userenv\n handling from an earlier patch]\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n config.mak.uname | 4 ++++\n 1 file changed, 4 insertions(+)\n\ndiff --git a/config.mak.uname b/config.mak.uname\nindex 1691c6ae6e..9161694dc4 100644\n--- a/config.mak.uname\n+++ b/config.mak.uname\n@@ -759,6 +759,10 @@ ifeq ($(uname_S),MINGW)\n \t\tCOMPAT_CFLAGS += -D_USE_32BIT_TIME_T\n \t\tBASIC_LDFLAGS += -Wl,--large-address-aware\n         endif\n+\n+\t# Unfortunately now needed because of Rust\n+\tEXTLIBS += -luserenv\n+\n \tCC = gcc\n \tCOMPAT_CFLAGS += -D__USE_MINGW_ANSI_STDIO=0 -DDETECT_MSYS_TTY \\\n \t\t-fstack-protector-strong\n-- \ngitgitgadget\n\n"},{"id":"526529","messageId":"1e13d6c45fcca68eae493689470c58b6be2e2f81.1758071798.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.v2.git.git.1758071798.gitgitgadget@gmail.com","subject":"[PATCH v2 11/18] github workflows: upload Cargo.lock","fromName":"Ezekiel Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-09-17T01:16:31Z","receivedAt":"2025-09-17T01:16:54Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"From: Ezekiel Newren <ezekielnewren@gmail.com>\n\nMake each ci workflow upload its Cargo.lock file as a build artifact so\nthat we can audit build dependencies.\n\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n .github/workflows/main.yml | 20 ++++++++++++++++++++\n 1 file changed, 20 insertions(+)\n\ndiff --git a/.github/workflows/main.yml b/.github/workflows/main.yml\nindex e986684a2f..ff314d48b7 100644\n--- a/.github/workflows/main.yml\n+++ b/.github/workflows/main.yml\n@@ -150,6 +150,11 @@ jobs:\n       with:\n         name: windows-artifacts\n         path: artifacts\n+    - name: upload Cargo.lock\n+      uses: actions/upload-artifact@v4\n+      with:\n+        name: cargo-lock-windows\n+        path: rust/Cargo.lock\n   windows-test:\n     name: win test\n     runs-on: windows-latest\n@@ -311,6 +316,11 @@ jobs:\n       with:\n         name: windows-meson-artifacts\n         path: build\n+    - name: Upload Cargo.lock\n+      uses: actions/upload-artifact@v4\n+      with:\n+        name: cargo-lock-windows-meson\n+        path: rust/Cargo.lock\n   windows-meson-test:\n     name: win+Meson test\n     runs-on: windows-latest\n@@ -393,6 +403,11 @@ jobs:\n       with:\n         name: failed-tests-${{matrix.vector.jobname}}\n         path: ${{env.FAILED_TEST_ARTIFACTS}}\n+    - name: Upload Cargo.lock\n+      uses: actions/upload-artifact@v4\n+      with:\n+        name: cargo-lock-${{matrix.vector.jobname}}\n+        path: rust/Cargo.lock\n   fuzz-smoke-test:\n     name: fuzz smoke test\n     needs: ci-config\n@@ -503,6 +518,11 @@ jobs:\n       with:\n         name: failed-tests-${{matrix.vector.jobname}}\n         path: ${{env.FAILED_TEST_ARTIFACTS}}\n+    - name: Upload Cargo.lock\n+      uses: actions/upload-artifact@v4\n+      with:\n+        name: cargo-lock-${{matrix.vector.jobname}}\n+        path: rust/Cargo.lock\n   static-analysis:\n     needs: ci-config\n     if: needs.ci-config.outputs.enabled == 'yes'\n-- \ngitgitgadget\n\n"},{"id":"526530","messageId":"1c923a71f27ba204ea17a725daf45576d0aa3ec5.1758071798.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.v2.git.git.1758071798.gitgitgadget@gmail.com","subject":"[PATCH v2 12/18] build: new crate, build-helper","fromName":"Ezekiel Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-09-17T01:16:32Z","receivedAt":"2025-09-17T01:16:56Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"From: Ezekiel Newren <ezekielnewren@gmail.com>\n\nMake a crate that takes care of the boiler-plate code that most\nbuild.rs files would need to write.\n\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n .gitignore                   | 1 +\n rust/Cargo.toml              | 4 +++-\n rust/build-helper/Cargo.toml | 7 +++++++\n rust/build-helper/src/lib.rs | 0\n 4 files changed, 11 insertions(+), 1 deletion(-)\n create mode 100644 rust/build-helper/Cargo.toml\n create mode 100644 rust/build-helper/src/lib.rs\n\ndiff --git a/.gitignore b/.gitignore\nindex 24292eb473..e595759d71 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -257,3 +257,4 @@ Release/\n .idea/\n /rust/target/\n /rust/Cargo.lock\n+\ndiff --git a/rust/Cargo.toml b/rust/Cargo.toml\nindex 9bc2f769fb..aaf008b5ee 100644\n--- a/rust/Cargo.toml\n+++ b/rust/Cargo.toml\n@@ -1,3 +1,5 @@\n [workspace]\n-members = []\n+members = [\n+    \"build-helper\",\n+]\n resolver = \"2\"\ndiff --git a/rust/build-helper/Cargo.toml b/rust/build-helper/Cargo.toml\nnew file mode 100644\nindex 0000000000..4455fff5ce\n--- /dev/null\n+++ b/rust/build-helper/Cargo.toml\n@@ -0,0 +1,7 @@\n+[package]\n+name = \"build-helper\"\n+version = \"0.1.0\"\n+edition = \"2021\"\n+\n+[dependencies]\n+\ndiff --git a/rust/build-helper/src/lib.rs b/rust/build-helper/src/lib.rs\nnew file mode 100644\nindex 0000000000..e69de29bb2\n-- \ngitgitgadget\n\n"},{"id":"526531","messageId":"6a27e07e6310b6cad0e3feae817269b9b8eaed69.1758071798.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.v2.git.git.1758071798.gitgitgadget@gmail.com","subject":"[PATCH v2 13/18] build-helper: link against libgit.a and any other required C libraries","fromName":"Ezekiel Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-09-17T01:16:33Z","receivedAt":"2025-09-17T01:16:57Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"From: Ezekiel Newren <ezekielnewren@gmail.com>\n\nDon't link against the C libraries when building with Make or Meson.\nRun cargo tests like this:\ncd rust && cargo clean && USE_LINKING=true cargo test\n\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n rust/build-helper/Cargo.toml |  1 -\n rust/build-helper/src/lib.rs | 84 ++++++++++++++++++++++++++++++++++++\n 2 files changed, 84 insertions(+), 1 deletion(-)\n\ndiff --git a/rust/build-helper/Cargo.toml b/rust/build-helper/Cargo.toml\nindex 4455fff5ce..8939b4b876 100644\n--- a/rust/build-helper/Cargo.toml\n+++ b/rust/build-helper/Cargo.toml\n@@ -4,4 +4,3 @@ version = \"0.1.0\"\n edition = \"2021\"\n \n [dependencies]\n-\ndiff --git a/rust/build-helper/src/lib.rs b/rust/build-helper/src/lib.rs\nindex e69de29bb2..70e95d16f5 100644\n--- a/rust/build-helper/src/lib.rs\n+++ b/rust/build-helper/src/lib.rs\n@@ -0,0 +1,84 @@\n+use std::collections::HashMap;\n+use std::path::PathBuf;\n+\n+\n+fn parse_bool_from_str(value: &str) -> bool {\n+    match value {\n+        \"1\" | \"true\"  | \"yes\" | \"on\"  => true,\n+        \"0\" | \"false\" | \"no\"  | \"off\" => false,\n+        _ => false\n+    }\n+}\n+\n+fn parse_bool_from_option(value: Option<&String>, default: bool) -> bool {\n+    match value {\n+        Some(v) => {\n+            parse_bool_from_str(v.as_str())\n+        },\n+        None => default,\n+    }\n+}\n+\n+/// To build without linking against C libraries run `USE_LINKING=false cargo build`\n+/// To run tests set GIT_BUILD_DIR and run `USE_LINKING=true cargo test`\n+pub struct BuildHelper {\n+    crate_env: HashMap<String, String>,\n+}\n+\n+\n+impl BuildHelper {\n+    pub fn new(crate_env: HashMap<String, String>) -> Self {\n+        let it = Self {crate_env};\n+\n+        let dir_crate = it.dir_crate();\n+        let dir_workspace = dir_crate.parent().unwrap();\n+        let dir_git = dir_workspace.parent().unwrap();\n+        let dir_interop = dir_git.join(\"interop\");\n+        if !dir_interop.exists() {\n+            std::fs::create_dir(dir_interop.clone()).unwrap();\n+        }\n+\n+        it\n+    }\n+\n+    pub fn crate_name(&self) -> String {\n+        self.crate_env[\"CARGO_PKG_NAME\"].clone()\n+    }\n+\n+    pub fn dir_crate(&self) -> PathBuf {\n+        PathBuf::from(self.crate_env[\"CARGO_MANIFEST_DIR\"].clone())\n+    }\n+\n+    pub fn build(self) {\n+        let use_linking = parse_bool_from_option(self.crate_env.get(\"USE_LINKING\"), self.crate_env.get(\"CARGO_TARGET_DIR\").is_none());\n+        let dir_crate = self.dir_crate();\n+        let dir_git = dir_crate.parent().unwrap().parent().unwrap();\n+\n+        println!(\"cargo:rerun-if-changed={}\", dir_git.display());\n+\n+        if use_linking {\n+            if let Some(git_build_dir) = self.crate_env.get(\"GIT_BUILD_DIR\") {\n+                let mut path_git_build_dir = PathBuf::from(git_build_dir);\n+                path_git_build_dir = path_git_build_dir.canonicalize().unwrap();\n+                if !path_git_build_dir.is_dir() {\n+                    panic!(\"'GIT_BUILD_DIR' is not a directory: {}\", path_git_build_dir.display());\n+                }\n+                println!(\"cargo:rustc-link-search=native={}\", git_build_dir);\n+            } else {\n+                panic!(\"environment variable 'GIT_BUILD_DIR' is not set\");\n+            }\n+\n+            println!(\"cargo:rustc-link-lib=static=git\");\n+            println!(\"cargo:rustc-link-lib=pcre2-8\");\n+            if self.crate_env.get(\"ZLIB_NG\").is_some() {\n+                println!(\"cargo:rustc-link-lib=z-ng\");\n+            } else {\n+                println!(\"cargo:rustc-link-lib=z\");\n+            }\n+        } else {\n+            println!(\"cargo:warning={} is not linking against C objects, `USE_LINKING=true cargo test`\", self.crate_env[\"CARGO_PKG_NAME\"]);\n+        }\n+    }\n+}\n+\n+\n-- \ngitgitgadget\n\n"},{"id":"526532","messageId":"fa334405686a329dd1508bf8d8cbfa12dc5dc7bb.1758071798.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.v2.git.git.1758071798.gitgitgadget@gmail.com","subject":"[PATCH v2 14/18] build-helper: cbindgen, let crates generate a header file","fromName":"Ezekiel Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-09-17T01:16:34Z","receivedAt":"2025-09-17T01:16:58Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"From: Ezekiel Newren <ezekielnewren@gmail.com>\n\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n rust/build-helper/Cargo.toml |  5 ++++\n rust/build-helper/src/lib.rs | 44 +++++++++++++++++++++++++++++++++++-\n rust/cbindgen-template.toml  | 16 +++++++++++++\n 3 files changed, 64 insertions(+), 1 deletion(-)\n create mode 100644 rust/cbindgen-template.toml\n\ndiff --git a/rust/build-helper/Cargo.toml b/rust/build-helper/Cargo.toml\nindex 8939b4b876..2e42bb5405 100644\n--- a/rust/build-helper/Cargo.toml\n+++ b/rust/build-helper/Cargo.toml\n@@ -4,3 +4,8 @@ version = \"0.1.0\"\n edition = \"2021\"\n \n [dependencies]\n+cbindgen = \"0.24.0\"\n+textwrap = \"=0.16.1\"\n+once_cell = \"=1.20.3\"\n+unicode-width = \"=0.1.13\"\n+\ndiff --git a/rust/build-helper/src/lib.rs b/rust/build-helper/src/lib.rs\nindex 70e95d16f5..640bc19734 100644\n--- a/rust/build-helper/src/lib.rs\n+++ b/rust/build-helper/src/lib.rs\n@@ -1,5 +1,7 @@\n use std::collections::HashMap;\n+use std::io::Write;\n use std::path::PathBuf;\n+use cbindgen::Config;\n \n \n fn parse_bool_from_str(value: &str) -> bool {\n@@ -23,12 +25,20 @@ fn parse_bool_from_option(value: Option<&String>, default: bool) -> bool {\n /// To run tests set GIT_BUILD_DIR and run `USE_LINKING=true cargo test`\n pub struct BuildHelper {\n     crate_env: HashMap<String, String>,\n+    generate_header: bool,\n+    file_out: PathBuf,\n+    config: Config,\n }\n \n \n impl BuildHelper {\n     pub fn new(crate_env: HashMap<String, String>) -> Self {\n-        let it = Self {crate_env};\n+        let mut it = Self {\n+            crate_env,\n+            generate_header: false,\n+            file_out: PathBuf::default(),\n+            config: Config::default(),\n+        };\n \n         let dir_crate = it.dir_crate();\n         let dir_workspace = dir_crate.parent().unwrap();\n@@ -38,6 +48,12 @@ impl BuildHelper {\n             std::fs::create_dir(dir_interop.clone()).unwrap();\n         }\n \n+        let file_cbindgen = dir_workspace.join(\"cbindgen-template.toml\");\n+        it.file_out = dir_interop.join(format!(\"{}.h\", it.crate_name()));\n+\n+        it.config = Config::from_file(file_cbindgen.display().to_string().as_str()).unwrap();\n+        it.config.include_guard = Some(format!(\"{}_H\", it.crate_name().to_uppercase()));\n+\n         it\n     }\n \n@@ -49,6 +65,16 @@ impl BuildHelper {\n         PathBuf::from(self.crate_env[\"CARGO_MANIFEST_DIR\"].clone())\n     }\n \n+    pub fn generate_header<F>(mut self, editor: F) -> Self\n+    where\n+        F: Fn(&mut Config)\n+    {\n+        self.generate_header = true;\n+        editor(&mut self.config);\n+\n+        self\n+    }\n+\n     pub fn build(self) {\n         let use_linking = parse_bool_from_option(self.crate_env.get(\"USE_LINKING\"), self.crate_env.get(\"CARGO_TARGET_DIR\").is_none());\n         let dir_crate = self.dir_crate();\n@@ -78,6 +104,22 @@ impl BuildHelper {\n         } else {\n             println!(\"cargo:warning={} is not linking against C objects, `USE_LINKING=true cargo test`\", self.crate_env[\"CARGO_PKG_NAME\"]);\n         }\n+\n+        if self.generate_header {\n+            let mut buffer = Vec::<u8>::new();\n+            cbindgen::Builder::new()\n+                .with_crate(dir_crate.clone())\n+                .with_config(self.config)\n+                .with_std_types(true)\n+                .generate()\n+                .expect(\"Unable to generate bindings\")\n+                .write(&mut buffer);\n+\n+            let mut fd = std::fs::File::create(self.file_out).unwrap();\n+            fd.write(buffer.as_slice()).unwrap();\n+        } else {\n+            let _ = std::fs::remove_file(self.file_out);\n+        }\n     }\n }\n \ndiff --git a/rust/cbindgen-template.toml b/rust/cbindgen-template.toml\nnew file mode 100644\nindex 0000000000..cab83218d0\n--- /dev/null\n+++ b/rust/cbindgen-template.toml\n@@ -0,0 +1,16 @@\n+## compat/posix.h includes stdbool.h where git-compat-util.h does not\n+## this is mandatory for correct bool C <-> Rust interop. Though the\n+## 'includes' (and all other variables in this file) can be\n+## overridden in build.rs.\n+sys_includes = [\"compat/posix.h\"]\n+\n+autogen_warning = \"/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */\"\n+\n+language = \"C\"\n+no_includes = true\n+usize_is_size_t = true\n+style = \"tag\"\n+tab_width = 4\n+\n+[parse]\n+parse_deps = false\n-- \ngitgitgadget\n\n"},{"id":"526533","messageId":"df837fdb4d399be1218a9a39eab47a85fa44e8d3.1758071798.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.v2.git.git.1758071798.gitgitgadget@gmail.com","subject":"[PATCH v2 15/18] varint: use explicit width for integers","fromName":"Patrick Steinhardt via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-09-17T01:16:35Z","receivedAt":"2025-09-17T01:16:59Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"From: Patrick Steinhardt <ps@pks.im>\n\nThe varint subsystem currently uses implcit widths for integers. On the\none hand we use `uintmax_t` for the actual value. On the other hand, we\nuse `int` for the length of the encoded varint.\n\nBoth of these have known maximum vaules, as we only support at most 16\nbytes when encoding varints. Thus, we know that we won't ever exceed\n`uint64_t` for the actual value and `uint8_t` for the prefix length.\n\nRefactor the code to use explicit widths. Besides making the logic\nplatform-independent, it also makes our life a bit easier in the next\ncommit, where we reimplement \"varint.c\" in Rust.\n\nSuggested-by: Ezekiel Newren <ezekielnewren@gmail.com>\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n dir.c        | 18 ++++++++++--------\n read-cache.c |  6 ++++--\n varint.c     |  6 +++---\n varint.h     |  4 ++--\n 4 files changed, 19 insertions(+), 15 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex dfb4d40103..b92a5d750e 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -3579,7 +3579,8 @@ static void write_one_dir(struct untracked_cache_dir *untracked,\n \tstruct stat_data stat_data;\n \tstruct strbuf *out = &wd->out;\n \tunsigned char intbuf[16];\n-\tunsigned int intlen, value;\n+\tunsigned int value;\n+\tuint8_t intlen;\n \tint i = wd->index++;\n \n \t/*\n@@ -3632,7 +3633,7 @@ void write_untracked_extension(struct strbuf *out, struct untracked_cache *untra\n \tstruct ondisk_untracked_cache *ouc;\n \tstruct write_data wd;\n \tunsigned char varbuf[16];\n-\tint varint_len;\n+\tuint8_t varint_len;\n \tconst unsigned hashsz = the_hash_algo->rawsz;\n \n \tCALLOC_ARRAY(ouc, 1);\n@@ -3738,7 +3739,7 @@ static int read_one_dir(struct untracked_cache_dir **untracked_,\n \tstruct untracked_cache_dir ud, *untracked;\n \tconst unsigned char *data = rd->data, *end = rd->end;\n \tconst unsigned char *eos;\n-\tunsigned int value;\n+\tuint64_t value;\n \tint i;\n \n \tmemset(&ud, 0, sizeof(ud));\n@@ -3830,7 +3831,8 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long\n \tstruct read_data rd;\n \tconst unsigned char *next = data, *end = (const unsigned char *)data + sz;\n \tconst char *ident;\n-\tint ident_len;\n+\tuint64_t ident_len;\n+\tuint64_t varint_len;\n \tssize_t len;\n \tconst char *exclude_per_dir;\n \tconst unsigned hashsz = the_hash_algo->rawsz;\n@@ -3867,8 +3869,8 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long\n \tif (next >= end)\n \t\tgoto done2;\n \n-\tlen = decode_varint(&next);\n-\tif (next > end || len == 0)\n+\tvarint_len = decode_varint(&next);\n+\tif (next > end || varint_len == 0)\n \t\tgoto done2;\n \n \trd.valid      = ewah_new();\n@@ -3877,9 +3879,9 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long\n \trd.data\t      = next;\n \trd.end\t      = end;\n \trd.index      = 0;\n-\tALLOC_ARRAY(rd.ucd, len);\n+\tALLOC_ARRAY(rd.ucd, varint_len);\n \n-\tif (read_one_dir(&uc->root, &rd) || rd.index != len)\n+\tif (read_one_dir(&uc->root, &rd) || rd.index != varint_len)\n \t\tgoto done;\n \n \tnext = rd.data;\ndiff --git a/read-cache.c b/read-cache.c\nindex 06ad74db22..41b44148b1 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -1807,7 +1807,7 @@ static struct cache_entry *create_from_disk(struct mem_pool *ce_mem_pool,\n \n \tif (expand_name_field) {\n \t\tconst unsigned char *cp = (const unsigned char *)name;\n-\t\tsize_t strip_len, previous_len;\n+\t\tuint64_t strip_len, previous_len;\n \n \t\t/* If we're at the beginning of a block, ignore the previous name */\n \t\tstrip_len = decode_varint(&cp);\n@@ -2655,8 +2655,10 @@ static int ce_write_entry(struct hashfile *f, struct cache_entry *ce,\n \t\thashwrite(f, ce->name, len);\n \t\thashwrite(f, padding, align_padding_size(size, len));\n \t} else {\n-\t\tint common, to_remove, prefix_size;\n+\t\tint common, to_remove;\n+\t\tuint8_t prefix_size;\n \t\tunsigned char to_remove_vi[16];\n+\n \t\tfor (common = 0;\n \t\t     (common < previous_name->len &&\n \t\t      ce->name[common] &&\ndiff --git a/varint.c b/varint.c\nindex 409c4977a1..03cd54416b 100644\n--- a/varint.c\n+++ b/varint.c\n@@ -1,11 +1,11 @@\n #include \"git-compat-util.h\"\n #include \"varint.h\"\n \n-uintmax_t decode_varint(const unsigned char **bufp)\n+uint64_t decode_varint(const unsigned char **bufp)\n {\n \tconst unsigned char *buf = *bufp;\n \tunsigned char c = *buf++;\n-\tuintmax_t val = c & 127;\n+\tuint64_t val = c & 127;\n \twhile (c & 128) {\n \t\tval += 1;\n \t\tif (!val || MSB(val, 7))\n@@ -17,7 +17,7 @@ uintmax_t decode_varint(const unsigned char **bufp)\n \treturn val;\n }\n \n-int encode_varint(uintmax_t value, unsigned char *buf)\n+uint8_t encode_varint(uint64_t value, unsigned char *buf)\n {\n \tunsigned char varint[16];\n \tunsigned pos = sizeof(varint) - 1;\ndiff --git a/varint.h b/varint.h\nindex f78bb0ca52..eb401935bd 100644\n--- a/varint.h\n+++ b/varint.h\n@@ -1,7 +1,7 @@\n #ifndef VARINT_H\n #define VARINT_H\n \n-int encode_varint(uintmax_t, unsigned char *);\n-uintmax_t decode_varint(const unsigned char **);\n+uint8_t encode_varint(uint64_t, unsigned char *);\n+uint64_t decode_varint(const unsigned char **);\n \n #endif /* VARINT_H */\n-- \ngitgitgadget\n\n"},{"id":"526534","messageId":"17143ced6feea5927b66c0578f1c2fbb07378504.1758071798.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.v2.git.git.1758071798.gitgitgadget@gmail.com","subject":"[PATCH v2 16/18] build: new crate, misc","fromName":"Ezekiel Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-09-17T01:16:36Z","receivedAt":"2025-09-17T01:17:01Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"From: Ezekiel Newren <ezekielnewren@gmail.com>\n\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n Makefile             |  2 +-\n meson.build          |  2 +-\n rust/Cargo.toml      |  1 +\n rust/misc/Cargo.toml | 14 ++++++++++++++\n rust/misc/src/lib.rs |  0\n 5 files changed, 17 insertions(+), 2 deletions(-)\n create mode 100644 rust/misc/Cargo.toml\n create mode 100644 rust/misc/src/lib.rs\n\ndiff --git a/Makefile b/Makefile\nindex ef55a8183c..c363a6365d 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -929,7 +929,7 @@ EXTLIBS =\n GIT_BUILD_DIR := $(CURDIR)\n export GIT_BUILD_DIR\n \n-RUST_CRATES :=\n+RUST_CRATES := misc\n .PHONY: compile_rust rustclean\n \n WITH_RUST ?= true\ndiff --git a/meson.build b/meson.build\nindex cbe1afac83..6911625f3b 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -267,7 +267,7 @@ version_gen_environment.set('GIT_DATE', get_option('build_date'))\n version_gen_environment.set('GIT_USER_AGENT', get_option('user_agent'))\n version_gen_environment.set('GIT_VERSION', get_option('version'))\n \n-rust_crates = []\n+rust_crates = ['misc']\n \n rust_builds = []\n with_rust = get_option('with_rust')\ndiff --git a/rust/Cargo.toml b/rust/Cargo.toml\nindex aaf008b5ee..010effef4c 100644\n--- a/rust/Cargo.toml\n+++ b/rust/Cargo.toml\n@@ -1,5 +1,6 @@\n [workspace]\n members = [\n     \"build-helper\",\n+    \"misc\",\n ]\n resolver = \"2\"\ndiff --git a/rust/misc/Cargo.toml b/rust/misc/Cargo.toml\nnew file mode 100644\nindex 0000000000..7195d7894d\n--- /dev/null\n+++ b/rust/misc/Cargo.toml\n@@ -0,0 +1,14 @@\n+[package]\n+name = \"misc\"\n+version = \"0.1.0\"\n+edition = \"2021\"\n+\n+[lib]\n+name = \"misc\"\n+path = \"src/lib.rs\"\n+## staticlib to generate xdiff.a for use by gcc\n+## cdylib (optional) to generate xdiff.so for use by gcc\n+## rlib is required by the rust unit tests\n+crate-type = [\"staticlib\", \"rlib\"]\n+\n+[dependencies]\ndiff --git a/rust/misc/src/lib.rs b/rust/misc/src/lib.rs\nnew file mode 100644\nindex 0000000000..e69de29bb2\n-- \ngitgitgadget\n\n"},{"id":"526535","messageId":"89bb7c3613802cd778abd88eada41a18bb54ff01.1758071798.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.v2.git.git.1758071798.gitgitgadget@gmail.com","subject":"[PATCH v2 17/18] misc: use BuildHelper","fromName":"Ezekiel Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-09-17T01:16:37Z","receivedAt":"2025-09-17T01:17:02Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"From: Ezekiel Newren <ezekielnewren@gmail.com>\n\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n rust/misc/Cargo.toml | 3 +++\n rust/misc/build.rs   | 6 ++++++\n 2 files changed, 9 insertions(+)\n create mode 100644 rust/misc/build.rs\n\ndiff --git a/rust/misc/Cargo.toml b/rust/misc/Cargo.toml\nindex 7195d7894d..0f93aa4ae4 100644\n--- a/rust/misc/Cargo.toml\n+++ b/rust/misc/Cargo.toml\n@@ -12,3 +12,6 @@ path = \"src/lib.rs\"\n crate-type = [\"staticlib\", \"rlib\"]\n \n [dependencies]\n+\n+[build-dependencies]\n+build-helper = { path = \"../build-helper\" }\ndiff --git a/rust/misc/build.rs b/rust/misc/build.rs\nnew file mode 100644\nindex 0000000000..291923ae1d\n--- /dev/null\n+++ b/rust/misc/build.rs\n@@ -0,0 +1,6 @@\n+use build_helper::BuildHelper;\n+\n+fn main() {\n+    BuildHelper::new(std::env::vars().collect())\n+        .build();\n+}\n-- \ngitgitgadget\n\n"},{"id":"526536","messageId":"3c521fa2e4bbaf9c6fb7682cd74b398acea350de.1758071798.git.gitgitgadget@gmail.com","threadId":"64060","inReplyTo":"pull.2043.v2.git.git.1758071798.gitgitgadget@gmail.com","subject":"[PATCH v2 18/18] misc::varint: reimplement as test balloon for Rust","fromName":"Patrick Steinhardt via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-09-17T01:16:38Z","receivedAt":"2025-09-17T01:17:03Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"From: Patrick Steinhardt <ps@pks.im>\n\nImplement a trivial test balloon for our Rust build infrastructure by\nreimplementing the \"varint.c\" subsystem in Rust. This subsystem is\nchosen because it is trivial to convert and because it doesn't have any\ndependencies to other components of Git.\n\nIf support for Rust is enabled, we stop compiling \"varint.c\" and instead\ncompile and use \"src/varint.rs\".\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\nSigned-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n---\n Makefile                |  2 +\n meson.build             |  7 +++-\n rust/misc/src/lib.rs    |  1 +\n rust/misc/src/varint.rs | 92 +++++++++++++++++++++++++++++++++++++++++\n 4 files changed, 101 insertions(+), 1 deletion(-)\n create mode 100644 rust/misc/src/varint.rs\n\ndiff --git a/Makefile b/Makefile\nindex c363a6365d..9d50f88809 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1259,7 +1259,9 @@ LIB_OBJS += urlmatch.o\n LIB_OBJS += usage.o\n LIB_OBJS += userdiff.o\n LIB_OBJS += utf8.o\n+ifeq ($(WITH_RUST),false)\n LIB_OBJS += varint.o\n+endif\n LIB_OBJS += version.o\n LIB_OBJS += versioncmp.o\n LIB_OBJS += walker.o\ndiff --git a/meson.build b/meson.build\nindex 6911625f3b..1b191b12a3 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -571,7 +571,6 @@ libgit_sources = [\n   'usage.c',\n   'userdiff.c',\n   'utf8.c',\n-  'varint.c',\n   'version.c',\n   'versioncmp.c',\n   'walker.c',\n@@ -591,6 +590,12 @@ libgit_sources = [\n   'xdiff/xutils.c',\n ]\n \n+if not with_rust\n+  libgit_sources += [\n+    'varint.c',\n+  ]\n+endif\n+\n libgit_sources += custom_target(\n   input: 'command-list.txt',\n   output: 'command-list.h',\ndiff --git a/rust/misc/src/lib.rs b/rust/misc/src/lib.rs\nindex e69de29bb2..9da70d8b57 100644\n--- a/rust/misc/src/lib.rs\n+++ b/rust/misc/src/lib.rs\n@@ -0,0 +1 @@\n+pub mod varint;\ndiff --git a/rust/misc/src/varint.rs b/rust/misc/src/varint.rs\nnew file mode 100644\nindex 0000000000..10c83e1f43\n--- /dev/null\n+++ b/rust/misc/src/varint.rs\n@@ -0,0 +1,92 @@\n+#[no_mangle]\n+pub unsafe extern \"C\" fn decode_varint(bufp: *mut *const u8) -> usize {\n+    let mut buf = *bufp;\n+    let mut c = *buf;\n+    let mut val = usize::from(c & 127);\n+\n+    buf = buf.add(1);\n+\n+    while (c & 128) != 0 {\n+        val = val.wrapping_add(1);\n+        if val == 0 || val.leading_zeros() < 7 {\n+            return 0; // overflow\n+        }\n+\n+        c = *buf;\n+        buf = buf.add(1);\n+\n+        val = (val << 7) + usize::from(c & 127);\n+    }\n+\n+    *bufp = buf;\n+    val\n+}\n+\n+#[no_mangle]\n+pub unsafe extern \"C\" fn encode_varint(value: usize, buf: *mut u8) -> u8 {\n+    let mut varint: [u8; 16] = [0; 16];\n+    let mut pos = varint.len() - 1;\n+\n+    varint[pos] = (value & 127) as u8;\n+\n+    let mut value = value >> 7;\n+    while value != 0 {\n+        pos -= 1;\n+        value -= 1;\n+        varint[pos] = 128 | (value & 127) as u8;\n+        value >>= 7;\n+    }\n+\n+    if !buf.is_null() {\n+        std::ptr::copy_nonoverlapping(varint.as_ptr().add(pos), buf, varint.len() - pos);\n+    }\n+\n+    (varint.len() - pos) as u8\n+}\n+\n+#[cfg(test)]\n+mod tests {\n+    use super::*;\n+\n+    #[test]\n+    fn test_decode_varint() {\n+        unsafe {\n+            assert_eq!(decode_varint(&mut [0x00].as_slice().as_ptr()), 0);\n+            assert_eq!(decode_varint(&mut [0x01].as_slice().as_ptr()), 1);\n+            assert_eq!(decode_varint(&mut [0x7f].as_slice().as_ptr()), 127);\n+            assert_eq!(decode_varint(&mut [0x80, 0x00].as_slice().as_ptr()), 128);\n+            assert_eq!(decode_varint(&mut [0x80, 0x01].as_slice().as_ptr()), 129);\n+            assert_eq!(decode_varint(&mut [0x80, 0x7f].as_slice().as_ptr()), 255);\n+\n+            // Overflows are expected to return 0.\n+            assert_eq!(decode_varint(&mut [0x88; 16].as_slice().as_ptr()), 0);\n+        }\n+    }\n+\n+    #[test]\n+    fn test_encode_varint() {\n+        unsafe {\n+            let mut varint: [u8; 16] = [0; 16];\n+\n+            assert_eq!(encode_varint(0, std::ptr::null_mut()), 1);\n+\n+            assert_eq!(encode_varint(0, varint.as_mut_slice().as_mut_ptr()), 1);\n+            assert_eq!(varint, [0; 16]);\n+\n+            assert_eq!(encode_varint(10, varint.as_mut_slice().as_mut_ptr()), 1);\n+            assert_eq!(varint, [10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);\n+\n+            assert_eq!(encode_varint(127, varint.as_mut_slice().as_mut_ptr()), 1);\n+            assert_eq!(varint, [127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);\n+\n+            assert_eq!(encode_varint(128, varint.as_mut_slice().as_mut_ptr()), 2);\n+            assert_eq!(varint, [128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);\n+\n+            assert_eq!(encode_varint(129, varint.as_mut_slice().as_mut_ptr()), 2);\n+            assert_eq!(varint, [128, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);\n+\n+            assert_eq!(encode_varint(255, varint.as_mut_slice().as_mut_ptr()), 2);\n+            assert_eq!(varint, [128, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);\n+        }\n+    }\n+}\n-- \ngitgitgadget\n"},{"id":"526540","messageId":"aMpODEpyaaVhFMO0@pks.im","threadId":"64060","inReplyTo":"pull.2043.v2.git.git.1758071798.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 00/18] Introduce rust: In xdiff","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-09-17T05:58:36Z","receivedAt":"2025-09-17T05:58:43Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Sep 17, 2025 at 01:16:20AM +0000, Ezekiel Newren via GitGitGadget wrote:\n> This is a continuation of\n> https://lore.kernel.org/git/pull.1980.v3.git.git.1755921356.gitgitgadget@gmail.com/,\n> but I am removing the RFC label.\n> \n> Suggestions on changes that I could make to this series is appreciated.\n> \n> Changes in v2: High level overview:\n> \n>  * patch 1: cleanup: rename variables that collide with Rust primitive type\n>    names\n>  * patches 2-4: Makefile now produces libgit.a correctly\n>  * patches 5,6: Documentation from Brian and Patrick\n>  * patches 7,8: Introduce Rust\n>  * patches 9-11: github workflows\n>  * patches 12-14: introduce crates build-helper, and cbindgen\n>  * patches 15-18: varint test balloon\n> \n> I would like feed back in two categories: big changes, and little changes.\n> Is my patch series even going in the right direction, and what are the\n> little details that I've missed? I know Brian asked for cbindgen and it made\n> the series several commits longer, but I think it's a great idea to have. I\n> am not happy with every choice that I made, but I currently don't see a\n> better way or at least an easier alternative to my approach in bringing Rust\n> to Git.\n> \n> Build results for these changes:\n> https://github.com/git/git/actions/runs/17783386212?pr=2043. Some of these\n> are failing.\n\nGiven that this isn't yet ready and given that this patch series is way\nbigger than the one I have in flight that focusses on introducing the\npolicy [1]: how about we first merge that one down and then rebase your\npatches on top of it to iterate? It would reduce the scope of your patch\nseries and allow us to make smaller steps towards our goal.\n\nTo be clear: I very much think that most of the steps here are quite\nsensible. We definitely want to introduce cbindgen, we obviously need to\nintroduce support on Windows, and I guess having multiple different\nworkspaces is also something that could provide benefit.\n\nBut there is no reason to have that all at once, so hence my suggestion\nto build out the infra one step at a time.\n\nWhat do you think?\n\nPatrick\n\n[1]: https://lore.kernel.org/git/20250904-b4-pks-rust-breaking-change-v1-0-3af1d25e0be9@pks.im/\n"},{"id":"526550","messageId":"CAPig+cQqKCbGpfp=ppmjKEOe+sDRu6BocDfenzqvQJHSMiKDHQ@mail.gmail.com","threadId":"64060","inReplyTo":"5f77f1bd5d986dc1f8d123919af24dd219e323e8.1758071798.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 01/18] cleanup: rename variables that collide with Rust primitive type names","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2025-09-17T07:42:56Z","receivedAt":"2025-09-17T07:43:09Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Sep 16, 2025 at 9:17 PM Ezekiel Newren via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n> cleanup: rename variables that collide with Rust primitive type names\n>\n> Use a regex to find and rename variables that collide with Rust\n> primitive integer and float type names:\n>\n>     git grep -n -E -e '\\<([ui](8|16|32|64|size)|(f(32|64)))\\>'\n\nThis explains what the patch is doing but doesn't explain why we want\nthis change. Please update the commit message to describe the\nproblem/issue the patch is trying to address, and then (if necessary)\nexplain what the patch is doing.\n\n> Matches were reviewed and renamed. The remaining matches don't count\n> because:\n> - Rust source files:\n>   contrib/libgit-rs/src/config.rs\n>   contrib/libgit-sys/src/lib.rs\n>   t/t4018/rust-impl\n>   t/t4018/rust-trait\n> - Intentional references:\n>   t/helper/test-parse-options.c (prints Rust int names)\n>   t/t0040-parse-options.sh (tests the above)\n>\n> View with --color-words to highlight the variable renames.\n>\n> Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n> ---\n> diff --git a/odb.c b/odb.c\n> @@ -913,7 +913,7 @@ void *odb_read_object_peeled(struct object_database *odb,\n>  {\n> -       unsigned long isize;\n> +       unsigned long isize_;\n\nNit: It's minor, but I can't say I'm a fan of this approach to\nrenaming variables. It would be better to come up with a name that is\nmore meaningful if possible rather than merely appending an\nunderscore. In this particular case, the original name, \"isize\",\nalready fails to convey much meaning, but appears to have been named\nthis way simply to avoid a collision with the existing function\nargument named \"size\". So, you could just as easily rename \"isize\" to\n\"sz\" or some such.\n\nSame comment applies to the other renamed variables...\n\n> -               OPT_UNSIGNED(0, \"u16\", &u16, \"get a 16 bit unsigned integer\"),\n> +               OPT_UNSIGNED(0, \"u16\", &u16_, \"get a 16 bit unsigned integer\"),\n\n... though with some of them, such as this one, it is admittedly more\ndifficult to come up with a better name since the original name is\nalready meaningful.\n"},{"id":"526551","messageId":"CAPig+cRGtMX9bQhb+7g5aXRVjpLKY-qDdQP95f9TmbY8j_BweQ@mail.gmail.com","threadId":"64060","inReplyTo":"7082e32c5975d2aaa277eddae7497eba0f2131e0.1758071798.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 02/18] make: add -fPIE flag","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2025-09-17T07:44:59Z","receivedAt":"2025-09-17T07:45:11Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Sep 16, 2025 at 9:17 PM Ezekiel Newren via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n> make: add -fPIE flag\n>\n> Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n> ---\n> diff --git a/Makefile b/Makefile\n> @@ -1402,7 +1402,7 @@ endif\n>  CC_LD_DYNPATH = -Wl,-rpath,\n> -BASIC_CFLAGS = -I.\n> +BASIC_CFLAGS = -I. -fPIE\n>  BASIC_LDFLAGS =\n\nThe commit message explains what the patch is doing (which is obvious\nfrom the patch itself) but doesn't explain why this change is needed,\nhence it is very difficult for a reviewer to understand why such a\nchange is necessary.\n"},{"id":"526553","messageId":"CAPig+cTDUM5nVbBF5L3k+Gr5xYxXdH2ungVvkQ9QVbZCqMsLHg@mail.gmail.com","threadId":"64060","inReplyTo":"7fe85f0eaca88fd3084bccff6754c57a919e7d4f.1758071798.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 03/18] make: merge xdiff lib into libgit.a","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2025-09-17T07:46:03Z","receivedAt":"2025-09-17T07:46:16Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Sep 16, 2025 at 9:17 PM Ezekiel Newren via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n> make: merge xdiff lib into libgit.a\n>\n> Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n> ---\n\nPlease extend the commit message to explain why this change is desirable.\n"},{"id":"526554","messageId":"CAPig+cSz3PxY23Z4Vioo+5zhi2hniocUHTmjLZw7tNgvjkuXDQ@mail.gmail.com","threadId":"64060","inReplyTo":"3061cc46c1910bdc4f66855db7a07d313b37fb27.1758071798.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 04/18] make: merge reftable lib into libgit.a","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2025-09-17T07:46:42Z","receivedAt":"2025-09-17T07:46:54Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Sep 16, 2025 at 9:17 PM Ezekiel Newren via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n> make: merge reftable lib into libgit.a\n>\n> Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n> ---\n\nSame comment as previous patch... please extend the commit message to\nexplain why this change is desirable.\n"},{"id":"526555","messageId":"CAPig+cT-2-s-TcZ-2TQujLkn8Eh-EmYa9QWHWpw3iczDuX5mUQ@mail.gmail.com","threadId":"64060","inReplyTo":"fcdfc55fb7d7da7d65405486f5eec10e5892a028.1758071798.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 09/18] github workflows: install rust","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2025-09-17T08:01:45Z","receivedAt":"2025-09-17T08:01:57Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Sep 16, 2025 at 9:17 PM Ezekiel Newren via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n> Prefer using actions-rs/toolchain@v1 where possible to install rustup,\n> but for docker targets use a script to install rustup. Consolidate the\n> Rust toolchain definitions in main.yaml. Use install-rust-toolchain.sh\n> to ensure the correct toolchain is used. Five overrides are used in\n> main.yaml:\n>\n>   * On Windows: Rust didn't resolve the bcrypt library on Windows\n>     correctly until version 1.78.0. Also since rustup mis-identifies\n>     the Rust toolchain, the Rust target triple must be set to\n>     x86_64-pc-windows-gnu for make (win build), and\n>     x86_64-pc-windows-msvc for meson (win+Meson build).\n>   * MSVC builds: Rearrange PATH to look in /mingw64/bin and /usr/bin\n>     last.\n\nPlease add an explanation as to why it is necessary to rearrange PATH.\nI saw in patch [7/18] that your \"build_rust.sh\" does the same but the\nreason is never spelled out (and it's still a mystery to me).\n\nAlso, this patch, [9/18], doesn't seem to touch PATH in the way\ndescribed here (unless I somehow overlooked it).\n\n>   * On musl: libc differences, such as ftruncate64 vs ftruncate, were\n>     not accounted for until Rust version 1.72.0. No older version of\n>     Rust will work on musl for our needs.\n>   * In a 32-bit docker container running on a 64-bit host, we need to\n>     override the Rust target triple. This is because rustup asks the\n>     kernel for the bitness of the system and it says 64, even though\n>     the container is 32-bit. This also allows us to remove the\n>     BITNESS environment variable in ci/lib.sh.\n>\n> The logic for selecting library names was initially provided in a patch\n> from Johannes, but was reworked and squashed into this commit.\n>\n> Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n> Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n> ---\n> diff --git a/ci/install-rust-toolchain.sh b/ci/install-rust-toolchain.sh\n> @@ -0,0 +1,30 @@\n> +#!/bin/sh\n> +\n> +if [ \"$CARGO_HOME\" = \"\" ]; then\n> +  echo >&2 \"::error:: CARGO_HOME is not set\"\n> +  exit 2\n> +fi\n\nLet's follow project coding guidelines for shell scripts:\n\n    if test \"$CARGO_HOME\" = \"\"\n    then\n        ..\n    fi\n\nor even:\n\n    if test -z \"$CARGO_HOME\"\n    then\n        ...\n    fi\n\nSame comment applies to the remainder of this script and other scripts\nin this patch.\n\n> diff --git a/ci/install-rustup.sh b/ci/install-rustup.sh\n> @@ -0,0 +1,25 @@\n> +if [ ! -f $CARGO_HOME/env ]; then\n> +  echo \"PATH=$CARGO_HOME/bin:\\$PATH\" > $CARGO_HOME/env\n> +fi\n\nStyle: drop space after '>' operator\n\n> diff --git a/ci/lib.sh b/ci/lib.sh\n> @@ -1,5 +1,6 @@\n>  # Library of functions shared by all CI scripts\n>\n> +\n>  if test true = \"$GITHUB_ACTIONS\"\n\nDo we need the extra blank line introduced above the `if`?\n"},{"id":"526557","messageId":"CAPig+cS_=YTBaCjn8-Th3yn3_k+a8_pMOmdv_Dq4S0tfp8BRCg@mail.gmail.com","threadId":"64060","inReplyTo":"6032a8740c0ba72420f42c3d8d801e1bdeec12d0.1758071798.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 07/18] build: introduce rust","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2025-09-17T08:26:16Z","receivedAt":"2025-09-17T08:26:28Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Sep 16, 2025 at 9:17 PM Ezekiel Newren via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n> Upcoming patches will simplify xdiff, while also porting parts of it to\n> Rust. In preparation, add some stubs and setup the Rust build. For now,\n> it is easier to let cargo build rust and have make or meson merely link\n> against the static library that cargo builds. In line with ongoing\n> libification efforts, use multiple crates to allow more modularity on\n> the Rust side. The xdiff and ivec crates are the focal point for this\n> series.\n>\n> cbindgen will be used to generate header files for each Rust crate for\n> C interoperability.\n>\n> Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n> ---\n> diff --git a/.gitignore b/.gitignore\n> @@ -254,3 +254,6 @@ Release/\n>  /contrib/buildsystems/out\n>  /contrib/libgit-rs/target\n>  /contrib/libgit-sys/target\n> +.idea/\n> +/rust/target/\n> +/rust/Cargo.lock\n\nIs \".idea\" directory detritus from your chosen editor? If so, it\nprobably ought not be added to this list since we don't otherwise\nignore detritus from foreign tools such as that.\n\n> diff --git a/Makefile b/Makefile\n> @@ -916,6 +924,51 @@ TEST_SHELL_PATH = $(SHELL_PATH)\n> +RUST_CRATES :=\n> +.PHONY: compile_rust rustclean\n\nThis is an extremely tiny nit, but multi-word target names in the Git\nMakefile are almost universally spelled with a hyphen rather than an\nunderscore, so \"compile-rust\" would be more consistent and\nappropriate.\n\n> +$(GIT_BUILD_DIR)/lib%.a:\n> +       ./build_rust.sh $(GIT_BUILD_DIR) $(RUST_BUILD_MODE) $*\n\nI wonder if this should follow the convention of most (but not quite\nall) other local shell script invocations in the Makefile of\nreferencing SHELL_PATH; for instance:\n\n    $(QUIET_GEN)$(SHELL_PATH) ./build_rust.sh ...etc...\n\nwith or without the $(QUIET_GEN) bit.\n\n> diff --git a/build_rust.sh b/build_rust.sh\n> @@ -0,0 +1,60 @@\n> +if [ \"$dir_git_root\" = \"\" ]; then\n> +  echo \"did not specify the directory for the root of git\"\n> +  exit 1\n> +fi\n\nLet's follow project style conventions for shell scripts:\n\n    if test \"$dir_git_root\" = \"\"\n    then\n        ...\n    fi\n\nor even:\n\n    if test -z \"$dir_git_root\"\n    then\n        ...\n    fi\n\nSame comment applies to the rest of the script.\n\nAlso, should the above error message be sent to stderr rather than\nstdout? (Genuine question since I'm not sure if other scripts are\nnecessarily consistent in that regard.)\n\n> +if [ \"$rust_build_profile\" = \"release\" ]; then\n> +  rust_args=\"--release\"\n> +  export RUSTFLAGS=''\n> +elif [ \"$rust_build_profile\" = \"debug\" ]; then\n> +  rust_args=\"\"\n> +  export RUSTFLAGS='-C debuginfo=2 -C opt-level=1 -C force-frame-pointers=yes'\n> +else\n> +  echo \"illegal rust_build_profile value $rust_build_profile\"\n> +  exit 1\n> +fi\n\nFor maximum portability, we don't use the:\n\n    export FOO=bar\n\nidiom in this project. Instead, do this:\n\n    FOO=bar\n    export FOO\n\nThe same comment applies to the remainder of the patch, as well.\n\nAlso, the empty-string assignments to RUSTFLAGS and rust_args above\ninconsistently use a mix of single and double quotes. However, we also\nusually just assign nothingness in such cases rather than an empty\nstring:\n\n    RUSTFLAGS=\n    export RUSTFLAGS\n    ...\n    rust_args=\n\n> +if rustup show active-toolchain | grep windows-msvc; then\n> +  libfile=\"${crate}.lib\"\n> +  PATH=\"$(echo $PATH | tr ':' '\\n' | grep -Ev \"^(/mingw64/bin|/usr/bin)$\" | paste -sd: -):/mingw64/bin:/usr/bin\"\n> +fi\n\nPlease add either an in-code comment or a sentence/paragraph to the\ncommit message explaining why this PATH munging is needed.\n\n> +cd $dir_rust && cargo clean && pwd && USE_LINKING=\"false\" cargo build -p $crate $rust_args; cd $dir_git_root\n\nWhen formatted like this, it is very easy to overlook the fact that\nthe final `cd` is (correctly) not &&-chained with the preceding\ncommands. Reformatting it like this would help:\n\n    cd $dir_rust &&\n        cargo clean &&\n        pwd &&\n        USE_LINKING=\"false\" cargo build -p $crate $rust_args\n    cd $dir_git_root\n\nAlternatively, use a subshell and drop the final `cd`:\n\n    (\n        cd $dir_rust &&\n        cargo clean &&\n        pwd &&\n        USE_LINKING=\"false\" cargo build -p $crate $rust_args\n    )\n\nBy the way, is the `pwd` command needed? What is its purpose?\n\n> +rm $dst 2>/dev/null\n\nI think we typically use `rm -f` without redirect to /dev/null to do\nthe \"right thing\".\n\n> +echo mv $src $dst\n> +mv $src $dst\n"},{"id":"526559","messageId":"CAPig+cTZch_pvfurtjBTNphMeRQL6jSBSjNY-4mffjoXZ4eqcw@mail.gmail.com","threadId":"64060","inReplyTo":"6a27e07e6310b6cad0e3feae817269b9b8eaed69.1758071798.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 13/18] build-helper: link against libgit.a and any other required C libraries","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2025-09-17T08:51:28Z","receivedAt":"2025-09-17T08:51:41Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Sep 16, 2025 at 9:18 PM Ezekiel Newren via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n> build-helper: link against libgit.a and any other required C libraries\n>\n> Don't link against the C libraries when building with Make or Meson.\n> Run cargo tests like this:\n> cd rust && cargo clean && USE_LINKING=true cargo test\n>\n> Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n> ---\n\nPerhaps it's because I haven't been following the discussion closely\nenough, but the above commit message leaves me entirely in the dark.\nAfter reading and rereading it several times, I suppose it is trying\nto address some difference between building with `cargo` vs. building\nwith Make or Meson, but it gives no explanation of what the\ndifferences are or what problem it is trying to solve. So, please\nenhance the commit message to begin with the \"why\" and then proceed to\nthe \"what\" or \"how\".\n\n> diff --git a/rust/build-helper/Cargo.toml b/rust/build-helper/Cargo.toml\n> @@ -4,4 +4,3 @@ version = \"0.1.0\"\n>  edition = \"2021\"\n>\n>  [dependencies]\n> -\n\nThis seems merely to be deleting a blank line which probably shouldn't\nhave been present in the first place. Rather than fixing the \"problem\"\nhere, it would make more sense to eliminate the blank line in the\npatch which introduced it in the first place.\n\n> diff --git a/rust/build-helper/src/lib.rs b/rust/build-helper/src/lib.rs\n> @@ -0,0 +1,84 @@\n> +use std::collections::HashMap;\n> +use std::path::PathBuf;\n> +\n> +\n\nIf I'm not mistaken, it is uncommon to have two blank lines like this\nin Rust code.\n\n> +fn parse_bool_from_str(value: &str) -> bool {\n> +    match value {\n> +        \"1\" | \"true\"  | \"yes\" | \"on\"  => true,\n> +        \"0\" | \"false\" | \"no\"  | \"off\" => false,\n> +        _ => false\n> +    }\n> +}\n\nOr, more simply:\n\n    fn parse_bool_from_str(value: &str) -> bool {\n        match value {\n            \"1\" | \"true\"  | \"yes\" | \"on\"  => true,\n            _ => false\n        }\n    }\n\n(Though, admittedly, I'd probably lean toward writing the function the\nsame way you did.)\n\n> +/// To build without linking against C libraries run `USE_LINKING=false cargo build`\n> +/// To run tests set GIT_BUILD_DIR and run `USE_LINKING=true cargo test`\n> +pub struct BuildHelper {\n> +    crate_env: HashMap<String, String>,\n> +}\n> +\n> +\n\nNit: unnecessary extra blank line\n\n> +impl BuildHelper {\n> +    pub fn build(self) {\n> +        let use_linking = parse_bool_from_option(self.crate_env.get(\"USE_LINKING\"), self.crate_env.get(\"CARGO_TARGET_DIR\").is_none());\n> +        ...\n> +            println!(\"cargo:warning={} is not linking against C objects, `USE_LINKING=true cargo test`\", self.crate_env[\"CARGO_PKG_NAME\"]);\n\nThere are more than a few developers on this project (including\nmyself) who still use 80-column editors and terminals. As a general\nstyle guideline, this project does recommend wrapping code to fit\nwithin 80 columns (except in cases when doing so would severely hurt\nreadability). I imagine that the same sort of guideline would be\nappreciated in Rust code, as well, by those who still stick with 80\ncolumns.\n\nI bring this up because, although it hasn't been such a big deal with\nthe existing C code, assuming that developers run `rustfmt` on the\ncode before sending a patch series, then this may become an issue if\ndifferent developers have `rustfmt` configured to enforce different\nmaximum column width, especially since `rustfmt` is likely to reformat\nthe entire file rather than just the region that has just been edited.\nSo, if this code gets checked in as-is with these very wide lines, and\nthen someone else, who has `rustfmt` configured for 80-columns edits\nthe file, then it becomes a problem.\n\nAs such, can we also add a project-wide `rustfmt.toml` which, at\nminimum, sets the maximum line width to 80? For instance:\n\n    max_width = 80\n"},{"id":"526560","messageId":"CAPig+cS+=OVNBBZ6Yq7s_ihWjkDO02eMH14P1xny4qAeT9ynXQ@mail.gmail.com","threadId":"64060","inReplyTo":"1c923a71f27ba204ea17a725daf45576d0aa3ec5.1758071798.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 12/18] build: new crate, build-helper","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2025-09-17T08:58:15Z","receivedAt":"2025-09-17T08:58:28Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Sep 16, 2025 at 9:18 PM Ezekiel Newren via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n> Make a crate that takes care of the boiler-plate code that most\n> build.rs files would need to write.\n>\n> Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n> ---\n> diff --git a/.gitignore b/.gitignore\n> @@ -257,3 +257,4 @@ Release/\n>  .idea/\n>  /rust/target/\n>  /rust/Cargo.lock\n> +\n\nThis seems to be adding an unnecessary blank line (for no apparent reason).\n\n> diff --git a/rust/build-helper/Cargo.toml b/rust/build-helper/Cargo.toml\n> @@ -0,0 +1,7 @@\n> +[package]\n> +name = \"build-helper\"\n> +version = \"0.1.0\"\n> +edition = \"2021\"\n> +\n> +[dependencies]\n> +\n\nI believe I saw a later patch make the sole change to this file of\nremoving the unnecessary blank line at the end of this file. It would\nbe better to fix this patch to omit the line in the first place.\n"},{"id":"526561","messageId":"CAPig+cQP-7z_TNvq7mM=kFqxeZYDg4CzvJLLV+UGyebywcbuGQ@mail.gmail.com","threadId":"64060","inReplyTo":"fa334405686a329dd1508bf8d8cbfa12dc5dc7bb.1758071798.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 14/18] build-helper: cbindgen, let crates generate a header file","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2025-09-17T09:08:54Z","receivedAt":"2025-09-17T09:09:06Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Sep 16, 2025 at 9:18 PM Ezekiel Newren via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n> build-helper: cbindgen, let crates generate a header file\n>\n> Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n> ---\n\nIt would be nice to have a bit more explanation of what this patch is\ndoing and why. I presume that this is adding boilerplate functionality\nbut that there are no clients of this functionality yet. Is that\ncorrect?\n\n> diff --git a/rust/cbindgen-template.toml b/rust/cbindgen-template.toml\n> @@ -0,0 +1,16 @@\n> +## compat/posix.h includes stdbool.h where git-compat-util.h does not\n> +## this is mandatory for correct bool C <-> Rust interop. Though the\n> +## 'includes' (and all other variables in this file) can be\n> +## overridden in build.rs.\n\nThere seems to be some missing punctuation in the above comment, and\nthe final sentence seems to be incomplete, both of which make it\ndifficult to understand what is being said.\n\n> +sys_includes = [\"compat/posix.h\"]\n> +\n> +autogen_warning = \"/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */\"\n> +\n> +language = \"C\"\n> +no_includes = true\n> +usize_is_size_t = true\n> +style = \"tag\"\n> +tab_width = 4\n\nIt's a minor thing and probably not super important but I'm wondering\nif it would be a good idea to follow project guidelines and make the\nTAB width 8 rather than 4. Doing so might be appreciated by people\nreading the generated header files.\n"},{"id":"526562","messageId":"CAPig+cRD84WZy3V0bCEwHpz=LpXSHH+ao8GZnn5ibcG8Cp4=PQ@mail.gmail.com","threadId":"64060","inReplyTo":"17143ced6feea5927b66c0578f1c2fbb07378504.1758071798.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 16/18] build: new crate, misc","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2025-09-17T09:16:48Z","receivedAt":"2025-09-17T09:17:00Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Sep 16, 2025 at 9:18 PM Ezekiel Newren via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n> build: new crate, misc\n>\n> Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n> ---\n\nPlease extend the commit message to at least give _some_ information\nabout what the \"misc\" crate is all about since it is not at all\napparent based upon the name.\n\nBy the way, is \"misc\" really a good name? It sounds like it's going to\nbe a dumping ground for anything which doesn't fit anywhere else.\n\n> diff --git a/rust/misc/Cargo.toml b/rust/misc/Cargo.toml\n> @@ -0,0 +1,14 @@\n> +[lib]\n> +name = \"misc\"\n> +path = \"src/lib.rs\"\n> +## staticlib to generate xdiff.a for use by gcc\n> +## cdylib (optional) to generate xdiff.so for use by gcc\n> +## rlib is required by the rust unit tests\n> +crate-type = [\"staticlib\", \"rlib\"]\n> +\n> +[dependencies]\n> diff --git a/rust/misc/src/lib.rs b/rust/misc/src/lib.rs\n> new file mode 100644\n> index 0000000000..e69de29bb2\n\nDo I understand correctly from the comment that this crate has\nsomething to do with \"xdiff\", yet there doesn't actually seem to be\nanything here referencing \"xdiff\"? Am I missing something?\n"},{"id":"526578","messageId":"xmqqa52tgne2.fsf@gitster.g","threadId":"64060","inReplyTo":"CAPig+cQqKCbGpfp=ppmjKEOe+sDRu6BocDfenzqvQJHSMiKDHQ@mail.gmail.com","subject":"Re: [PATCH v2 01/18] cleanup: rename variables that collide with Rust primitive type names","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-09-17T14:32:53Z","receivedAt":"2025-09-17T14:32:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> Same comment applies to the other renamed variables...\n>\n>> -               OPT_UNSIGNED(0, \"u16\", &u16, \"get a 16 bit unsigned integer\"),\n>> +               OPT_UNSIGNED(0, \"u16\", &u16_, \"get a 16 bit unsigned integer\"),\n>\n> ... though with some of them, such as this one, it is admittedly more\n> difficult to come up with a better name since the original name is\n> already meaningful.\n\nOn top of that, just like a name that begins with an underscore is\nreserved by C standard, a name taht ends with an underscore is used\nfor specific purposes by convention in this project.\n\nTypically 'foo_' appears as a parameter to a function, whose type is\nspecified with our interhal API, and when that type is cumbersome to\nwork with, we have a local equivalent 'foo' of a more appropriate\ntype whose value is populated from 'foo_' before being used, and use\nof 'foo' thanks to its better type is more ergonomic.\n\n    static int compare_pt(const void *a_, const void *b_)\n    {\n            struct possible_tag *a = (struct possible_tag *)a_;\n            struct possible_tag *b = (struct possible_tag *)b_;\n            if (a->depth != b->depth)\n                    return a->depth - b->depth;\n            if (a->found_order != b->found_order)\n                    return a->found_order - b->found_order;\n            return 0;\n    }\n\n    static int path_is_beyond_symlink(struct apply_state *state, const char *name_)\n    {\n            int ret;\n            struct strbuf name = STRBUF_INIT;\n\n            assert(*name_ != '\\0');\n            strbuf_addstr(&name, name_);\n            ret = path_is_beyond_symlink_1(state, &name);\n            strbuf_release(&name);\n\n            return ret;\n    }\n\nare examples.\n\nThere are existing crappy code that uses foo_ without corresponding foo;\nwe should clean them up, not emulating or spreading the pattern.\n\nBy the way, in this partcular case, why not use \"uint16_t u16\"?\n\nIsn't the true cause of the trouble the (I might say \"misguided\")\ndesire to use \"u16\" as a type in C code?  As long as we all agree\nthat the data that can be passed across the ffi barrier should be of\nthe types of known size, and let C side use uint(8|16|32|64)_t and\nRust side use u(8|16|32|64) consistently, we do not need to have\nthis \"cleanup\", do we?\n\nThanks.\n"},{"id":"526581","messageId":"xmqqh5x1f7tz.fsf@gitster.g","threadId":"64060","inReplyTo":"CAPig+cS_=YTBaCjn8-Th3yn3_k+a8_pMOmdv_Dq4S0tfp8BRCg@mail.gmail.com","subject":"Re: [PATCH v2 07/18] build: introduce rust","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-09-17T14:54:16Z","receivedAt":"2025-09-17T14:54:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n>> diff --git a/.gitignore b/.gitignore\n>> @@ -254,3 +254,6 @@ Release/\n>>  /contrib/buildsystems/out\n>>  /contrib/libgit-rs/target\n>>  /contrib/libgit-sys/target\n>> +.idea/\n>> +/rust/target/\n>> +/rust/Cargo.lock\n>\n> Is \".idea\" directory detritus from your chosen editor? If so, it\n> probably ought not be added to this list since we don't otherwise\n> ignore detritus from foreign tools such as that.\n\nI knew that the above was our official stance but somehow thought\nthat we loosened at some point to add common ones like *~ and\n*.sw[op] to make life simpler for majority of developers.  But I was\nwrong.  We do not even have *~, which means we haven't officially\nloosened.\n\nBut there are oddballs that violate this policy like \".cache\"\nintroduced by a5c01603 (gitignore: ignore clangd .cache directory,\n2023-08-04).  Three are many other that are *not* our droppings,\nbetween /.vscode/ to /contrib/buildsystems/out in .gitignore file.\n\n    /.vscode/\n    /tags\n    /TAGS\n    /cscope*\n    /compile_commands.json\n    /.cache/\n    *.hcc\n    *.obj\n    *.lib\n    *.sln\n    *.sp\n    *.suo\n    *.ncb\n    *.vcproj\n    *.user\n    *.idb\n    *.pdb\n    *.ilk\n    *.iobj\n    *.ipdb\n    *.dll\n    .vs/\n    Debug/\n    Release/\n    /UpgradeLog*.htm\n    /git.VC.VC.opendb\n    /git.VC.db\n    *.dSYM\n\nSome (like TAGS and *.hcc) are our droppings (in other words, what\n\"make\" with some build targets may produce), but most of these are\ntool specific and according to our original official stance, they\nshould never have been added, but there they are.\n\nI actually do not mind having common ones to the project .gitignore\nas long as it does not get bloated too much with droppings from\nesoteric tools that majority of us have never heard of.  It seems\nthat we have been punishing needlessly Emacs and vim users while\nbeing sloppy about others' droppings.  A #leftoverbit may be to\nhave a brief discussion to gain consensus and add a few common ones\nand/or remove too esoteric ones?  I dunno. \n\n>> +$(GIT_BUILD_DIR)/lib%.a:\n>> +       ./build_rust.sh $(GIT_BUILD_DIR) $(RUST_BUILD_MODE) $*\n>\n> I wonder if this should follow the convention of most (but not quite\n> all) other local shell script invocations in the Makefile of\n> referencing SHELL_PATH; for instance:\n>\n>     $(QUIET_GEN)$(SHELL_PATH) ./build_rust.sh ...etc...\n>\n> with or without the $(QUIET_GEN) bit.\n\nGood suggestion.  This reminds me of my favorite gripe, which is\nthat \"make clean\" makes no effort to be quiet by default; perhaps\nleaving a #leftover bit marker here would help?\n\nThanks.\n"},{"id":"526604","messageId":"xmqqcy7pc8ix.fsf@gitster.g","threadId":"64060","inReplyTo":"pull.2043.v2.git.git.1758071798.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 00/18] Introduce rust: In xdiff","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-09-17T17:07:34Z","receivedAt":"2025-09-17T17:07:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Ezekiel Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> This is a continuation of\n> https://lore.kernel.org/git/pull.1980.v3.git.git.1755921356.gitgitgadget@gmail.com/,\n> but I am removing the RFC label.\n\nContinuation meaning that these patches build on top of that earlier\n15-patch series?\n\n> Suggestions on changes that I could make to this series is appreciated.\n>\n> Changes in v2: High level overview:\n>\n>  * patch 1: cleanup: rename variables that collide with Rust primitive type\n>    names\n>  * patches 2-4: Makefile now produces libgit.a correctly\n>  * patches 5,6: Documentation from Brian and Patrick\n>  * patches 7,8: Introduce Rust\n>  * patches 9-11: github workflows\n>  * patches 12-14: introduce crates build-helper, and cbindgen\n>  * patches 15-18: varint test balloon\n>\n> I would like feed back in two categories: big changes, and little changes.\n\nThis seems to also mix in some patches from Patrick's series that\nare already in flight.  What's the intention of the inclusion?  Do\nyou expect us to discard Patrick's series and replace with this,\nwhich would lose some from them and then add more from here?  Your\n\"pull request\" may target my \"master/main\" branch, but it needs to\nplay well together with other topics in flight that are cooking in\n'next' and also with other topics that are aspiring to be in 'next'.\n\nSo I can figure out that these patches are designed to apply cleanly\non top of Git v2.51.0, I am somewhat lost what you want to do with\nthe resulting branch.  Having duplicate commits that happen to do\nthe same thing in multiple branches \"git\" the tool can handle just\nfine, but that certainly is a bad communication among developers\nthat we do not want to particularly encourage.\n\nBefore talking about \"big\" and \"little\" changes, do we need to talk\nabout the series organization and working well among multiple\ndevelopers?\n\nThanks.\n"},{"id":"526630","messageId":"xmqqms6sbyhg.fsf@gitster.g","threadId":"64060","inReplyTo":"xmqqcy7pc8ix.fsf@gitster.g","subject":"Re: [PATCH v2 00/18] Introduce rust: In xdiff","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-09-17T20:44:27Z","receivedAt":"2025-09-17T20:44:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> \"Ezekiel Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n>> This is a continuation of\n>> https://lore.kernel.org/git/pull.1980.v3.git.git.1755921356.gitgitgadget@gmail.com/,\n>> but I am removing the RFC label.\n>\n> Continuation meaning that these patches build on top of that earlier\n> 15-patch series?\n>\n>> Suggestions on changes that I could make to this series is appreciated.\n>>\n>> Changes in v2: High level overview:\n>>\n>>  * patch 1: cleanup: rename variables that collide with Rust primitive type\n>>    names\n>>  * patches 2-4: Makefile now produces libgit.a correctly\n>>  * patches 5,6: Documentation from Brian and Patrick\n>>  * patches 7,8: Introduce Rust\n>>  * patches 9-11: github workflows\n>>  * patches 12-14: introduce crates build-helper, and cbindgen\n>>  * patches 15-18: varint test balloon\n>>\n>> I would like feed back in two categories: big changes, and little changes.\n>\n> This seems to also mix in some patches from Patrick's series that\n> are already in flight. ...\n> ...  Having duplicate commits that happen to do\n> the same thing in multiple branches \"git\" the tool can handle just\n> fine, but that certainly is a bad communication among developers\n> that we do not want to particularly encourage.\n\nThis seems to be even more confusing than I thought.  The text in\nthe breaking changes document may be based on Patrick's patch, but\nit says something different from any of the five iterations of the\noriginal series.  Since RFC v2 on Sep 5th [*], the introduction\nschedule described there had multiple steps before Git 3.0 but the\ntext we see in this series plans to start Rust \"on\" by default and\ngoes directly to Git 3.0 without any other intermediate steps, for\nexample.  How was [v2 06/18], which attributes authorship to\nPatrick, came into this shape?  Is this his update over the [v5 7/9]\nof his series sent outside the mailing list, or did you and Patrick\nworking together to arrive at this updated text?\n\n\n[References]\n\n* Patrick's rust-balloon series (various iterations)\n\nhttps://lore.kernel.org/git/20250905-b4-pks-rust-breaking-change-v2-5-6939cbf4a0b8@pks.im/\nhttps://lore.kernel.org/git/20250908-b4-pks-rust-breaking-change-v3-6-1cd7189fed3b@pks.im/\nhttps://lore.kernel.org/git/20250910-b4-pks-rust-breaking-change-v4-7-4a63fc69278d@pks.im/\nhttps://lore.kernel.org/git/20250915-b4-pks-rust-breaking-change-v5-7-dc3a32fbb216@pks.im/\n\n"},{"id":"526636","messageId":"CABPp-BHJUkSERQon6xx=sHrhN7i=6ekv+Hz1+P+2mh0=Xw15Mg@mail.gmail.com","threadId":"64060","inReplyTo":"xmqqcy7pc8ix.fsf@gitster.g","subject":"Re: [PATCH v2 00/18] Introduce rust: In xdiff","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2025-09-17T21:34:55Z","receivedAt":"2025-09-17T21:35:07Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Sep 17, 2025 at 10:07 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> \"Ezekiel Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > This is a continuation of\n> > https://lore.kernel.org/git/pull.1980.v3.git.git.1755921356.gitgitgadget@gmail.com/,\n> > but I am removing the RFC label.\n>\n> Continuation meaning that these patches build on top of that earlier\n> 15-patch series?\n\nNo, continuation just meaning \"this is v4, even if not labelled as\nsuch by gitgitgadget\".  \"Replacement for\" would have been clearer.\n\n> > Suggestions on changes that I could make to this series is appreciated.\n> >\n> > Changes in v2: High level overview:\n> >\n> >  * patch 1: cleanup: rename variables that collide with Rust primitive type\n> >    names\n> >  * patches 2-4: Makefile now produces libgit.a correctly\n> >  * patches 5,6: Documentation from Brian and Patrick\n> >  * patches 7,8: Introduce Rust\n> >  * patches 9-11: github workflows\n> >  * patches 12-14: introduce crates build-helper, and cbindgen\n> >  * patches 15-18: varint test balloon\n> >\n> > I would like feed back in two categories: big changes, and little changes.\n>\n> This seems to also mix in some patches from Patrick's series that\n> are already in flight.  What's the intention of the inclusion?  Do\n> you expect us to discard Patrick's series and replace with this,\n> which would lose some from them and then add more from here?  Your\n> \"pull request\" may target my \"master/main\" branch, but it needs to\n> play well together with other topics in flight that are cooking in\n> 'next' and also with other topics that are aspiring to be in 'next'.\n\nThe lack of context in the cover letter is a good point; I apologize\nfor not having been available to advise and avoid that problem;\nEzekiel's been doing the best he can on his own for a while now with\nonly very sporadic comments from me, due to my daughter's recent\nmedical emergency (and then me being multiple weeks behind on\neverything else because of that).\n\n> So I can figure out that these patches are designed to apply cleanly\n> on top of Git v2.51.0, I am somewhat lost what you want to do with\n> the resulting branch.  Having duplicate commits that happen to do\n> the same thing in multiple branches \"git\" the tool can handle just\n> fine, but that certainly is a bad communication among developers\n> that we do not want to particularly encourage.\n>\n> Before talking about \"big\" and \"little\" changes, do we need to talk\n> about the series organization and working well among multiple\n> developers?\n\nI think that would be helpful, including some guidance on next steps\nbecause even I don't know what to advise at this point.  The\nbackground so far:\n\n* Ezekiel contributed a series to introduce Rust[1].  Someone\ncontributed a competing series and Ezekiel was asked to target\nsomewhere else[2].\n* I made a suggestion for that somewhere else, you agreed that it\nsounded reasonable[3], and Ezekiel complied and redid the series\naccordingly.\n* Patrick wanted the introduction to be different, which is fair.\nHowever, instead of providing feedback or suggesting doing things an\nalternative way, Patrick submitted a competing series that redid the\nRust introduction without incorporating any of Ezekiel's work[3], that\nwas far less complete (e.g. no Makefile support, not running on all CI\nplatforms) and saying he'd personally add that stuff later[5], and the\nseries had a few things that both brian and Ezekiel objected to (e.g.\ncargo delegation, ambiguous types, minimum version which Ezekiel\nalready demonstrated was insufficient).  I think the thought here on\nPatrick's side may have been that Ezekiel's focus was solely on xdiff,\nrather than having a dual focus on xdiff and Rust, but Patrick left no\nguidance for Ezekiel on how he could move forward with the other Rust\nparts Ezekiel did or even whether he was welcome to make contributions\nin the area.\n* With no feedback on how to move forward, Ezekiel wanted to try to\nmerge the relevant parts of the two series, including playing nice by\nincorporating some of Patrick's changes -- and commented on Patrick's\nseries to that effect[6].  Linking that email (or even incorporating\nit) into the cover letter would have been helpful, but he's new and\nmissed that.\n\n(Personally, I think it would have been much better for Patrick to\nsend in a series with _just_ the BreakingChanges stuff, and then send\npatches to Ezekiel with the help & varint stuff, similar to what Dscho\ndid with git-for-windows & Rust[7], and ask Ezekiel to make a test\nballoon for introducing Rust.  Allowing new contributors to get some\ncredit for their work instead of replacing/discarding it tends to go\nover better.  And, to be fully blunt, I think introducing a competing\nor replacement series to something actively being worked ought to be\nmore of a last resort whether or not the other contributor is new; but\nI'll stop there since otherwise folks might dig up my blow up -- that\nI'm not too proud of -- from some years ago when someone did this to\nme.)\n\n[1] https://lore.kernel.org/git/pull.1980.git.git.1752784344.gitgitgadget@gmail.com/\n[2] https://lore.kernel.org/git/xmqqldnggt2v.fsf@gitster.g/\n[3] https://lore.kernel.org/git/xmqqzfbvfxs6.fsf@gitster.g/\n[4] https://lore.kernel.org/git/20250910-b4-pks-rust-breaking-change-v4-0-4a63fc69278d@pks.im/\n[5] https://lore.kernel.org/git/aME1ETcGAbhoO49n@pks.im/\n[6] https://lore.kernel.org/git/CAH=ZcbB0Qv=b-hdB2EVW-D-dob4NnzyWDYGEThYZm94S0V7OGg@mail.gmail.com/\n[7] https://lore.kernel.org/git/ac871bc4-df93-31f4-55f2-d6fc538a422d@gmx.de/\n\nSo, how to move forward?\n\nA) Modify Patrick's series to just take patch 7 of his v5.  Patrick\ndid say that the roadmap was  \"the more important discussion compared\nto the technical discussion\", and merging that patch would achieve his\ngoal of getting an initial roadmap.  Then Ezekiel could grab other\npieces from Patrick's series (e.g. the help and varint stuff) and\nincorporate it into an \"introduce rust\" series.[*]\n\nB) Merge Patrick's series and tell Ezekiel to rebase, while noting to\nEzekiel that the roadmap is the important bit from Patrick's series[*]\nand he can suggest changes to any of the other bits.\n\nC) Create a consolidated \"introduce Rust\" series with bits of both --\nwhat I think Ezekiel was trying to do with this series.\n\nD) Something else?\n\n\nI think Ezekiel's willing to be flexible here, but was doing the best\nhe could as a new contributor with no guidance.  What would you like\nto see, Junio?\n\n\n\n[*] Either of these routes are slightly funny, though because Patrick\nintroduced a one-quarter delay on notification to end users of the\nupcoming change due to the incompleteness of his series.  With\nEzekiel's changes to build on all platforms in CI, that delay would\nserve no purpose (that I see) and only harm users by giving them less\nnotice of an upcoming change; so Ezekiel would likely want to tweak\nthe BreakingChanges document in either of these cases to note that\nwe'll give earlier notification to users.\n"},{"id":"526643","messageId":"xmqqy0qcae6z.fsf@gitster.g","threadId":"64060","inReplyTo":"CABPp-BHJUkSERQon6xx=sHrhN7i=6ekv+Hz1+P+2mh0=Xw15Mg@mail.gmail.com","subject":"Re: [PATCH v2 00/18] Introduce rust: In xdiff","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-09-17T22:48:04Z","receivedAt":"2025-09-17T22:48:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n> So, how to move forward?\n>\n> A) Modify Patrick's series to just take patch 7 of his v5.  Patrick\n> did say that the roadmap was  \"the more important discussion compared\n> to the technical discussion\", and merging that patch would achieve his\n> goal of getting an initial roadmap.  Then Ezekiel could grab other\n> pieces from Patrick's series (e.g. the help and varint stuff) and\n> incorporate it into an \"introduce rust\" series.[*]\n>\n> B) Merge Patrick's series and tell Ezekiel to rebase, while noting to\n> Ezekiel that the roadmap is the important bit from Patrick's series[*]\n> and he can suggest changes to any of the other bits.\n>\n> C) Create a consolidated \"introduce Rust\" series with bits of both --\n> what I think Ezekiel was trying to do with this series.\n\nAh, I didn't even realize C was what this series was trying to do.\n\nI do not have particular preference between A and B, but I thought A\nwas closer to what was being done with this series, and as long as\nEzekiel and Patrick can join forces that way, it would be perfect.\n\nThanks.\n"},{"id":"526645","messageId":"CALnO6CAmNTAOj0wkkPoEMOhHB1bgM4xBKa1=4zuS1Cs=UAq_BA@mail.gmail.com","threadId":"64060","inReplyTo":"CAPig+cTZch_pvfurtjBTNphMeRQL6jSBSjNY-4mffjoXZ4eqcw@mail.gmail.com","subject":"Re: [PATCH v2 13/18] build-helper: link against libgit.a and any other required C libraries","fromName":"D. Ben Knoble","fromEmail":"ben.knoble@gmail.com","sentAt":"2025-09-17T23:07:43Z","receivedAt":"2025-09-17T23:07:56Z","isPatch":true,"sender":{"key":"ben.knoble@gmail.com","avatar":"https://avatars.githubusercontent.com/u/22802209?v=4"},"body":"On Wed, Sep 17, 2025 at 4:52 AM Eric Sunshine <sunshine@sunshineco.com> wrote:\n>\n> On Tue, Sep 16, 2025 at 9:18 PM Ezekiel Newren via GitGitGadget\n> > +impl BuildHelper {\n> > +    pub fn build(self) {\n> > +        let use_linking = parse_bool_from_option(self.crate_env.get(\"USE_LINKING\"), self.crate_env.get(\"CARGO_TARGET_DIR\").is_none());\n> > +        ...\n> > +            println!(\"cargo:warning={} is not linking against C objects, `USE_LINKING=true cargo test`\", self.crate_env[\"CARGO_PKG_NAME\"]);\n>\n> There are more than a few developers on this project (including\n> myself) who still use 80-column editors and terminals. As a general\n> style guideline, this project does recommend wrapping code to fit\n> within 80 columns (except in cases when doing so would severely hurt\n> readability). I imagine that the same sort of guideline would be\n> appreciated in Rust code, as well, by those who still stick with 80\n> columns.\n>\n> I bring this up because, although it hasn't been such a big deal with\n> the existing C code, assuming that developers run `rustfmt` on the\n> code before sending a patch series, then this may become an issue if\n> different developers have `rustfmt` configured to enforce different\n> maximum column width, especially since `rustfmt` is likely to reformat\n> the entire file rather than just the region that has just been edited.\n> So, if this code gets checked in as-is with these very wide lines, and\n> then someone else, who has `rustfmt` configured for 80-columns edits\n> the file, then it becomes a problem.\n>\n> As such, can we also add a project-wide `rustfmt.toml` which, at\n> minimum, sets the maximum line width to 80? For instance:\n>\n>     max_width = 80\n>\n\nI believe rustfmt and Rust generally uses a 100 column limit.\n\nSee\n- https://github.com/rust-lang/rust/pull/5340\n- https://rust-lang.github.io/rustfmt/?version=v1.8.0&search=#max_width\n\nPerhaps you knew this, and are really suggesting we break with Rust\nstyle for our own style?\n\nFor handling personal configs, we could try to maintain a config here\nthat repeats all the defaults, but that seems like more maintenance\nheadache than noticing unnecessary reformatting and reminding folks we\nuse the defaults (except where we have config that specifies\notherwise). Although the docs site doesn’t suggest the configs are\nlayered like for Git, so perhaps a blank config is sufficient to\nenforce « we use the defaults »?\n\n-- \nD. Ben Knoble\n"},{"id":"526647","messageId":"CAPig+cTdJAjuekz6YXDkxTjTRxsPEzSUxhoD8nK9k7uA4s=rHQ@mail.gmail.com","threadId":"64060","inReplyTo":"CALnO6CAmNTAOj0wkkPoEMOhHB1bgM4xBKa1=4zuS1Cs=UAq_BA@mail.gmail.com","subject":"Re: [PATCH v2 13/18] build-helper: link against libgit.a and any other required C libraries","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2025-09-17T23:31:04Z","receivedAt":"2025-09-17T23:31:16Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Wed, Sep 17, 2025 at 7:07 PM D. Ben Knoble <ben.knoble@gmail.com> wrote:\n> On Wed, Sep 17, 2025 at 4:52 AM Eric Sunshine <sunshine@sunshineco.com> wrote:\n> > There are more than a few developers on this project (including\n> > myself) who still use 80-column editors and terminals. As a general\n> > style guideline, this project does recommend wrapping code to fit\n> > within 80 columns (except in cases when doing so would severely hurt\n> > readability). I imagine that the same sort of guideline would be\n> > appreciated in Rust code, as well, by those who still stick with 80\n> > columns.\n> >\n> > I bring this up because, although it hasn't been such a big deal with\n> > the existing C code, assuming that developers run `rustfmt` on the\n> > code before sending a patch series, then this may become an issue if\n> > different developers have `rustfmt` configured to enforce different\n> > maximum column width, especially since `rustfmt` is likely to reformat\n> > the entire file rather than just the region that has just been edited.\n> > So, if this code gets checked in as-is with these very wide lines, and\n> > then someone else, who has `rustfmt` configured for 80-columns edits\n> > the file, then it becomes a problem.\n> >\n> > As such, can we also add a project-wide `rustfmt.toml` which, at\n> > minimum, sets the maximum line width to 80? For instance:\n> >\n> >     max_width = 80\n>\n> I believe rustfmt and Rust generally uses a 100 column limit.\n>\n> Perhaps you knew this, and are really suggesting we break with Rust\n> style for our own style?\n\nCorrect, I did know that, and I also know that there are a number of\ndevelopers on the Git project who still adhere to (perhaps archaic)\n80-column editor and terminal window sizes.\n\nProject guidelines have long suggested 80 columns as a desirable\nmaximum not only for C code, but for pretty much all other resources,\nincluding shell code, Perl code, and documentation files. This\nsuggested maximum works well for adherents of 80-columns and\n(presumably) hasn't been too onerous for developers who use wider\nwindows; at least we haven't heard people clamoring to increase the\nsuggested maximum column limit. As such, it does not seem far-fetched\nto expect that the project guidelines should/could/would also apply to\nRust code.\n\n> For handling personal configs, we could try to maintain a config here\n> that repeats all the defaults, but that seems like more maintenance\n> headache than noticing unnecessary reformatting and reminding folks we\n> use the defaults (except where we have config that specifies\n> otherwise). Although the docs site doesn’t suggest the configs are\n> layered like for Git, so perhaps a blank config is sufficient to\n> enforce « we use the defaults »?\n\nSorry, I'm not following what you are asking. I'm simply suggesting\nthe possibility of adding a toplevel \"$GIT/.rustfmt.toml\" to the\nproject with the single line content:\n\n    max_width = 80\n\nThat aside, we probably also ought to insist (via documentation) that\ncontributors run `rustfmt` on Rust code before submitting a patch\nseries.\n"},{"id":"526680","messageId":"CAPig+cTHtxwr6TRJOmLj9ktaaRdQFLXDewfQU0zxZ0m8ADmMnA@mail.gmail.com","threadId":"64060","inReplyTo":"xmqqh5x1f7tz.fsf@gitster.g","subject":"Re: [PATCH v2 07/18] build: introduce rust","fromName":"Eric Sunshine","fromEmail":"ericsunshine@gmail.com","sentAt":"2025-09-18T07:06:30Z","receivedAt":"2025-09-18T07:06:42Z","isPatch":true,"sender":{"key":"ericsunshine@gmail.com","avatar":null},"body":"On Wed, Sep 17, 2025 at 10:54 AM Junio C Hamano <gitster@pobox.com> wrote:\n> Eric Sunshine <sunshine@sunshineco.com> writes:\n> >> +.idea/\n> >\n> > Is \".idea\" directory detritus from your chosen editor? If so, it\n> > probably ought not be added to this list since we don't otherwise\n> > ignore detritus from foreign tools such as that.\n>\n> I knew that the above was our official stance but somehow thought\n> that we loosened at some point to add common ones like *~ and\n> *.sw[op] to make life simpler for majority of developers.  But I was\n> wrong.  We do not even have *~, which means we haven't officially\n> loosened.\n>\n> But there are oddballs that violate this policy like \".cache\"\n> introduced by a5c01603 (gitignore: ignore clangd .cache directory,\n> 2023-08-04).  Three are many other that are *not* our droppings,\n> between /.vscode/ to /contrib/buildsystems/out in .gitignore file.\n>\n>     /.vscode/\n>     /tags\n>     /TAGS\n>     /cscope*\n>     /compile_commands.json\n>     /.cache/\n>     *.hcc\n>     *.obj\n>     *.lib\n>     *.sln\n>     *.sp\n>     *.suo\n>     *.ncb\n>     *.vcproj\n>     *.user\n>     *.idb\n>     *.pdb\n>     *.ilk\n>     *.iobj\n>     *.ipdb\n>     *.dll\n>     .vs/\n>     Debug/\n>     Release/\n>     /UpgradeLog*.htm\n>     /git.VC.VC.opendb\n>     /git.VC.db\n>     *.dSYM\n>\n> Some (like TAGS and *.hcc) are our droppings (in other words, what\n> \"make\" with some build targets may produce), but most of these are\n> tool specific and according to our original official stance, they\n> should never have been added, but there they are.\n\nIt's been a while since I had to build code with Visual Studio (the\nproprietary Microsoft product, not the open-source VScode), but if I\nrecall correctly, all the entries from \".obj\" through \"Release/\" are\nbuild detritus from compiling with that tool. Assuming we still\nsupport building Git with Visual Studio (which I believe is the case),\nthen those entries all fall within the same categorization as \"our\ndroppings\" similar to the `make` case, so having these in \".gitignore\"\nis probably in line with the project's official stance.\n\nOthers, such as \"/.vscode/\", on the other hand, fall into the other\ncategory of \"someone's favorite editor or handy tool\", which has thus\nfar been frowned upon.\n\n> I actually do not mind having common ones to the project .gitignore\n> as long as it does not get bloated too much with droppings from\n> esoteric tools that majority of us have never heard of.  It seems\n> that we have been punishing needlessly Emacs and vim users while\n> being sloppy about others' droppings.  A #leftoverbit may be to\n> have a brief discussion to gain consensus and add a few common ones\n> and/or remove too esoteric ones?  I dunno.\n\nI don't have a strong opinion aside from avoiding bloat; I've seen\n(and inherited at $DAYJOBS) far too many projects which grab some\noverly bloated .gitignore template from somewhere in which most of the\nentries are meaningless for the project at hand, yet which is used\nas-is rather than pruning out the unneeded entries (typically >95% of\nthem). As with dead code, those unneeded .gitignore entries tend to be\na source of potential confusion, which is rarely or never the case\nwith a well-curated .gitignore (or with well-curated code).\n\nThat said, I also probably do not mind having the common ones in Git's\n.gitignore. A few which come to mind include:\n\n* editor-specific droppings (Emacs, vi / vim, VScode)\n* .DS_Store files which macOS's Finder drops into *every* directory it\nvisits, thus which litter the filesystem\n"},{"id":"526798","messageId":"xmqq348i5kqx.fsf@gitster.g","threadId":"64060","inReplyTo":"3061cc46c1910bdc4f66855db7a07d313b37fb27.1758071798.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 04/18] make: merge reftable lib into libgit.a","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-09-19T19:02:14Z","receivedAt":"2025-09-19T19:02:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Ezekiel Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Ezekiel Newren <ezekielnewren@gmail.com>\n>\n> Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n> ---\n>  Makefile | 39 ++++++++++++++++-----------------------\n>  1 file changed, 16 insertions(+), 23 deletions(-)\n\nAside from the comment already given about the fact that the\nproposed log message does not explain any reason why these change\nare necessary, this step and the previous step are fairly hostile to\nmerging the topic to play well with other topics, especially given\nthat there would be topics in flight that may want to add, remove,\nor reorder these two existing lists.\n\nI wonder if these could have been arranged like the following instead?\n\n * Drop \"REFTABLE_LIB = reftable/libreftable.a\" and the target that\n   runs \"ar\" to mantain that archive.\n\n * Leave \"REFTABLE_OBJS += $objects.o\" lines alone.\n\n * Add them into LIB_OBJS so that they are included in libgit.a,\n   perhaps a single line like this:\n\n\tLIB_OBJS += $(REFTABLE_OBJS)\n\nWouldn't that have worked equally well for the (unstated) purpose of\nthese two patches without incurring unnecessary risk of mismerges?\n\nSimilar arrangement for xdiff.\n\nThanks.\n"},{"id":"526803","messageId":"CAH=ZcbDn0tEPu-xuY4uOMk1uajT_Wma=9G5mQ3PBsC-mQm1EqQ@mail.gmail.com","threadId":"64060","inReplyTo":"xmqqa52tgne2.fsf@gitster.g","subject":"Re: [PATCH v2 01/18] cleanup: rename variables that collide with Rust primitive type names","fromName":"Ezekiel Newren","fromEmail":"ezekielnewren@gmail.com","sentAt":"2025-09-19T19:36:43Z","receivedAt":"2025-09-19T19:36:57Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"On Wed, Sep 17, 2025 at 8:32 AM Junio C Hamano <gitster@pobox.com> wrote:\n> Isn't the true cause of the trouble the (I might say \"misguided\")\n> desire to use \"u16\" as a type in C code?  As long as we all agree\n> that the data that can be passed across the ffi barrier should be of\n> the types of known size, and let C side use uint(8|16|32|64)_t and\n> Rust side use u(8|16|32|64) consistently, we do not need to have\n> this \"cleanup\", do we?\n\nIt appears that I am the only one in favor of using Rust type names in\nC. Going forward I'll use [ui]int(8|16|32|64)_t types going forward.\nI'll drop this commit.\n"},{"id":"526805","messageId":"CAH=ZcbAOBnEVfyNy_4hdpe_dvsqDxsKVTcvurJz_iUWiqOzG3g@mail.gmail.com","threadId":"64060","inReplyTo":"CAPig+cRGtMX9bQhb+7g5aXRVjpLKY-qDdQP95f9TmbY8j_BweQ@mail.gmail.com","subject":"Re: [PATCH v2 02/18] make: add -fPIE flag","fromName":"Ezekiel Newren","fromEmail":"ezekielnewren@gmail.com","sentAt":"2025-09-19T19:48:18Z","receivedAt":"2025-09-19T19:48:31Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"On Wed, Sep 17, 2025 at 1:45 AM Eric Sunshine <sunshine@sunshineco.com> wrote:\n> The commit message explains what the patch is doing (which is obvious\n> from the patch itself) but doesn't explain why this change is needed,\n> hence it is very difficult for a reviewer to understand why such a\n> change is necessary.\n\nI was trying to get the rust compiler to link against libgit.a. Both\nMeson and Makefile produce libgit.a. No changes were to meson.build\nwere necessary for that to work, but but with Makefile I would get\nerrors like:\nrelocation R_X86_64_32 against `.rodata' can not be used when making a\nPIE object; recompile with -fPIE\n...\n\nI should have added this to the commit message.\n"},{"id":"526807","messageId":"CAH=ZcbBsE3AwoE2t=recg9kwtq8y0tXECRtU9YnbH0WVWZdCCA@mail.gmail.com","threadId":"64060","inReplyTo":"CAPig+cTDUM5nVbBF5L3k+Gr5xYxXdH2ungVvkQ9QVbZCqMsLHg@mail.gmail.com","subject":"Re: [PATCH v2 03/18] make: merge xdiff lib into libgit.a","fromName":"Ezekiel Newren","fromEmail":"ezekielnewren@gmail.com","sentAt":"2025-09-19T19:54:22Z","receivedAt":"2025-09-19T19:54:35Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"On Wed, Sep 17, 2025 at 1:46 AM Eric Sunshine <sunshine@sunshineco.com> wrote:\n> Please extend the commit message to explain why this change is desirable.\n\nThis commit continues the effort of making the Rust compiler link\nagainst libgit.a. Meson already includes xdiff when it builds\nlibgit.a, but Makefile does not. Rather than telling the Rust compiler\nto also link against xdiff/lib.a, if Makefile was used as opposed to\nMeson, seemed like a clunkier solution.\n\nI should have asked for a better way to accomplish this as part of my\ncommit message or as a question on the mailing list.\n"},{"id":"526809","messageId":"CAH=ZcbCRzGGR1RFTWV1Zo7bm+DScx=zOJ=Ov-WkaQNrDN9w1Nw@mail.gmail.com","threadId":"64060","inReplyTo":"xmqq348i5kqx.fsf@gitster.g","subject":"Re: [PATCH v2 04/18] make: merge reftable lib into libgit.a","fromName":"Ezekiel Newren","fromEmail":"ezekielnewren@gmail.com","sentAt":"2025-09-19T20:00:30Z","receivedAt":"2025-09-19T20:00:43Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"On Fri, Sep 19, 2025 at 1:02 PM Junio C Hamano <gitster@pobox.com> wrote:\n> Aside from the comment already given about the fact that the\n> proposed log message does not explain any reason why these change\n> are necessary, this step and the previous step are fairly hostile to\n> merging the topic to play well with other topics, especially given\n> that there would be topics in flight that may want to add, remove,\n> or reorder these two existing lists.\n>\n> I wonder if these could have been arranged like the following instead?\n>\n>  * Drop \"REFTABLE_LIB = reftable/libreftable.a\" and the target that\n>    runs \"ar\" to mantain that archive.\n>\n>  * Leave \"REFTABLE_OBJS += $objects.o\" lines alone.\n>\n>  * Add them into LIB_OBJS so that they are included in libgit.a,\n>    perhaps a single line like this:\n>\n>         LIB_OBJS += $(REFTABLE_OBJS)\n>\n> Wouldn't that have worked equally well for the (unstated) purpose of\n> these two patches without incurring unnecessary risk of mismerges?\n>\n> Similar arrangement for xdiff.\n\nLike the previous two commits; This one continues the effort to get\nthe Rust compiler to link against libgit.a. Meson already includes the\nreftable in its libgit.a, but Makefile does not.\n\nThe reason why I was trying to get the Rust compiler to link against\nlibgit.a is because I wanted to get Rusts unit testing to work. If the\nRust code calls a C function from Git then 'cargo test' needs to know\nabout it.\n\nHowever I think I'll drop these 3 commits since 'cargo test' doesn't\nneed to be part of the introduction of Rust. It would be nice for make\nto be able to run Rust unit tests at some point though.\n"},{"id":"526810","messageId":"xmqq5xde435i.fsf@gitster.g","threadId":"64060","inReplyTo":"CAH=ZcbAOBnEVfyNy_4hdpe_dvsqDxsKVTcvurJz_iUWiqOzG3g@mail.gmail.com","subject":"Re: [PATCH v2 02/18] make: add -fPIE flag","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-09-19T20:07:37Z","receivedAt":"2025-09-19T20:07:40Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ezekiel Newren <ezekielnewren@gmail.com> writes:\n\n> I was trying to get the rust compiler to link against libgit.a. Both\n> Meson and Makefile produce libgit.a. No changes were to meson.build\n> were necessary for that to work, but but with Makefile I would get\n> errors like:\n> relocation R_X86_64_32 against `.rodata' can not be used when making a\n> PIE object; recompile with -fPIE\n\nThis sounds like a workaround that is somewhat platform and compiler\nspecific limitation, at least to me.  Does this need to be conditional\nand if so on what?\n\nThanks.\n"},{"id":"526811","messageId":"CAH=ZcbBBkk2B3PxKf54MRnAmURMK8W7ofFZBRS=ZzkuDNWsY9w@mail.gmail.com","threadId":"64060","inReplyTo":"CAPig+cS_=YTBaCjn8-Th3yn3_k+a8_pMOmdv_Dq4S0tfp8BRCg@mail.gmail.com","subject":"Re: [PATCH v2 07/18] build: introduce rust","fromName":"Ezekiel Newren","fromEmail":"ezekielnewren@gmail.com","sentAt":"2025-09-19T20:11:01Z","receivedAt":"2025-09-19T20:11:16Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"On Wed, Sep 17, 2025 at 2:26 AM Eric Sunshine <sunshine@sunshineco.com> wrote:\n> Is \".idea\" directory detritus from your chosen editor? If so, it\n> probably ought not be added to this list since we don't otherwise\n> ignore detritus from foreign tools such as that.\n\nYes. I use the Jetbrains IDE's CLion and RustRover for C and Rust\nrespectively. Jetbrains has an IDE for MANY languages and all of them\nuse .idea/ as the folder for IDE specific configuration. I'm fine with\nkeeping it out of .gitignore, but I wanted to know what the community\nthought. Which I should have made it more clear that this patch series\nwas more of an RFC than proposed code to be merged.\n\n> > diff --git a/Makefile b/Makefile\n> > @@ -916,6 +924,51 @@ TEST_SHELL_PATH = $(SHELL_PATH)\n> > +RUST_CRATES :=\n> > +.PHONY: compile_rust rustclean\n>\n> This is an extremely tiny nit, but multi-word target names in the Git\n> Makefile are almost universally spelled with a hyphen rather than an\n> underscore, so \"compile-rust\" would be more consistent and\n> appropriate.\n>\n> > +$(GIT_BUILD_DIR)/lib%.a:\n> > +       ./build_rust.sh $(GIT_BUILD_DIR) $(RUST_BUILD_MODE) $*\n>\n> I wonder if this should follow the convention of most (but not quite\n> all) other local shell script invocations in the Makefile of\n> referencing SHELL_PATH; for instance:\n>\n>     $(QUIET_GEN)$(SHELL_PATH) ./build_rust.sh ...etc...\n>\n> with or without the $(QUIET_GEN) bit.\n>\n> > diff --git a/build_rust.sh b/build_rust.sh\n> > @@ -0,0 +1,60 @@\n> > +if [ \"$dir_git_root\" = \"\" ]; then\n> > +  echo \"did not specify the directory for the root of git\"\n> > +  exit 1\n> > +fi\n>\n> Let's follow project style conventions for shell scripts:\n>\n>     if test \"$dir_git_root\" = \"\"\n>     then\n>         ...\n>     fi\n>\n> or even:\n>\n>     if test -z \"$dir_git_root\"\n>     then\n>         ...\n>     fi\n>\n> Same comment applies to the rest of the script.\n>\n> Also, should the above error message be sent to stderr rather than\n> stdout? (Genuine question since I'm not sure if other scripts are\n> necessarily consistent in that regard.)\n>\n> > +if [ \"$rust_build_profile\" = \"release\" ]; then\n> > +  rust_args=\"--release\"\n> > +  export RUSTFLAGS=''\n> > +elif [ \"$rust_build_profile\" = \"debug\" ]; then\n> > +  rust_args=\"\"\n> > +  export RUSTFLAGS='-C debuginfo=2 -C opt-level=1 -C force-frame-pointers=yes'\n> > +else\n> > +  echo \"illegal rust_build_profile value $rust_build_profile\"\n> > +  exit 1\n> > +fi\n>\n> For maximum portability, we don't use the:\n>\n>     export FOO=bar\n>\n> idiom in this project. Instead, do this:\n>\n>     FOO=bar\n>     export FOO\n>\n> The same comment applies to the remainder of the patch, as well.\n>\n> Also, the empty-string assignments to RUSTFLAGS and rust_args above\n> inconsistently use a mix of single and double quotes. However, we also\n> usually just assign nothingness in such cases rather than an empty\n> string:\n>\n>     RUSTFLAGS=\n>     export RUSTFLAGS\n>     ...\n>     rust_args=\n\nI'll try to keep these in mind for the future.\n\n> > +if rustup show active-toolchain | grep windows-msvc; then\n> > +  libfile=\"${crate}.lib\"\n> > +  PATH=\"$(echo $PATH | tr ':' '\\n' | grep -Ev \"^(/mingw64/bin|/usr/bin)$\" | paste -sd: -):/mingw64/bin:/usr/bin\"\n> > +fi\n>\n> Please add either an in-code comment or a sentence/paragraph to the\n> commit message explaining why this PATH munging is needed.\n\nI will amend the commit with something like:\nOn windows when building with msvc using shell scripts it looks for\nlink in /mingw64/bin|/usr/bin when it actually needs to look somewhere\nelse for the msvc linker program. Since removing these from PATH would\nbreak everything else in the shell; move them to be at the end of\nPATH.\n\n> > +cd $dir_rust && cargo clean && pwd && USE_LINKING=\"false\" cargo build -p $crate $rust_args; cd $dir_git_root\n>\n> When formatted like this, it is very easy to overlook the fact that\n> the final `cd` is (correctly) not &&-chained with the preceding\n> commands. Reformatting it like this would help:\n>\n>     cd $dir_rust &&\n>         cargo clean &&\n>         pwd &&\n>         USE_LINKING=\"false\" cargo build -p $crate $rust_args\n>     cd $dir_git_root\n>\n> Alternatively, use a subshell and drop the final `cd`:\n>\n>     (\n>         cd $dir_rust &&\n>         cargo clean &&\n>         pwd &&\n>         USE_LINKING=\"false\" cargo build -p $crate $rust_args\n>     )\n>\n> By the way, is the `pwd` command needed? What is its purpose?\n>\n> > +rm $dst 2>/dev/null\n>\n> I think we typically use `rm -f` without redirect to /dev/null to do\n> the \"right thing\".\n>\n> > +echo mv $src $dst\n> > +mv $src $dst\n\npwd was for debugging and I forgot to take it out. I'll clean up my script.\n"},{"id":"526812","messageId":"xmqq1po242uo.fsf@gitster.g","threadId":"64060","inReplyTo":"CAH=ZcbCRzGGR1RFTWV1Zo7bm+DScx=zOJ=Ov-WkaQNrDN9w1Nw@mail.gmail.com","subject":"Re: [PATCH v2 04/18] make: merge reftable lib into libgit.a","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-09-19T20:14:07Z","receivedAt":"2025-09-19T20:14:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ezekiel Newren <ezekielnewren@gmail.com> writes:\n\n> On Fri, Sep 19, 2025 at 1:02 PM Junio C Hamano <gitster@pobox.com> wrote:\n>> Aside from the comment already given about the fact that the\n>> proposed log message does not explain any reason why these change\n>> are necessary, this step and the previous step are fairly hostile to\n>> merging the topic to play well with other topics, especially given\n>> that there would be topics in flight that may want to add, remove,\n>> or reorder these two existing lists.\n>>\n>> I wonder if these could have been arranged like the following instead?\n>>\n>>  * Drop \"REFTABLE_LIB = reftable/libreftable.a\" and the target that\n>>    runs \"ar\" to mantain that archive.\n>>\n>>  * Leave \"REFTABLE_OBJS += $objects.o\" lines alone.\n>>\n>>  * Add them into LIB_OBJS so that they are included in libgit.a,\n>>    perhaps a single line like this:\n>>\n>>         LIB_OBJS += $(REFTABLE_OBJS)\n>>\n>> Wouldn't that have worked equally well for the (unstated) purpose of\n>> these two patches without incurring unnecessary risk of mismerges?\n>>\n>> Similar arrangement for xdiff.\n>\n> Like the previous two commits; This one continues the effort to get\n> ...\n> The reason why ...\n\nNeither answers my main question, though.\n\nInstead of rolling everything into LIB_OBJS directly, wouldn't it\nhave been much easier to work with if reftable-related ones are left\nin REFTABLE_OBJS and then RERFTABLE_OBJS gets added to LIB_OBJS?\nWouldn't it have been less prone to mismerges to do it that way?\n\n> However I think I'll drop these 3 commits since 'cargo test' doesn't\n> need to be part of the introduction of Rust. It would be nice for make\n> to be able to run Rust unit tests at some point though.\n\nAs we can always extend things more, getting something close to the\nminimally viable set with some tests for sanity checking would be a\ngood first goal.  If you pare down way too much, however, we may end\nup to be pretty close to what Patrick sent out originally with the\nvarint conversion, so let's make sure we do not drop below the\nminimum that still demonstrates that we have Rust integration that\nis viable going forward.\n\nThanks.\n"},{"id":"526814","messageId":"CAPig+cSBEX5QGnzpBnVs_hKM2iUqcmA4-DzKDgkwpG9ZzWZ__w@mail.gmail.com","threadId":"64060","inReplyTo":"CAH=ZcbBBkk2B3PxKf54MRnAmURMK8W7ofFZBRS=ZzkuDNWsY9w@mail.gmail.com","subject":"Re: [PATCH v2 07/18] build: introduce rust","fromName":"Eric Sunshine","fromEmail":"ericsunshine@gmail.com","sentAt":"2025-09-19T20:24:34Z","receivedAt":"2025-09-19T20:24:46Z","isPatch":true,"sender":{"key":"ericsunshine@gmail.com","avatar":null},"body":"On Fri, Sep 19, 2025 at 4:11 PM Ezekiel Newren <ezekielnewren@gmail.com> wrote:\n> On Wed, Sep 17, 2025 at 2:26 AM Eric Sunshine <sunshine@sunshineco.com> wrote:\n> > Is \".idea\" directory detritus from your chosen editor? If so, it\n> > probably ought not be added to this list since we don't otherwise\n> > ignore detritus from foreign tools such as that.\n>\n> Yes. I use the Jetbrains IDE's CLion and RustRover for C and Rust\n> respectively. Jetbrains has an IDE for MANY languages and all of them\n> use .idea/ as the folder for IDE specific configuration. I'm fine with\n> keeping it out of .gitignore, but I wanted to know what the community\n> thought. [...]\n\nThere is a bit of discussion about this later in this same email\nthread. If the project does ultimately decide to accept these\n.gitignore entries, adding them would be done via a patch or series of\npatches specifically aimed at that goal. Hence, I'd recommend omitting\nthe \".idea\" entry from this particular patch series.\n\n> > > +if rustup show active-toolchain | grep windows-msvc; then\n> > > +  libfile=\"${crate}.lib\"\n> > > +  PATH=\"$(echo $PATH | tr ':' '\\n' | grep -Ev \"^(/mingw64/bin|/usr/bin)$\" | paste -sd: -):/mingw64/bin:/usr/bin\"\n> > > +fi\n> >\n> > Please add either an in-code comment or a sentence/paragraph to the\n> > commit message explaining why this PATH munging is needed.\n>\n> I will amend the commit with something like:\n> On windows when building with msvc using shell scripts it looks for\n> link in /mingw64/bin|/usr/bin when it actually needs to look somewhere\n> else for the msvc linker program. Since removing these from PATH would\n> break everything else in the shell; move them to be at the end of\n> PATH.\n\nI had to read and reread this several times but I think I get what it\nis saying. To paraphrase your explanation...\n\nWhen building with `cargo` (I presume), and it comes time to link the\nprogram, the build process is looking for the Microsoft linker named\nLINK.exe but, due to PATH order, is instead finding the Unix command\n`link` (which is a specialized invocation of the more common `ln`\ncommand). As such, the build process incorrectly invokes the Unix\n`link` rather than the Microsoft LINK.exe and fails. To work around\nthis problem, you move the standard Unix command-containing paths to\nthe end of PATH so that the Microsoft LINK.exe is found first.\n\n...does that sound correct?\n"},{"id":"526815","messageId":"CAH=ZcbAO8143RLqhriPY-W6=TzOFa5_CBfbJmM1-WWFhjAJrxw@mail.gmail.com","threadId":"64060","inReplyTo":"CAPig+cTZch_pvfurtjBTNphMeRQL6jSBSjNY-4mffjoXZ4eqcw@mail.gmail.com","subject":"Re: [PATCH v2 13/18] build-helper: link against libgit.a and any other required C libraries","fromName":"Ezekiel Newren","fromEmail":"ezekielnewren@gmail.com","sentAt":"2025-09-19T20:25:48Z","receivedAt":"2025-09-19T20:26:02Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"On Wed, Sep 17, 2025 at 2:51 AM Eric Sunshine <sunshine@sunshineco.com> wrote:\n>\n> On Tue, Sep 16, 2025 at 9:18 PM Ezekiel Newren via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> > build-helper: link against libgit.a and any other required C libraries\n> >\n> > Don't link against the C libraries when building with Make or Meson.\n> > Run cargo tests like this:\n> > cd rust && cargo clean && USE_LINKING=true cargo test\n> >\n> > Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>\n> > ---\n>\n> Perhaps it's because I haven't been following the discussion closely\n> enough, but the above commit message leaves me entirely in the dark.\n> After reading and rereading it several times, I suppose it is trying\n> to address some difference between building with `cargo` vs. building\n> with Make or Meson, but it gives no explanation of what the\n> differences are or what problem it is trying to solve. So, please\n> enhance the commit message to begin with the \"why\" and then proceed to\n> the \"what\" or \"how\".\n\nAs I partially explained in previous comments about my commits. I was\ntrying to get the Rust compiler to link against libgit.a so that any\nRust code that references C code in Git would work in Rust unit tests.\nThe way that Cargo invokes rustc and links to libraries can be\ncontrolled through build.rs. To use an example; When build.rs prints\nthe following lines to stdout:\ncargo:rustc-link-search=native=/path/to/git/build/dir\ncargo:rustc-link-lib=static=git\nThe first line tells cargo to tell the Rust compiler to look in\n'/path/to/git/build/dir' for objects. The second line tells Cargo to\ntell the Rust compiler to link against libgit.a. Telling Rust how to\nlink against libgit.a is only needed for Rust unit tests that\nreference Git defined C code.\n\nI'll drop build-helper for this series, since it only helps with\nlinking against libgit.a for Rust unit tests.\n"},{"id":"526816","messageId":"CAH=ZcbDcvDJ1syZEonnwoWTw-677y8+cG3Zpc8umiHKtVeEAcQ@mail.gmail.com","threadId":"64060","inReplyTo":"CAPig+cQP-7z_TNvq7mM=kFqxeZYDg4CzvJLLV+UGyebywcbuGQ@mail.gmail.com","subject":"Re: [PATCH v2 14/18] build-helper: cbindgen, let crates generate a header file","fromName":"Ezekiel Newren","fromEmail":"ezekielnewren@gmail.com","sentAt":"2025-09-19T20:34:56Z","receivedAt":"2025-09-19T20:35:10Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"On Wed, Sep 17, 2025 at 3:09 AM Eric Sunshine <sunshine@sunshineco.com> wrote:\n> It would be nice to have a bit more explanation of what this patch is\n> doing and why. I presume that this is adding boilerplate functionality\n> but that there are no clients of this functionality yet. Is that\n> correct?\n\nI included cbindgen at Brian's request. Its job is to generate C\nheader files based on Rust code. This eliminates errors of manually\nkeeping definitions in sync between Rust and C. You are correct that\nnothing in my code uses cbindgen yet. I wanted people to see what\nwould be required to make cbindgen exist in Git. I think I'll drop\ncbindgen for this series since varint is so trivial.\n\nI think it would be worthwhile to go over some Rust terminology:\n[rustc]: The rust compiler.\n[cargo]: Canonical build system + package manager. Even rustc uses\ncargo to build itself.\n[rustup]: Rust toolchain manager. This provides rustc and cargo + other stuff.\n[crate]: The unit of compilation. In C it's akin to a single library\nfile or executable. It follows the structure of\nmy_crate\n├── Cargo.toml\n├── build.rs\n└── src\n    ├── do_that.rs\n    ├── do_this.rs\n    └── lib.rs\nWhere src/lib.rs (the entry point) means it's a library crate and\nmain.rs (the entry point) would mean it's an executable crate (though\nyou can define both in the same crate).\n\nThis means for each crate there will be lib<crate>.a and optionally\ninterop/<crate>.h. So places like xdiff and reftable would be easy to\nfit into the concept of a crate. The rest of Git would take some doing\nto organize into crates.\n\n> > diff --git a/rust/cbindgen-template.toml b/rust/cbindgen-template.toml\n> > @@ -0,0 +1,16 @@\n> > +## compat/posix.h includes stdbool.h where git-compat-util.h does not\n> > +## this is mandatory for correct bool C <-> Rust interop. Though the\n> > +## 'includes' (and all other variables in this file) can be\n> > +## overridden in build.rs.\n>\n> There seems to be some missing punctuation in the above comment, and\n> the final sentence seems to be incomplete, both of which make it\n> difficult to understand what is being said.\n\nI'll fix that wording.\n\n> > +sys_includes = [\"compat/posix.h\"]\n> > +\n> > +autogen_warning = \"/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */\"\n> > +\n> > +language = \"C\"\n> > +no_includes = true\n> > +usize_is_size_t = true\n> > +style = \"tag\"\n> > +tab_width = 4\n>\n> It's a minor thing and probably not super important but I'm wondering\n> if it would be a good idea to follow project guidelines and make the\n> TAB width 8 rather than 4. Doing so might be appreciated by people\n> reading the generated header files.\n\nI'm fine with changing this value.\n\nI was aware that Git uses a tab width of 8, but the problem is that\nGit uses tab characters instead of spaces and as far as I can tell\ncbindgen doesn't support using a tab character for indentation. So\ntab_width in cbindgen-template.toml means X space characters per\nindent.\n"},{"id":"526817","messageId":"CAH=ZcbBf65E6aNN-cCp_0Nupk9GD3apjKRWrfi_JfJcwWi_mCg@mail.gmail.com","threadId":"64060","inReplyTo":"17143ced6feea5927b66c0578f1c2fbb07378504.1758071798.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 16/18] build: new crate, misc","fromName":"Ezekiel Newren","fromEmail":"ezekielnewren@gmail.com","sentAt":"2025-09-19T20:42:05Z","receivedAt":"2025-09-19T20:42:18Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"On Tue, Sep 16, 2025 at 7:17 PM Ezekiel Newren via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\nTo: Eric Sunshine\n\nIt looks like my gmail never received your email, so I'll respond to\nmy own patch.\n> Please extend the commit message to at least give _some_ information\n> about what the \"misc\" crate is all about since it is not at all\n> apparent based upon the name.\n>\n> By the way, is \"misc\" really a good name? It sounds like it's going to\n> be a dumping ground for anything which doesn't fit anywhere else.\n\nI don't like the name misc either. What should we call the crate that\nwill be the new home for .c and .h files that live in the root of the\nGit repository? varint is so tiny that creating a crate just for it\nseems unjustified.\n\n> Do I understand correctly from the comment that this crate has\n> something to do with \"xdiff\", yet there doesn't actually seem to be\n> anything here referencing \"xdiff\"? Am I missing something?\n\nThis crate has nothing to do with xdiff. I copy pasted the Cargo.toml\nfile from the xdiff crate and forgot to refactor the comment.\n"},{"id":"526818","messageId":"CAPig+cR19fUmzCyH2yaHjom4ifGNi8tBz8eDJwe8dfpJVQXyCg@mail.gmail.com","threadId":"64060","inReplyTo":"CAH=ZcbBf65E6aNN-cCp_0Nupk9GD3apjKRWrfi_JfJcwWi_mCg@mail.gmail.com","subject":"Re: [PATCH v2 16/18] build: new crate, misc","fromName":"Eric Sunshine","fromEmail":"ericsunshine@gmail.com","sentAt":"2025-09-19T20:50:33Z","receivedAt":"2025-09-19T20:50:46Z","isPatch":true,"sender":{"key":"ericsunshine@gmail.com","avatar":null},"body":"On Fri, Sep 19, 2025 at 4:42 PM Ezekiel Newren <ezekielnewren@gmail.com> wrote:\n> On Tue, Sep 16, 2025 at 7:17 PM Ezekiel Newren via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> To: Eric Sunshine\n> It looks like my gmail never received your email, so I'll respond to\n> my own patch.\n\nIndeed, by sending reviews about earlier patches in this series, it\nsomehow triggered Gmail to think that I was spamming you, hence Gmail\nthenceforth refused to deliver all subsequent emails to you (and\nseveral other Gmail addresses in the recipient list, including Ben and\nGitGitGadget). Hence, you would (presumably) have only received my\nreview emails and other responses indirectly via the Git mailing list\nitself (assuming you are subscribed). As far as I know, Gmail still\nconsiders my email address as being a spammer to you, so I'm using a\ndifferent email address to respond here.\n\n> > Please extend the commit message to at least give _some_ information\n> > about what the \"misc\" crate is all about since it is not at all\n> > apparent based upon the name.\n> >\n> > By the way, is \"misc\" really a good name? It sounds like it's going to\n> > be a dumping ground for anything which doesn't fit anywhere else.\n>\n> I don't like the name misc either. What should we call the crate that\n> will be the new home for .c and .h files that live in the root of the\n> Git repository? varint is so tiny that creating a crate just for it\n> seems unjustified.\n\nWould the name `gitcore` or `git-core` be suitable?\n"},{"id":"526819","messageId":"CAH=ZcbBNFSwU7E+P7hkQnt9UrMKiCRESgGvWPL7pRUa0i2U5-Q@mail.gmail.com","threadId":"64060","inReplyTo":"aMpODEpyaaVhFMO0@pks.im","subject":"Re: [PATCH v2 00/18] Introduce rust: In xdiff","fromName":"Ezekiel Newren","fromEmail":"ezekielnewren@gmail.com","sentAt":"2025-09-19T20:57:58Z","receivedAt":"2025-09-19T20:58:11Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"On Tue, Sep 16, 2025 at 11:58 PM Patrick Steinhardt <ps@pks.im> wrote:\n> Given that this isn't yet ready and given that this patch series is way\n> bigger than the one I have in flight that focusses on introducing the\n> policy [1]: how about we first merge that one down and then rebase your\n> patches on top of it to iterate? It would reduce the scope of your patch\n> series and allow us to make smaller steps towards our goal.\n>\n> To be clear: I very much think that most of the steps here are quite\n> sensible. We definitely want to introduce cbindgen, we obviously need to\n> introduce support on Windows, and I guess having multiple different\n> workspaces is also something that could provide benefit.\n>\n> But there is no reason to have that all at once, so hence my suggestion\n> to build out the infra one step at a time.\n>\n> What do you think?\n\nI think I made a big mistake of not making it clear that I intended v2\nas more of an RFC. My worry (that I expressed very poorly or not at\nall) is how hard will it be to apply my patches on top of your\nfoundation? I don't know if minor or major changes to your current\npath would be needed, so I proposed a different way to Introduce Rust\nwhile trying to incorporate work from others.\n\nI wanted feedback on:\n  * Cleaning up Rust type name collisions\n    * People don't like it, so I'll drop that\n  * Have Makefile produce libgit.a correctly.\n    * I think this is a good idea, but it doesn't belong in this patch series.\n  * Adding Rust unit test which required fixing Make and adding\nbuild-helper to reduce boilerplate code\n    * I think this is a good idea, but should be added later.\n  * Introduce cbindgen to avoid desync errors between Rust and C.\n    * LIke other points above; This should be added later.\n  * What should we call the crate that will be the new home for .c and\n.h files in the root of Git?\n"},{"id":"526821","messageId":"CAH=ZcbC9sgZg+Ghv=rRbq8aMbpgo+6ZvRPBs7+4876EL0w4NJg@mail.gmail.com","threadId":"64060","inReplyTo":"CAPig+cSBEX5QGnzpBnVs_hKM2iUqcmA4-DzKDgkwpG9ZzWZ__w@mail.gmail.com","subject":"Re: [PATCH v2 07/18] build: introduce rust","fromName":"Ezekiel Newren","fromEmail":"ezekielnewren@gmail.com","sentAt":"2025-09-19T21:28:52Z","receivedAt":"2025-09-19T21:29:05Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"On Fri, Sep 19, 2025 at 2:24 PM Eric Sunshine <ericsunshine@gmail.com> wrote:\n> When building with `cargo` (I presume), and it comes time to link the\n> program, the build process is looking for the Microsoft linker named\n> LINK.exe but, due to PATH order, is instead finding the Unix command\n> `link` (which is a specialized invocation of the more common `ln`\n> command). As such, the build process incorrectly invokes the Unix\n> `link` rather than the Microsoft LINK.exe and fails. To work around\n> this problem, you move the standard Unix command-containing paths to\n> the end of PATH so that the Microsoft LINK.exe is found first.\n\nYour understanding is correct.\n"},{"id":"526823","messageId":"CAH=ZcbDvwbCdnpeNv6vp=+Zy1h8RpG125ZCQNUzNOc5qLi8gNg@mail.gmail.com","threadId":"64060","inReplyTo":"xmqq5xde435i.fsf@gitster.g","subject":"Re: [PATCH v2 02/18] make: add -fPIE flag","fromName":"Ezekiel Newren","fromEmail":"ezekielnewren@gmail.com","sentAt":"2025-09-19T21:52:53Z","receivedAt":"2025-09-19T21:53:08Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"On Fri, Sep 19, 2025 at 2:07 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Ezekiel Newren <ezekielnewren@gmail.com> writes:\n>\n> > I was trying to get the rust compiler to link against libgit.a. Both\n> > Meson and Makefile produce libgit.a. No changes were to meson.build\n> > were necessary for that to work, but but with Makefile I would get\n> > errors like:\n> > relocation R_X86_64_32 against `.rodata' can not be used when making a\n> > PIE object; recompile with -fPIE\n>\n> This sounds like a workaround that is somewhat platform and compiler\n> specific limitation, at least to me.  Does this need to be conditional\n> and if so on what?\n\nI don't know, but I have another question to add to yours. Why does\nMake need -fPIE in order for the Rust compiler to link against\nlibgit.a created by Make, when Meson doesn't seem to specify PIE\nanywhere and yet the Rust compiler can work with Meson's libgit.a\noutput?\n"},{"id":"526824","messageId":"CAH=ZcbCpX-QZ8mEWE8RaNwF-vOuw0o8V128otknNdLp=XmQrjA@mail.gmail.com","threadId":"64060","inReplyTo":"CAPig+cR19fUmzCyH2yaHjom4ifGNi8tBz8eDJwe8dfpJVQXyCg@mail.gmail.com","subject":"Re: [PATCH v2 16/18] build: new crate, misc","fromName":"Ezekiel Newren","fromEmail":"ezekielnewren@gmail.com","sentAt":"2025-09-19T21:54:31Z","receivedAt":"2025-09-19T21:54:45Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"On Fri, Sep 19, 2025 at 2:50 PM Eric Sunshine <ericsunshine@gmail.com> wrote:\n> Would the name `gitcore` or `git-core` be suitable?\n\nThe only other name I could think of was `util`, but I think I prefer\n`gitcore`. Does anybody else have any name suggestions?\n"},{"id":"526835","messageId":"CAH=ZcbD8zyw882D3HNSVNhovVCMO18fsJy6k5UfXncXv0u05dg@mail.gmail.com","threadId":"64060","inReplyTo":"xmqq1po242uo.fsf@gitster.g","subject":"Re: [PATCH v2 04/18] make: merge reftable lib into libgit.a","fromName":"Ezekiel Newren","fromEmail":"ezekielnewren@gmail.com","sentAt":"2025-09-19T23:02:30Z","receivedAt":"2025-09-19T23:02:44Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"On Fri, Sep 19, 2025 at 2:14 PM Junio C Hamano <gitster@pobox.com> wrote:\n> Instead of rolling everything into LIB_OBJS directly, wouldn't it\n> have been much easier to work with if reftable-related ones are left\n> in REFTABLE_OBJS and then RERFTABLE_OBJS gets added to LIB_OBJS?\n> Wouldn't it have been less prone to mismerges to do it that way?\n\nI didn't do it that way because I didn't think of that. I prefer your\nway because it's much cleaner.\n\n> As we can always extend things more, getting something close to the\n> minimally viable set with some tests for sanity checking would be a\n> good first goal.  If you pare down way too much, however, we may end\n> up to be pretty close to what Patrick sent out originally with the\n> varint conversion, so let's make sure we do not drop below the\n> minimum that still demonstrates that we have Rust integration that\n> is viable going forward.\n\nI'm torn between A and B from [1]:\nI'm thinking of choosing A because my approach and Patrick's approach\nare incompatible. In order to use multiple crates and ensure that Rust\nis built and tested the same way with Make and Meson I'd have to rip\nout lots of things from Patrick's patch series.\n\nBut maybe I should go with B because there's a lot of prep work that\nshould be done with Git before I can start doing what I'd like to do\nin Rust. So let Patrick's stuff merge while I work on cleaning up\nxdiff and updating Makefile's libgit.a build process and then deal\nwith ugly merging later.\n\nI can't decide between the two.\n\n[1] https://lore.kernel.org/git/CABPp-BHJUkSERQon6xx=sHrhN7i=6ekv+Hz1+P+2mh0=Xw15Mg@mail.gmail.com/\n"},{"id":"526844","messageId":"xmqqa52qypo8.fsf@gitster.g","threadId":"64060","inReplyTo":"CAH=ZcbDvwbCdnpeNv6vp=+Zy1h8RpG125ZCQNUzNOc5qLi8gNg@mail.gmail.com","subject":"Re: [PATCH v2 02/18] make: add -fPIE flag","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-09-19T23:43:03Z","receivedAt":"2025-09-19T23:43:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ezekiel Newren <ezekielnewren@gmail.com> writes:\n\n> On Fri, Sep 19, 2025 at 2:07 PM Junio C Hamano <gitster@pobox.com> wrote:\n>>\n>> Ezekiel Newren <ezekielnewren@gmail.com> writes:\n>>\n>> > I was trying to get the rust compiler to link against libgit.a. Both\n>> > Meson and Makefile produce libgit.a. No changes were to meson.build\n>> > were necessary for that to work, but but with Makefile I would get\n>> > errors like:\n>> > relocation R_X86_64_32 against `.rodata' can not be used when making a\n>> > PIE object; recompile with -fPIE\n>>\n>> This sounds like a workaround that is somewhat platform and compiler\n>> specific limitation, at least to me.  Does this need to be conditional\n>> and if so on what?\n>\n> I don't know, but I have another question to add to yours. Why does\n> Make need -fPIE in order for the Rust compiler to link against\n> libgit.a created by Make, when Meson doesn't seem to specify PIE\n> anywhere and yet the Rust compiler can work with Meson's libgit.a\n> output?\n\nI do not know what the build procedure using meson exactly does to\ncreate a library archive.  On the Make side, we do not compile for\nposition independence, and don't do anything other than bog standard\n\"ar rcs\".  Patrick, any hint?\n\n"},{"id":"526845","messageId":"87plbmyox4.fsf@gmail.com","threadId":"64060","inReplyTo":"xmqqa52qypo8.fsf@gitster.g","subject":"Re: [PATCH v2 02/18] make: add -fPIE flag","fromName":"Collin Funk","fromEmail":"collin.funk1@gmail.com","sentAt":"2025-09-19T23:59:19Z","receivedAt":"2025-09-19T23:59:21Z","isPatch":true,"sender":{"key":"collin.funk1@gmail.com","avatar":"https://avatars.githubusercontent.com/u/65689063?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Ezekiel Newren <ezekielnewren@gmail.com> writes:\n>\n>> On Fri, Sep 19, 2025 at 2:07 PM Junio C Hamano <gitster@pobox.com> wrote:\n>>>\n>>> Ezekiel Newren <ezekielnewren@gmail.com> writes:\n>>>\n>>> > I was trying to get the rust compiler to link against libgit.a. Both\n>>> > Meson and Makefile produce libgit.a. No changes were to meson.build\n>>> > were necessary for that to work, but but with Makefile I would get\n>>> > errors like:\n>>> > relocation R_X86_64_32 against `.rodata' can not be used when making a\n>>> > PIE object; recompile with -fPIE\n>>>\n>>> This sounds like a workaround that is somewhat platform and compiler\n>>> specific limitation, at least to me.  Does this need to be conditional\n>>> and if so on what?\n>>\n>> I don't know, but I have another question to add to yours. Why does\n>> Make need -fPIE in order for the Rust compiler to link against\n>> libgit.a created by Make, when Meson doesn't seem to specify PIE\n>> anywhere and yet the Rust compiler can work with Meson's libgit.a\n>> output?\n>\n> I do not know what the build procedure using meson exactly does to\n> create a library archive.  On the Make side, we do not compile for\n> position independence, and don't do anything other than bog standard\n> \"ar rcs\".  Patrick, any hint?\n\nNot too familiar with Rust, but looks like the answer is here [1].\nSpecificially, this part:\n\n    pic - fully relocatable position independent code, machine\n    instructions need to use relative addressing modes. Equivalent to\n    the \"uppercase\" -fPIC or -fPIE options in other compilers, depending\n    on the produced crate types. This is the default model for majority\n    of supported targets.\n\nCollin\n\n[1] https://doc.rust-lang.org/rustc/codegen-options/index.html#relocation-model\n"},{"id":"526854","messageId":"xmqq1po1ysxk.fsf@gitster.g","threadId":"64060","inReplyTo":"87plbmyox4.fsf@gmail.com","subject":"Re: [PATCH v2 02/18] make: add -fPIE flag","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-09-20T16:44:55Z","receivedAt":"2025-09-20T16:44:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Collin Funk <collin.funk1@gmail.com> writes:\n\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n>> Ezekiel Newren <ezekielnewren@gmail.com> writes:\n>>\n>>> I don't know, but I have another question to add to yours. Why does\n>>> Make need -fPIE in order for the Rust compiler to link against\n>>> libgit.a created by Make, when Meson doesn't seem to specify PIE\n>>> anywhere and yet the Rust compiler can work with Meson's libgit.a\n>>> output?\n>>\n>> I do not know what the build procedure using meson exactly does to\n>> create a library archive.  On the Make side, we do not compile for\n>> position independence, and don't do anything other than bog standard\n>> \"ar rcs\".  Patrick, any hint?\n>\n> Not too familiar with Rust, but looks like the answer is here [1].\n> Specificially, this part:\n>\n>     pic - fully relocatable position independent code, machine\n>     instructions need to use relative addressing modes. Equivalent to\n>     the \"uppercase\" -fPIC or -fPIE options in other compilers, depending\n>     on the produced crate types. This is the default model for majority\n>     of supported targets.\n>\n> Collin\n>\n> [1] https://doc.rust-lang.org/rustc/codegen-options/index.html#relocation-model\n\nWell, thanks for joining the conversation, but that much I think is\nalready shared by those who are involved in this thread.\n\nThe background was that Ezekiel did not have to do anything special\nas far as he knows to get things compiled in a relocatable way with\nmeson based build, and with make based build, (which I know has\nnever used nor required any PIE-ness) he of course needs to add it\nin a way that is portable across platforms and compilers we care\nabout if we wanted to have PIE objects.\n\nAnd the question was what, if anything, does meson do specially to\ndo so in a portable way to enable PIE.  We'd want to see if it meets\nour needs (it could be that Ezekiel is getting lucky and meson based\nbuild covered the platform+compiler combinations that he happened to\nbe working with) and if so mimick that on the Makefile side.\n\n"},{"id":"526883","messageId":"0104ae22-e5cb-41c9-af8c-4d9b0035c219@ramsayjones.plus.com","threadId":"64060","inReplyTo":"xmqq1po1ysxk.fsf@gitster.g","subject":"Re: [PATCH v2 02/18] make: add -fPIE flag","fromName":"Ramsay Jones","fromEmail":"ramsay@ramsayjones.plus.com","sentAt":"2025-09-21T01:14:02Z","receivedAt":"2025-09-21T01:17:12Z","isPatch":true,"sender":{"key":"ramsay@ramsayjones.plus.com","avatar":"https://avatars.githubusercontent.com/u/33702710?v=4"},"body":"\n\nOn 20/09/2025 17:44, Junio C Hamano wrote:\n> Collin Funk <collin.funk1@gmail.com> writes:\n> \n>> Junio C Hamano <gitster@pobox.com> writes:\n>>\n>>> Ezekiel Newren <ezekielnewren@gmail.com> writes:\n[snip]\n>> Not too familiar with Rust, but looks like the answer is here [1].\n>> Specificially, this part:\n>>\n>>     pic - fully relocatable position independent code, machine\n>>     instructions need to use relative addressing modes. Equivalent to\n>>     the \"uppercase\" -fPIC or -fPIE options in other compilers, depending\n>>     on the produced crate types. This is the default model for majority\n>>     of supported targets.\n>>\n>> Collin\n>>\n>> [1] https://doc.rust-lang.org/rustc/codegen-options/index.html#relocation-model\n> \n> Well, thanks for joining the conversation, but that much I think is\n> already shared by those who are involved in this thread.\n> \n> The background was that Ezekiel did not have to do anything special\n> as far as he knows to get things compiled in a relocatable way with\n> meson based build, and with make based build, (which I know has\n> never used nor required any PIE-ness) he of course needs to add it\n> in a way that is portable across platforms and compilers we care\n> about if we wanted to have PIE objects.\n> \n> And the question was what, if anything, does meson do specially to\n> do so in a portable way to enable PIE.  We'd want to see if it meets\n> our needs (it could be that Ezekiel is getting lucky and meson based\n> build covered the platform+compiler combinations that he happened to\n> be working with) and if so mimick that on the Makefile side.\n\n\nYes, meson defaults the compiler flags to -fPIC (see e.g. near the end\nof the original cover letter of say [1]). You can change the compiler\nflags that meson uses (can't remember off-hand how you do that, but it\nis documented), you can even set -fPIE, or remove -fPIC etc,.  ;)\n\nSo, I don't know why rust (seems to) require -fPIC/-fPIE to be able to\nlink to libgit.a; maybe we need to add some flags to rustc to _not_ use\nthe equivalent of -fPIC/-fPIE. (I haven't looked, but I will note that\nthe GNU nm program complains about the rust built library!)\n\n\n[1] https://lore.kernel.org/git/20250416231835.2492562-1-ramsay@ramsayjones.plus.com/\n\n\nATB,\nRamsay Jones\n\n\n\n"},{"id":"526926","messageId":"aNFIozagGc0MoseL@pks.im","threadId":"64060","inReplyTo":"CAH=ZcbBNFSwU7E+P7hkQnt9UrMKiCRESgGvWPL7pRUa0i2U5-Q@mail.gmail.com","subject":"Re: [PATCH v2 00/18] Introduce rust: In xdiff","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-09-22T13:01:23Z","receivedAt":"2025-09-22T13:01:28Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Sep 19, 2025 at 02:57:58PM -0600, Ezekiel Newren wrote:\n> On Tue, Sep 16, 2025 at 11:58 PM Patrick Steinhardt <ps@pks.im> wrote:\n> > Given that this isn't yet ready and given that this patch series is way\n> > bigger than the one I have in flight that focusses on introducing the\n> > policy [1]: how about we first merge that one down and then rebase your\n> > patches on top of it to iterate? It would reduce the scope of your patch\n> > series and allow us to make smaller steps towards our goal.\n> >\n> > To be clear: I very much think that most of the steps here are quite\n> > sensible. We definitely want to introduce cbindgen, we obviously need to\n> > introduce support on Windows, and I guess having multiple different\n> > workspaces is also something that could provide benefit.\n> >\n> > But there is no reason to have that all at once, so hence my suggestion\n> > to build out the infra one step at a time.\n> >\n> > What do you think?\n> \n> I think I made a big mistake of not making it clear that I intended v2\n> as more of an RFC. My worry (that I expressed very poorly or not at\n> all) is how hard will it be to apply my patches on top of your\n> foundation? I don't know if minor or major changes to your current\n> path would be needed, so I proposed a different way to Introduce Rust\n> while trying to incorporate work from others.\n\nAh, fair enough. Thanks for clarifying your intentions!\n\nI don't think there'd have to be major changes to the current version\nof my patch series. The idea of that patch series is very much getting\nbuy-in regarding our roadmap and focus less on the actual build infra.\nSo the changes introduced are mostly an MVC, and I very much think that\nwe'll have to iterate quite a bit on it, but that's intended.\n\nI think that a lot of the steps you outline below are logical next steps\nto get there. If I can change anything to make these next steps easier\nfor you I'm happy to do so. But I also don't think it's too bad if we\nhave to change the current infra quite significantly to get there.\n\n> I wanted feedback on:\n>   * Cleaning up Rust type name collisions\n>     * People don't like it, so I'll drop that\n\nI don't have a strong opinion on this. If it creates issues I personally\ndon't mind fixing it.\n\n>   * Have Makefile produce libgit.a correctly.\n>     * I think this is a good idea, but it doesn't belong in this patch series.\n>   * Adding Rust unit test which required fixing Make and adding\n> build-helper to reduce boilerplate code\n>     * I think this is a good idea, but should be added later.\n>   * Introduce cbindgen to avoid desync errors between Rust and C.\n>     * LIke other points above; This should be added later.\n\nAgreed, these all make sense to me.\n\n>   * What should we call the crate that will be the new home for .c and\n> .h files in the root of Git?\n\nWe could call this something like \"libgit-ffi\", but I don't care too\nmuch.\n\nThanks!\n\nPatrick\n"},{"id":"526927","messageId":"aNFIwFD6E6Lngy5M@pks.im","threadId":"64060","inReplyTo":"xmqqy0qcae6z.fsf@gitster.g","subject":"Re: [PATCH v2 00/18] Introduce rust: In xdiff","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-09-22T13:01:52Z","receivedAt":"2025-09-22T13:01:59Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Sep 17, 2025 at 03:48:04PM -0700, Junio C Hamano wrote:\n> Elijah Newren <newren@gmail.com> writes:\n> \n> > So, how to move forward?\n> >\n> > A) Modify Patrick's series to just take patch 7 of his v5.  Patrick\n> > did say that the roadmap was  \"the more important discussion compared\n> > to the technical discussion\", and merging that patch would achieve his\n> > goal of getting an initial roadmap.  Then Ezekiel could grab other\n> > pieces from Patrick's series (e.g. the help and varint stuff) and\n> > incorporate it into an \"introduce rust\" series.[*]\n> >\n> > B) Merge Patrick's series and tell Ezekiel to rebase, while noting to\n> > Ezekiel that the roadmap is the important bit from Patrick's series[*]\n> > and he can suggest changes to any of the other bits.\n> >\n> > C) Create a consolidated \"introduce Rust\" series with bits of both --\n> > what I think Ezekiel was trying to do with this series.\n> \n> Ah, I didn't even realize C was what this series was trying to do.\n> \n> I do not have particular preference between A and B, but I thought A\n> was closer to what was being done with this series, and as long as\n> Ezekiel and Patrick can join forces that way, it would be perfect.\n\nI personally think either (A) or (B) would be good choices. I would\nslightly lean towards (B) just so that we have something that we can\nalready play around with while building the next steps.\n\nBy the way: I'm also happy to change attribution of some of the patches\nin my patch series to mention Ezekiel as author. I don't care much who\nis listed for the initial patches that introduce Rust, but would retain\nmy own authorship for the \"varint\" and \"BreakingChanges\" commits.\n\nPatrick\n"},{"id":"526940","messageId":"CAH=ZcbDkDfc6j_gQCt_q9RLP_ozYqr09i1-xe2mvhSQRgf8MGg@mail.gmail.com","threadId":"64060","inReplyTo":"aNFIwFD6E6Lngy5M@pks.im","subject":"Re: [PATCH v2 00/18] Introduce rust: In xdiff","fromName":"Ezekiel Newren","fromEmail":"ezekielnewren@gmail.com","sentAt":"2025-09-22T15:18:14Z","receivedAt":"2025-09-22T15:18:27Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"On Mon, Sep 22, 2025 at 7:01 AM Patrick Steinhardt <ps@pks.im> wrote:\n> I personally think either (A) or (B) would be good choices. I would\n> slightly lean towards (B) just so that we have something that we can\n> already play around with while building the next steps.\n\nI'm fine with B if you fix the wording in your Breaking Changes about\nRust being introduced in version 2.52. Rust was introduced to Git in\n2.49.\n\nElijah points this out in 1 and 2:\n[1] https://lore.kernel.org/git/CABPp-BFXRbaHk9U3BX+d12bZ+ryGOp+btR0ODMw+HtD7xd+MBQ@mail.gmail.com/\n[2] https://lore.kernel.org/git/CABPp-BEiK49f_UB5UPe3qM9O7vQGGFJ8Nshw1f6W_6Lw7HRL6Q@mail.gmail.com/\n\n> By the way: I'm also happy to change attribution of some of the patches\n> in my patch series to mention Ezekiel as author. I don't care much who\n> is listed for the initial patches that introduce Rust, but would retain\n> my own authorship for the \"varint\" and \"BreakingChanges\" commits.\n\nMy only other concern is with varint. You use usize on the Rust side\nand then uint64_t on the C side, but I'm ok with fixing that later as\nit only breaks 'linux32 (i386/ubuntu:focal)' in the github workflows.\n"},{"id":"526941","messageId":"CAH=ZcbCZXavx52521cFHdXZn=BCWBiR1aG10ekZVg3PVVJb2VA@mail.gmail.com","threadId":"64060","inReplyTo":"aNFIozagGc0MoseL@pks.im","subject":"Re: [PATCH v2 00/18] Introduce rust: In xdiff","fromName":"Ezekiel Newren","fromEmail":"ezekielnewren@gmail.com","sentAt":"2025-09-22T15:31:10Z","receivedAt":"2025-09-22T15:31:24Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"On Mon, Sep 22, 2025 at 7:01 AM Patrick Steinhardt <ps@pks.im> wrote:\n> I don't think there'd have to be major changes to the current version\n> of my patch series. The idea of that patch series is very much getting\n> buy-in regarding our roadmap and focus less on the actual build infra.\n> So the changes introduced are mostly an MVC, and I very much think that\n> we'll have to iterate quite a bit on it, but that's intended.\n\nSo long as you're flexible with the build infra details then I'm ok\nwith rebasing on your work.\n\nWhat does MVC stand for?\n\n> I think that a lot of the steps you outline below are logical next steps\n> to get there. If I can change anything to make these next steps easier\n> for you I'm happy to do so. But I also don't think it's too bad if we\n> have to change the current infra quite significantly to get there.\n\nI think we can discuss those code and infra changes in a later patch series.\n\n> > I wanted feedback on:\n> >   * Cleaning up Rust type name collisions\n> >     * People don't like it, so I'll drop that\n>\n> I don't have a strong opinion on this. If it creates issues I personally\n> don't mind fixing it.\n\nJunio doesn't like it, so I'm not going to do it.\n\n> >   * Have Makefile produce libgit.a correctly.\n> >     * I think this is a good idea, but it doesn't belong in this patch series.\n> >   * Adding Rust unit test which required fixing Make and adding\n> > build-helper to reduce boilerplate code\n> >     * I think this is a good idea, but should be added later.\n> >   * Introduce cbindgen to avoid desync errors between Rust and C.\n> >     * LIke other points above; This should be added later.\n>\n> Agreed, these all make sense to me.\n\nI think the changes to Makefile to build ligbit.a correctly can be an\northogonal patch series. The rest can come later.\n\n> >   * What should we call the crate that will be the new home for .c and\n> > .h files in the root of Git?\n>\n> We could call this something like \"libgit-ffi\", but I don't care too\n> much.\n\nI prefer Eric Sunshine's suggestion of gitcore, no hyphen.\n"},{"id":"526945","messageId":"aNF0YJGxB4ZmociE@pks.im","threadId":"64060","inReplyTo":"CAH=ZcbCZXavx52521cFHdXZn=BCWBiR1aG10ekZVg3PVVJb2VA@mail.gmail.com","subject":"Re: [PATCH v2 00/18] Introduce rust: In xdiff","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-09-22T16:08:00Z","receivedAt":"2025-09-22T16:08:14Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Sep 22, 2025 at 09:31:10AM -0600, Ezekiel Newren wrote:\n> On Mon, Sep 22, 2025 at 7:01 AM Patrick Steinhardt <ps@pks.im> wrote:\n> > I don't think there'd have to be major changes to the current version\n> > of my patch series. The idea of that patch series is very much getting\n> > buy-in regarding our roadmap and focus less on the actual build infra.\n> > So the changes introduced are mostly an MVC, and I very much think that\n> > we'll have to iterate quite a bit on it, but that's intended.\n> \n> So long as you're flexible with the build infra details then I'm ok\n> with rebasing on your work.\n> \n> What does MVC stand for?\n\nMinimum viable... candidate? I guess I rather meant \"product\", so MVP\nwould be the better term.\n\n[snip]\n> > >   * What should we call the crate that will be the new home for .c and\n> > > .h files in the root of Git?\n> >\n> > We could call this something like \"libgit-ffi\", but I don't care too\n> > much.\n> \n> I prefer Eric Sunshine's suggestion of gitcore, no hyphen.\n\nSounds reasonable to me.\n\nPatrick\n"},{"id":"526946","messageId":"aNFz-0rO79l7crZ5@pks.im","threadId":"64060","inReplyTo":"CAH=ZcbDkDfc6j_gQCt_q9RLP_ozYqr09i1-xe2mvhSQRgf8MGg@mail.gmail.com","subject":"Re: [PATCH v2 00/18] Introduce rust: In xdiff","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-09-22T16:15:57Z","receivedAt":"2025-09-22T16:16:02Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Sep 22, 2025 at 09:18:14AM -0600, Ezekiel Newren wrote:\n> On Mon, Sep 22, 2025 at 7:01 AM Patrick Steinhardt <ps@pks.im> wrote:\n> > I personally think either (A) or (B) would be good choices. I would\n> > slightly lean towards (B) just so that we have something that we can\n> > already play around with while building the next steps.\n> \n> I'm fine with B if you fix the wording in your Breaking Changes about\n> Rust being introduced in version 2.52. Rust was introduced to Git in\n> 2.49.\n> \n> Elijah points this out in 1 and 2:\n> [1] https://lore.kernel.org/git/CABPp-BFXRbaHk9U3BX+d12bZ+ryGOp+btR0ODMw+HtD7xd+MBQ@mail.gmail.com/\n> [2] https://lore.kernel.org/git/CABPp-BEiK49f_UB5UPe3qM9O7vQGGFJ8Nshw1f6W_6Lw7HRL6Q@mail.gmail.com/\n\nWill adjust.\n\n> > By the way: I'm also happy to change attribution of some of the patches\n> > in my patch series to mention Ezekiel as author. I don't care much who\n> > is listed for the initial patches that introduce Rust, but would retain\n> > my own authorship for the \"varint\" and \"BreakingChanges\" commits.\n> \n> My only other concern is with varint. You use usize on the Rust side\n> and then uint64_t on the C side, but I'm ok with fixing that later as\n> it only breaks 'linux32 (i386/ubuntu:focal)' in the github workflows.\n\nOh, this is actually an oversight, good catch! I refactored \"varint.c\"\nto use `uint64_t`, but then forgot to adjust the Rust side in the same\nspirit. Will fix.\n\nI suggested in [1] that I can change authorship of the patches that\nintroduce the initial infrastructure into Meson and our Makefile (so I\nguess patches 1 and 3) to instead list you as author and myself as\nCo-authored-by. Is that something you want? Given that you have\nkickstarted the whole effort around introducing Rust again I wouldn't\nmind that at all.\n\nIn any case, I'll send a new version of the series tomorrow.\n\nThanks!\n\nPatrick\n\n[1]: <aNFIwFD6E6Lngy5M@pks.im>\n"},{"id":"526950","messageId":"CAH=ZcbB_AZ7a=kQ_tHRtm7jPD6h_7AAiQYyaMQrG+ijj8Dyvrw@mail.gmail.com","threadId":"64060","inReplyTo":"aNFz-0rO79l7crZ5@pks.im","subject":"Re: [PATCH v2 00/18] Introduce rust: In xdiff","fromName":"Ezekiel Newren","fromEmail":"ezekielnewren@gmail.com","sentAt":"2025-09-22T16:27:32Z","receivedAt":"2025-09-22T16:27:45Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"On Mon, Sep 22, 2025 at 10:16 AM Patrick Steinhardt <ps@pks.im> wrote:\n> On Mon, Sep 22, 2025 at 09:18:14AM -0600, Ezekiel Newren wrote:\n> > On Mon, Sep 22, 2025 at 7:01 AM Patrick Steinhardt <ps@pks.im> wrote:\n> > > I personally think either (A) or (B) would be good choices. I would\n> > > slightly lean towards (B) just so that we have something that we can\n> > > already play around with while building the next steps.\n> >\n> > I'm fine with B if you fix the wording in your Breaking Changes about\n> > Rust being introduced in version 2.52. Rust was introduced to Git in\n> > 2.49.\n> >\n> > Elijah points this out in 1 and 2:\n> > [1] https://lore.kernel.org/git/CABPp-BFXRbaHk9U3BX+d12bZ+ryGOp+btR0ODMw+HtD7xd+MBQ@mail.gmail.com/\n> > [2] https://lore.kernel.org/git/CABPp-BEiK49f_UB5UPe3qM9O7vQGGFJ8Nshw1f6W_6Lw7HRL6Q@mail.gmail.com/\n>\n> Will adjust.\n\nThank you.\n\n> > > By the way: I'm also happy to change attribution of some of the patches\n> > > in my patch series to mention Ezekiel as author. I don't care much who\n> > > is listed for the initial patches that introduce Rust, but would retain\n> > > my own authorship for the \"varint\" and \"BreakingChanges\" commits.\n> >\n> > My only other concern is with varint. You use usize on the Rust side\n> > and then uint64_t on the C side, but I'm ok with fixing that later as\n> > it only breaks 'linux32 (i386/ubuntu:focal)' in the github workflows.\n>\n> Oh, this is actually an oversight, good catch! I refactored \"varint.c\"\n> to use `uint64_t`, but then forgot to adjust the Rust side in the same\n> spirit. Will fix.\n\nYou also missed updating varint.h.\n\n> I suggested in [1] that I can change authorship of the patches that\n> introduce the initial infrastructure into Meson and our Makefile (so I\n> guess patches 1 and 3) to instead list you as author and myself as\n> Co-authored-by. Is that something you want? Given that you have\n> kickstarted the whole effort around introducing Rust again I wouldn't\n> mind that at all.\n\nIt doesn't make sense to me to list myself as the author of any of\nyour commits, but I would like my name referenced in your commit\nmessages.\n\nThanks.\n\nEzekiel.\n"},{"id":"526952","messageId":"xmqqikhav3i0.fsf@gitster.g","threadId":"64060","inReplyTo":"CAH=ZcbCZXavx52521cFHdXZn=BCWBiR1aG10ekZVg3PVVJb2VA@mail.gmail.com","subject":"Re: [PATCH v2 00/18] Introduce rust: In xdiff","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-09-22T16:47:03Z","receivedAt":"2025-09-22T16:47:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ezekiel Newren <ezekielnewren@gmail.com> writes:\n\n>> > I wanted feedback on:\n>> >   * Cleaning up Rust type name collisions\n>> >   * People don't like it, so I'll drop that\n>>\n>> I don't have a strong opinion on this. If it creates issues I personally\n>> don't mind fixing it.\n>\n> Junio doesn't like it, so I'm not going to do it.\n\nIt was not \"I do not line u16 as a typename when a perfectly well\nestablished uint16_t is available\", though.\n\nIt was more about asking to explain the reason behind insisting to\nuse u(8|16|32|64) types in C code.  Perhaps there is a compelling\nreason to do so that I was missing.\n\nI know that the kernel has used these types for a long time, but\nthat way predates their more recent flirt with Rust.  If your answer\nwas \"the kernel uses them\", then I'd want that answer to cover a few\nadditional questions, like \n\n - Have they benefitted from their use of u(8|16|32|64) when they\n   started working with Rust and if so how?  \n\n - Would we be expected to reap the same benefit if we used these\n   types?\n\nfor example.\n\nThanks.\n"},{"id":"526955","messageId":"CAH=ZcbCsKdZAPxBEPUMvE471ogTNptjFy7FFksWnJfnX=J-F2A@mail.gmail.com","threadId":"64060","inReplyTo":"xmqqikhav3i0.fsf@gitster.g","subject":"Re: [PATCH v2 00/18] Introduce rust: In xdiff","fromName":"Ezekiel Newren","fromEmail":"ezekielnewren@gmail.com","sentAt":"2025-09-22T17:23:39Z","receivedAt":"2025-09-22T17:23:52Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"On Mon, Sep 22, 2025 at 10:47 AM Junio C Hamano <gitster@pobox.com> wrote:\n> Ezekiel Newren <ezekielnewren@gmail.com> writes:\n>\n> >> > I wanted feedback on:\n> >> >   * Cleaning up Rust type name collisions\n> >> >   * People don't like it, so I'll drop that\n> >>\n> >> I don't have a strong opinion on this. If it creates issues I personally\n> >> don't mind fixing it.\n> >\n> > Junio doesn't like it, so I'm not going to do it.\n>\n> It was not \"I do not like u16 as a typename when a perfectly well\n> established uint16_t is available\", though.\n>\n> It was more about asking to explain the reason behind insisting to\n> use u(8|16|32|64) types in C code.  Perhaps there is a compelling\n> reason to do so that I was missing.\n\nI listed 5 reasons in my commit message[1], and I just thought of\nanother one now.\n  * Pseudo reserved keywords: 'new' is not a reserved keyword in C,\nbut Git treats it as such. Since Rust will likely be added to Git, the\nRust primitive types should also be treated as reserved keywords.\nCbindgen parse's Rust and generates C header files; If a field in a\nstruct uses u16 as the name then Rust won't compile, and cbindgen\ncan't create the C header file. Using [ui](8|16|32|64|size) as the\ntype in C also spreads awareness that those are reserved keywords and\nshould not be used as variable names.\n\nIf these 6 reasons are not enough to explain why we should be using\nthe Rust primitive type names in C, then please explain how it is\ninsufficient.\n\n> I know that the kernel has used these types for a long time, but\n> that way predates their more recent flirt with Rust.  If your answer\n> was \"the kernel uses them\", then I'd want that answer to cover a few\n> additional questions, like\n>\n>  - Have they benefited from their use of u(8|16|32|64) when they\n>    started working with Rust and if so how?\n\nI did not know this.\n\n[1] https://lore.kernel.org/git/2a7d5b05c18d4a96f1905b7043d47c62d367cd2a.1757274320.git.gitgitgadget@gmail.com/\n"},{"id":"526956","messageId":"CAH=ZcbC_ecyPeLS_0DfN3+w9RTqcrt6Je08RPXqb6pJVGaPjuw@mail.gmail.com","threadId":"64060","inReplyTo":"CAH=ZcbCsKdZAPxBEPUMvE471ogTNptjFy7FFksWnJfnX=J-F2A@mail.gmail.com","subject":"Re: [PATCH v2 00/18] Introduce rust: In xdiff","fromName":"Ezekiel Newren","fromEmail":"ezekielnewren@gmail.com","sentAt":"2025-09-22T17:32:27Z","receivedAt":"2025-09-22T17:32:41Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"On Mon, Sep 22, 2025 at 11:23 AM Ezekiel Newren <ezekielnewren@gmail.com> wrote:\n> Cbindgen parse's Rust and generates C header files; If a field in a\n> struct uses u16 as the name then Rust won't compile, and cbindgen\n> can't create the C header file.\n\nI just tried this in Rust and it turns out you actually can use u16 as\na struct field name. My bad. I think that's a bad idea and should be\ndiscouraged though.\n"},{"id":"526959","messageId":"xmqqv7latkz3.fsf@gitster.g","threadId":"64060","inReplyTo":"CAH=ZcbCsKdZAPxBEPUMvE471ogTNptjFy7FFksWnJfnX=J-F2A@mail.gmail.com","subject":"Re: [PATCH v2 00/18] Introduce rust: In xdiff","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-09-22T18:12:32Z","receivedAt":"2025-09-22T18:12:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ezekiel Newren <ezekielnewren@gmail.com> writes:\n\n> If a field in a\n> struct uses u16 as the name then Rust won't compile, and cbindgen\n> can't create the C header file.\n\nDo you mean that cbindgen fails to help us make a C code we have\nthat says \"struct { uint16_t u16; }\" to work with Rust?\n\nDeclaring \"typedef uint16_t u16\" would not help such a case at all,\nas it would not make \"struct { u16 u16; }\" an invalid C, and would\nnot force us to avoid such names that cbindgen may have problems\nwith.\n\nSo regardless of what to do with type names, we would need to adjust\nsome variable names to avoid clashes with Rust, which I am fine with.\n\n> Using [ui](8|16|32|64|size) as the type in C also spreads\n> awareness that those are reserved keywords and should not be used\n> as variable names.\n\nWe certainly need to train our developers to avoid problematic names\nlike \"u16\" just like we do so for \"new\".\n\nI am skeptical that using u16 as a type would have a good chance to\ncontribute to that effort (otherwise we would have added \"new\" as a\ntype to solve this issue already), but I am willing to be talked\ninto trying, with a few conditions to prevent unnecessary churning,\ni.e. We do \"typedef uint16_t u16\" and friends, and new and old code\nthat are written to directly interact with Rust written code would\nbe better written with u16 and friends, so the same thing is called\nsimilarly across the wall, but we want to avoid replacing uint16_t\nwith u16 blindly.\n\n"},{"id":"526960","messageId":"xmqqqzvytkqi.fsf@gitster.g","threadId":"64060","inReplyTo":"CAH=ZcbC_ecyPeLS_0DfN3+w9RTqcrt6Je08RPXqb6pJVGaPjuw@mail.gmail.com","subject":"Re: [PATCH v2 00/18] Introduce rust: In xdiff","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-09-22T18:17:41Z","receivedAt":"2025-09-22T18:17:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ezekiel Newren <ezekielnewren@gmail.com> writes:\n\n> On Mon, Sep 22, 2025 at 11:23 AM Ezekiel Newren <ezekielnewren@gmail.com> wrote:\n>> Cbindgen parse's Rust and generates C header files; If a field in a\n>> struct uses u16 as the name then Rust won't compile, and cbindgen\n>> can't create the C header file.\n>\n> I just tried this in Rust and it turns out you actually can use u16 as\n> a struct field name. My bad. I think that's a bad idea and should be\n> discouraged though.\n\nThat's a great news.\n\nIt means we do not have to worry about existing variables and\nstructure member names at all while working with cbindgen.\n\n"},{"id":"526961","messageId":"CAH=ZcbA5hAM9kmO410KzVW7RXWWiwX2oJk6GGPP+6oVYatRVOw@mail.gmail.com","threadId":"64060","inReplyTo":"xmqqqzvytkqi.fsf@gitster.g","subject":"Re: [PATCH v2 00/18] Introduce rust: In xdiff","fromName":"Ezekiel Newren","fromEmail":"ezekielnewren@gmail.com","sentAt":"2025-09-22T18:33:50Z","receivedAt":"2025-09-22T18:34:04Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"On Mon, Sep 22, 2025 at 12:17 PM Junio C Hamano <gitster@pobox.com> wrote:\n> > I just tried this in Rust and it turns out you actually can use u16 as\n> > a struct field name. My bad. I think that's a bad idea and should be\n> > discouraged though.\n>\n> That's a great news.\n>\n> It means we do not have to worry about existing variables and\n> structure member names at all while working with cbindgen.\n\nYou've convinced me that we shouldn't use Rust type names in C. I've\nalready refactored my code to use [ui]int(8|16|32|64)_t in part 2 of\nmy xdiff cleanup.\n"},{"id":"526962","messageId":"xmqqms6mtjmx.fsf@gitster.g","threadId":"64060","inReplyTo":"CAH=ZcbA5hAM9kmO410KzVW7RXWWiwX2oJk6GGPP+6oVYatRVOw@mail.gmail.com","subject":"Re: [PATCH v2 00/18] Introduce rust: In xdiff","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-09-22T18:41:26Z","receivedAt":"2025-09-22T18:41:29Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ezekiel Newren <ezekielnewren@gmail.com> writes:\n\n> already refactored my code to use [ui]int(8|16|32|64)_t in part 2 of\n> my xdiff cleanup.\n\nNoted.  I think [u]int(8|16|32|64)_t would be familiar to both C\nwriters and Rust folks who need to peek into C for working with it.\n\nThanks.\n"},{"id":"527023","messageId":"aNIr_P6HMEvwERUL@pks.im","threadId":"64060","inReplyTo":"CAH=ZcbB_AZ7a=kQ_tHRtm7jPD6h_7AAiQYyaMQrG+ijj8Dyvrw@mail.gmail.com","subject":"Re: [PATCH v2 00/18] Introduce rust: In xdiff","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-09-23T05:11:24Z","receivedAt":"2025-09-23T05:11:30Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Sep 22, 2025 at 10:27:32AM -0600, Ezekiel Newren wrote:\n> On Mon, Sep 22, 2025 at 10:16 AM Patrick Steinhardt <ps@pks.im> wrote:\n> > On Mon, Sep 22, 2025 at 09:18:14AM -0600, Ezekiel Newren wrote:\n> > > > By the way: I'm also happy to change attribution of some of the patches\n> > > > in my patch series to mention Ezekiel as author. I don't care much who\n> > > > is listed for the initial patches that introduce Rust, but would retain\n> > > > my own authorship for the \"varint\" and \"BreakingChanges\" commits.\n> > >\n> > > My only other concern is with varint. You use usize on the Rust side\n> > > and then uint64_t on the C side, but I'm ok with fixing that later as\n> > > it only breaks 'linux32 (i386/ubuntu:focal)' in the github workflows.\n> >\n> > Oh, this is actually an oversight, good catch! I refactored \"varint.c\"\n> > to use `uint64_t`, but then forgot to adjust the Rust side in the same\n> > spirit. Will fix.\n> \n> You also missed updating varint.h.\n\nHm, am I missing anything? It does use `uint64_t`, and if it didn't it\nwould cause a compiler error due to mismatching declarations.\n\n> > I suggested in [1] that I can change authorship of the patches that\n> > introduce the initial infrastructure into Meson and our Makefile (so I\n> > guess patches 1 and 3) to instead list you as author and myself as\n> > Co-authored-by. Is that something you want? Given that you have\n> > kickstarted the whole effort around introducing Rust again I wouldn't\n> > mind that at all.\n> \n> It doesn't make sense to me to list myself as the author of any of\n> your commits, but I would like my name referenced in your commit\n> messages.\n\nOkay, will do. Is it sufficient if I say something \"Based-on-patch-by\"\nor \"Inspired-by\"? Don't really have much of a better idea for how to\ninclude it, but please let me know in case you have any preference.\n\nPatrick\n"},{"id":"527102","messageId":"CAH=ZcbCLMHzMXZu3SQzJ-HvLuiG=goZ-xQWwr=J2Qs=k1=fy7g@mail.gmail.com","threadId":"64060","inReplyTo":"aNIr_P6HMEvwERUL@pks.im","subject":"Re: [PATCH v2 00/18] Introduce rust: In xdiff","fromName":"Ezekiel Newren","fromEmail":"ezekielnewren@gmail.com","sentAt":"2025-09-23T16:32:41Z","receivedAt":"2025-09-23T16:32:55Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"On Mon, Sep 22, 2025 at 11:11 PM Patrick Steinhardt <ps@pks.im> wrote:\n>\n> On Mon, Sep 22, 2025 at 10:27:32AM -0600, Ezekiel Newren wrote:\n> > On Mon, Sep 22, 2025 at 10:16 AM Patrick Steinhardt <ps@pks.im> wrote:\n> > > On Mon, Sep 22, 2025 at 09:18:14AM -0600, Ezekiel Newren wrote:\n> > > > > By the way: I'm also happy to change attribution of some of the patches\n> > > > > in my patch series to mention Ezekiel as author. I don't care much who\n> > > > > is listed for the initial patches that introduce Rust, but would retain\n> > > > > my own authorship for the \"varint\" and \"BreakingChanges\" commits.\n> > > >\n> > > > My only other concern is with varint. You use usize on the Rust side\n> > > > and then uint64_t on the C side, but I'm ok with fixing that later as\n> > > > it only breaks 'linux32 (i386/ubuntu:focal)' in the github workflows.\n> > >\n> > > Oh, this is actually an oversight, good catch! I refactored \"varint.c\"\n> > > to use `uint64_t`, but then forgot to adjust the Rust side in the same\n> > > spirit. Will fix.\n> >\n> > You also missed updating varint.h.\n\nOh, I think when I was editing your patches locally varint.h got out of sync.\n\n> Hm, am I missing anything? It does use `uint64_t`, and if it didn't it\n> would cause a compiler error due to mismatching declarations.\n\nunsigned char should be replaced with uint8_t. I don't know the exact\nlocation that causes linux32 (i386/ubuntu:focal) to fail, I think it\nhad to do with specifying uint64_t on the C side, and usize on the\nRust side. usize is 32-bits on i386/ubuntu:focal.\n\n> > > I suggested in [1] that I can change authorship of the patches that\n> > > introduce the initial infrastructure into Meson and our Makefile (so I\n> > > guess patches 1 and 3) to instead list you as author and myself as\n> > > Co-authored-by. Is that something you want? Given that you have\n> > > kickstarted the whole effort around introducing Rust again I wouldn't\n> > > mind that at all.\n> >\n> > It doesn't make sense to me to list myself as the author of any of\n> > your commits, but I would like my name referenced in your commit\n> > messages.\n>\n> Okay, will do. Is it sufficient if I say something \"Based-on-patch-by\"\n> or \"Inspired-by\"? Don't really have much of a better idea for how to\n> include it, but please let me know in case you have any preference.\n\nInspired-by would be my preference.\n"},{"id":"527120","messageId":"CAH=ZcbBO9bkZTTteZkLK6Ca0qS9v-_Xyw+Y471TFzpNSr7escg@mail.gmail.com","threadId":"64060","inReplyTo":"pull.2043.v2.git.git.1758071798.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 00/18] Introduce rust: In xdiff","fromName":"Ezekiel Newren","fromEmail":"ezekielnewren@gmail.com","sentAt":"2025-09-23T18:05:56Z","receivedAt":"2025-09-23T18:06:10Z","isPatch":true,"sender":{"key":"ezekielnewren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/18313324?v=4"},"body":"On Tue, Sep 16, 2025 at 7:16 PM Ezekiel Newren via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\nDROP PATCH SERIES: I am dropping this patch series in favor of\nPatrick's. Everything I want to add can be rebased on top of his work.\n"},{"id":"527138","messageId":"xmqqy0q4op7e.fsf@gitster.g","threadId":"64060","inReplyTo":"CAH=ZcbBO9bkZTTteZkLK6Ca0qS9v-_Xyw+Y471TFzpNSr7escg@mail.gmail.com","subject":"Re: [PATCH v2 00/18] Introduce rust: In xdiff","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-09-23T21:04:37Z","receivedAt":"2025-09-23T21:04:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ezekiel Newren <ezekielnewren@gmail.com> writes:\n\n> On Tue, Sep 16, 2025 at 7:16 PM Ezekiel Newren via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> DROP PATCH SERIES: I am dropping this patch series in favor of\n> Patrick's. Everything I want to add can be rebased on top of his work.\n\nOK.  Thanks for expressing intentions clearly.  Will adjust my end\nby ejecting them from 'seen'.\n\n"}]}