Close open repository before moving to Recycle Bin and improve locked-folder...
Brought to you by:
thebguy
Originally created by: theBGuy
This change ensures users can successfully remove a repository from the app and Recycle Bin, even if it is the currently open one. The dialog now closes the open repo before attempting to move its folder, preventing failures due to locked files (especially on Windows). If a lock still occurs, the error message clearly explains what to do instead of giving a raw error.
RemoveRepoDialog in src/features/repository/RepoDialogs.tsx to close the open repository with closeRepo() before calling deleteRepoFolder when the "Also move to Recycle Bin" option is selected.delete_repo_folder in src-tauri/src/fsops.rs to attempt trashing the folder up to three times, waiting briefly between tries to give in-flight git subprocesses time to exit.changelog.d/fixed-remove-open-repo-to-recycle-bin.md summarizing the improvement and clearer user-facing error behavior.
Originally posted by: cloudflare-workers-and-pages[bot]
Deploying gitdesktop with
Cloudflare Pages
2516c1bView logs
Originally posted by: theBGuy
AI review (sonnet) · automated
This change fixes a real bug (moving the currently-open repo to the Recycle Bin failing because git-status polling keeps spawning subprocesses) and adds a Rust retry loop to handle subprocess teardown lag. The approach is sound for the happy path, but has a genuine UX regression on failure and a cross-platform string bug.
Correctness
should-fix —
RepoDialogs.tsx,handleRemove(thecloseRepocall)closeRepo()is now called unconditionally beforedeleteRepoFolder, even if the trash operation is about to fail. WhendeleteRepoFolderthrows (all three retries exhausted), the catch block fires, but the repo is already closed and the user has been navigated away — from a repo that still exists on disk. The original code left the user in their repo on failure; this version silently ejects them. The comment says "the repo stays listed so you can close external programs and retry," which is true, but omits that the user also has to reopen the repo manually.Concrete case: user has an uncommitted change visible, checks "Also move to Recycle Bin," confirms; an IDE holds a file handle; after ~900ms of retries the toast error appears, but the app is now at the no-repo screen instead of back in the repo.
Fix: guard
closeRepo()inside theif (moveToTrash)block so the non-trash path is unaffected, and accept the current eject-on-failure behaviour only for the trash path — or restore the open repo in the catch block (if (moveToTrash && repo.path === repoPath) openRepo(repo.path)).should-fix —
src-tauri/src/fsops.rs, the error stringThe message hardcodes
"Recycle Bin", which is Windows-only terminology. Thetrashcrate is cross-platform; on macOS the destination is Trash and on Linux it is the freedesktop trash. Mac users who hit this error will read "Recycle Bin" and be confused.Fix: use a platform-branched message (
#[cfg(target_os = "windows")]/#[cfg(target_os = "macos")]/ else), or use the vaguer but accurate"the system trash".Readability
nit —
src-tauri/src/fsops.rs,last_err.unwrap_or_else(|| "unknown error".to_string())last_erris alwaysSomeat the point it is consumed — the loop body is the only way to reach the code after thefor, and it always setslast_err = Some(e)on every iteration. Theunwrap_or_elsefallback is dead code and obscures the invariant. Use.expect("last_err set in loop")or restructure to avoid theOptionentirely (e.g.let last_err: trash::Errorwith a sentinel /unwrap()).Originally posted by: theBGuy
AI security audit (sonnet) · automated
Let me examine the full context of the changed functions before concluding.No security issues introduced by these changes.
The
delete_repo_folderguard (dir.join(".git").exists()) predates this diff and is unchanged. The{cause}interpolated into the error message is sourced exclusively from thetrashcrate's internal error — not from any attacker-controlled input — and is returned only to the local user who initiated the operation via a toast. The reordering inRepoDialogs.tsx(closeRepo()beforedeleteRepoFolder) is a pure UX sequencing change with no security-relevant trust-boundary crossing.Originally posted by: theBGuy
AI review (sonnet) · automated
This PR fixes three distinct bugs: the currently-open-repo trash failure on Windows (lock contention), the GitHub "Delete branch" flag also deleting the user's local branch, and the local-PR merge leaving the user on
baseinstead of their original branch. The approach for all three is sound and the previous review's concerns have been addressed.Resolved since last review
RepoDialogs.tsx): TheopenRepo({ root: repo.path, name: repo.name })call in the catch block correctly restores the repo whenwasOpen && !trashed, covering every failure point before the folder is actually gone."Recycle Bin"cross-platform string (fsops.rs): Replaced with a compile-time#[cfg(windows)]/#[cfg(not(windows))]split; the frontend mirrors this viaisWindows ? "Recycle Bin" : "Trash".unwrap_or_else(fsops.rs): Replaced cleanly withlet mut cause = String::new()set unconditionally in the loop body.Correctness
nit —
RepoDialogs.tsx, catch blockIf
trash::deletesucceeds butremoveRecent.mutateAsyncthen throws (admittedly rare — it's a local settings write),trashedistruesoopenRepois skipped, leaving a stale recent-repo entry pointing at a deleted folder. Not a blocker, but worth noting in a comment so future maintainers don't "fix" the condition.Previous findings from other reviewers
Copilot: partial move of
headingh_delete_remote_head_branch— not an issue.head.head_repository_ownerandhead.head_repositoryare consumed by.map(...)before the let-else, buthead.is_cross_repositoryisbool(Copy) so Rust allows accessing it on a partially-moved struct, andbranchis a separately-ownedStringconstructed earlier via.trim().to_string(). This compiles correctly.Copilot:
closeRepo()called even whenmoveToTrashis false — the current code is a deliberate design choice: removing a repo from GitDesktop closes it regardless of whether the folder is trashed. The catch block restores the open repo on any failure, so the UX regression from the previous version is gone. This is correct.Copilot: 404 "Not found" being swallowed in
gh_delete_remote_head_branch— fixed in the current diff; only"reference does not exist"(GitHub's 422 response) is now suppressed.Overall the change is solid. The logic for all three fixes is correct, the cross-platform string handling is done at both layers (Rust compile-time, TS runtime), and the rollback path in the frontend is right.
Ticket changed by: theBGuy