fake_git_repo.rs

  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}