Rearrange git tests in worktree

Add support for renaming work directories
This commit is contained in:
Mikayla Maki 2023-05-19 16:23:21 -07:00
parent 623a177fe6
commit c795c9b844
No known key found for this signature in database
3 changed files with 503 additions and 412 deletions

View File

@ -42,7 +42,6 @@ use language::{
};
use project::ProjectPath;
use smallvec::SmallVec;
use text::Point;
use std::{
borrow::Cow,
cmp::{self, Ordering},
@ -51,6 +50,7 @@ use std::{
ops::Range,
sync::Arc,
};
use text::Point;
use workspace::{item::Item, GitGutterSetting, WorkspaceSettings};
enum FoldMarkers {}
@ -1059,8 +1059,10 @@ impl EditorElement {
.buffer_snapshot
.git_diff_hunks_in_range(0..(max_row.floor() as u32), false)
{
let start_display = Point::new(hunk.buffer_range.start, 0).to_display_point(&layout.position_map.snapshot.display_snapshot);
let end_display = Point::new(hunk.buffer_range.end, 0).to_display_point(&layout.position_map.snapshot.display_snapshot);
let start_display = Point::new(hunk.buffer_range.start, 0)
.to_display_point(&layout.position_map.snapshot.display_snapshot);
let end_display = Point::new(hunk.buffer_range.end, 0)
.to_display_point(&layout.position_map.snapshot.display_snapshot);
let start_y = y_for_row(start_display.row() as f32);
let mut end_y = if hunk.buffer_range.start == hunk.buffer_range.end {
y_for_row((end_display.row() + 1) as f32)

View File

@ -1,4 +1,3 @@
use std::ops::Range;
use git::diff::{DiffHunk, DiffHunkStatus};
@ -78,10 +77,7 @@ pub fn diff_hunk_to_display(hunk: DiffHunk<u32>, snapshot: &DisplaySnapshot) ->
} else {
let start = hunk_start_point.to_display_point(snapshot).row();
let hunk_end_row_inclusive = hunk
.buffer_range
.end
.max(hunk.buffer_range.start);
let hunk_end_row_inclusive = hunk.buffer_range.end.max(hunk.buffer_range.start);
let hunk_end_point = Point::new(hunk_end_row_inclusive, 0);
let end = hunk_end_point.to_display_point(snapshot).row();

View File

@ -150,13 +150,6 @@ impl RepositoryEntry {
.map(|entry| RepositoryWorkDirectory(entry.path.clone()))
}
pub fn status_for_file(&self, snapshot: &Snapshot, path: &Path) -> Option<GitFileStatus> {
self.work_directory
.relativize(snapshot, path)
.and_then(|repo_path| self.statuses.get(&repo_path))
.cloned()
}
pub fn status_for_path(&self, snapshot: &Snapshot, path: &Path) -> Option<GitFileStatus> {
self.work_directory
.relativize(snapshot, path)
@ -182,6 +175,14 @@ impl RepositoryEntry {
})
}
#[cfg(any(test, feature = "test-support"))]
pub fn status_for_file(&self, snapshot: &Snapshot, path: &Path) -> Option<GitFileStatus> {
self.work_directory
.relativize(snapshot, path)
.and_then(|repo_path| (&self.statuses).get(&repo_path))
.cloned()
}
pub fn build_update(&self, other: &Self) -> proto::RepositoryEntry {
let mut updated_statuses: Vec<proto::StatusEntry> = Vec::new();
let mut removed_statuses: Vec<String> = Vec::new();
@ -1638,6 +1639,11 @@ impl Snapshot {
.map(|(path, entry)| (&path.0, entry))
}
/// Get the repository whose work directory contains the given path.
pub fn repository_for_work_directory(&self, path: &Path) -> Option<RepositoryEntry> {
self.repository_entries.get(&RepositoryWorkDirectory(path.into())).cloned()
}
/// Get the repository whose work directory contains the given path.
pub fn repository_for_path(&self, path: &Path) -> Option<RepositoryEntry> {
let mut max_len = 0;
@ -1653,7 +1659,7 @@ impl Snapshot {
}
}
current_candidate.map(|entry| entry.to_owned())
current_candidate.cloned()
}
/// Given an ordered iterator of entries, returns an iterator of those entries,
@ -3105,6 +3111,17 @@ impl BackgroundScanner {
.any(|component| component.as_os_str() == *DOT_GIT)
{
let scan_id = snapshot.scan_id;
if let Some(repository) = snapshot.repository_for_work_directory(path) {
let entry = repository.work_directory.0;
snapshot.git_repositories.remove(&entry);
snapshot
.snapshot
.repository_entries
.remove(&RepositoryWorkDirectory(path.into()));
return Some(());
}
let repo = snapshot.repository_for_path(&path)?;
let repo_path = repo.work_directory.relativize(&snapshot, &path)?;
@ -3975,6 +3992,8 @@ mod tests {
#[gpui::test]
async fn test_rescan_with_gitignore(cx: &mut TestAppContext) {
// .gitignores are handled explicitly by Zed and do not use the git
// machinery that the git_tests module checks
let parent_dir = temp_tree(json!({
".gitignore": "ancestor-ignored-file1\nancestor-ignored-file2\n",
"tree": {
@ -4052,402 +4071,6 @@ mod tests {
});
}
#[gpui::test]
async fn test_git_repository_for_path(cx: &mut TestAppContext) {
let root = temp_tree(json!({
"c.txt": "",
"dir1": {
".git": {},
"deps": {
"dep1": {
".git": {},
"src": {
"a.txt": ""
}
}
},
"src": {
"b.txt": ""
}
},
}));
let http_client = FakeHttpClient::with_404_response();
let client = cx.read(|cx| Client::new(http_client, cx));
let tree = Worktree::local(
client,
root.path(),
true,
Arc::new(RealFs),
Default::default(),
&mut cx.to_async(),
)
.await
.unwrap();
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
.await;
tree.flush_fs_events(cx).await;
tree.read_with(cx, |tree, _cx| {
let tree = tree.as_local().unwrap();
assert!(tree.repository_for_path("c.txt".as_ref()).is_none());
let entry = tree.repository_for_path("dir1/src/b.txt".as_ref()).unwrap();
assert_eq!(
entry
.work_directory(tree)
.map(|directory| directory.as_ref().to_owned()),
Some(Path::new("dir1").to_owned())
);
let entry = tree
.repository_for_path("dir1/deps/dep1/src/a.txt".as_ref())
.unwrap();
assert_eq!(
entry
.work_directory(tree)
.map(|directory| directory.as_ref().to_owned()),
Some(Path::new("dir1/deps/dep1").to_owned())
);
let entries = tree.files(false, 0);
let paths_with_repos = tree
.entries_with_repositories(entries)
.map(|(entry, repo)| {
(
entry.path.as_ref(),
repo.and_then(|repo| {
repo.work_directory(&tree)
.map(|work_directory| work_directory.0.to_path_buf())
}),
)
})
.collect::<Vec<_>>();
assert_eq!(
paths_with_repos,
&[
(Path::new("c.txt"), None),
(
Path::new("dir1/deps/dep1/src/a.txt"),
Some(Path::new("dir1/deps/dep1").into())
),
(Path::new("dir1/src/b.txt"), Some(Path::new("dir1").into())),
]
);
});
let repo_update_events = Arc::new(Mutex::new(vec![]));
tree.update(cx, |_, cx| {
let repo_update_events = repo_update_events.clone();
cx.subscribe(&tree, move |_, _, event, _| {
if let Event::UpdatedGitRepositories(update) = event {
repo_update_events.lock().push(update.clone());
}
})
.detach();
});
std::fs::write(root.path().join("dir1/.git/random_new_file"), "hello").unwrap();
tree.flush_fs_events(cx).await;
assert_eq!(
repo_update_events.lock()[0]
.keys()
.cloned()
.collect::<Vec<Arc<Path>>>(),
vec![Path::new("dir1").into()]
);
std::fs::remove_dir_all(root.path().join("dir1/.git")).unwrap();
tree.flush_fs_events(cx).await;
tree.read_with(cx, |tree, _cx| {
let tree = tree.as_local().unwrap();
assert!(tree
.repository_for_path("dir1/src/b.txt".as_ref())
.is_none());
});
}
#[gpui::test]
async fn test_git_status(cx: &mut TestAppContext) {
#[track_caller]
fn git_init(path: &Path) -> git2::Repository {
git2::Repository::init(path).expect("Failed to initialize git repository")
}
#[track_caller]
fn git_add(path: &Path, repo: &git2::Repository) {
let mut index = repo.index().expect("Failed to get index");
index.add_path(path).expect("Failed to add a.txt");
index.write().expect("Failed to write index");
}
#[track_caller]
fn git_remove_index(path: &Path, repo: &git2::Repository) {
let mut index = repo.index().expect("Failed to get index");
index.remove_path(path).expect("Failed to add a.txt");
index.write().expect("Failed to write index");
}
#[track_caller]
fn git_commit(msg: &'static str, repo: &git2::Repository) {
use git2::Signature;
let signature = Signature::now("test", "test@zed.dev").unwrap();
let oid = repo.index().unwrap().write_tree().unwrap();
let tree = repo.find_tree(oid).unwrap();
if let Some(head) = repo.head().ok() {
let parent_obj = head.peel(git2::ObjectType::Commit).unwrap();
let parent_commit = parent_obj.as_commit().unwrap();
repo.commit(
Some("HEAD"),
&signature,
&signature,
msg,
&tree,
&[parent_commit],
)
.expect("Failed to commit with parent");
} else {
repo.commit(Some("HEAD"), &signature, &signature, msg, &tree, &[])
.expect("Failed to commit");
}
}
#[track_caller]
fn git_stash(repo: &mut git2::Repository) {
use git2::Signature;
let signature = Signature::now("test", "test@zed.dev").unwrap();
repo.stash_save(&signature, "N/A", None)
.expect("Failed to stash");
}
#[track_caller]
fn git_reset(offset: usize, repo: &git2::Repository) {
let head = repo.head().expect("Couldn't get repo head");
let object = head.peel(git2::ObjectType::Commit).unwrap();
let commit = object.as_commit().unwrap();
let new_head = commit
.parents()
.inspect(|parnet| {
parnet.message();
})
.skip(offset)
.next()
.expect("Not enough history");
repo.reset(&new_head.as_object(), git2::ResetType::Soft, None)
.expect("Could not reset");
}
#[allow(dead_code)]
#[track_caller]
fn git_status(repo: &git2::Repository) -> HashMap<String, git2::Status> {
repo.statuses(None)
.unwrap()
.iter()
.map(|status| (status.path().unwrap().to_string(), status.status()))
.collect()
}
const IGNORE_RULE: &'static str = "**/target";
let root = temp_tree(json!({
"project": {
"a.txt": "a",
"b.txt": "bb",
"c": {
"d": {
"e.txt": "eee"
}
},
"f.txt": "ffff",
"target": {
"build_file": "???"
},
".gitignore": IGNORE_RULE
},
}));
let http_client = FakeHttpClient::with_404_response();
let client = cx.read(|cx| Client::new(http_client, cx));
let tree = Worktree::local(
client,
root.path(),
true,
Arc::new(RealFs),
Default::default(),
&mut cx.to_async(),
)
.await
.unwrap();
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
.await;
const A_TXT: &'static str = "a.txt";
const B_TXT: &'static str = "b.txt";
const E_TXT: &'static str = "c/d/e.txt";
const F_TXT: &'static str = "f.txt";
const DOTGITIGNORE: &'static str = ".gitignore";
const BUILD_FILE: &'static str = "target/build_file";
let work_dir = root.path().join("project");
let mut repo = git_init(work_dir.as_path());
repo.add_ignore_rule(IGNORE_RULE).unwrap();
git_add(Path::new(A_TXT), &repo);
git_add(Path::new(E_TXT), &repo);
git_add(Path::new(DOTGITIGNORE), &repo);
git_commit("Initial commit", &repo);
std::fs::write(work_dir.join(A_TXT), "aa").unwrap();
tree.flush_fs_events(cx).await;
// Check that the right git state is observed on startup
tree.read_with(cx, |tree, _cx| {
let snapshot = tree.snapshot();
assert_eq!(snapshot.repository_entries.iter().count(), 1);
let (dir, repo) = snapshot.repository_entries.iter().next().unwrap();
assert_eq!(dir.0.as_ref(), Path::new("project"));
assert_eq!(repo.statuses.iter().count(), 3);
assert_eq!(
repo.statuses.get(&Path::new(A_TXT).into()),
Some(&GitFileStatus::Modified)
);
assert_eq!(
repo.statuses.get(&Path::new(B_TXT).into()),
Some(&GitFileStatus::Added)
);
assert_eq!(
repo.statuses.get(&Path::new(F_TXT).into()),
Some(&GitFileStatus::Added)
);
});
git_add(Path::new(A_TXT), &repo);
git_add(Path::new(B_TXT), &repo);
git_commit("Committing modified and added", &repo);
tree.flush_fs_events(cx).await;
// Check that repo only changes are tracked
tree.read_with(cx, |tree, _cx| {
let snapshot = tree.snapshot();
let (_, repo) = snapshot.repository_entries.iter().next().unwrap();
assert_eq!(repo.statuses.iter().count(), 1);
assert_eq!(
repo.statuses.get(&Path::new(F_TXT).into()),
Some(&GitFileStatus::Added)
);
});
git_reset(0, &repo);
git_remove_index(Path::new(B_TXT), &repo);
git_stash(&mut repo);
std::fs::write(work_dir.join(E_TXT), "eeee").unwrap();
std::fs::write(work_dir.join(BUILD_FILE), "this should be ignored").unwrap();
tree.flush_fs_events(cx).await;
// Check that more complex repo changes are tracked
tree.read_with(cx, |tree, _cx| {
let snapshot = tree.snapshot();
let (_, repo) = snapshot.repository_entries.iter().next().unwrap();
assert_eq!(repo.statuses.iter().count(), 3);
assert_eq!(repo.statuses.get(&Path::new(A_TXT).into()), None);
assert_eq!(
repo.statuses.get(&Path::new(B_TXT).into()),
Some(&GitFileStatus::Added)
);
assert_eq!(
repo.statuses.get(&Path::new(E_TXT).into()),
Some(&GitFileStatus::Modified)
);
assert_eq!(
repo.statuses.get(&Path::new(F_TXT).into()),
Some(&GitFileStatus::Added)
);
});
std::fs::remove_file(work_dir.join(B_TXT)).unwrap();
std::fs::remove_dir_all(work_dir.join("c")).unwrap();
std::fs::write(
work_dir.join(DOTGITIGNORE),
[IGNORE_RULE, "f.txt"].join("\n"),
)
.unwrap();
git_add(Path::new(DOTGITIGNORE), &repo);
git_commit("Committing modified git ignore", &repo);
tree.flush_fs_events(cx).await;
// Check that non-repo behavior is tracked
tree.read_with(cx, |tree, _cx| {
let snapshot = tree.snapshot();
let (_, repo) = snapshot.repository_entries.iter().next().unwrap();
assert_eq!(repo.statuses.iter().count(), 0);
});
let mut renamed_dir_name = "first_directory/second_directory";
const RENAMED_FILE: &'static str = "rf.txt";
std::fs::create_dir_all(work_dir.join(renamed_dir_name)).unwrap();
std::fs::write(
work_dir.join(renamed_dir_name).join(RENAMED_FILE),
"new-contents",
)
.unwrap();
tree.flush_fs_events(cx).await;
tree.read_with(cx, |tree, _cx| {
let snapshot = tree.snapshot();
let (_, repo) = snapshot.repository_entries.iter().next().unwrap();
assert_eq!(repo.statuses.iter().count(), 1);
assert_eq!(
repo.statuses
.get(&Path::new(renamed_dir_name).join(RENAMED_FILE).into()),
Some(&GitFileStatus::Added)
);
});
renamed_dir_name = "new_first_directory/second_directory";
std::fs::rename(
work_dir.join("first_directory"),
work_dir.join("new_first_directory"),
)
.unwrap();
tree.flush_fs_events(cx).await;
tree.read_with(cx, |tree, _cx| {
let snapshot = tree.snapshot();
let (_, repo) = snapshot.repository_entries.iter().next().unwrap();
assert_eq!(repo.statuses.iter().count(), 1);
assert_eq!(
repo.statuses
.get(&Path::new(renamed_dir_name).join(RENAMED_FILE).into()),
Some(&GitFileStatus::Added)
);
});
}
#[gpui::test]
async fn test_write_file(cx: &mut TestAppContext) {
let dir = temp_tree(json!({
@ -5100,4 +4723,474 @@ mod tests {
paths
}
}
mod git_tests {
use super::*;
use pretty_assertions::assert_eq;
#[gpui::test]
async fn test_rename_work_directory(cx: &mut TestAppContext) {
let root = temp_tree(json!({
"projects": {
"project1": {
"a": "",
"b": "",
}
},
}));
let root_path = root.path();
let http_client = FakeHttpClient::with_404_response();
let client = cx.read(|cx| Client::new(http_client, cx));
let tree = Worktree::local(
client,
root_path,
true,
Arc::new(RealFs),
Default::default(),
&mut cx.to_async(),
)
.await
.unwrap();
let repo = git_init(&root_path.join("projects/project1"));
git_add("a", &repo);
git_commit("init", &repo);
std::fs::write(root_path.join("projects/project1/a"), "aa").ok();
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
.await;
tree.flush_fs_events(cx).await;
cx.read(|cx| {
let tree = tree.read(cx);
let (work_dir, repo) = tree.repositories().next().unwrap();
assert_eq!(work_dir.as_ref(), Path::new("projects/project1"));
assert_eq!(
repo.status_for_file(tree, Path::new("projects/project1/a")),
Some(GitFileStatus::Modified)
);
assert_eq!(
repo.status_for_file(tree, Path::new("projects/project1/b")),
Some(GitFileStatus::Added)
);
});
dbg!("RENAMING");
std::fs::rename(
root_path.join("projects/project1"),
root_path.join("projects/project2"),
)
.ok();
tree.flush_fs_events(cx).await;
cx.read(|cx| {
let tree = tree.read(cx);
let (work_dir, repo) = tree.repositories().next().unwrap();
assert_eq!(work_dir.as_ref(), Path::new("projects/project2"));
assert_eq!(
repo.status_for_file(tree, Path::new("projects/project2/a")),
Some(GitFileStatus::Modified)
);
assert_eq!(
repo.status_for_file(tree, Path::new("projects/project2/b")),
Some(GitFileStatus::Added)
);
});
}
#[gpui::test]
async fn test_git_repository_for_path(cx: &mut TestAppContext) {
let root = temp_tree(json!({
"c.txt": "",
"dir1": {
".git": {},
"deps": {
"dep1": {
".git": {},
"src": {
"a.txt": ""
}
}
},
"src": {
"b.txt": ""
}
},
}));
let http_client = FakeHttpClient::with_404_response();
let client = cx.read(|cx| Client::new(http_client, cx));
let tree = Worktree::local(
client,
root.path(),
true,
Arc::new(RealFs),
Default::default(),
&mut cx.to_async(),
)
.await
.unwrap();
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
.await;
tree.flush_fs_events(cx).await;
tree.read_with(cx, |tree, _cx| {
let tree = tree.as_local().unwrap();
assert!(tree.repository_for_path("c.txt".as_ref()).is_none());
let entry = tree.repository_for_path("dir1/src/b.txt".as_ref()).unwrap();
assert_eq!(
entry
.work_directory(tree)
.map(|directory| directory.as_ref().to_owned()),
Some(Path::new("dir1").to_owned())
);
let entry = tree.repository_for_path("dir1/deps/dep1/src/a.txt".as_ref()).unwrap();
assert_eq!(
entry
.work_directory(tree)
.map(|directory| directory.as_ref().to_owned()),
Some(Path::new("dir1/deps/dep1").to_owned())
);
let entries = tree.files(false, 0);
let paths_with_repos = tree
.entries_with_repositories(entries)
.map(|(entry, repo)| {
(
entry.path.as_ref(),
repo.and_then(|repo| {
repo.work_directory(&tree)
.map(|work_directory| work_directory.0.to_path_buf())
}),
)
})
.collect::<Vec<_>>();
assert_eq!(
paths_with_repos,
&[
(Path::new("c.txt"), None),
(
Path::new("dir1/deps/dep1/src/a.txt"),
Some(Path::new("dir1/deps/dep1").into())
),
(Path::new("dir1/src/b.txt"), Some(Path::new("dir1").into())),
]
);
});
let repo_update_events = Arc::new(Mutex::new(vec![]));
tree.update(cx, |_, cx| {
let repo_update_events = repo_update_events.clone();
cx.subscribe(&tree, move |_, _, event, _| {
if let Event::UpdatedGitRepositories(update) = event {
repo_update_events.lock().push(update.clone());
}
})
.detach();
});
std::fs::write(root.path().join("dir1/.git/random_new_file"), "hello").unwrap();
tree.flush_fs_events(cx).await;
assert_eq!(
repo_update_events.lock()[0]
.keys()
.cloned()
.collect::<Vec<Arc<Path>>>(),
vec![Path::new("dir1").into()]
);
std::fs::remove_dir_all(root.path().join("dir1/.git")).unwrap();
tree.flush_fs_events(cx).await;
tree.read_with(cx, |tree, _cx| {
let tree = tree.as_local().unwrap();
assert!(tree.repository_for_path("dir1/src/b.txt".as_ref()).is_none());
});
}
#[gpui::test]
async fn test_git_status(cx: &mut TestAppContext) {
const IGNORE_RULE: &'static str = "**/target";
let root = temp_tree(json!({
"project": {
"a.txt": "a",
"b.txt": "bb",
"c": {
"d": {
"e.txt": "eee"
}
},
"f.txt": "ffff",
"target": {
"build_file": "???"
},
".gitignore": IGNORE_RULE
},
}));
let http_client = FakeHttpClient::with_404_response();
let client = cx.read(|cx| Client::new(http_client, cx));
let tree = Worktree::local(
client,
root.path(),
true,
Arc::new(RealFs),
Default::default(),
&mut cx.to_async(),
)
.await
.unwrap();
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
.await;
const A_TXT: &'static str = "a.txt";
const B_TXT: &'static str = "b.txt";
const E_TXT: &'static str = "c/d/e.txt";
const F_TXT: &'static str = "f.txt";
const DOTGITIGNORE: &'static str = ".gitignore";
const BUILD_FILE: &'static str = "target/build_file";
let work_dir = root.path().join("project");
let mut repo = git_init(work_dir.as_path());
repo.add_ignore_rule(IGNORE_RULE).unwrap();
git_add(Path::new(A_TXT), &repo);
git_add(Path::new(E_TXT), &repo);
git_add(Path::new(DOTGITIGNORE), &repo);
git_commit("Initial commit", &repo);
std::fs::write(work_dir.join(A_TXT), "aa").unwrap();
tree.flush_fs_events(cx).await;
// Check that the right git state is observed on startup
tree.read_with(cx, |tree, _cx| {
let snapshot = tree.snapshot();
assert_eq!(snapshot.repository_entries.iter().count(), 1);
let (dir, repo) = snapshot.repository_entries.iter().next().unwrap();
assert_eq!(dir.0.as_ref(), Path::new("project"));
assert_eq!(repo.statuses.iter().count(), 3);
assert_eq!(
repo.statuses.get(&Path::new(A_TXT).into()),
Some(&GitFileStatus::Modified)
);
assert_eq!(
repo.statuses.get(&Path::new(B_TXT).into()),
Some(&GitFileStatus::Added)
);
assert_eq!(
repo.statuses.get(&Path::new(F_TXT).into()),
Some(&GitFileStatus::Added)
);
});
git_add(Path::new(A_TXT), &repo);
git_add(Path::new(B_TXT), &repo);
git_commit("Committing modified and added", &repo);
tree.flush_fs_events(cx).await;
// Check that repo only changes are tracked
tree.read_with(cx, |tree, _cx| {
let snapshot = tree.snapshot();
let (_, repo) = snapshot.repository_entries.iter().next().unwrap();
assert_eq!(repo.statuses.iter().count(), 1);
assert_eq!(
repo.statuses.get(&Path::new(F_TXT).into()),
Some(&GitFileStatus::Added)
);
});
git_reset(0, &repo);
git_remove_index(Path::new(B_TXT), &repo);
git_stash(&mut repo);
std::fs::write(work_dir.join(E_TXT), "eeee").unwrap();
std::fs::write(work_dir.join(BUILD_FILE), "this should be ignored").unwrap();
tree.flush_fs_events(cx).await;
// Check that more complex repo changes are tracked
tree.read_with(cx, |tree, _cx| {
let snapshot = tree.snapshot();
let (_, repo) = snapshot.repository_entries.iter().next().unwrap();
assert_eq!(repo.statuses.iter().count(), 3);
assert_eq!(repo.statuses.get(&Path::new(A_TXT).into()), None);
assert_eq!(
repo.statuses.get(&Path::new(B_TXT).into()),
Some(&GitFileStatus::Added)
);
assert_eq!(
repo.statuses.get(&Path::new(E_TXT).into()),
Some(&GitFileStatus::Modified)
);
assert_eq!(
repo.statuses.get(&Path::new(F_TXT).into()),
Some(&GitFileStatus::Added)
);
});
std::fs::remove_file(work_dir.join(B_TXT)).unwrap();
std::fs::remove_dir_all(work_dir.join("c")).unwrap();
std::fs::write(
work_dir.join(DOTGITIGNORE),
[IGNORE_RULE, "f.txt"].join("\n"),
)
.unwrap();
git_add(Path::new(DOTGITIGNORE), &repo);
git_commit("Committing modified git ignore", &repo);
tree.flush_fs_events(cx).await;
// Check that non-repo behavior is tracked
tree.read_with(cx, |tree, _cx| {
let snapshot = tree.snapshot();
let (_, repo) = snapshot.repository_entries.iter().next().unwrap();
assert_eq!(repo.statuses.iter().count(), 0);
});
let mut renamed_dir_name = "first_directory/second_directory";
const RENAMED_FILE: &'static str = "rf.txt";
std::fs::create_dir_all(work_dir.join(renamed_dir_name)).unwrap();
std::fs::write(
work_dir.join(renamed_dir_name).join(RENAMED_FILE),
"new-contents",
)
.unwrap();
tree.flush_fs_events(cx).await;
tree.read_with(cx, |tree, _cx| {
let snapshot = tree.snapshot();
let (_, repo) = snapshot.repository_entries.iter().next().unwrap();
assert_eq!(repo.statuses.iter().count(), 1);
assert_eq!(
repo.statuses
.get(&Path::new(renamed_dir_name).join(RENAMED_FILE).into()),
Some(&GitFileStatus::Added)
);
});
renamed_dir_name = "new_first_directory/second_directory";
std::fs::rename(
work_dir.join("first_directory"),
work_dir.join("new_first_directory"),
)
.unwrap();
tree.flush_fs_events(cx).await;
tree.read_with(cx, |tree, _cx| {
let snapshot = tree.snapshot();
let (_, repo) = snapshot.repository_entries.iter().next().unwrap();
assert_eq!(repo.statuses.iter().count(), 1);
assert_eq!(
repo.statuses
.get(&Path::new(renamed_dir_name).join(RENAMED_FILE).into()),
Some(&GitFileStatus::Added)
);
});
}
#[track_caller]
fn git_init(path: &Path) -> git2::Repository {
git2::Repository::init(path).expect("Failed to initialize git repository")
}
#[track_caller]
fn git_add<P: AsRef<Path>>(path: P, repo: &git2::Repository) {
let path = path.as_ref();
let mut index = repo.index().expect("Failed to get index");
index.add_path(path).expect("Failed to add a.txt");
index.write().expect("Failed to write index");
}
#[track_caller]
fn git_remove_index(path: &Path, repo: &git2::Repository) {
let mut index = repo.index().expect("Failed to get index");
index.remove_path(path).expect("Failed to add a.txt");
index.write().expect("Failed to write index");
}
#[track_caller]
fn git_commit(msg: &'static str, repo: &git2::Repository) {
use git2::Signature;
let signature = Signature::now("test", "test@zed.dev").unwrap();
let oid = repo.index().unwrap().write_tree().unwrap();
let tree = repo.find_tree(oid).unwrap();
if let Some(head) = repo.head().ok() {
let parent_obj = head.peel(git2::ObjectType::Commit).unwrap();
let parent_commit = parent_obj.as_commit().unwrap();
repo.commit(
Some("HEAD"),
&signature,
&signature,
msg,
&tree,
&[parent_commit],
)
.expect("Failed to commit with parent");
} else {
repo.commit(Some("HEAD"), &signature, &signature, msg, &tree, &[])
.expect("Failed to commit");
}
}
#[track_caller]
fn git_stash(repo: &mut git2::Repository) {
use git2::Signature;
let signature = Signature::now("test", "test@zed.dev").unwrap();
repo.stash_save(&signature, "N/A", None)
.expect("Failed to stash");
}
#[track_caller]
fn git_reset(offset: usize, repo: &git2::Repository) {
let head = repo.head().expect("Couldn't get repo head");
let object = head.peel(git2::ObjectType::Commit).unwrap();
let commit = object.as_commit().unwrap();
let new_head = commit
.parents()
.inspect(|parnet| {
parnet.message();
})
.skip(offset)
.next()
.expect("Not enough history");
repo.reset(&new_head.as_object(), git2::ResetType::Soft, None)
.expect("Could not reset");
}
#[allow(dead_code)]
#[track_caller]
fn git_status(repo: &git2::Repository) -> HashMap<String, git2::Status> {
repo.statuses(None)
.unwrap()
.iter()
.map(|status| (status.path().unwrap().to_string(), status.status()))
.collect()
}
}
}