Skip to content

gix-transport: run git-upload-pack for local repositories even if it's not in PATH#2700

Open
Amey Pawar (ameyypawar) wants to merge 2 commits into
GitoxideLabs:mainfrom
ameyypawar:fix/2313-upload-pack-fallback
Open

gix-transport: run git-upload-pack for local repositories even if it's not in PATH#2700
Amey Pawar (ameyypawar) wants to merge 2 commits into
GitoxideLabs:mainfrom
ameyypawar:fix/2313-upload-pack-fallback

Conversation

@ameyypawar

Copy link
Copy Markdown
Contributor

Fixes #2313.

Problem

Local clones spawn the service program, like git-upload-pack, by name, and fail with NotFound when it isn't in PATH — even though git itself is findable. As Eliah Kagan (@EliahKagan)'s analysis in the issue lays out, this is common on Windows, where installations like scoop's put git in PATH but not its subcommands, and where we already support git not being in PATH at all.

Changes

Following the resolution order discussed in the issue:

  1. Unchanged first attempt: spawn the service program by name, using PATH as before.
  2. On NotFound, find it where git finds it: the new gix_path::env::core_dir_program(name) looks the program up in the directory reported by git --exec-path (via the cached core_dir()), which is the location git itself uses to run its subcommands — and run it from there by absolute path. This works on all platforms and is the "conscious choice" variant: it's very clear which program runs.
  3. Otherwise, let git run it: spawn exe_invocation() with the service as its subcommand — passed as a separate argument, no shell involved.

The fallback can only ever engage for local repositories: it is gated on the non-SSH arm of the transport, so remote invocations keep the standard git-upload-pack command name that servers may customize. The async client has no file transport, so nothing changes there. As the retry is keyed on the failed spawn itself, there is no TOCTOU between checking and running. git-receive-pack gets the same treatment as the logic is service-generic.

The auxiliary.rs machinery for Git-for-Windows directories is deliberately left untouched — --exec-path already answers this particular question on every platform, while the broader executable-finding facility discussed in the issue (and #1886) remains open for its own design.

Validation

Reproduced the issue and verified the fix end-to-end on macOS with a PATH containing only a git symlink:

  • before: gix clone file://… fails with No such file or directory (os error 2)
  • after: the clone fully succeeds via the exec-path program
  • with GIT_EXEC_PATH additionally pointing at an empty directory, the clone still succeeds via the git upload-pack form, exercising the last fallback.

Tests:

  • gix-path: core_dir_program returns an existing absolute path for git-upload-pack and None for programs that don't exist.
  • gix-transport: the fallback invocation shape is asserted for both branches — never a shell, repository path as final argument, sub-command as a separate argument in the git-run form.
  • cargo test -p gix-path -p gix-transport --features blocking-client -p gix -p gitoxide-core, cargo clippy --workspace --all-targets -- -D warnings, cargo fmt --all -- --check all pass.

…oxideLabs#2313)

Local clones spawn the service program, like git-upload-pack, by name and would
fail if it could not be found in PATH. This is common on Windows, where git can
be installed such that git itself is available but its subcommands are not, for
instance with scoop.

Now, when spawning the service program for a local repository fails because it
cannot be found, find the same program in the directory that git --exec-path
reports - the location git itself uses to run its subcommands - and run it from
there. If it cannot be found there either, run the git binary that is always
findable with the service as its subcommand, which it can always dispatch.

Remote transports are unaffected - they keep the standard invocation so servers
can provide their own implementations - and no shell is involved in any of this.

The newly added gix_path::env::core_dir_program() provides the lookup and may
serve other Git-provided programs in the future.
Git for Windows builds with SKIP_DASHED_BUILT_INS and thus does not provide
programs for builtin subcommands like git-upload-pack in its core directory.
The test now grounds itself in a listing of that directory instead, and the
documentation of core_dir_program() points out the difference - such programs
are still run through git itself thanks to the second fallback.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bf86e75b59

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "Codex (@codex) address that feedback".

Comment thread gix-path/src/env/mod.rs
/// with `SKIP_DASHED_BUILT_INS`, like Git for Windows, omit programs for builtin subcommands, which
/// can then still be run through `git` itself.
pub fn core_dir_program(name: &str) -> Option<PathBuf> {
let path = core_dir()?.join(format!("{name}{}", std::env::consts::EXE_SUFFIX));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject path components in core_dir_program names

Because this helper is public and documented as finding a Git-provided program within core_dir(), callers that pass a user/config-provided name containing .., separators, or an absolute path can get back any existing file outside Git's exec path (Path::join does not confine the result to the base directory). Please validate that name is only a bare program name before joining so the returned path cannot escape the core dir.

Useful? React with 👍 / 👎.

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.

Local clones fail if git-upload-pack is available but not in PATH

1 participant