Rearrange git tests in worktree
Add support for renaming work directories
This commit is contained in:
parent
623a177fe6
commit
c795c9b844
3 changed files with 503 additions and 412 deletions
|
@ -42,7 +42,6 @@ use language::{
|
||||||
};
|
};
|
||||||
use project::ProjectPath;
|
use project::ProjectPath;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use text::Point;
|
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
cmp::{self, Ordering},
|
cmp::{self, Ordering},
|
||||||
|
@ -51,6 +50,7 @@ use std::{
|
||||||
ops::Range,
|
ops::Range,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
use text::Point;
|
||||||
use workspace::{item::Item, GitGutterSetting, WorkspaceSettings};
|
use workspace::{item::Item, GitGutterSetting, WorkspaceSettings};
|
||||||
|
|
||||||
enum FoldMarkers {}
|
enum FoldMarkers {}
|
||||||
|
@ -1059,8 +1059,10 @@ impl EditorElement {
|
||||||
.buffer_snapshot
|
.buffer_snapshot
|
||||||
.git_diff_hunks_in_range(0..(max_row.floor() as u32), false)
|
.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 start_display = Point::new(hunk.buffer_range.start, 0)
|
||||||
let end_display = Point::new(hunk.buffer_range.end, 0).to_display_point(&layout.position_map.snapshot.display_snapshot);
|
.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 start_y = y_for_row(start_display.row() as f32);
|
||||||
let mut end_y = if hunk.buffer_range.start == hunk.buffer_range.end {
|
let mut end_y = if hunk.buffer_range.start == hunk.buffer_range.end {
|
||||||
y_for_row((end_display.row() + 1) as f32)
|
y_for_row((end_display.row() + 1) as f32)
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
use git::diff::{DiffHunk, DiffHunkStatus};
|
use git::diff::{DiffHunk, DiffHunkStatus};
|
||||||
|
@ -78,10 +77,7 @@ pub fn diff_hunk_to_display(hunk: DiffHunk<u32>, snapshot: &DisplaySnapshot) ->
|
||||||
} else {
|
} else {
|
||||||
let start = hunk_start_point.to_display_point(snapshot).row();
|
let start = hunk_start_point.to_display_point(snapshot).row();
|
||||||
|
|
||||||
let hunk_end_row_inclusive = hunk
|
let hunk_end_row_inclusive = hunk.buffer_range.end.max(hunk.buffer_range.start);
|
||||||
.buffer_range
|
|
||||||
.end
|
|
||||||
.max(hunk.buffer_range.start);
|
|
||||||
let hunk_end_point = Point::new(hunk_end_row_inclusive, 0);
|
let hunk_end_point = Point::new(hunk_end_row_inclusive, 0);
|
||||||
let end = hunk_end_point.to_display_point(snapshot).row();
|
let end = hunk_end_point.to_display_point(snapshot).row();
|
||||||
|
|
||||||
|
|
|
@ -150,13 +150,6 @@ impl RepositoryEntry {
|
||||||
.map(|entry| RepositoryWorkDirectory(entry.path.clone()))
|
.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> {
|
pub fn status_for_path(&self, snapshot: &Snapshot, path: &Path) -> Option<GitFileStatus> {
|
||||||
self.work_directory
|
self.work_directory
|
||||||
.relativize(snapshot, path)
|
.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 {
|
pub fn build_update(&self, other: &Self) -> proto::RepositoryEntry {
|
||||||
let mut updated_statuses: Vec<proto::StatusEntry> = Vec::new();
|
let mut updated_statuses: Vec<proto::StatusEntry> = Vec::new();
|
||||||
let mut removed_statuses: Vec<String> = Vec::new();
|
let mut removed_statuses: Vec<String> = Vec::new();
|
||||||
|
@ -1638,6 +1639,11 @@ impl Snapshot {
|
||||||
.map(|(path, entry)| (&path.0, entry))
|
.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.
|
/// Get the repository whose work directory contains the given path.
|
||||||
pub fn repository_for_path(&self, path: &Path) -> Option<RepositoryEntry> {
|
pub fn repository_for_path(&self, path: &Path) -> Option<RepositoryEntry> {
|
||||||
let mut max_len = 0;
|
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,
|
/// 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)
|
.any(|component| component.as_os_str() == *DOT_GIT)
|
||||||
{
|
{
|
||||||
let scan_id = snapshot.scan_id;
|
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 = snapshot.repository_for_path(&path)?;
|
||||||
|
|
||||||
let repo_path = repo.work_directory.relativize(&snapshot, &path)?;
|
let repo_path = repo.work_directory.relativize(&snapshot, &path)?;
|
||||||
|
@ -3975,6 +3992,8 @@ mod tests {
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_rescan_with_gitignore(cx: &mut TestAppContext) {
|
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!({
|
let parent_dir = temp_tree(json!({
|
||||||
".gitignore": "ancestor-ignored-file1\nancestor-ignored-file2\n",
|
".gitignore": "ancestor-ignored-file1\nancestor-ignored-file2\n",
|
||||||
"tree": {
|
"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]
|
#[gpui::test]
|
||||||
async fn test_write_file(cx: &mut TestAppContext) {
|
async fn test_write_file(cx: &mut TestAppContext) {
|
||||||
let dir = temp_tree(json!({
|
let dir = temp_tree(json!({
|
||||||
|
@ -5100,4 +4723,474 @@ mod tests {
|
||||||
paths
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue