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!(
40 worktrees[1].ref_name,
41 Some("refs/heads/feature-branch".into())
42 );
43 assert_eq!(worktrees[1].sha.as_ref(), "abc123");
44
45 // Directory should exist in FakeFs after create
46 assert!(fs.is_dir(&worktrees_dir.join("feature-branch")).await);
47
48 // Create a second worktree (without explicit commit)
49 let worktree_2_dir = worktrees_dir.join("bugfix-branch");
50 repo.create_worktree("bugfix-branch".to_string(), worktree_2_dir.clone(), None)
51 .await
52 .unwrap();
53
54 let worktrees = repo.worktrees().await.unwrap();
55 assert_eq!(worktrees.len(), 3);
56 assert!(fs.is_dir(&worktree_2_dir).await);
57
58 // Rename the first worktree
59 repo.rename_worktree(worktree_1_dir, worktrees_dir.join("renamed-branch"))
60 .await
61 .unwrap();
62
63 let worktrees = repo.worktrees().await.unwrap();
64 assert_eq!(worktrees.len(), 3);
65 assert!(
66 worktrees
67 .iter()
68 .any(|w| w.path == worktrees_dir.join("renamed-branch")),
69 );
70 assert!(
71 worktrees
72 .iter()
73 .all(|w| w.path != worktrees_dir.join("feature-branch")),
74 );
75
76 // Directory should be moved in FakeFs after rename
77 assert!(!fs.is_dir(&worktrees_dir.join("feature-branch")).await);
78 assert!(fs.is_dir(&worktrees_dir.join("renamed-branch")).await);
79
80 // Rename a nonexistent worktree should fail
81 let result = repo
82 .rename_worktree(PathBuf::from("/nonexistent"), PathBuf::from("/somewhere"))
83 .await;
84 assert!(result.is_err());
85
86 // Remove a worktree
87 repo.remove_worktree(worktrees_dir.join("renamed-branch"), false)
88 .await
89 .unwrap();
90
91 let worktrees = repo.worktrees().await.unwrap();
92 assert_eq!(worktrees.len(), 2);
93 assert_eq!(worktrees[0].path, PathBuf::from("/project"));
94 assert_eq!(worktrees[1].path, worktree_2_dir);
95
96 // Directory should be removed from FakeFs after remove
97 assert!(!fs.is_dir(&worktrees_dir.join("renamed-branch")).await);
98
99 // Remove a nonexistent worktree should fail
100 let result = repo
101 .remove_worktree(PathBuf::from("/nonexistent"), false)
102 .await;
103 assert!(result.is_err());
104
105 // Remove the last worktree
106 repo.remove_worktree(worktree_2_dir.clone(), false)
107 .await
108 .unwrap();
109
110 let worktrees = repo.worktrees().await.unwrap();
111 assert_eq!(worktrees.len(), 1);
112 assert_eq!(worktrees[0].path, PathBuf::from("/project"));
113 assert!(!fs.is_dir(&worktree_2_dir).await);
114}
115
116#[gpui::test]
117async fn test_checkpoints(executor: BackgroundExecutor) {
118 let fs = FakeFs::new(executor);
119 fs.insert_tree(
120 path!("/"),
121 json!({
122 "bar": {
123 "baz": "qux"
124 },
125 "foo": {
126 ".git": {},
127 "a": "lorem",
128 "b": "ipsum",
129 },
130 }),
131 )
132 .await;
133 fs.with_git_state(Path::new("/foo/.git"), true, |_git| {})
134 .unwrap();
135 let repository = fs
136 .open_repo(Path::new("/foo/.git"), Some("git".as_ref()))
137 .unwrap();
138
139 let checkpoint_1 = repository.checkpoint().await.unwrap();
140 fs.write(Path::new("/foo/b"), b"IPSUM").await.unwrap();
141 fs.write(Path::new("/foo/c"), b"dolor").await.unwrap();
142 let checkpoint_2 = repository.checkpoint().await.unwrap();
143 let checkpoint_3 = repository.checkpoint().await.unwrap();
144
145 assert!(
146 repository
147 .compare_checkpoints(checkpoint_2.clone(), checkpoint_3.clone())
148 .await
149 .unwrap()
150 );
151 assert!(
152 !repository
153 .compare_checkpoints(checkpoint_1.clone(), checkpoint_2.clone())
154 .await
155 .unwrap()
156 );
157
158 repository.restore_checkpoint(checkpoint_1).await.unwrap();
159 assert_eq!(
160 fs.files_with_contents(Path::new("")),
161 [
162 (Path::new(path!("/bar/baz")).into(), b"qux".into()),
163 (Path::new(path!("/foo/a")).into(), b"lorem".into()),
164 (Path::new(path!("/foo/b")).into(), b"ipsum".into())
165 ]
166 );
167}