Skip to content

git: Use the real gitdir for checkpoints#51563

Merged
cole-miller merged 1 commit intozed-industries:mainfrom
steveej-forks:steveej/fix-git-checkpoint-gitdir
Mar 19, 2026
Merged

git: Use the real gitdir for checkpoints#51563
cole-miller merged 1 commit intozed-industries:mainfrom
steveej-forks:steveej/fix-git-checkpoint-gitdir

Conversation

@steveej
Copy link
Copy Markdown
Contributor

@steveej steveej commented Mar 14, 2026

Summary

Zed's git checkpoint code was assuming that repository-local metadata always lives under <working-directory>/.git/....

That assumption breaks for repositories where .git is a file instead of a directory, most notably:

  • git submodules
  • git worktrees

In those layouts, .git contains a gitdir: ... pointer to the real git directory. When checkpoint creation or restore tries to build paths like .git/index-<uuid>.tmp or .git/info/exclude, those path operations fail with Not a directory (os error 20).

In practice, that shows up as checkpoint failures in ACP threads for worktrees and submodules.

Root Cause

GitBinary tracked the repository working directory, but it did not track the resolved git directory.

As a result, checkpoint-related helpers derived temporary index and exclude paths from the working tree:

  • temp index path creation
  • copying the default index into the temp index
  • exclude override handling

That works for a plain repository where .git is a directory, but not when .git is a pointer file.

Changes

  • Thread the resolved git directory from RealGitRepository::path() into GitBinary.
  • Add a dedicated git_directory field to GitBinary.
  • Use git_directory instead of <working-directory>/.git for:
    • temp index file paths
    • copying the default index
    • info/exclude override paths
  • Keep the working directory unchanged for command execution; only metadata path resolution changes.
  • Add a regression test that verifies temp index files are created under the real gitdir, not under <working-directory>/.git.

Why this approach

The minimal correct fix is to distinguish between:

  • the working directory used to run git commands, and
  • the actual git directory used to store repository metadata

Git itself already makes that distinction, and git2::Repository::path() gives us the resolved gitdir directly. Reusing that value keeps the fix small and avoids special-casing submodules or worktrees elsewhere.

User-visible impact

Before this change, checkpoint operations could fail in repositories where .git is not a directory, producing errors like:

Not a directory (os error 20)

After this change, checkpoint-related git metadata is read and written from the real gitdir, so ACP checkpoints work in submodules and worktrees the same way they do in regular repositories.

Testing

  • cargo fmt -p git
  • cargo test -p git --lib
  • cargo clippy -p git --lib --tests -- -D warnings

Added regression coverage:

  • test_path_for_index_id_uses_real_git_directory

If you want, I can also compress this into a more GitHub-PR-native version with sections like ## Problem, ## Fix, and ## Validation.

Release Notes

  • Fixed .git handling when .git is a file instead of directory, e.g. when using worktrees or submodules.

@cla-bot cla-bot bot added the cla-signed The user has signed the Contributor License Agreement label Mar 14, 2026
@zed-community-bot zed-community-bot bot added the first contribution the author's first pull request to Zed. NOTE: the label application is automated via github actions label Mar 14, 2026
@maxdeviant maxdeviant changed the title git: use the real gitdir for checkpoints git: Use the real gitdir for checkpoints Mar 14, 2026
@zed-industries-bot
Copy link
Copy Markdown
Contributor

zed-industries-bot commented Mar 14, 2026

Warnings
⚠️

This PR is missing release notes.

Please add a "Release Notes" section that describes the change:

Release Notes:

- Added/Fixed/Improved ...

If your change is not user-facing, you can use "N/A" for the entry:

Release Notes:

- N/A

Generated by 🚫 dangerJS against 0566652

Changes:
- thread the resolved git directory from RealGitRepository into GitBinary
- keep the working directory and git metadata directory separate in
  checkpoint-related code paths
- create temp index files from the real gitdir instead of assuming
  <working-directory>/.git is a directory
- read the default index and info/exclude overrides from the real gitdir
- add regression coverage for temp index path generation in
  worktree/submodule-style layouts

Why:
- checkpoint operations fail in submodules and git worktrees because
  .git is a pointer file in those layouts, not a directory
- that currently surfaces as "Not a directory" errors when ACP
  checkpoints try to create temporary index files or apply exclude
  overrides
- git commands should still execute from the worktree, but repository
  metadata must be resolved from the actual gitdir

Validation:
- cargo fmt -p git
- cargo test -p git --lib
- cargo clippy -p git --lib --tests -- -D warnings
@steveej steveej force-pushed the steveej/fix-git-checkpoint-gitdir branch from cdb2545 to 0566652 Compare March 17, 2026 20:40
@cole-miller
Copy link
Copy Markdown
Member

Thanks!

@cole-miller cole-miller enabled auto-merge (squash) March 19, 2026 14:11
@cole-miller cole-miller merged commit 1bca0c5 into zed-industries:main Mar 19, 2026
29 checks passed
@steveej steveej deleted the steveej/fix-git-checkpoint-gitdir branch March 20, 2026 08:43
AmaanBilwar pushed a commit to AmaanBilwar/zed that referenced this pull request Mar 20, 2026
## Summary

Zed's git checkpoint code was assuming that repository-local metadata
always lives under `<working-directory>/.git/...`.

That assumption breaks for repositories where `.git` is a file instead
of a directory, most notably:

- git submodules
- git worktrees

In those layouts, `.git` contains a `gitdir: ...` pointer to the real
git directory. When checkpoint creation or restore tries to build paths
like `.git/index-<uuid>.tmp` or `.git/info/exclude`, those path
operations fail with `Not a directory (os error 20)`.

In practice, that shows up as checkpoint failures in ACP threads for
worktrees and submodules.

## Root Cause

`GitBinary` tracked the repository working directory, but it did not
track the resolved git directory.

As a result, checkpoint-related helpers derived temporary index and
exclude paths from the working tree:

- temp index path creation
- copying the default index into the temp index
- exclude override handling

That works for a plain repository where `.git` is a directory, but not
when `.git` is a pointer file.

## Changes

- Thread the resolved git directory from `RealGitRepository::path()`
into `GitBinary`.
- Add a dedicated `git_directory` field to `GitBinary`.
- Use `git_directory` instead of `<working-directory>/.git` for:
  - temp index file paths
  - copying the default `index`
  - `info/exclude` override paths
- Keep the working directory unchanged for command execution; only
metadata path resolution changes.
- Add a regression test that verifies temp index files are created under
the real gitdir, not under `<working-directory>/.git`.

## Why this approach

The minimal correct fix is to distinguish between:

- the working directory used to run git commands, and
- the actual git directory used to store repository metadata

Git itself already makes that distinction, and
`git2::Repository::path()` gives us the resolved gitdir directly.
Reusing that value keeps the fix small and avoids special-casing
submodules or worktrees elsewhere.

## User-visible impact

Before this change, checkpoint operations could fail in repositories
where `.git` is not a directory, producing errors like:

    Not a directory (os error 20)

After this change, checkpoint-related git metadata is read and written
from the real gitdir, so ACP checkpoints work in submodules and
worktrees the same way they do in regular repositories.

## Testing

- `cargo fmt -p git`
- `cargo test -p git --lib`
- `cargo clippy -p git --lib --tests -- -D warnings`

Added regression coverage:

- `test_path_for_index_id_uses_real_git_directory`

If you want, I can also compress this into a more GitHub-PR-native
version with sections like `## Problem`, `## Fix`, and `## Validation`.

Release Notes
- Fixed `.git` handling when `.git` is a file instead of directory, e.g.
when using worktrees or submodules.
toshmukhamedov pushed a commit to toshmukhamedov/zed that referenced this pull request Mar 20, 2026
## Summary

Zed's git checkpoint code was assuming that repository-local metadata
always lives under `<working-directory>/.git/...`.

That assumption breaks for repositories where `.git` is a file instead
of a directory, most notably:

- git submodules
- git worktrees

In those layouts, `.git` contains a `gitdir: ...` pointer to the real
git directory. When checkpoint creation or restore tries to build paths
like `.git/index-<uuid>.tmp` or `.git/info/exclude`, those path
operations fail with `Not a directory (os error 20)`.

In practice, that shows up as checkpoint failures in ACP threads for
worktrees and submodules.

## Root Cause

`GitBinary` tracked the repository working directory, but it did not
track the resolved git directory.

As a result, checkpoint-related helpers derived temporary index and
exclude paths from the working tree:

- temp index path creation
- copying the default index into the temp index
- exclude override handling

That works for a plain repository where `.git` is a directory, but not
when `.git` is a pointer file.

## Changes

- Thread the resolved git directory from `RealGitRepository::path()`
into `GitBinary`.
- Add a dedicated `git_directory` field to `GitBinary`.
- Use `git_directory` instead of `<working-directory>/.git` for:
  - temp index file paths
  - copying the default `index`
  - `info/exclude` override paths
- Keep the working directory unchanged for command execution; only
metadata path resolution changes.
- Add a regression test that verifies temp index files are created under
the real gitdir, not under `<working-directory>/.git`.

## Why this approach

The minimal correct fix is to distinguish between:

- the working directory used to run git commands, and
- the actual git directory used to store repository metadata

Git itself already makes that distinction, and
`git2::Repository::path()` gives us the resolved gitdir directly.
Reusing that value keeps the fix small and avoids special-casing
submodules or worktrees elsewhere.

## User-visible impact

Before this change, checkpoint operations could fail in repositories
where `.git` is not a directory, producing errors like:

    Not a directory (os error 20)

After this change, checkpoint-related git metadata is read and written
from the real gitdir, so ACP checkpoints work in submodules and
worktrees the same way they do in regular repositories.

## Testing

- `cargo fmt -p git`
- `cargo test -p git --lib`
- `cargo clippy -p git --lib --tests -- -D warnings`

Added regression coverage:

- `test_path_for_index_id_uses_real_git_directory`

If you want, I can also compress this into a more GitHub-PR-native
version with sections like `## Problem`, `## Fix`, and `## Validation`.

Release Notes
- Fixed `.git` handling when `.git` is a file instead of directory, e.g.
when using worktrees or submodules.
AmaanBilwar pushed a commit to AmaanBilwar/zed that referenced this pull request Mar 23, 2026
## Summary

Zed's git checkpoint code was assuming that repository-local metadata
always lives under `<working-directory>/.git/...`.

That assumption breaks for repositories where `.git` is a file instead
of a directory, most notably:

- git submodules
- git worktrees

In those layouts, `.git` contains a `gitdir: ...` pointer to the real
git directory. When checkpoint creation or restore tries to build paths
like `.git/index-<uuid>.tmp` or `.git/info/exclude`, those path
operations fail with `Not a directory (os error 20)`.

In practice, that shows up as checkpoint failures in ACP threads for
worktrees and submodules.

## Root Cause

`GitBinary` tracked the repository working directory, but it did not
track the resolved git directory.

As a result, checkpoint-related helpers derived temporary index and
exclude paths from the working tree:

- temp index path creation
- copying the default index into the temp index
- exclude override handling

That works for a plain repository where `.git` is a directory, but not
when `.git` is a pointer file.

## Changes

- Thread the resolved git directory from `RealGitRepository::path()`
into `GitBinary`.
- Add a dedicated `git_directory` field to `GitBinary`.
- Use `git_directory` instead of `<working-directory>/.git` for:
  - temp index file paths
  - copying the default `index`
  - `info/exclude` override paths
- Keep the working directory unchanged for command execution; only
metadata path resolution changes.
- Add a regression test that verifies temp index files are created under
the real gitdir, not under `<working-directory>/.git`.

## Why this approach

The minimal correct fix is to distinguish between:

- the working directory used to run git commands, and
- the actual git directory used to store repository metadata

Git itself already makes that distinction, and
`git2::Repository::path()` gives us the resolved gitdir directly.
Reusing that value keeps the fix small and avoids special-casing
submodules or worktrees elsewhere.

## User-visible impact

Before this change, checkpoint operations could fail in repositories
where `.git` is not a directory, producing errors like:

    Not a directory (os error 20)

After this change, checkpoint-related git metadata is read and written
from the real gitdir, so ACP checkpoints work in submodules and
worktrees the same way they do in regular repositories.

## Testing

- `cargo fmt -p git`
- `cargo test -p git --lib`
- `cargo clippy -p git --lib --tests -- -D warnings`

Added regression coverage:

- `test_path_for_index_id_uses_real_git_directory`

If you want, I can also compress this into a more GitHub-PR-native
version with sections like `## Problem`, `## Fix`, and `## Validation`.

Release Notes
- Fixed `.git` handling when `.git` is a file instead of directory, e.g.
when using worktrees or submodules.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed The user has signed the Contributor License Agreement first contribution the author's first pull request to Zed. NOTE: the label application is automated via github actions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants