From 967b9dfbbbe1727c96881058bad98bbe10495296 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Thu, 28 Nov 2024 21:06:34 -0500 Subject: [PATCH 01/10] Partial sketch of `check_mode` "translation" to Rust --- tests/it/src/args.rs | 11 +++++++ tests/it/src/commands/check_mode.rs | 50 +++++++++++++++++++++++++++++ tests/it/src/commands/mod.rs | 3 ++ tests/it/src/main.rs | 1 + 4 files changed, 65 insertions(+) create mode 100644 tests/it/src/commands/check_mode.rs diff --git a/tests/it/src/args.rs b/tests/it/src/args.rs index 8123b9d36df..4dab0372c88 100644 --- a/tests/it/src/args.rs +++ b/tests/it/src/args.rs @@ -62,6 +62,17 @@ pub enum Subcommands { #[clap(value_parser = AsPathSpec)] patterns: Vec, }, + /// Check for executable bits that disagree with shebangs. + /// + /// This checks and staged files, but not any unstaged files or changes, to find shell scripts + /// that either begin with a `#!` but not `+x` permissions, or do not begin with `#!` but do + /// have `+x` permissions. Such mismatches are reported but not automatically corrected. Some + /// plaforms (at least Windows) do not support such permissions, but Git still represents them. + /// + /// This currently only checks files name with an `.sh` suffix, and only operates on the + /// current repository. Its main use is checking that fixture scripts are have correct modes. + #[clap(visible_alias = "cm")] + CheckMode {}, } #[derive(Clone)] diff --git a/tests/it/src/commands/check_mode.rs b/tests/it/src/commands/check_mode.rs new file mode 100644 index 00000000000..1947088996d --- /dev/null +++ b/tests/it/src/commands/check_mode.rs @@ -0,0 +1,50 @@ +pub(super) mod function { + use anyhow::{bail, Context}; + use gix::bstr::ByteSlice; + use std::ffi::OsString; + use std::io::{BufRead, BufReader}; + use std::process::{Command, Stdio}; + + pub fn check_mode() -> anyhow::Result<()> { + let root = find_root()?; + let mut mismatch = false; + + let cmd = Command::new("git") + .arg("-C") + .arg(root) + .args(["ls-files", "-sz", "--", "*.sh"]) + .stdout(Stdio::piped()) + .spawn() + .context("Can't run `git` to list index")?; + + let stdout = cmd.stdout.expect("should have captured stdout"); + let reader = BufReader::new(stdout); + for record in reader.split(b'\0') { + // FIXME: Use the record, displaying messages and updating `mismatch`. + } + + // FIXME: If `cmd` did not report successful completion, bail. + // FIXME: If `mismatch` (any mismatches), bail. + bail!("not yet implemented"); + } + + fn find_root() -> anyhow::Result { + let output = Command::new("git") + .args(["rev-parse", "--show-toplevel"]) + .output() + .context("Can't run `git` to find worktree root")?; + + if !output.status.success() { + bail!("`git` failed to find worktree root"); + } + + let root = output + .stdout + .strip_suffix(b"\n") + .context("Can't parse worktree root")? + .to_os_str()? + .to_owned(); + + Ok(root) + } +} diff --git a/tests/it/src/commands/mod.rs b/tests/it/src/commands/mod.rs index 81158eaad7f..adc7a6decb2 100644 --- a/tests/it/src/commands/mod.rs +++ b/tests/it/src/commands/mod.rs @@ -3,3 +3,6 @@ pub use copy_royal::function::copy_royal; pub mod git_to_sh; pub use git_to_sh::function::git_to_sh; + +pub mod check_mode; +pub use check_mode::function::check_mode; diff --git a/tests/it/src/main.rs b/tests/it/src/main.rs index fad4190cf2c..847978481df 100644 --- a/tests/it/src/main.rs +++ b/tests/it/src/main.rs @@ -31,6 +31,7 @@ fn main() -> anyhow::Result<()> { destination_dir, patterns, } => commands::copy_royal(dry_run, &worktree_root, destination_dir, patterns), + Subcommands::CheckMode {} => commands::check_mode(), } } From 76268f83996dabe647bd1014a5af0a7a3760e9b8 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Fri, 29 Nov 2024 01:01:18 -0500 Subject: [PATCH 02/10] More of a sketch of `check_mode` "translation" to Rust All parts are included, but this is not yet believed to be correct. --- Cargo.lock | 2 + tests/it/Cargo.toml | 5 +- tests/it/src/commands/check_mode.rs | 98 ++++++++++++++++++++++++----- 3 files changed, 89 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7f226e80914..ffde5316554 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3357,6 +3357,8 @@ dependencies = [ "anyhow", "clap", "gix", + "once_cell", + "regex", ] [[package]] diff --git a/tests/it/Cargo.toml b/tests/it/Cargo.toml index aad0dfbbf82..1432f77e4ae 100644 --- a/tests/it/Cargo.toml +++ b/tests/it/Cargo.toml @@ -14,7 +14,8 @@ name = "it" path = "src/main.rs" [dependencies] -clap = { version = "4.5.16", features = ["derive"] } anyhow = "1.0.86" - +clap = { version = "4.5.16", features = ["derive"] } gix = { version = "^0.68.0", path = "../../gix", default-features = false, features = ["attributes", "revision"] } +once_cell = "1.20.2" +regex = { version = "1.11.1", default-features = false, features = ["std"] } diff --git a/tests/it/src/commands/check_mode.rs b/tests/it/src/commands/check_mode.rs index 1947088996d..03504e5ddec 100644 --- a/tests/it/src/commands/check_mode.rs +++ b/tests/it/src/commands/check_mode.rs @@ -1,33 +1,41 @@ pub(super) mod function { use anyhow::{bail, Context}; use gix::bstr::ByteSlice; - use std::ffi::OsString; - use std::io::{BufRead, BufReader}; + use once_cell::sync::Lazy; + use regex::bytes::Regex; + use std::ffi::{OsStr, OsString}; + use std::io::{BufRead, BufReader, Read}; use std::process::{Command, Stdio}; pub fn check_mode() -> anyhow::Result<()> { let root = find_root()?; - let mut mismatch = false; + let mut any_mismatch = false; - let cmd = Command::new("git") - .arg("-C") - .arg(root) + let mut child = git_on(&root) .args(["ls-files", "-sz", "--", "*.sh"]) .stdout(Stdio::piped()) .spawn() - .context("Can't run `git` to list index")?; + .context("Can't start `git` subprocess to list index")?; - let stdout = cmd.stdout.expect("should have captured stdout"); - let reader = BufReader::new(stdout); - for record in reader.split(b'\0') { - // FIXME: Use the record, displaying messages and updating `mismatch`. + let stdout = child.stdout.take().expect("should have captured stdout"); + for result in BufReader::new(stdout).split(b'\0') { + let record = result.context(r"Can't read '\0'-terminated record")?; + if check_for_mismatch(&root, &record)? { + any_mismatch = true; + } } - // FIXME: If `cmd` did not report successful completion, bail. - // FIXME: If `mismatch` (any mismatches), bail. - bail!("not yet implemented"); + let status = child.wait().context("Failure running `git` subprocess to list index")?; + if !status.success() { + bail!("`git` subprocess to list index did not complete successfully"); + } + if any_mismatch { + bail!("Mismatch found - scan completed, finding at least one `#!` vs. `+x` mismatch"); + } + Ok(()) } + /// Find the top-level directory of the current repository working tree. fn find_root() -> anyhow::Result { let output = Command::new("git") .args(["rev-parse", "--show-toplevel"]) @@ -47,4 +55,66 @@ pub(super) mod function { Ok(root) } + + /// Prepare a `git` command, passing `root` as an operand to `-C`. + /// + /// This is suitable when `git` gave us the path `root`. Then it should already be in a form + /// where `git -C` will be able to use it, without alteration, regardless of the platform. + /// (Otherwise, it may be preferable to set `root` as the `cwd` of the `git` process instead.) + fn git_on(root: &OsStr) -> Command { + let mut cmd = Command::new("git"); + cmd.arg("-C").arg(root); + cmd + } + + static RECORD_REGEX: Lazy = Lazy::new(|| { + let pattern = r"(?-u)\A([0-7]+) ([[:xdigit:]]+) [[:digit:]]+\t(.+)\z"; + Regex::new(pattern).expect("regex should be valid") + }); + + /// On mismatch, report it and return `Some(true)`. + fn check_for_mismatch(root: &OsStr, record: &[u8]) -> anyhow::Result { + let fields = RECORD_REGEX.captures(record).context("Malformed record from `git`")?; + let mode = fields.get(1).expect("match should get mode").as_bytes(); + let oid = fields + .get(2) + .expect("match should get oid") + .as_bytes() + .to_os_str() + .expect("oid field verified as hex digits, should be valid OsStr"); + let path = fields.get(3).expect("match should get path").as_bytes().as_bstr(); + + match mode { + b"100644" if blob_has_shebang(root, oid)? => { + println!("mode -x but has shebang: {}\n", path); + Ok(true) + } + b"100755" if !blob_has_shebang(root, oid)? => { + println!("mode +x but no shebang: {}\n", path); + Ok(true) + } + _ => Ok(false), + } + } + + fn blob_has_shebang(root: &OsStr, oid: &OsStr) -> anyhow::Result { + let mut buf = [0u8; 2]; + + let mut child = git_on(root) + .args(["cat-file", "blob"]) + .arg(oid) + .stdout(Stdio::piped()) + .spawn() + .context("Can't start `git` subprocess to read blob")?; + + let mut stdout = child.stdout.take().expect("should have captured stdout"); + let count = stdout.read(&mut buf).context("Error reading data from blob")?; + drop(stdout); // Let the pipe break rather than waiting for the rest of the blob. + + // TODO: Maybe check status? On Unix, it should be 0 or SIGPIPE. Not sure about Windows. + _ = child.wait().context("Failure running `git` subprocess to read blob")?; + + let magic = &buf[..count]; + Ok(magic == b"#!") + } } From b17935b19cd5e18e12fc607b1d0489fcd84b72ee Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Fri, 29 Nov 2024 01:12:51 -0500 Subject: [PATCH 03/10] Fix path formatting; refactor slightly for clarity Paths of files/blobs with mismatches had been shown literally, even when containing unusual characters, and had been followed by two newlines instead of one. This fixes that, and also includes some small stylistic refactoring. --- tests/it/src/commands/check_mode.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/it/src/commands/check_mode.rs b/tests/it/src/commands/check_mode.rs index 03504e5ddec..f6f8d0cfb76 100644 --- a/tests/it/src/commands/check_mode.rs +++ b/tests/it/src/commands/check_mode.rs @@ -86,11 +86,11 @@ pub(super) mod function { match mode { b"100644" if blob_has_shebang(root, oid)? => { - println!("mode -x but has shebang: {}\n", path); + println!("mode -x but has shebang: {path:?}"); Ok(true) } b"100755" if !blob_has_shebang(root, oid)? => { - println!("mode +x but no shebang: {}\n", path); + println!("mode +x but no shebang: {path:?}"); Ok(true) } _ => Ok(false), @@ -114,7 +114,7 @@ pub(super) mod function { // TODO: Maybe check status? On Unix, it should be 0 or SIGPIPE. Not sure about Windows. _ = child.wait().context("Failure running `git` subprocess to read blob")?; - let magic = &buf[..count]; - Ok(magic == b"#!") + let possible_shebang = &buf[..count]; + Ok(possible_shebang == b"#!") } } From 2107e172d2cefae6737c7e1fa6061369390d2fc3 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Fri, 29 Nov 2024 02:30:07 -0500 Subject: [PATCH 04/10] Use `it check-mode` instead of `check-mode.sh`; test on CI - Delete the old `etc/check-mode.sh` script. - Update `justfile` recipe `check-mode` to use internal-tools. - Modify and expand `check-mode` CI job to use internal-tools. - Temporarily make `check-mode` job a matrix job to test platforms. The latter of those changes should be undone once things looks like they are working. I have manually tested `it check-mode` on Arch Linux and Windows in the same ways `check-mode.sh` was tested (and unlike with a shell-script based approach, it is not anticipated to differ on macOS). So trying three platforms on CI is a secondary supporting strategy to catch possible problems. --- .github/workflows/ci.yml | 10 +++++++-- etc/check-mode.sh | 47 ---------------------------------------- justfile | 4 +++- 3 files changed, 11 insertions(+), 50 deletions(-) delete mode 100755 etc/check-mode.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 42246e7e52f..64f2aec0d04 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -360,12 +360,18 @@ jobs: run: cd gix-pack && cargo build --all-features --target "$TARGET" check-mode: - runs-on: ubuntu-latest + strategy: + matrix: + os: [ ubuntu-latest, macos-latest, windows-latest ] + + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: extractions/setup-just@v2 - name: Find scripts with mode/shebang mismatch - run: etc/check-mode.sh + run: just check-mode check-packetline: strategy: diff --git a/etc/check-mode.sh b/etc/check-mode.sh deleted file mode 100755 index 80d524c98f4..00000000000 --- a/etc/check-mode.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env bash - -set -eu -o pipefail - -# Go to the worktree's root. (Even if the dir name ends in a newline.) -root_padded="$(git rev-parse --show-toplevel && echo -n .)" -root="${root_padded%$'\n.'}" -cd -- "$root" - -symbolic_shebang="$(printf '#!' | od -An -ta)" -status=0 - -function check_item () { - local mode="$1" oid="$2" path="$3" symbolic_magic - - # Extract the first two bytes (or less if shorter) and put in symbolic form. - symbolic_magic="$(git cat-file blob "$oid" | od -N2 -An -ta)" - - # Check for inconsistency between the mode and whether `#!` is present. - if [ "$mode" = 100644 ] && [ "$symbolic_magic" = "$symbolic_shebang" ]; then - printf 'mode -x but has shebang: %q\n' "$path" - elif [ "$mode" = 100755 ] && [ "$symbolic_magic" != "$symbolic_shebang" ]; then - printf 'mode +x but no shebang: %q\n' "$path" - else - return 0 - fi - - status=1 -} - -readonly record_pattern='^([0-7]+) ([[:xdigit:]]+) [[:digit:]]+'$'\t''(.+)$' - -# Check regular files named with a `.sh` suffix. -while IFS= read -rd '' record; do - [[ $record =~ $record_pattern ]] || exit 2 # bash 3.2 `set -e` doesn't cover this. - mode="${BASH_REMATCH[1]}" - oid="${BASH_REMATCH[2]}" - path="${BASH_REMATCH[3]}" - - case "$mode" in - 100644 | 100755) - check_item "$mode" "$oid" "$path" - ;; - esac -done < <(git ls-files -sz -- '*.sh') - -exit "$status" diff --git a/justfile b/justfile index 42b8cb0c449..065da8927e1 100755 --- a/justfile +++ b/justfile @@ -196,6 +196,7 @@ target_dir := `cargo metadata --format-version 1 | jq -r .target_directory` ein := target_dir / "debug/ein" gix := target_dir / "debug/gix" jtt := target_dir / "debug/jtt" +it := target_dir / "debug/it" # run journey tests (max) journey-tests: @@ -257,7 +258,8 @@ find-yanked: # Find shell scripts whose +x/-x bits and magic bytes (e.g. `#!`) disagree check-mode: - ./etc/check-mode.sh + cargo build -p internal-tools + {{ it }} check-mode # Delete gix-packetline-blocking/src and regenerate from gix-packetline/src copy-packetline: From 3f5d31bce259d7285d5c90c6787a91005b401052 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Fri, 29 Nov 2024 03:00:47 -0500 Subject: [PATCH 05/10] Quote `{{ it }}` in `justfile` to fix recipe in Windows Since Windows was failing due to the shell called by `just` treating `\` path separators as escape characters (the main effect of which was that they were removed in quote removal). This was not a problem for internal-tools, but it broke the `just` recipe at least in some ways of running it on Windows, including CI and at least one local system tested. --- justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/justfile b/justfile index 065da8927e1..337528e6f5d 100755 --- a/justfile +++ b/justfile @@ -259,7 +259,7 @@ find-yanked: # Find shell scripts whose +x/-x bits and magic bytes (e.g. `#!`) disagree check-mode: cargo build -p internal-tools - {{ it }} check-mode + "{{ it }}" check-mode # Delete gix-packetline-blocking/src and regenerate from gix-packetline/src copy-packetline: From 2305099a408be551d467b676dbe7abed770d5678 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Fri, 29 Nov 2024 02:38:34 -0500 Subject: [PATCH 06/10] Run the `check-mode` CI job only on Ubuntu And not on macOS on Windows. (If it is to be kept as a separate job, then possibly caching should be enabled for it. But I suspect it can be moved into another existing job, most likely the `test` job, which already runs a number of distinct checks via `just`.) --- .github/workflows/ci.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 64f2aec0d04..c7300a8725d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -360,11 +360,7 @@ jobs: run: cd gix-pack && cargo build --all-features --target "$TARGET" check-mode: - strategy: - matrix: - os: [ ubuntu-latest, macos-latest, windows-latest ] - - runs-on: ${{ matrix.os }} + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 From 8729858ed1fb2657e6f918a4952e3ca31dbc325d Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Fri, 29 Nov 2024 03:09:38 -0500 Subject: [PATCH 07/10] Run `check-mode` recipe on CI via `ci-test` recipe This adds the `check-mode` recipe to the `test` recipe and, more significantly because of its effect on CI, to the `ci-test` recipe. This causes the CI `test` job to run the `check-mode` recipe (and thus `it check-mode` through it). While the separate `check-mode` CI job was useful for developing the tool, it imposes extra work, due to the need to build dependencies that are, to a substantial extent, shared with those already present (and cached) for the `test` job. Now that it is run via the `test` CI job, the separate `check-mode` CI job is removed. --- .github/workflows/ci.yml | 11 ----------- justfile | 4 ++-- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c7300a8725d..afc1cea684a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -359,16 +359,6 @@ jobs: - name: gix-pack with all features (including wasm) run: cd gix-pack && cargo build --all-features --target "$TARGET" - check-mode: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - uses: extractions/setup-just@v2 - - name: Find scripts with mode/shebang mismatch - run: just check-mode - check-packetline: strategy: fail-fast: false @@ -451,7 +441,6 @@ jobs: - test-32bit-cross - lint - cargo-deny - - check-mode - check-packetline - check-blocking diff --git a/justfile b/justfile index 337528e6f5d..f5a49d82641 100755 --- a/justfile +++ b/justfile @@ -10,10 +10,10 @@ alias c := check alias nt := nextest # run all tests, clippy, including journey tests, try building docs -test: clippy check doc unit-tests journey-tests-pure journey-tests-small journey-tests-async journey-tests +test: clippy check doc unit-tests journey-tests-pure journey-tests-small journey-tests-async journey-tests check-mode # run all tests, without clippy, and try building docs -ci-test: check doc unit-tests +ci-test: check doc unit-tests check-mode # run all journey tests - should be run in a fresh clone or after `cargo clean` ci-journey-tests: journey-tests-pure journey-tests-small journey-tests-async journey-tests From e57167f8448c81e4db3059e113494eaa5c777a76 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Fri, 29 Nov 2024 04:38:30 -0500 Subject: [PATCH 08/10] Copyedit and clarify `CheckMode` doc comment --- tests/it/src/args.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/it/src/args.rs b/tests/it/src/args.rs index 4dab0372c88..e0ccc820ae6 100644 --- a/tests/it/src/args.rs +++ b/tests/it/src/args.rs @@ -64,10 +64,10 @@ pub enum Subcommands { }, /// Check for executable bits that disagree with shebangs. /// - /// This checks and staged files, but not any unstaged files or changes, to find shell scripts + /// This checks committed and staged files, but not anything unstaged, to find shell scripts /// that either begin with a `#!` but not `+x` permissions, or do not begin with `#!` but do /// have `+x` permissions. Such mismatches are reported but not automatically corrected. Some - /// plaforms (at least Windows) do not support such permissions, but Git still represents them. + /// platforms (at least Windows) do not have such permissions, but Git still represents them. /// /// This currently only checks files name with an `.sh` suffix, and only operates on the /// current repository. Its main use is checking that fixture scripts are have correct modes. From 5a803b34b5797fc3e4f290a22ec9828d4199d927 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Fri, 29 Nov 2024 07:10:57 -0500 Subject: [PATCH 09/10] Eliminate a variable that didn't make things clearer --- tests/it/src/commands/check_mode.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/it/src/commands/check_mode.rs b/tests/it/src/commands/check_mode.rs index f6f8d0cfb76..f2b6a60f8a8 100644 --- a/tests/it/src/commands/check_mode.rs +++ b/tests/it/src/commands/check_mode.rs @@ -114,7 +114,6 @@ pub(super) mod function { // TODO: Maybe check status? On Unix, it should be 0 or SIGPIPE. Not sure about Windows. _ = child.wait().context("Failure running `git` subprocess to read blob")?; - let possible_shebang = &buf[..count]; - Ok(possible_shebang == b"#!") + Ok(&buf[..count] == b"#!") } } From 7e8aedff9a05a84038f885e1a17ef5cc41d9fe2e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 29 Nov 2024 13:53:33 +0100 Subject: [PATCH 10/10] minor refactors --- tests/it/src/commands/check_mode.rs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/tests/it/src/commands/check_mode.rs b/tests/it/src/commands/check_mode.rs index f2b6a60f8a8..35f95d78cad 100644 --- a/tests/it/src/commands/check_mode.rs +++ b/tests/it/src/commands/check_mode.rs @@ -18,11 +18,9 @@ pub(super) mod function { .context("Can't start `git` subprocess to list index")?; let stdout = child.stdout.take().expect("should have captured stdout"); - for result in BufReader::new(stdout).split(b'\0') { + for result in BufReader::new(stdout).split(0) { let record = result.context(r"Can't read '\0'-terminated record")?; - if check_for_mismatch(&root, &record)? { - any_mismatch = true; - } + any_mismatch |= check_for_mismatch(&root, &record)?; } let status = child.wait().context("Failure running `git` subprocess to list index")?; @@ -37,7 +35,7 @@ pub(super) mod function { /// Find the top-level directory of the current repository working tree. fn find_root() -> anyhow::Result { - let output = Command::new("git") + let output = Command::new(gix::path::env::exe_invocation()) .args(["rev-parse", "--show-toplevel"]) .output() .context("Can't run `git` to find worktree root")?; @@ -52,7 +50,6 @@ pub(super) mod function { .context("Can't parse worktree root")? .to_os_str()? .to_owned(); - Ok(root) } @@ -62,18 +59,18 @@ pub(super) mod function { /// where `git -C` will be able to use it, without alteration, regardless of the platform. /// (Otherwise, it may be preferable to set `root` as the `cwd` of the `git` process instead.) fn git_on(root: &OsStr) -> Command { - let mut cmd = Command::new("git"); + let mut cmd = Command::new(gix::path::env::exe_invocation()); cmd.arg("-C").arg(root); cmd } - static RECORD_REGEX: Lazy = Lazy::new(|| { - let pattern = r"(?-u)\A([0-7]+) ([[:xdigit:]]+) [[:digit:]]+\t(.+)\z"; - Regex::new(pattern).expect("regex should be valid") - }); - /// On mismatch, report it and return `Some(true)`. fn check_for_mismatch(root: &OsStr, record: &[u8]) -> anyhow::Result { + static RECORD_REGEX: Lazy = Lazy::new(|| { + let pattern = r"(?-u)\A([0-7]+) ([[:xdigit:]]+) [[:digit:]]+\t(.+)\z"; + Regex::new(pattern).expect("regex should be valid") + }); + let fields = RECORD_REGEX.captures(record).context("Malformed record from `git`")?; let mode = fields.get(1).expect("match should get mode").as_bytes(); let oid = fields