Skip to content

perf(gitx): run git's own binary, not a launcher in front of it - #19

Closed
develdeco wants to merge 1 commit into
perf/gitx-lookpathfrom
perf/gitx-core-binary
Closed

develdeco wants to merge 1 commit into
perf/gitx-lookpathfrom
perf/gitx-core-binary

Conversation

@develdeco

Copy link
Copy Markdown
Owner

What

  • Why: PATH's git on Windows is Git for Windows' launcher (cmd\git.exe in a developer's shell, bin\git.exe on GitHub runners), which starts mingw64\bin\git.exe as a second process on every call: about 10 ms of each 25-40 ms call.
  • gitx now runs the git in the directory git --exec-path names, where every git install puts one. It is resolved once per PATH through perf(gitx): search PATH for git once per PATH, not on every call #18's cache, and gitx falls back to PATH's git when that directory holds none.
  • git.exe gives what it starts the same environment the launcher does. On Linux, and on the macOS runner's Homebrew git, both paths name the same file. The measurements are recorded in DECISIONS.md.

Verification

  • go build ./..., go vet ./... and gofmt -l . are clean. The full go test -count=1 ./... passes locally on Windows with the claude CLI off PATH, run with the rest of the series stacked.

  • New TestRunRunsGitsOwnBinary fails when gitx keeps PATH's git. New TestRunFallsBackToTheGitPATHFinds puts a fake git on PATH whose exec path holds no git, and fails when the fallback is removed. Both were mutation-checked.

  • Same environment either way: --exec-path, --html-path, config --system --list --show-origin, and an alias printing MSYSTEM, HOME and the sh/ssh it resolves print the same through cmd\git.exe, mingw64\bin\git.exe and libexec\git-core\git.exe, even with every Git directory taken off PATH.

  • On GitHub windows-2025 runners, traced and paired: verifydeliver's publish tests, two runners, three rounds each, variant order rotated:

    main perf(gitx): search PATH for git once per PATH, not on every call #18 + this PR
    simple git call (rev-parse, ls-files, diff, symbolic-ref) 24.8-27.3 ms 15.7-17.9 ms
    one round 124-164 s, mean 145 s 118-142 s, mean 130 s

    perf(gitx): search PATH for git once per PATH, not on every call #18 alone was within noise on those runners, so the difference is this PR.

  • On a Windows 11 machine: git --version takes 37.8 ms through cmd\git.exe and 26.2 ms direct; git rev-parse HEAD takes 43.8 ms and 32.3 ms.

🤖 Generated with Claude Code

PATH's git on Windows is Git for Windows' cmd\git.exe or bin\git.exe, a
launcher that starts mingw64\bin\git.exe as a second process on every
call: about 10 ms of each 25-40 ms call on a GitHub runner and on a
developer machine. gitx now runs the git in the directory
"git --exec-path" names, where every git install puts one, and falls
back to PATH's git when that directory holds none. git.exe sets up the
same environment for what it starts as the launcher does.
@develdeco

Copy link
Copy Markdown
Owner Author

Reviewer notes

  • Stacked on perf(gitx): search PATH for git once per PATH, not on every call #18, since it resolves the core binary once per PATH through perf(gitx): search PATH for git once per PATH, not on every call #18's cache. Merge perf(gitx): search PATH for git once per PATH, not on every call #18 first; this PR's base then moves to main.
  • Where to look: coreGit in internal/gitx/gitx.go, and GIT_EXEC_PATH added to lookGit's key, since it changes what --exec-path prints.
  • Behavior change, deliberate: jig runs <exec-path>/git instead of the first git on PATH. A wrapper named git that only starts git (Git for Windows' launchers, a scoop shim) is skipped. A wrapper that does more (say hub aliased as git) is skipped too, for jig's own calls. Everything git reads from config, the environment, and its own install is the same either way.
  • Why --exec-path and not mingw64\bin: it is git's own answer to where its programs live, on every platform. git's Makefile always places git there (a link, symlink or copy), so no install-layout knowledge is needed. Git for Windows also ships the DLLs that git.exe needs in that directory.
  • Cost: one extra git --exec-path process per distinct PATH, per process. A traced suite run starts 34 jig processes that call git at all (median 55 calls each), so this pays for itself on a process's second call.
  • Safe to skim: the tests' assertSameFile helper and the DECISIONS.md entry.

@develdeco

Copy link
Copy Markdown
Owner Author

Closing in favor of #23.

The pre-merge review found a real behavior change here, and I reproduced it. Git for Windows' git.exe only adds its mingw64\bin and usr\bin to the PATH of what it starts when MSYSTEM is unset, and it puts them behind %HOME%\bin; the cmd\git.exe launcher always adds them, ahead. With MSYSTEM=MINGW64 and Git's directories off PATH, a #!/bin/sh pre-commit hook runs through the launcher and fails with "cannot spawn .git/hooks/pre-commit" through git.exe. The same condition would hide the credential manager and git-lfs. jig drives users' repos, so it keeps running the first git on PATH.

#23 gets the CI benefit without touching the product: the Windows test leg puts git --exec-path first on PATH, where pwsh runs with MSYSTEM unset. Measured on the same runners, that is −23% on verifydeliver's publish tests (152 s → 117 s mean), more than this PR's −10%, because the nested git processes of a local push skip the launcher too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant