1use fs::{FakeFs, Fs};
2use gpui::{BackgroundExecutor, TestAppContext};
3use serde_json::json;
4use std::path::{Path, PathBuf};
5use util::path;
6
7#[gpui::test]
8async fn test_fake_worktree_lifecycle(cx: &mut TestAppContext) {
9 let fs = FakeFs::new(cx.executor());
10 fs.insert_tree("/project", json!({".git": {}, "file.txt": "content"}))
11 .await;
12 let repo = fs
13 .open_repo(Path::new("/project/.git"), None)
14 .expect("should open fake repo");
15
16 // Initially only the main worktree exists
17 let worktrees = repo.worktrees().await.unwrap();
18 assert_eq!(worktrees.len(), 1);
19 assert_eq!(worktrees[0].path, PathBuf::from("/project"));
20
21 fs.create_dir("/my-worktrees".as_ref()).await.unwrap();
22 let worktrees_dir = Path::new("/my-worktrees");
23
24 // Create a worktree
25 let worktree_1_dir = worktrees_dir.join("feature-branch");
26 repo.create_worktree(
27 "feature-branch".to_string(),
28 worktree_1_dir.clone(),
29 Some("abc123".to_string()),
30 )
31 .await
32 .unwrap();
33
34 // List worktrees — should have main + one created
35 let worktrees = repo.worktrees().await.unwrap();
36 assert_eq!(worktrees.len(), 2);
37 assert_eq!(worktrees[0].path, PathBuf::from("/project"));
38 assert_eq!(worktrees[1].path, worktree_1_dir);
39 assert_eq!(worktrees[1].ref_name.as_ref(), "refs/heads/feature-branch");
40 assert_eq!(worktrees[1].sha.as_ref(), "abc123");
41
42 // Directory should exist in FakeFs after create
43 assert!(fs.is_dir(&worktrees_dir.join("feature-branch")).await);
44
45 // Create a second worktree (without explicit commit)
46 let worktree_2_dir = worktrees_dir.join("bugfix-branch");
47 repo.create_worktree("bugfix-branch".to_string(), worktree_2_dir.clone(), None)
48 .await
49 .unwrap();
50
51 let worktrees = repo.worktrees().await.unwrap();
52 assert_eq!(worktrees.len(), 3);
53 assert!(fs.is_dir(&worktree_2_dir).await);
54
55 // Rename the first worktree
56 repo.rename_worktree(worktree_1_dir, worktrees_dir.join("renamed-branch"))
57 .await
58 .unwrap();
59
60 let worktrees = repo.worktrees().await.unwrap();
61 assert_eq!(worktrees.len(), 3);
62 assert!(
63 worktrees
64 .iter()
65 .any(|w| w.path == worktrees_dir.join("renamed-branch")),
66 );
67 assert!(
68 worktrees
69 .iter()
70 .all(|w| w.path != worktrees_dir.join("feature-branch")),
71 );
72
73 // Directory should be moved in FakeFs after rename
74 assert!(!fs.is_dir(&worktrees_dir.join("feature-branch")).await);
75 assert!(fs.is_dir(&worktrees_dir.join("renamed-branch")).await);
76
77 // Rename a nonexistent worktree should fail
78 let result = repo
79 .rename_worktree(PathBuf::from("/nonexistent"), PathBuf::from("/somewhere"))
80 .await;
81 assert!(result.is_err());
82
83 // Remove a worktree
84 repo.remove_worktree(worktrees_dir.join("renamed-branch"), false)
85 .await
86 .unwrap();
87
88 let worktrees = repo.worktrees().await.unwrap();
89 assert_eq!(worktrees.len(), 2);
90 assert_eq!(worktrees[0].path, PathBuf::from("/project"));
91 assert_eq!(worktrees[1].path, worktree_2_dir);
92
93 // Directory should be removed from FakeFs after remove
94 assert!(!fs.is_dir(&worktrees_dir.join("renamed-branch")).await);
95
96 // Remove a nonexistent worktree should fail
97 let result = repo
98 .remove_worktree(PathBuf::from("/nonexistent"), false)
99 .await;
100 assert!(result.is_err());
101
102 // Remove the last worktree
103 repo.remove_worktree(worktree_2_dir.clone(), false)
104 .await
105 .unwrap();
106
107 let worktrees = repo.worktrees().await.unwrap();
108 assert_eq!(worktrees.len(), 1);
109 assert_eq!(worktrees[0].path, PathBuf::from("/project"));
110 assert!(!fs.is_dir(&worktree_2_dir).await);
111}
112
113#[gpui::test]
114async fn test_checkpoints(executor: BackgroundExecutor) {
115 let fs = FakeFs::new(executor);
116 fs.insert_tree(
117 path!("/"),
118 json!({
119 "bar": {
120 "baz": "qux"
121 },
122 "foo": {
123 ".git": {},
124 "a": "lorem",
125 "b": "ipsum",
126 },
127 }),
128 )
129 .await;
130 fs.with_git_state(Path::new("/foo/.git"), true, |_git| {})
131 .unwrap();
132 let repository = fs
133 .open_repo(Path::new("/foo/.git"), Some("git".as_ref()))
134 .unwrap();
135
136 let checkpoint_1 = repository.checkpoint().await.unwrap();
137 fs.write(Path::new("/foo/b"), b"IPSUM").await.unwrap();
138 fs.write(Path::new("/foo/c"), b"dolor").await.unwrap();
139 let checkpoint_2 = repository.checkpoint().await.unwrap();
140 let checkpoint_3 = repository.checkpoint().await.unwrap();
141
142 assert!(
143 repository
144 .compare_checkpoints(checkpoint_2.clone(), checkpoint_3.clone())
145 .await
146 .unwrap()
147 );
148 assert!(
149 !repository
150 .compare_checkpoints(checkpoint_1.clone(), checkpoint_2.clone())
151 .await
152 .unwrap()
153 );
154
155 repository.restore_checkpoint(checkpoint_1).await.unwrap();
156 assert_eq!(
157 fs.files_with_contents(Path::new("")),
158 [
159 (Path::new(path!("/bar/baz")).into(), b"qux".into()),
160 (Path::new(path!("/foo/a")).into(), b"lorem".into()),
161 (Path::new(path!("/foo/b")).into(), b"ipsum".into())
162 ]
163 );
164}