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