You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Drop the `raw/` escape hatch and merge real `.git/*` entries (HEAD, config, hooks/, info/, objects/, packed-refs, refs/, …) directly into the portal root listing.
- `list_root` reads the resolved gitdir via `std::fs::read_dir` (follows gitlinks), filters out names colliding with virtual categories so the deprecated real `.git/branches/` and the `.git/worktrees/` internals stay hidden behind the friendly virtual entries, sorts dirs-first alphabetical, then appends the six virtual categories in fixed order.
- `Cat::Raw` and `VirtualGitPath::Raw` are gone. `path::classify` now returns `None` for any `.git/*` segment that isn't a virtual category, so the LocalPosixVolume real-FS path takes over for `HEAD`, `config`, `objects/`, etc. — no new code on the read side.
- `try_open_blob_stream` no longer needs a raw passthrough — real `.git/*` files stream through the standard local read path.
- Updated `git/CLAUDE.md` (file map, decisions block, M-overview), `volume/CLAUDE.md` (delegation hooks), `worktrees.rs` collision note, and CHANGELOG.
- New tests cover the mixed-listing shape, the dirs-first sort within real entries, and that real `.git/*` entries drop out of `classify`.
Copy file name to clipboardExpand all lines: apps/desktop/src-tauri/src/file_system/git/CLAUDE.md
+14-9Lines changed: 14 additions & 9 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,18 +2,21 @@
2
2
3
3
Backend module for the git browser. M1 shipped repo discovery, repo
4
4
info, status, the watcher, and the friendly-error skeleton. M2 added
5
-
the virtual `.git` portal – `branches/`, `tags/`, `raw/` browsable as
5
+
the virtual `.git` portal – `branches/` and `tags/` browsable as
6
6
virtual trees, with cross-volume copy "for free" because git blobs flow
7
7
through the existing `VolumeReadStream` abstraction. M3 filled in
8
8
commits, stash, worktrees, and submodules: the first two browse a
9
9
commit tree just like branches/tags; the latter two surface
10
10
`redirectToPath` so the frontend opens the worktree's / submodule's
11
-
working dir directly. **M4 (this milestone) adds three things: a live
12
-
toggle for the portal so `cd .git` can fall through to raw on-disk
13
-
contents, FriendlyError integration end-to-end so every git failure
14
-
reaches `ErrorPane` with a warm title + explanation + suggestion, and
15
-
three new error variants (`ShallowBoundary`, `MissingObject`,
16
-
`GitDirPermissionDenied`).**
11
+
working dir directly. M4 added three things: a live toggle for the
12
+
portal so `cd .git` can fall through to raw on-disk contents,
13
+
FriendlyError integration end-to-end so every git failure reaches
14
+
`ErrorPane` with a warm title + explanation + suggestion, and three
15
+
new error variants (`ShallowBoundary`, `MissingObject`,
16
+
`GitDirPermissionDenied`). **The portal root listing now mixes real
17
+
`.git/*` entries (HEAD, config, hooks/, objects/, refs/, …) with the
18
+
six virtual categories so the user sees everything in one place; the
19
+
old `raw/` escape hatch is gone.**
17
20
18
21
## File map
19
22
@@ -22,7 +25,7 @@ three new error variants (`ShallowBoundary`, `MissingObject`,
22
25
|`mod.rs`| Public API + the three volume hooks (`try_route_listing`, `try_route_metadata`, `try_open_blob_stream`) plus `is_virtual` for the mutation guards |
23
26
|`repo.rs`|`discover_repo(path)` walking up via `gix::discover` (follows gitlinks). `repo_info(handle, root)` collects branch, detached SHA, unborn flag, upstream, ahead/behind, and `is_dirty`. Process-global `RepoCache` (`Arc<RwLock<HashMap>>`) keyed by canonical worktree root |
24
27
|`path.rs`|`VirtualGitPath` enum, `Cat` enum, `classify(path)` parser, `to_path` inverse, `is_virtual(path)` for the volume hook short-circuits. Greedy ref-name match against the repo's known refs so `feature/foo` parses as one ref |
25
-
|`virtual_listing.rs`|`list_root` (M2 exposes `branches/`, `tags/`, `raw/`), `list_branches`, `list_tags`, `list_raw` (real-FS passthrough), `get_metadata_for`, `resolve_ref_commit` (annotated tags peel through). Real-FS reads use `std::fs` to avoid recursing through the volume hook |
28
+
|`virtual_listing.rs`|`list_root` (mixes real `.git/*` entries + the six virtual categories), `list_branches`, `list_tags`, `get_metadata_for`, `resolve_ref_commit` (annotated tags peel through), `real_gitdir_path` (follows gitlinks). Real-FS reads use `std::fs` to avoid recursing through the volume hook |
26
29
|`log.rs`|`list_commits` – gix `rev_walk` over HEAD-reachable commits; cap 5000, batch 200, `#[cfg(test)]` cooperative cancel flag (production relies on `spawn_blocking` task abort). `resolve_commit_id` resolves a SHA prefix even for unreachable commits |
27
30
|`stash.rs`|`list_stashes(repo_root)`, `resolve_stash_commit(handle, n)` – shells out to `git stash list -z` and `git rev-parse stash@{n}` (gix has no public stash API). `list_stashes` doesn't take a `RepoHandle` because the shell-out only needs `repo_root` for `git -C`|
28
31
|`worktrees.rs`|`list_worktrees` – gix `Repository::worktrees()`. Each entry sets `redirect_to_path` to the worktree's working dir |
@@ -128,7 +131,6 @@ Every virtual entry carries a real `modified_at` and most carry a `display_size`
**Why**: Hiding real `.git/*` contents behind a separate `raw/` category meant two extra clicks (open `.git/`, open `raw/`) for anyone wanting to peek at `HEAD`, `config`, `hooks/`, `objects/`, etc. The virtual entries already cover the friendly view; surfacing the real entries in the same listing gives power users one-click access without the `raw/` indirection. The classifier (`path::classify`) returns `None` for any `.git/*` segment that isn't a virtual category name, so the volume hook falls through to the real-FS path automatically — no new code on the read side, the existing LocalPosixVolume handles it. Real entries whose name collides with a virtual category get filtered out: the deprecated `.git/branches/` directory (git itself stopped writing to it years ago) and `.git/worktrees/` in linked-worktree setups (its internals belong to git, not to the user) hide behind the friendly virtual entries. Power users who really want the raw bytes open the gitdir from the terminal. Sort order: real entries dirs-first alphabetical (matching the listing pipeline default), then the six virtual categories in fixed order (branches, tags, commits, stash, worktrees, submodules).
**Why**: The snapshot date ("when this commit landed") is the same value for every file inside a `branches/main/`, `commits/<sha>/`, etc. listing — semantically correct as a "frozen point in time", but useless as a "when did I last work on this?" hint. We now run a single rev-walk per `(commit_id, dir_path)` listing: from the snapshot commit backwards by commit time, first-parent only, diffing each commit against its first parent (gix's `Tree::changes()::for_each_to_obtain_tree`). Each `Change.location` is matched against the directory's top-level entries; the first-seen commit's committer time wins. The walk stops early when every entry is dated, after `MAX_COMMITS_PER_WALK` (1000), or when the rev-walk exits. Initial commits short-circuit. Cache is process-global, FIFO-bounded at 50 keys, content-addressable so it never invalidates. Bench: 100 entries × 5000 commits cold p95=21 ms (budget 200 ms), warm p95=2 µs. 50k-commit fixture sits inside the 500 ms budget too. Entries that don't surface within the cap fall back to the snapshot date so the cell never reads as blank.
0 commit comments