Improve when the commit suggestions would show (#26313)
Release Notes: - Git Beta: Fixed a few bugs where the suggested commit text wouldn't show in certain cases, or would update slowly.
This commit is contained in:
parent
e70d0edfac
commit
f6345a6995
4 changed files with 180 additions and 89 deletions
|
@ -54,6 +54,39 @@ impl From<TrackedStatus> for FileStatus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
|
pub enum StageStatus {
|
||||||
|
Staged,
|
||||||
|
Unstaged,
|
||||||
|
PartiallyStaged,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StageStatus {
|
||||||
|
pub fn is_fully_staged(&self) -> bool {
|
||||||
|
matches!(self, StageStatus::Staged)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_fully_unstaged(&self) -> bool {
|
||||||
|
matches!(self, StageStatus::Unstaged)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_staged(&self) -> bool {
|
||||||
|
matches!(self, StageStatus::Staged | StageStatus::PartiallyStaged)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_unstaged(&self) -> bool {
|
||||||
|
matches!(self, StageStatus::Unstaged | StageStatus::PartiallyStaged)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_bool(self) -> Option<bool> {
|
||||||
|
match self {
|
||||||
|
StageStatus::Staged => Some(true),
|
||||||
|
StageStatus::Unstaged => Some(false),
|
||||||
|
StageStatus::PartiallyStaged => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FileStatus {
|
impl FileStatus {
|
||||||
pub const fn worktree(worktree_status: StatusCode) -> Self {
|
pub const fn worktree(worktree_status: StatusCode) -> Self {
|
||||||
FileStatus::Tracked(TrackedStatus {
|
FileStatus::Tracked(TrackedStatus {
|
||||||
|
@ -106,15 +139,15 @@ impl FileStatus {
|
||||||
Ok(status)
|
Ok(status)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_staged(self) -> Option<bool> {
|
pub fn staging(self) -> StageStatus {
|
||||||
match self {
|
match self {
|
||||||
FileStatus::Untracked | FileStatus::Ignored | FileStatus::Unmerged { .. } => {
|
FileStatus::Untracked | FileStatus::Ignored | FileStatus::Unmerged { .. } => {
|
||||||
Some(false)
|
StageStatus::Unstaged
|
||||||
}
|
}
|
||||||
FileStatus::Tracked(tracked) => match (tracked.index_status, tracked.worktree_status) {
|
FileStatus::Tracked(tracked) => match (tracked.index_status, tracked.worktree_status) {
|
||||||
(StatusCode::Unmodified, _) => Some(false),
|
(StatusCode::Unmodified, _) => StageStatus::Unstaged,
|
||||||
(_, StatusCode::Unmodified) => Some(true),
|
(_, StatusCode::Unmodified) => StageStatus::Staged,
|
||||||
_ => None,
|
_ => StageStatus::PartiallyStaged,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ use git::repository::{
|
||||||
Branch, CommitDetails, CommitSummary, DiffType, PushOptions, Remote, RemoteCommandOutput,
|
Branch, CommitDetails, CommitSummary, DiffType, PushOptions, Remote, RemoteCommandOutput,
|
||||||
ResetMode, Upstream, UpstreamTracking, UpstreamTrackingStatus,
|
ResetMode, Upstream, UpstreamTracking, UpstreamTrackingStatus,
|
||||||
};
|
};
|
||||||
|
use git::status::StageStatus;
|
||||||
use git::{repository::RepoPath, status::FileStatus, Commit, ToggleStaged};
|
use git::{repository::RepoPath, status::FileStatus, Commit, ToggleStaged};
|
||||||
use git::{ExpandCommitEditor, RestoreTrackedFiles, StageAll, TrashUntrackedFiles, UnstageAll};
|
use git::{ExpandCommitEditor, RestoreTrackedFiles, StageAll, TrashUntrackedFiles, UnstageAll};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
@ -195,7 +196,7 @@ pub struct GitStatusEntry {
|
||||||
pub(crate) worktree_path: Arc<Path>,
|
pub(crate) worktree_path: Arc<Path>,
|
||||||
pub(crate) abs_path: PathBuf,
|
pub(crate) abs_path: PathBuf,
|
||||||
pub(crate) status: FileStatus,
|
pub(crate) status: FileStatus,
|
||||||
pub(crate) is_staged: Option<bool>,
|
pub(crate) staging: StageStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
@ -209,7 +210,7 @@ enum TargetStatus {
|
||||||
struct PendingOperation {
|
struct PendingOperation {
|
||||||
finished: bool,
|
finished: bool,
|
||||||
target_status: TargetStatus,
|
target_status: TargetStatus,
|
||||||
repo_paths: HashSet<RepoPath>,
|
entries: Vec<GitStatusEntry>,
|
||||||
op_id: usize,
|
op_id: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,6 +227,8 @@ pub struct GitPanel {
|
||||||
add_coauthors: bool,
|
add_coauthors: bool,
|
||||||
generate_commit_message_task: Option<Task<Option<()>>>,
|
generate_commit_message_task: Option<Task<Option<()>>>,
|
||||||
entries: Vec<GitListEntry>,
|
entries: Vec<GitListEntry>,
|
||||||
|
single_staged_entry: Option<GitStatusEntry>,
|
||||||
|
single_tracked_entry: Option<GitStatusEntry>,
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
hide_scrollbar_task: Option<Task<()>>,
|
hide_scrollbar_task: Option<Task<()>>,
|
||||||
|
@ -365,6 +368,8 @@ impl GitPanel {
|
||||||
pending: Vec::new(),
|
pending: Vec::new(),
|
||||||
pending_commit: None,
|
pending_commit: None,
|
||||||
pending_serialization: Task::ready(None),
|
pending_serialization: Task::ready(None),
|
||||||
|
single_staged_entry: None,
|
||||||
|
single_tracked_entry: None,
|
||||||
project,
|
project,
|
||||||
scroll_handle,
|
scroll_handle,
|
||||||
scrollbar_state,
|
scrollbar_state,
|
||||||
|
@ -828,13 +833,13 @@ impl GitPanel {
|
||||||
.repo_path_to_project_path(&entry.repo_path)?;
|
.repo_path_to_project_path(&entry.repo_path)?;
|
||||||
let workspace = self.workspace.clone();
|
let workspace = self.workspace.clone();
|
||||||
|
|
||||||
if entry.status.is_staged() != Some(false) {
|
if entry.status.staging().has_staged() {
|
||||||
self.perform_stage(false, vec![entry.repo_path.clone()], cx);
|
self.change_file_stage(false, vec![entry.clone()], cx);
|
||||||
}
|
}
|
||||||
let filename = path.path.file_name()?.to_string_lossy();
|
let filename = path.path.file_name()?.to_string_lossy();
|
||||||
|
|
||||||
if !entry.status.is_created() {
|
if !entry.status.is_created() {
|
||||||
self.perform_checkout(vec![entry.repo_path.clone()], cx);
|
self.perform_checkout(vec![entry.clone()], cx);
|
||||||
} else {
|
} else {
|
||||||
let prompt = prompt(&format!("Trash {}?", filename), None, window, cx);
|
let prompt = prompt(&format!("Trash {}?", filename), None, window, cx);
|
||||||
cx.spawn_in(window, |_, mut cx| async move {
|
cx.spawn_in(window, |_, mut cx| async move {
|
||||||
|
@ -863,7 +868,7 @@ impl GitPanel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perform_checkout(&mut self, repo_paths: Vec<RepoPath>, cx: &mut Context<Self>) {
|
fn perform_checkout(&mut self, entries: Vec<GitStatusEntry>, cx: &mut Context<Self>) {
|
||||||
let workspace = self.workspace.clone();
|
let workspace = self.workspace.clone();
|
||||||
let Some(active_repository) = self.active_repository.clone() else {
|
let Some(active_repository) = self.active_repository.clone() else {
|
||||||
return;
|
return;
|
||||||
|
@ -873,19 +878,19 @@ impl GitPanel {
|
||||||
self.pending.push(PendingOperation {
|
self.pending.push(PendingOperation {
|
||||||
op_id,
|
op_id,
|
||||||
target_status: TargetStatus::Reverted,
|
target_status: TargetStatus::Reverted,
|
||||||
repo_paths: repo_paths.iter().cloned().collect(),
|
entries: entries.clone(),
|
||||||
finished: false,
|
finished: false,
|
||||||
});
|
});
|
||||||
self.update_visible_entries(cx);
|
self.update_visible_entries(cx);
|
||||||
let task = cx.spawn(|_, mut cx| async move {
|
let task = cx.spawn(|_, mut cx| async move {
|
||||||
let tasks: Vec<_> = workspace.update(&mut cx, |workspace, cx| {
|
let tasks: Vec<_> = workspace.update(&mut cx, |workspace, cx| {
|
||||||
workspace.project().update(cx, |project, cx| {
|
workspace.project().update(cx, |project, cx| {
|
||||||
repo_paths
|
entries
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|repo_path| {
|
.filter_map(|entry| {
|
||||||
let path = active_repository
|
let path = active_repository
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.repo_path_to_project_path(&repo_path)?;
|
.repo_path_to_project_path(&entry.repo_path)?;
|
||||||
Some(project.open_buffer(path, cx))
|
Some(project.open_buffer(path, cx))
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
@ -895,7 +900,15 @@ impl GitPanel {
|
||||||
let buffers = futures::future::join_all(tasks).await;
|
let buffers = futures::future::join_all(tasks).await;
|
||||||
|
|
||||||
active_repository
|
active_repository
|
||||||
.update(&mut cx, |repo, _| repo.checkout_files("HEAD", repo_paths))?
|
.update(&mut cx, |repo, _| {
|
||||||
|
repo.checkout_files(
|
||||||
|
"HEAD",
|
||||||
|
entries
|
||||||
|
.iter()
|
||||||
|
.map(|entries| entries.repo_path.clone())
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
})?
|
||||||
.await??;
|
.await??;
|
||||||
|
|
||||||
let tasks: Vec<_> = cx.update(|cx| {
|
let tasks: Vec<_> = cx.update(|cx| {
|
||||||
|
@ -983,8 +996,7 @@ impl GitPanel {
|
||||||
match prompt.await {
|
match prompt.await {
|
||||||
Ok(RestoreCancel::RestoreTrackedFiles) => {
|
Ok(RestoreCancel::RestoreTrackedFiles) => {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
let repo_paths = entries.into_iter().map(|entry| entry.repo_path).collect();
|
this.perform_checkout(entries, cx);
|
||||||
this.perform_checkout(repo_paths, cx);
|
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
@ -1053,16 +1065,10 @@ impl GitPanel {
|
||||||
})?;
|
})?;
|
||||||
let to_unstage = to_delete
|
let to_unstage = to_delete
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|entry| {
|
.filter(|entry| !entry.status.staging().is_fully_unstaged())
|
||||||
if entry.status.is_staged() != Some(false) {
|
|
||||||
Some(entry.repo_path.clone())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.perform_stage(false, to_unstage, cx)
|
this.change_file_stage(false, to_unstage, cx)
|
||||||
})?;
|
})?;
|
||||||
for task in tasks {
|
for task in tasks {
|
||||||
task.await?;
|
task.await?;
|
||||||
|
@ -1075,25 +1081,25 @@ impl GitPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stage_all(&mut self, _: &StageAll, _window: &mut Window, cx: &mut Context<Self>) {
|
fn stage_all(&mut self, _: &StageAll, _window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let repo_paths = self
|
let entries = self
|
||||||
.entries
|
.entries
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|entry| entry.status_entry())
|
.filter_map(|entry| entry.status_entry())
|
||||||
.filter(|status_entry| status_entry.is_staged != Some(true))
|
.filter(|status_entry| status_entry.staging.has_unstaged())
|
||||||
.map(|status_entry| status_entry.repo_path.clone())
|
.cloned()
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
self.perform_stage(true, repo_paths, cx);
|
self.change_file_stage(true, entries, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unstage_all(&mut self, _: &UnstageAll, _window: &mut Window, cx: &mut Context<Self>) {
|
fn unstage_all(&mut self, _: &UnstageAll, _window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let repo_paths = self
|
let entries = self
|
||||||
.entries
|
.entries
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|entry| entry.status_entry())
|
.filter_map(|entry| entry.status_entry())
|
||||||
.filter(|status_entry| status_entry.is_staged != Some(false))
|
.filter(|status_entry| status_entry.staging.has_staged())
|
||||||
.map(|status_entry| status_entry.repo_path.clone())
|
.cloned()
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
self.perform_stage(false, repo_paths, cx);
|
self.change_file_stage(false, entries, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_staged_for_entry(
|
fn toggle_staged_for_entry(
|
||||||
|
@ -1107,10 +1113,10 @@ impl GitPanel {
|
||||||
};
|
};
|
||||||
let (stage, repo_paths) = match entry {
|
let (stage, repo_paths) = match entry {
|
||||||
GitListEntry::GitStatusEntry(status_entry) => {
|
GitListEntry::GitStatusEntry(status_entry) => {
|
||||||
if status_entry.status.is_staged().unwrap_or(false) {
|
if status_entry.status.staging().is_fully_staged() {
|
||||||
(false, vec![status_entry.repo_path.clone()])
|
(false, vec![status_entry.clone()])
|
||||||
} else {
|
} else {
|
||||||
(true, vec![status_entry.repo_path.clone()])
|
(true, vec![status_entry.clone()])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GitListEntry::Header(section) => {
|
GitListEntry::Header(section) => {
|
||||||
|
@ -1122,18 +1128,23 @@ impl GitPanel {
|
||||||
.filter_map(|entry| entry.status_entry())
|
.filter_map(|entry| entry.status_entry())
|
||||||
.filter(|status_entry| {
|
.filter(|status_entry| {
|
||||||
section.contains(&status_entry, repository)
|
section.contains(&status_entry, repository)
|
||||||
&& status_entry.is_staged != Some(goal_staged_state)
|
&& status_entry.staging.as_bool() != Some(goal_staged_state)
|
||||||
})
|
})
|
||||||
.map(|status_entry| status_entry.repo_path.clone())
|
.map(|status_entry| status_entry.clone())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
(goal_staged_state, entries)
|
(goal_staged_state, entries)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.perform_stage(stage, repo_paths, cx);
|
self.change_file_stage(stage, repo_paths, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perform_stage(&mut self, stage: bool, repo_paths: Vec<RepoPath>, cx: &mut Context<Self>) {
|
fn change_file_stage(
|
||||||
|
&mut self,
|
||||||
|
stage: bool,
|
||||||
|
entries: Vec<GitStatusEntry>,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
let Some(active_repository) = self.active_repository.clone() else {
|
let Some(active_repository) = self.active_repository.clone() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
@ -1145,10 +1156,9 @@ impl GitPanel {
|
||||||
} else {
|
} else {
|
||||||
TargetStatus::Unstaged
|
TargetStatus::Unstaged
|
||||||
},
|
},
|
||||||
repo_paths: repo_paths.iter().cloned().collect(),
|
entries: entries.clone(),
|
||||||
finished: false,
|
finished: false,
|
||||||
});
|
});
|
||||||
let repo_paths = repo_paths.clone();
|
|
||||||
let repository = active_repository.read(cx);
|
let repository = active_repository.read(cx);
|
||||||
self.update_counts(repository);
|
self.update_counts(repository);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
@ -1158,11 +1168,21 @@ impl GitPanel {
|
||||||
let result = cx
|
let result = cx
|
||||||
.update(|cx| {
|
.update(|cx| {
|
||||||
if stage {
|
if stage {
|
||||||
active_repository
|
active_repository.update(cx, |repo, cx| {
|
||||||
.update(cx, |repo, cx| repo.stage_entries(repo_paths.clone(), cx))
|
let repo_paths = entries
|
||||||
|
.iter()
|
||||||
|
.map(|entry| entry.repo_path.clone())
|
||||||
|
.collect();
|
||||||
|
repo.stage_entries(repo_paths, cx)
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
active_repository
|
active_repository.update(cx, |repo, cx| {
|
||||||
.update(cx, |repo, cx| repo.unstage_entries(repo_paths.clone(), cx))
|
let repo_paths = entries
|
||||||
|
.iter()
|
||||||
|
.map(|entry| entry.repo_path.clone())
|
||||||
|
.collect();
|
||||||
|
repo.unstage_entries(repo_paths, cx)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})?
|
})?
|
||||||
.await;
|
.await;
|
||||||
|
@ -1397,21 +1417,13 @@ impl GitPanel {
|
||||||
|
|
||||||
/// Suggests a commit message based on the changed files and their statuses
|
/// Suggests a commit message based on the changed files and their statuses
|
||||||
pub fn suggest_commit_message(&self) -> Option<String> {
|
pub fn suggest_commit_message(&self) -> Option<String> {
|
||||||
if self.total_staged_count() != 1 {
|
let git_status_entry = if let Some(staged_entry) = &self.single_staged_entry {
|
||||||
return None;
|
Some(staged_entry)
|
||||||
}
|
} else if let Some(single_tracked_entry) = &self.single_tracked_entry {
|
||||||
|
Some(single_tracked_entry)
|
||||||
let entry = self
|
} else {
|
||||||
.entries
|
None
|
||||||
.iter()
|
}?;
|
||||||
.find(|entry| match entry.status_entry() {
|
|
||||||
Some(entry) => entry.is_staged.unwrap_or(false),
|
|
||||||
_ => false,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let GitListEntry::GitStatusEntry(git_status_entry) = entry.clone() else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
let action_text = if git_status_entry.status.is_deleted() {
|
let action_text = if git_status_entry.status.is_deleted() {
|
||||||
Some("Delete")
|
Some("Delete")
|
||||||
|
@ -1970,9 +1982,13 @@ impl GitPanel {
|
||||||
|
|
||||||
fn update_visible_entries(&mut self, cx: &mut Context<Self>) {
|
fn update_visible_entries(&mut self, cx: &mut Context<Self>) {
|
||||||
self.entries.clear();
|
self.entries.clear();
|
||||||
|
self.single_staged_entry.take();
|
||||||
|
self.single_staged_entry.take();
|
||||||
let mut changed_entries = Vec::new();
|
let mut changed_entries = Vec::new();
|
||||||
let mut new_entries = Vec::new();
|
let mut new_entries = Vec::new();
|
||||||
let mut conflict_entries = Vec::new();
|
let mut conflict_entries = Vec::new();
|
||||||
|
let mut last_staged = None;
|
||||||
|
let mut staged_count = 0;
|
||||||
|
|
||||||
let Some(repo) = self.active_repository.as_ref() else {
|
let Some(repo) = self.active_repository.as_ref() else {
|
||||||
// Just clear entries if no repository is active.
|
// Just clear entries if no repository is active.
|
||||||
|
@ -1985,12 +2001,15 @@ impl GitPanel {
|
||||||
for entry in repo.status() {
|
for entry in repo.status() {
|
||||||
let is_conflict = repo.has_conflict(&entry.repo_path);
|
let is_conflict = repo.has_conflict(&entry.repo_path);
|
||||||
let is_new = entry.status.is_created();
|
let is_new = entry.status.is_created();
|
||||||
let is_staged = entry.status.is_staged();
|
let staging = entry.status.staging();
|
||||||
|
|
||||||
if self.pending.iter().any(|pending| {
|
if self.pending.iter().any(|pending| {
|
||||||
pending.target_status == TargetStatus::Reverted
|
pending.target_status == TargetStatus::Reverted
|
||||||
&& !pending.finished
|
&& !pending.finished
|
||||||
&& pending.repo_paths.contains(&entry.repo_path)
|
&& pending
|
||||||
|
.entries
|
||||||
|
.iter()
|
||||||
|
.any(|pending| pending.repo_path == entry.repo_path)
|
||||||
}) {
|
}) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -2007,9 +2026,14 @@ impl GitPanel {
|
||||||
worktree_path,
|
worktree_path,
|
||||||
abs_path,
|
abs_path,
|
||||||
status: entry.status,
|
status: entry.status,
|
||||||
is_staged,
|
staging,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if staging.has_staged() {
|
||||||
|
staged_count += 1;
|
||||||
|
last_staged = Some(entry.clone());
|
||||||
|
}
|
||||||
|
|
||||||
if is_conflict {
|
if is_conflict {
|
||||||
conflict_entries.push(entry);
|
conflict_entries.push(entry);
|
||||||
} else if is_new {
|
} else if is_new {
|
||||||
|
@ -2019,6 +2043,40 @@ impl GitPanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut pending_staged_count = 0;
|
||||||
|
let mut last_pending_staged = None;
|
||||||
|
let mut pending_status_for_last_staged = None;
|
||||||
|
for pending in self.pending.iter() {
|
||||||
|
if pending.target_status == TargetStatus::Staged {
|
||||||
|
pending_staged_count += pending.entries.len();
|
||||||
|
last_pending_staged = pending.entries.iter().next().cloned();
|
||||||
|
}
|
||||||
|
if let Some(last_staged) = &last_staged {
|
||||||
|
if pending
|
||||||
|
.entries
|
||||||
|
.iter()
|
||||||
|
.any(|entry| entry.repo_path == last_staged.repo_path)
|
||||||
|
{
|
||||||
|
pending_status_for_last_staged = Some(pending.target_status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if conflict_entries.len() == 0 && staged_count == 1 && pending_staged_count == 0 {
|
||||||
|
match pending_status_for_last_staged {
|
||||||
|
Some(TargetStatus::Staged) | None => {
|
||||||
|
self.single_staged_entry = last_staged;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
} else if conflict_entries.len() == 0 && pending_staged_count == 1 {
|
||||||
|
self.single_staged_entry = last_pending_staged;
|
||||||
|
}
|
||||||
|
|
||||||
|
if conflict_entries.len() == 0 && changed_entries.len() == 1 {
|
||||||
|
self.single_tracked_entry = changed_entries.first().cloned();
|
||||||
|
}
|
||||||
|
|
||||||
if conflict_entries.len() > 0 {
|
if conflict_entries.len() > 0 {
|
||||||
self.entries.push(GitListEntry::Header(GitHeaderEntry {
|
self.entries.push(GitListEntry::Header(GitHeaderEntry {
|
||||||
header: Section::Conflict,
|
header: Section::Conflict,
|
||||||
|
@ -2083,35 +2141,39 @@ impl GitPanel {
|
||||||
};
|
};
|
||||||
if repo.has_conflict(&status_entry.repo_path) {
|
if repo.has_conflict(&status_entry.repo_path) {
|
||||||
self.conflicted_count += 1;
|
self.conflicted_count += 1;
|
||||||
if self.entry_is_staged(status_entry) != Some(false) {
|
if self.entry_staging(status_entry).has_staged() {
|
||||||
self.conflicted_staged_count += 1;
|
self.conflicted_staged_count += 1;
|
||||||
}
|
}
|
||||||
} else if status_entry.status.is_created() {
|
} else if status_entry.status.is_created() {
|
||||||
self.new_count += 1;
|
self.new_count += 1;
|
||||||
if self.entry_is_staged(status_entry) != Some(false) {
|
if self.entry_staging(status_entry).has_staged() {
|
||||||
self.new_staged_count += 1;
|
self.new_staged_count += 1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.tracked_count += 1;
|
self.tracked_count += 1;
|
||||||
if self.entry_is_staged(status_entry) != Some(false) {
|
if self.entry_staging(status_entry).has_staged() {
|
||||||
self.tracked_staged_count += 1;
|
self.tracked_staged_count += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn entry_is_staged(&self, entry: &GitStatusEntry) -> Option<bool> {
|
fn entry_staging(&self, entry: &GitStatusEntry) -> StageStatus {
|
||||||
for pending in self.pending.iter().rev() {
|
for pending in self.pending.iter().rev() {
|
||||||
if pending.repo_paths.contains(&entry.repo_path) {
|
if pending
|
||||||
|
.entries
|
||||||
|
.iter()
|
||||||
|
.any(|pending_entry| pending_entry.repo_path == entry.repo_path)
|
||||||
|
{
|
||||||
match pending.target_status {
|
match pending.target_status {
|
||||||
TargetStatus::Staged => return Some(true),
|
TargetStatus::Staged => return StageStatus::Staged,
|
||||||
TargetStatus::Unstaged => return Some(false),
|
TargetStatus::Unstaged => return StageStatus::Unstaged,
|
||||||
TargetStatus::Reverted => continue,
|
TargetStatus::Reverted => continue,
|
||||||
TargetStatus::Unchanged => continue,
|
TargetStatus::Unchanged => continue,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
entry.is_staged
|
entry.staging
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn has_staged_changes(&self) -> bool {
|
pub(crate) fn has_staged_changes(&self) -> bool {
|
||||||
|
@ -2604,9 +2666,9 @@ impl GitPanel {
|
||||||
let ix = self.entry_by_path(&repo_path)?;
|
let ix = self.entry_by_path(&repo_path)?;
|
||||||
let entry = self.entries.get(ix)?;
|
let entry = self.entries.get(ix)?;
|
||||||
|
|
||||||
let is_staged = self.entry_is_staged(entry.status_entry()?);
|
let entry_staging = self.entry_staging(entry.status_entry()?);
|
||||||
|
|
||||||
let checkbox = Checkbox::new("stage-file", is_staged.into())
|
let checkbox = Checkbox::new("stage-file", entry_staging.as_bool().into())
|
||||||
.disabled(!self.has_write_access(cx))
|
.disabled(!self.has_write_access(cx))
|
||||||
.fill()
|
.fill()
|
||||||
.elevation(ElevationIndex::Surface)
|
.elevation(ElevationIndex::Surface)
|
||||||
|
@ -2752,7 +2814,7 @@ impl GitPanel {
|
||||||
let Some(entry) = self.entries.get(ix).and_then(|e| e.status_entry()) else {
|
let Some(entry) = self.entries.get(ix).and_then(|e| e.status_entry()) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let stage_title = if entry.status.is_staged() == Some(true) {
|
let stage_title = if entry.status.staging().is_fully_staged() {
|
||||||
"Unstage File"
|
"Unstage File"
|
||||||
} else {
|
} else {
|
||||||
"Stage File"
|
"Stage File"
|
||||||
|
@ -2858,8 +2920,8 @@ impl GitPanel {
|
||||||
let checkbox_id: ElementId =
|
let checkbox_id: ElementId =
|
||||||
ElementId::Name(format!("entry_{}_{}_checkbox", display_name, ix).into());
|
ElementId::Name(format!("entry_{}_{}_checkbox", display_name, ix).into());
|
||||||
|
|
||||||
let is_entry_staged = self.entry_is_staged(entry);
|
let entry_staging = self.entry_staging(entry);
|
||||||
let mut is_staged: ToggleState = self.entry_is_staged(entry).into();
|
let mut is_staged: ToggleState = self.entry_staging(entry).as_bool().into();
|
||||||
|
|
||||||
if !self.has_staged_changes() && !self.has_conflicts() && !entry.status.is_created() {
|
if !self.has_staged_changes() && !self.has_conflicts() && !entry.status.is_created() {
|
||||||
is_staged = ToggleState::Selected;
|
is_staged = ToggleState::Selected;
|
||||||
|
@ -2978,7 +3040,7 @@ impl GitPanel {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.tooltip(move |window, cx| {
|
.tooltip(move |window, cx| {
|
||||||
let tooltip_name = if is_entry_staged.unwrap_or(false) {
|
let tooltip_name = if entry_staging.is_fully_staged() {
|
||||||
"Unstage"
|
"Unstage"
|
||||||
} else {
|
} else {
|
||||||
"Stage"
|
"Stage"
|
||||||
|
@ -4150,14 +4212,14 @@ mod tests {
|
||||||
repo_path: "crates/gpui/gpui.rs".into(),
|
repo_path: "crates/gpui/gpui.rs".into(),
|
||||||
worktree_path: Path::new("gpui.rs").into(),
|
worktree_path: Path::new("gpui.rs").into(),
|
||||||
status: StatusCode::Modified.worktree(),
|
status: StatusCode::Modified.worktree(),
|
||||||
is_staged: Some(false),
|
staging: StageStatus::Unstaged,
|
||||||
}),
|
}),
|
||||||
GitListEntry::GitStatusEntry(GitStatusEntry {
|
GitListEntry::GitStatusEntry(GitStatusEntry {
|
||||||
abs_path: path!("/root/zed/crates/util/util.rs").into(),
|
abs_path: path!("/root/zed/crates/util/util.rs").into(),
|
||||||
repo_path: "crates/util/util.rs".into(),
|
repo_path: "crates/util/util.rs".into(),
|
||||||
worktree_path: Path::new("../util/util.rs").into(),
|
worktree_path: Path::new("../util/util.rs").into(),
|
||||||
status: StatusCode::Modified.worktree(),
|
status: StatusCode::Modified.worktree(),
|
||||||
is_staged: Some(false),
|
staging: StageStatus::Unstaged,
|
||||||
},),
|
},),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -4224,14 +4286,14 @@ mod tests {
|
||||||
repo_path: "crates/gpui/gpui.rs".into(),
|
repo_path: "crates/gpui/gpui.rs".into(),
|
||||||
worktree_path: Path::new("../../gpui/gpui.rs").into(),
|
worktree_path: Path::new("../../gpui/gpui.rs").into(),
|
||||||
status: StatusCode::Modified.worktree(),
|
status: StatusCode::Modified.worktree(),
|
||||||
is_staged: Some(false),
|
staging: StageStatus::Unstaged,
|
||||||
}),
|
}),
|
||||||
GitListEntry::GitStatusEntry(GitStatusEntry {
|
GitListEntry::GitStatusEntry(GitStatusEntry {
|
||||||
abs_path: path!("/root/zed/crates/util/util.rs").into(),
|
abs_path: path!("/root/zed/crates/util/util.rs").into(),
|
||||||
repo_path: "crates/util/util.rs".into(),
|
repo_path: "crates/util/util.rs".into(),
|
||||||
worktree_path: Path::new("util.rs").into(),
|
worktree_path: Path::new("util.rs").into(),
|
||||||
status: StatusCode::Modified.worktree(),
|
status: StatusCode::Modified.worktree(),
|
||||||
is_staged: Some(false),
|
staging: StageStatus::Unstaged,
|
||||||
},),
|
},),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
@ -1353,7 +1353,7 @@ impl Repository {
|
||||||
let to_stage = self
|
let to_stage = self
|
||||||
.repository_entry
|
.repository_entry
|
||||||
.status()
|
.status()
|
||||||
.filter(|entry| !entry.status.is_staged().unwrap_or(false))
|
.filter(|entry| !entry.status.staging().is_fully_staged())
|
||||||
.map(|entry| entry.repo_path.clone())
|
.map(|entry| entry.repo_path.clone())
|
||||||
.collect();
|
.collect();
|
||||||
self.stage_entries(to_stage, cx)
|
self.stage_entries(to_stage, cx)
|
||||||
|
@ -1363,7 +1363,7 @@ impl Repository {
|
||||||
let to_unstage = self
|
let to_unstage = self
|
||||||
.repository_entry
|
.repository_entry
|
||||||
.status()
|
.status()
|
||||||
.filter(|entry| entry.status.is_staged().unwrap_or(true))
|
.filter(|entry| entry.status.staging().has_staged())
|
||||||
.map(|entry| entry.repo_path.clone())
|
.map(|entry| entry.repo_path.clone())
|
||||||
.collect();
|
.collect();
|
||||||
self.unstage_entries(to_unstage, cx)
|
self.unstage_entries(to_unstage, cx)
|
||||||
|
|
|
@ -3960,10 +3960,6 @@ pub struct StatusEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StatusEntry {
|
impl StatusEntry {
|
||||||
pub fn is_staged(&self) -> Option<bool> {
|
|
||||||
self.status.is_staged()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_proto(&self) -> proto::StatusEntry {
|
fn to_proto(&self) -> proto::StatusEntry {
|
||||||
let simple_status = match self.status {
|
let simple_status = match self.status {
|
||||||
FileStatus::Ignored | FileStatus::Untracked => proto::GitStatus::Added as i32,
|
FileStatus::Ignored | FileStatus::Untracked => proto::GitStatus::Added as i32,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue