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
- Snapshot listings (`.git/branches/main/src/`, `.git/commits/<sha>/`, etc.) now show each entry's last-touched commit date instead of the snapshot's commit date.
- New `git/snapshot_dates.rs` module: walks commits backwards from the snapshot, diffs each against its first parent, and attributes the committer time to any pending top-level entry the diff touches. Stops when every entry is dated, after 1000 commits, or when the rev-walk runs out.
- Initial commits short-circuit (every entry gets the initial commit's date).
- Subdirs get the date of the most recent commit that touched any file underneath.
- Process-global FIFO cache (50 keys), content-addressable so it never invalidates.
- Bench: 100 entries × 5000 commits cold p95=21 ms (budget 200 ms), warm p95=2 µs cache hit. New ignored bench at 50k commits stays inside the 500 ms budget.
- Tests: per-file dates on three-files / dir / cache-hit / cap-fallback / initial-commit fixtures.
- Falls back to the snapshot date when the cap fires so the cell never reads as blank.
Copy file name to clipboardExpand all lines: apps/desktop/src-tauri/src/file_system/git/CLAUDE.md
+7-3Lines changed: 7 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -27,7 +27,8 @@ three new error variants (`ShallowBoundary`, `MissingObject`,
27
27
|`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
28
|`worktrees.rs`|`list_worktrees` – gix `Repository::worktrees()`. Each entry sets `redirect_to_path` to the worktree's working dir |
29
29
|`submodules.rs`|`list_submodules` – gix `Repository::submodules()`. Each entry sets `redirect_to_path` to `<repo_root>/<rel-path>`|
30
-
|`tree.rs`|`list_tree`, `get_tree_entry`, `lookup_blob_id`, `read_blob` – gix tree walks. Permissions reflect `EntryKind::BlobExecutable` so cross-volume copy preserves the executable bit |
30
+
|`tree.rs`|`list_tree`, `get_tree_entry`, `lookup_blob_id`, `read_blob` – gix tree walks. Permissions reflect `EntryKind::BlobExecutable` so cross-volume copy preserves the executable bit. `list_tree` calls `snapshot_dates::decode_per_file_dates` for per-file Modified dates, falling back to the snapshot date |
31
+
|`snapshot_dates.rs`|`decode_per_file_dates(commit, dir_path)` walks commits backwards from `commit`, diffs each against its first parent, and attributes the committer time to any pending top-level entry the diff touches. Capped at `MAX_COMMITS_PER_WALK` (1000). FIFO-bounded process-global cache keyed on `(commit_id, dir_path)` — cache is content-addressable so it never goes stale |
31
32
|`read_blob.rs`|`GitBlobReadStream` – owns the full `Vec<u8>` and yields 256 KB chunks. See *Honest blob streaming* below |
32
33
|`status.rs`|`list_status(repo, dir)` runs a full-repo `git status --porcelain=v2 -z` once per `.git/index` mtime, caches the result in a process-global `RwLock<HashMap<RepoRoot, CachedStatus>>`, and slices it by `dir`. The watcher invalidates the snapshot whenever `.git/*` changes. Parses porcelain v2 in `parse_porcelain_v2`. |
33
34
|`watcher.rs`|`GitWatcherRegistry` – per-repo notify-rs debouncer. `subscribe(app, root)` returns the current `RepoInfo` synchronously and emits `git-state-changed` on relevant `.git/*` mutations. 200 ms debounce. M2: also calls `notify_directory_changed(.., FullRefresh)` for any cached `.git/{branches,tags}/` listings on the local volume |
@@ -134,8 +135,8 @@ Every virtual entry carries a real `modified_at` and most carry a `display_size`
134
135
|`stash/<n>/`| stash creation date |`on main` (parsed from stash subject) | 0 |
135
136
|`worktrees/<name>` (redirect) | worktree HEAD date |`on feature-x` or short SHA | 0 |
136
137
|`submodules/<name>` (redirect) | pinned commit date | short SHA | 0 |
Cross-category Size sort is meaningless (ahead-count vs files-changed vs item count); that's an honest tradeoff — each cell is self-explaining via `display_size_tooltip` (also used as the aria-label).
141
142
@@ -146,6 +147,9 @@ The frontend reads `display_size` / `display_size_tooltip` from `FileEntry`; the
**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.
152
+
149
153
**Decision (M4 follow-up)**: Cache `list_status` results keyed by `.git/index` mtime
150
154
**Why**: Status used to walk the worktree on every `listing-complete` (every nav,
151
155
every diff). On a 50k-file repo that's ~75 ms per nav. We now run one full-repo
0 commit comments