Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
52565b8
Add allow_empty commits, detached worktree creation, and new git oper…
rtfeldman Apr 6, 2026
ae02f9e
Remove redundent --no-optional-locks argument in new git commands
Anthony-Eid Apr 6, 2026
d46dd16
Unify the create worktree functions
Anthony-Eid Apr 6, 2026
aa4bedb
Remove stage_all_including_untracked method
Anthony-Eid Apr 6, 2026
ef03cd8
Add resolve_commit and repair_worktrees to git layer
rtfeldman Apr 6, 2026
7978e5c
Fix proto migration
Anthony-Eid Apr 6, 2026
d815b0a
Add resolve_commit and repair_worktrees to git layer
rtfeldman Apr 6, 2026
4107daf
dedup repair_worktrees calls
Anthony-Eid Apr 6, 2026
59b87fd
Add ArchivedGitWorktree data model and DB operations
rtfeldman Apr 6, 2026
24b1cad
Wire up worktree archival on thread archive and restoration on unarchive
rtfeldman Apr 6, 2026
a1587ec
Wire up original_commit_hash in archive and restore flows
rtfeldman Apr 6, 2026
146b852
Wire up Sidebar as archive orchestrator with cancellation support
rtfeldman Apr 7, 2026
86a1c4d
Replace smol channels with futures equivalents
rtfeldman Apr 8, 2026
05f7853
Address code review: remove dead code, fix cancellation checks, add e…
rtfeldman Apr 8, 2026
b796d7c
Fix CI: remove redundant clone and unused futures dependency
rtfeldman Apr 8, 2026
eb72c74
Restore worktree deletion when archiving the last thread
rtfeldman Apr 8, 2026
b9f5351
Revert channels from futures back to smol
rtfeldman Apr 8, 2026
0c851eb
Use detached commits for worktree archival instead of moving branches
rtfeldman Apr 8, 2026
0cacec5
Fix rebase artifacts: remove duplicate methods and stale validation
rtfeldman Apr 8, 2026
d2a6eeb
Force-remove worktree directory since detached commits leave it dirty
rtfeldman Apr 8, 2026
55d3229
Restore explicit error message for archive rollback failure
rtfeldman Apr 8, 2026
0581577
Address review feedback on detached commit archival
rtfeldman Apr 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions crates/agent_ui/src/thread_metadata_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,30 @@ impl ThreadMetadataStore {
}
}

pub fn complete_worktree_restore(
&mut self,
session_id: &acp::SessionId,
path_replacements: &[(PathBuf, PathBuf)],
cx: &mut Context<Self>,
) {
if let Some(thread) = self.threads.get(session_id).cloned() {
let mut paths: Vec<PathBuf> = thread.folder_paths.paths().to_vec();
for (old_path, new_path) in path_replacements {
for path in &mut paths {
if path == old_path {
*path = new_path.clone();
}
}
}
let new_folder_paths = PathList::new(&paths);
self.save_internal(ThreadMetadata {
folder_paths: new_folder_paths,
..thread
});
cx.notify();
}
}

pub fn create_archived_worktree(
&self,
worktree_path: String,
Expand Down Expand Up @@ -2319,6 +2343,97 @@ mod tests {
assert_eq!(wt1[0].id, wt2[0].id);
}

#[gpui::test]
async fn test_complete_worktree_restore_multiple_paths(cx: &mut TestAppContext) {
init_test(cx);
let store = cx.update(|cx| ThreadMetadataStore::global(cx));

let original_paths = PathList::new(&[
Path::new("/projects/worktree-a"),
Path::new("/projects/worktree-b"),
Path::new("/other/unrelated"),
]);
let meta = make_metadata("session-multi", "Multi Thread", Utc::now(), original_paths);

store.update(cx, |store, cx| {
store.save_manually(meta, cx);
});

let replacements = vec![
(
PathBuf::from("/projects/worktree-a"),
PathBuf::from("/restored/worktree-a"),
),
(
PathBuf::from("/projects/worktree-b"),
PathBuf::from("/restored/worktree-b"),
),
];

store.update(cx, |store, cx| {
store.complete_worktree_restore(
&acp::SessionId::new("session-multi"),
&replacements,
cx,
);
});

let entry = store.read_with(cx, |store, _cx| {
store.entry(&acp::SessionId::new("session-multi")).cloned()
});
let entry = entry.unwrap();
let paths = entry.folder_paths.paths();
assert_eq!(paths.len(), 3);
assert!(paths.contains(&PathBuf::from("/restored/worktree-a")));
assert!(paths.contains(&PathBuf::from("/restored/worktree-b")));
assert!(paths.contains(&PathBuf::from("/other/unrelated")));
}

#[gpui::test]
async fn test_complete_worktree_restore_preserves_unmatched_paths(cx: &mut TestAppContext) {
init_test(cx);
let store = cx.update(|cx| ThreadMetadataStore::global(cx));

let original_paths =
PathList::new(&[Path::new("/projects/worktree-a"), Path::new("/other/path")]);
let meta = make_metadata("session-partial", "Partial", Utc::now(), original_paths);

store.update(cx, |store, cx| {
store.save_manually(meta, cx);
});

let replacements = vec![
(
PathBuf::from("/projects/worktree-a"),
PathBuf::from("/new/worktree-a"),
),
(
PathBuf::from("/nonexistent/path"),
PathBuf::from("/should/not/appear"),
),
];

store.update(cx, |store, cx| {
store.complete_worktree_restore(
&acp::SessionId::new("session-partial"),
&replacements,
cx,
);
});

let entry = store.read_with(cx, |store, _cx| {
store
.entry(&acp::SessionId::new("session-partial"))
.cloned()
});
let entry = entry.unwrap();
let paths = entry.folder_paths.paths();
assert_eq!(paths.len(), 2);
assert!(paths.contains(&PathBuf::from("/new/worktree-a")));
assert!(paths.contains(&PathBuf::from("/other/path")));
assert!(!paths.contains(&PathBuf::from("/should/not/appear")));
}

#[gpui::test]
async fn test_update_restored_worktree_paths_multiple(cx: &mut TestAppContext) {
init_test(cx);
Expand Down
Loading
Loading