Skip to content

feat: add experimental gitoxide backend for git operations#161

Merged
avihut merged 3 commits intomasterfrom
daft-157/migrate-to-gitoxide
Feb 3, 2026
Merged

feat: add experimental gitoxide backend for git operations#161
avihut merged 3 commits intomasterfrom
daft-157/migrate-to-gitoxide

Conversation

@avihut
Copy link
Owner

@avihut avihut commented Feb 3, 2026

Summary

  • Adds gitoxide (gix crate) as an alternative git backend, controlled by the runtime config flag daft.experimental.gitoxide
  • When enabled, daft uses gitoxide's native Rust library for ~20 supported read-only operations instead of spawning git subprocesses
  • Old subprocess code stays intact and remains the default behavior
  • Gracefully falls back to subprocess for operations gitoxide can't handle (worktree management, push, pull, stash, checkout, etc.)

Architecture

  • GitCommand struct gains use_gitoxide: bool field and gix_repo: OnceLock<ThreadSafeRepository> for lazy repo discovery
  • Builder pattern (.with_gitoxide(enabled)) avoids touching existing call sites
  • New src/git_oxide.rs module (~700 lines) contains all gitoxide implementations
  • Conditional dispatch at the top of each migrated method: if self.use_gitoxide { return git_oxide::...; }
  • Runtime notice printed once via std::sync::Once: [experimental] Using gitoxide backend for git operations

Operations migrated

  • Repository discovery & state: rev_parse_git_common_dir, is_inside_git_repo, is_inside_work_tree, is_bare_repository, get_git_dir, get_current_worktree_path
  • References & branches: symbolic_ref_short_head, show_ref_exists, for_each_ref, branch_list_verbose
  • Config reading: config_get, config_get_global
  • Status & commit info: has_uncommitted_changes, rev_list_count
  • Remote info: remote_list, remote_get_url
  • Remote network: ls_remote_symref, ls_remote_heads, ls_remote_branch_exists

Bug fixes included

  • Prune worktree deletion with CWD inside: gix::discover(".") stored relative paths, causing workdir() to return "." instead of an absolute path. Fixed by passing current_dir() to discover().
  • Flow-eject on bare-repo layout: The cached gix repo discovered from the bare project root had no workdir, causing repo.status() to fail. Fixed by falling back to subprocess when the cached repo is bare.

Turning on the feature

git config --global daft.experimental.gitoxide true

Test plan

  • cargo fmt -- --check passes
  • cargo clippy -- -D warnings passes
  • cargo test --lib — 183 passed, 0 failed
  • just test-integration — default mode (gitoxide off)
  • just test-integration with daft.experimental.gitoxide=true
  • Manual smoke test: enable flag globally, run git worktree-prune, git worktree-flow-eject

🤖 Generated with Claude Code

avihut and others added 3 commits February 4, 2026 00:16
Add gitoxide (gix crate) as a native Rust alternative to git subprocess
calls, controlled by the runtime config key `daft.experimental.gitoxide`.
When enabled, daft uses gitoxide for supported read-only operations
(repository discovery, refs, config, status, remote queries) instead of
spawning git subprocesses. All existing subprocess code remains intact
as the default behavior.

- Add gix dependency with blocking-network-client and status features
- Create src/git_oxide.rs with implementations for 19 git operations
- Add use_gitoxide field to DaftSettings, loaded from git config
- Add with_gitoxide() builder on GitCommand for conditional dispatch
- Propagate gitoxide flag through all command files and remote.rs
- Print "[experimental] Using gitoxide backend" notice when enabled

Enable with: git config daft.experimental.gitoxide true

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
gix::discover(".") stores internal paths relative to ".", causing
repo.workdir() to return "." instead of an absolute path. This broke
worktree-prune when running inside the worktree being pruned: the
relative path never matched the absolute paths from git worktree list,
so the branch was not deferred and the worktree was deleted while CWD
was still inside it.

Pass std::env::current_dir() to discover() so gix stores absolute
paths, matching subprocess git behavior.

Fixes #157

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When the gix repo is discovered from a bare-repo root (as flow-eject
does by changing CWD to the project root), the cached repo has no
workdir. Calling repo.status() then fails with "A working tree is
required to perform a directory walk".

Fall back to subprocess git for has_uncommitted_changes when the cached
gix repo is bare, since the subprocess correctly uses the current CWD.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@avihut avihut linked an issue Feb 3, 2026 that may be closed by this pull request
@avihut avihut added this to the v1.1.0 milestone Feb 3, 2026
@avihut avihut added enhancement New feature or request feat New feature labels Feb 3, 2026
@avihut avihut self-assigned this Feb 3, 2026
@avihut avihut merged commit 9f79bf9 into master Feb 3, 2026
5 checks passed
@avihut avihut deleted the daft-157/migrate-to-gitoxide branch February 3, 2026 22:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request feat New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate to use gitoxide where possible

1 participant