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