git: Implement commit creation (#23263)
- [x] Basic implementation - [x] Disable commit buttons when committing is not possible (empty message, no changes) - [x] Upgrade GitSummary to efficiently figure out whether there are any staged changes - [x] Make CommitAll work - [x] Surface errors with toasts - [x] Channel shutdown - [x] Empty commit message or no changes - [x] Failed git operations - [x] Fix added files no longer appearing correctly in the project panel (GitSummary breakage) - [x] Fix handling of commit message Release Notes: - N/A --------- Co-authored-by: Nate <nate@zed.dev>
This commit is contained in:
parent
3767e7e5f0
commit
5da67899b7
10 changed files with 387 additions and 198 deletions
|
@ -231,6 +231,10 @@ impl RepositoryEntry {
|
|||
self.statuses_by_path.summary().item_summary.count
|
||||
}
|
||||
|
||||
pub fn status_summary(&self) -> GitSummary {
|
||||
self.statuses_by_path.summary().item_summary
|
||||
}
|
||||
|
||||
pub fn status_for_path(&self, path: &RepoPath) -> Option<StatusEntry> {
|
||||
self.statuses_by_path
|
||||
.get(&PathKey(path.0.clone()), &())
|
||||
|
|
|
@ -6,7 +6,8 @@ use anyhow::Result;
|
|||
use fs::{FakeFs, Fs, RealFs, RemoveOptions};
|
||||
use git::{
|
||||
status::{
|
||||
FileStatus, GitSummary, StatusCode, TrackedStatus, UnmergedStatus, UnmergedStatusCode,
|
||||
FileStatus, GitSummary, StatusCode, TrackedStatus, TrackedSummary, UnmergedStatus,
|
||||
UnmergedStatusCode,
|
||||
},
|
||||
GITIGNORE,
|
||||
};
|
||||
|
@ -745,7 +746,7 @@ async fn test_rescan_with_gitignore(cx: &mut TestAppContext) {
|
|||
Path::new("/root/tree/.git"),
|
||||
&[(
|
||||
Path::new("tracked-dir/tracked-file2"),
|
||||
FileStatus::worktree(StatusCode::Added),
|
||||
StatusCode::Added.index(),
|
||||
)],
|
||||
);
|
||||
|
||||
|
@ -830,7 +831,7 @@ async fn test_update_gitignore(cx: &mut TestAppContext) {
|
|||
|
||||
fs.set_status_for_repo_via_working_copy_change(
|
||||
Path::new("/root/.git"),
|
||||
&[(Path::new("b.txt"), FileStatus::worktree(StatusCode::Added))],
|
||||
&[(Path::new("b.txt"), StatusCode::Added.index())],
|
||||
);
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
|
@ -1500,10 +1501,7 @@ async fn test_bump_mtime_of_git_repo_workdir(cx: &mut TestAppContext) {
|
|||
// detected.
|
||||
fs.set_status_for_repo_via_git_operation(
|
||||
Path::new("/root/.git"),
|
||||
&[(
|
||||
Path::new("b/c.txt"),
|
||||
FileStatus::worktree(StatusCode::Modified),
|
||||
)],
|
||||
&[(Path::new("b/c.txt"), StatusCode::Modified.index())],
|
||||
);
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
|
@ -2199,7 +2197,7 @@ async fn test_rename_work_directory(cx: &mut TestAppContext) {
|
|||
assert_eq!(repo.path.as_ref(), Path::new("projects/project1"));
|
||||
assert_eq!(
|
||||
tree.status_for_file(Path::new("projects/project1/a")),
|
||||
Some(FileStatus::worktree(StatusCode::Modified)),
|
||||
Some(StatusCode::Modified.worktree()),
|
||||
);
|
||||
assert_eq!(
|
||||
tree.status_for_file(Path::new("projects/project1/b")),
|
||||
|
@ -2220,7 +2218,7 @@ async fn test_rename_work_directory(cx: &mut TestAppContext) {
|
|||
assert_eq!(repo.path.as_ref(), Path::new("projects/project2"));
|
||||
assert_eq!(
|
||||
tree.status_for_file(Path::new("projects/project2/a")),
|
||||
Some(FileStatus::worktree(StatusCode::Modified)),
|
||||
Some(StatusCode::Modified.worktree()),
|
||||
);
|
||||
assert_eq!(
|
||||
tree.status_for_file(Path::new("projects/project2/b")),
|
||||
|
@ -2421,7 +2419,7 @@ async fn test_file_status(cx: &mut TestAppContext) {
|
|||
let snapshot = tree.snapshot();
|
||||
assert_eq!(
|
||||
snapshot.status_for_file(project_path.join(A_TXT)),
|
||||
Some(FileStatus::worktree(StatusCode::Modified)),
|
||||
Some(StatusCode::Modified.worktree()),
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -2463,7 +2461,7 @@ async fn test_file_status(cx: &mut TestAppContext) {
|
|||
);
|
||||
assert_eq!(
|
||||
snapshot.status_for_file(project_path.join(E_TXT)),
|
||||
Some(FileStatus::worktree(StatusCode::Modified)),
|
||||
Some(StatusCode::Modified.worktree()),
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -2575,14 +2573,11 @@ async fn test_git_repository_status(cx: &mut TestAppContext) {
|
|||
|
||||
assert_eq!(entries.len(), 3);
|
||||
assert_eq!(entries[0].repo_path.as_ref(), Path::new("a.txt"));
|
||||
assert_eq!(
|
||||
entries[0].status,
|
||||
FileStatus::worktree(StatusCode::Modified)
|
||||
);
|
||||
assert_eq!(entries[0].status, StatusCode::Modified.worktree());
|
||||
assert_eq!(entries[1].repo_path.as_ref(), Path::new("b.txt"));
|
||||
assert_eq!(entries[1].status, FileStatus::Untracked);
|
||||
assert_eq!(entries[2].repo_path.as_ref(), Path::new("d.txt"));
|
||||
assert_eq!(entries[2].status, FileStatus::worktree(StatusCode::Deleted));
|
||||
assert_eq!(entries[2].status, StatusCode::Deleted.worktree());
|
||||
});
|
||||
|
||||
std::fs::write(work_dir.join("c.txt"), "some changes").unwrap();
|
||||
|
@ -2600,20 +2595,14 @@ async fn test_git_repository_status(cx: &mut TestAppContext) {
|
|||
|
||||
std::assert_eq!(entries.len(), 4, "entries: {entries:?}");
|
||||
assert_eq!(entries[0].repo_path.as_ref(), Path::new("a.txt"));
|
||||
assert_eq!(
|
||||
entries[0].status,
|
||||
FileStatus::worktree(StatusCode::Modified)
|
||||
);
|
||||
assert_eq!(entries[0].status, StatusCode::Modified.worktree());
|
||||
assert_eq!(entries[1].repo_path.as_ref(), Path::new("b.txt"));
|
||||
assert_eq!(entries[1].status, FileStatus::Untracked);
|
||||
// Status updated
|
||||
assert_eq!(entries[2].repo_path.as_ref(), Path::new("c.txt"));
|
||||
assert_eq!(
|
||||
entries[2].status,
|
||||
FileStatus::worktree(StatusCode::Modified)
|
||||
);
|
||||
assert_eq!(entries[2].status, StatusCode::Modified.worktree());
|
||||
assert_eq!(entries[3].repo_path.as_ref(), Path::new("d.txt"));
|
||||
assert_eq!(entries[3].status, FileStatus::worktree(StatusCode::Deleted));
|
||||
assert_eq!(entries[3].status, StatusCode::Deleted.worktree());
|
||||
});
|
||||
|
||||
git_add("a.txt", &repo);
|
||||
|
@ -2646,7 +2635,7 @@ async fn test_git_repository_status(cx: &mut TestAppContext) {
|
|||
&entries
|
||||
);
|
||||
assert_eq!(entries[0].repo_path.as_ref(), Path::new("a.txt"));
|
||||
assert_eq!(entries[0].status, FileStatus::worktree(StatusCode::Deleted));
|
||||
assert_eq!(entries[0].status, StatusCode::Deleted.worktree());
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -2769,11 +2758,8 @@ async fn test_traverse_with_git_status(cx: &mut TestAppContext) {
|
|||
fs.set_status_for_repo_via_git_operation(
|
||||
Path::new("/root/x/.git"),
|
||||
&[
|
||||
(
|
||||
Path::new("x2.txt"),
|
||||
FileStatus::worktree(StatusCode::Modified),
|
||||
),
|
||||
(Path::new("z.txt"), FileStatus::worktree(StatusCode::Added)),
|
||||
(Path::new("x2.txt"), StatusCode::Modified.index()),
|
||||
(Path::new("z.txt"), StatusCode::Added.index()),
|
||||
],
|
||||
);
|
||||
fs.set_status_for_repo_via_git_operation(
|
||||
|
@ -2782,7 +2768,7 @@ async fn test_traverse_with_git_status(cx: &mut TestAppContext) {
|
|||
);
|
||||
fs.set_status_for_repo_via_git_operation(
|
||||
Path::new("/root/z/.git"),
|
||||
&[(Path::new("z2.txt"), FileStatus::worktree(StatusCode::Added))],
|
||||
&[(Path::new("z2.txt"), StatusCode::Added.index())],
|
||||
);
|
||||
|
||||
let tree = Worktree::local(
|
||||
|
@ -2862,14 +2848,8 @@ async fn test_propagate_git_statuses(cx: &mut TestAppContext) {
|
|||
fs.set_status_for_repo_via_git_operation(
|
||||
Path::new("/root/.git"),
|
||||
&[
|
||||
(
|
||||
Path::new("a/b/c1.txt"),
|
||||
FileStatus::worktree(StatusCode::Added),
|
||||
),
|
||||
(
|
||||
Path::new("a/d/e2.txt"),
|
||||
FileStatus::worktree(StatusCode::Modified),
|
||||
),
|
||||
(Path::new("a/b/c1.txt"), StatusCode::Added.index()),
|
||||
(Path::new("a/d/e2.txt"), StatusCode::Modified.index()),
|
||||
(Path::new("g/h2.txt"), CONFLICT),
|
||||
],
|
||||
);
|
||||
|
@ -2971,24 +2951,18 @@ async fn test_propagate_statuses_for_repos_under_project(cx: &mut TestAppContext
|
|||
|
||||
fs.set_status_for_repo_via_git_operation(
|
||||
Path::new("/root/x/.git"),
|
||||
&[(Path::new("x1.txt"), FileStatus::worktree(StatusCode::Added))],
|
||||
&[(Path::new("x1.txt"), StatusCode::Added.index())],
|
||||
);
|
||||
fs.set_status_for_repo_via_git_operation(
|
||||
Path::new("/root/y/.git"),
|
||||
&[
|
||||
(Path::new("y1.txt"), CONFLICT),
|
||||
(
|
||||
Path::new("y2.txt"),
|
||||
FileStatus::worktree(StatusCode::Modified),
|
||||
),
|
||||
(Path::new("y2.txt"), StatusCode::Modified.index()),
|
||||
],
|
||||
);
|
||||
fs.set_status_for_repo_via_git_operation(
|
||||
Path::new("/root/z/.git"),
|
||||
&[(
|
||||
Path::new("z2.txt"),
|
||||
FileStatus::worktree(StatusCode::Modified),
|
||||
)],
|
||||
&[(Path::new("z2.txt"), StatusCode::Modified.index())],
|
||||
);
|
||||
|
||||
let tree = Worktree::local(
|
||||
|
@ -3081,11 +3055,8 @@ async fn test_propagate_statuses_for_nested_repos(cx: &mut TestAppContext) {
|
|||
fs.set_status_for_repo_via_git_operation(
|
||||
Path::new("/root/x/.git"),
|
||||
&[
|
||||
(
|
||||
Path::new("x2.txt"),
|
||||
FileStatus::worktree(StatusCode::Modified),
|
||||
),
|
||||
(Path::new("z.txt"), FileStatus::worktree(StatusCode::Added)),
|
||||
(Path::new("x2.txt"), StatusCode::Modified.index()),
|
||||
(Path::new("z.txt"), StatusCode::Added.index()),
|
||||
],
|
||||
);
|
||||
fs.set_status_for_repo_via_git_operation(
|
||||
|
@ -3095,7 +3066,7 @@ async fn test_propagate_statuses_for_nested_repos(cx: &mut TestAppContext) {
|
|||
|
||||
fs.set_status_for_repo_via_git_operation(
|
||||
Path::new("/root/z/.git"),
|
||||
&[(Path::new("z2.txt"), FileStatus::worktree(StatusCode::Added))],
|
||||
&[(Path::new("z2.txt"), StatusCode::Added.index())],
|
||||
);
|
||||
|
||||
let tree = Worktree::local(
|
||||
|
@ -3227,12 +3198,12 @@ fn check_git_statuses(snapshot: &Snapshot, expected_statuses: &[(&Path, GitSumma
|
|||
}
|
||||
|
||||
const ADDED: GitSummary = GitSummary {
|
||||
added: 1,
|
||||
index: TrackedSummary::ADDED,
|
||||
count: 1,
|
||||
..GitSummary::UNCHANGED
|
||||
};
|
||||
const MODIFIED: GitSummary = GitSummary {
|
||||
modified: 1,
|
||||
index: TrackedSummary::MODIFIED,
|
||||
count: 1,
|
||||
..GitSummary::UNCHANGED
|
||||
};
|
||||
|
@ -3378,15 +3349,15 @@ fn init_test(cx: &mut gpui::TestAppContext) {
|
|||
fn assert_entry_git_state(
|
||||
tree: &Worktree,
|
||||
path: &str,
|
||||
worktree_status: Option<StatusCode>,
|
||||
index_status: Option<StatusCode>,
|
||||
is_ignored: bool,
|
||||
) {
|
||||
let entry = tree.entry_for_path(path).expect("entry {path} not found");
|
||||
let status = tree.status_for_file(Path::new(path));
|
||||
let expected = worktree_status.map(|worktree_status| {
|
||||
let expected = index_status.map(|index_status| {
|
||||
TrackedStatus {
|
||||
worktree_status,
|
||||
index_status: StatusCode::Unmodified,
|
||||
index_status,
|
||||
worktree_status: StatusCode::Unmodified,
|
||||
}
|
||||
.into()
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue