Fixes to commit button in Git Panel (#24425)

Git Panel updates:

* Fixes commit/commit all button to work (and be disabled correctly in
merge conflict status)
* Updates keyboard shortcuts and sets focus on the button (enter now
does the same as click; tab cycles between editor and change list)


Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...

---------

Co-authored-by: Cole Miller <cole@zed.dev>
This commit is contained in:
Conrad Irwin 2025-02-07 00:21:28 -07:00 committed by GitHub
parent 6534e0bafd
commit 1f9d02607b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 202 additions and 268 deletions

View file

@ -710,15 +710,20 @@
"enter": "menu::Confirm", "enter": "menu::Confirm",
"space": "git::ToggleStaged", "space": "git::ToggleStaged",
"ctrl-space": "git::StageAll", "ctrl-space": "git::StageAll",
"ctrl-shift-space": "git::UnstageAll" "ctrl-shift-space": "git::UnstageAll",
"tab": "git_panel::FocusEditor",
"shift-tab": "git_panel::FocusEditor",
"escape": "git_panel::ToggleFocus"
} }
}, },
{ {
"context": "GitPanel && CommitEditor > Editor", "context": "GitPanel > Editor",
"bindings": { "bindings": {
"escape": "git_panel::FocusChanges", "escape": "git_panel::FocusChanges",
"ctrl-enter": "git::CommitChanges", "ctrl-enter": "git::Commit",
"ctrl-shift-enter": "git::CommitAllChanges" "tab": "git_panel::FocusChanges",
"shift-tab": "git_panel::FocusChanges",
"alt-up": "git_panel::FocusChanges"
} }
}, },
{ {

View file

@ -715,13 +715,6 @@
"space": "project_panel::Open" "space": "project_panel::Open"
} }
}, },
{
"context": "GitPanel && !CommitEditor",
"use_key_equivalents": true,
"bindings": {
"escape": "git_panel::Close"
}
},
{ {
"context": "GitPanel && ChangesList", "context": "GitPanel && ChangesList",
"use_key_equivalents": true, "use_key_equivalents": true,
@ -734,17 +727,20 @@
"space": "git::ToggleStaged", "space": "git::ToggleStaged",
"cmd-shift-space": "git::StageAll", "cmd-shift-space": "git::StageAll",
"ctrl-shift-space": "git::UnstageAll", "ctrl-shift-space": "git::UnstageAll",
"alt-down": "git_panel::FocusEditor" "alt-down": "git_panel::FocusEditor",
"tab": "git_panel::FocusEditor",
"shift-tab": "git_panel::FocusEditor",
"escape": "git_panel::ToggleFocus"
} }
}, },
{ {
"context": "GitPanel && CommitEditor > Editor", "context": "GitPanel > Editor",
"use_key_equivalents": true, "use_key_equivalents": true,
"bindings": { "bindings": {
"alt-up": "git_panel::FocusChanges", "cmd-enter": "git::Commit",
"escape": "git_panel::FocusChanges", "tab": "git_panel::FocusChanges",
"cmd-enter": "git::CommitChanges", "shift-tab": "git_panel::FocusChanges",
"cmd-alt-enter": "git::CommitAllChanges" "alt-up": "git_panel::FocusChanges"
} }
}, },
{ {

View file

@ -567,7 +567,7 @@
} }
}, },
{ {
"context": "ProjectPanel || CollabPanel || OutlinePanel || ChatPanel || VimControl || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView", "context": "GitPanel || ProjectPanel || CollabPanel || OutlinePanel || ChatPanel || VimControl || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView",
"bindings": { "bindings": {
// window related commands (ctrl-w X) // window related commands (ctrl-w X)
"ctrl-w": null, "ctrl-w": null,
@ -625,7 +625,7 @@
} }
}, },
{ {
"context": "EmptyPane || SharedScreen || MarkdownPreview || KeyContextView || Welcome", "context": "GitPanel || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView || Welcome",
"bindings": { "bindings": {
":": "command_palette::Toggle", ":": "command_palette::Toggle",
"g /": "pane::DeploySearch" "g /": "pane::DeploySearch"

View file

@ -38,8 +38,7 @@ actions!(
StageAll, StageAll,
UnstageAll, UnstageAll,
RevertAll, RevertAll,
CommitChanges, Commit,
CommitAllChanges,
ClearCommitMessage ClearCommitMessage
] ]
); );

View file

@ -293,13 +293,16 @@ impl GitRepository for RealGitRepository {
.to_path_buf(); .to_path_buf();
if !paths.is_empty() { if !paths.is_empty() {
let status = new_std_command(&self.git_binary_path) let output = new_std_command(&self.git_binary_path)
.current_dir(&working_directory) .current_dir(&working_directory)
.args(["update-index", "--add", "--remove", "--"]) .args(["update-index", "--add", "--remove", "--"])
.args(paths.iter().map(|p| p.as_ref())) .args(paths.iter().map(|p| p.as_ref()))
.status()?; .output()?;
if !status.success() { if !output.status.success() {
return Err(anyhow!("Failed to stage paths: {status}")); return Err(anyhow!(
"Failed to stage paths:\n{}",
String::from_utf8_lossy(&output.stderr)
));
} }
} }
Ok(()) Ok(())
@ -314,13 +317,16 @@ impl GitRepository for RealGitRepository {
.to_path_buf(); .to_path_buf();
if !paths.is_empty() { if !paths.is_empty() {
let cmd = new_std_command(&self.git_binary_path) let output = new_std_command(&self.git_binary_path)
.current_dir(&working_directory) .current_dir(&working_directory)
.args(["reset", "--quiet", "--"]) .args(["reset", "--quiet", "--"])
.args(paths.iter().map(|p| p.as_ref())) .args(paths.iter().map(|p| p.as_ref()))
.status()?; .output()?;
if !cmd.success() { if !output.status.success() {
return Err(anyhow!("Failed to unstage paths: {cmd}")); return Err(anyhow!(
"Failed to unstage:\n{}",
String::from_utf8_lossy(&output.stderr)
));
} }
} }
Ok(()) Ok(())
@ -340,12 +346,16 @@ impl GitRepository for RealGitRepository {
args.push(author); args.push(author);
} }
let cmd = new_std_command(&self.git_binary_path) let output = new_std_command(&self.git_binary_path)
.current_dir(&working_directory) .current_dir(&working_directory)
.args(args) .args(args)
.status()?; .output()?;
if !cmd.success() {
return Err(anyhow!("Failed to commit: {cmd}")); if !output.status.success() {
return Err(anyhow!(
"Failed to commit:\n{}",
String::from_utf8_lossy(&output.stderr)
));
} }
Ok(()) Ok(())
} }

View file

@ -4,7 +4,6 @@ use crate::ProjectDiff;
use crate::{ use crate::{
git_panel_settings::GitPanelSettings, git_status_icon, repository_selector::RepositorySelector, git_panel_settings::GitPanelSettings, git_status_icon, repository_selector::RepositorySelector,
}; };
use anyhow::Result;
use collections::HashMap; use collections::HashMap;
use db::kvp::KEY_VALUE_STORE; use db::kvp::KEY_VALUE_STORE;
use editor::actions::MoveToEnd; use editor::actions::MoveToEnd;
@ -12,7 +11,7 @@ use editor::scroll::ScrollbarAutoHide;
use editor::{Editor, EditorMode, EditorSettings, MultiBuffer, ShowScrollbar}; use editor::{Editor, EditorMode, EditorSettings, MultiBuffer, ShowScrollbar};
use git::repository::RepoPath; use git::repository::RepoPath;
use git::status::FileStatus; use git::status::FileStatus;
use git::{CommitAllChanges, CommitChanges, ToggleStaged}; use git::{Commit, ToggleStaged};
use gpui::*; use gpui::*;
use language::{Buffer, File}; use language::{Buffer, File};
use menu::{SelectFirst, SelectLast, SelectNext, SelectPrev}; use menu::{SelectFirst, SelectLast, SelectNext, SelectPrev};
@ -26,7 +25,7 @@ use std::{collections::HashSet, path::PathBuf, sync::Arc, time::Duration, usize}
use theme::ThemeSettings; use theme::ThemeSettings;
use ui::{ use ui::{
prelude::*, ButtonLike, Checkbox, Divider, DividerColor, ElevationIndex, IndentGuideColors, prelude::*, ButtonLike, Checkbox, Divider, DividerColor, ElevationIndex, IndentGuideColors,
ListHeader, ListItem, ListItemSpacing, Scrollbar, ScrollbarState, Tooltip, ListItem, ListItemSpacing, Scrollbar, ScrollbarState, Tooltip,
}; };
use util::{maybe, ResultExt, TryFutureExt}; use util::{maybe, ResultExt, TryFutureExt};
use workspace::notifications::{DetachAndPromptErr, NotificationId}; use workspace::notifications::{DetachAndPromptErr, NotificationId};
@ -58,6 +57,17 @@ pub fn init(cx: &mut App) {
workspace.register_action(|workspace, _: &ToggleFocus, window, cx| { workspace.register_action(|workspace, _: &ToggleFocus, window, cx| {
workspace.toggle_panel_focus::<GitPanel>(window, cx); workspace.toggle_panel_focus::<GitPanel>(window, cx);
}); });
workspace.register_action(|workspace, _: &Commit, window, cx| {
workspace.open_panel::<GitPanel>(window, cx);
if let Some(git_panel) = workspace.panel::<GitPanel>(cx) {
git_panel
.read(cx)
.commit_editor
.focus_handle(cx)
.focus(window);
}
});
}, },
) )
.detach(); .detach();
@ -156,8 +166,7 @@ pub struct GitPanel {
entries_by_path: collections::HashMap<RepoPath, usize>, entries_by_path: collections::HashMap<RepoPath, usize>,
width: Option<Pixels>, width: Option<Pixels>,
pending: Vec<PendingOperation>, pending: Vec<PendingOperation>,
commit_task: Task<Result<()>>, pending_commit: Option<Task<()>>,
commit_pending: bool,
conflicted_staged_count: usize, conflicted_staged_count: usize,
conflicted_count: usize, conflicted_count: usize,
@ -269,8 +278,7 @@ impl GitPanel {
show_scrollbar: false, show_scrollbar: false,
hide_scrollbar_task: None, hide_scrollbar_task: None,
update_visible_entries_task: Task::ready(()), update_visible_entries_task: Task::ready(()),
commit_task: Task::ready(Ok(())), pending_commit: None,
commit_pending: false,
active_repository, active_repository,
scroll_handle, scroll_handle,
fs, fs,
@ -308,7 +316,12 @@ impl GitPanel {
git_panel git_panel
} }
pub fn set_focused_path(&mut self, path: ProjectPath, _: &mut Window, cx: &mut Context<Self>) { pub fn select_entry_by_path(
&mut self,
path: ProjectPath,
_: &mut Window,
cx: &mut Context<Self>,
) {
let Some(git_repo) = self.active_repository.as_ref() else { let Some(git_repo) = self.active_repository.as_ref() else {
return; return;
}; };
@ -318,7 +331,6 @@ impl GitPanel {
let Some(ix) = self.entries_by_path.get(&repo_path) else { let Some(ix) = self.entries_by_path.get(&repo_path) else {
return; return;
}; };
self.selected_entry = Some(*ix); self.selected_entry = Some(*ix);
cx.notify(); cx.notify();
} }
@ -555,10 +567,17 @@ impl GitPanel {
self.selected_entry.and_then(|i| self.entries.get(i)) self.selected_entry.and_then(|i| self.entries.get(i))
} }
fn open_selected(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) { fn open_selected(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
if let Some(entry) = self.selected_entry.and_then(|i| self.entries.get(i)) { maybe!({
self.open_entry(entry, cx); let entry = self.entries.get(self.selected_entry?)?.status_entry()?;
}
self.workspace
.update(cx, |workspace, cx| {
ProjectDiff::deploy_at(workspace, Some(entry.clone()), window, cx);
})
.ok()
});
self.focus_handle.focus(window);
} }
fn toggle_staged_for_entry( fn toggle_staged_for_entry(
@ -652,135 +671,89 @@ impl GitPanel {
} }
} }
fn open_entry(&self, entry: &GitListEntry, cx: &mut Context<Self>) {
let Some(status_entry) = entry.status_entry() else {
return;
};
let Some(active_repository) = self.active_repository.as_ref() else {
return;
};
let Some(path) = active_repository
.read(cx)
.repo_path_to_project_path(&status_entry.repo_path)
else {
return;
};
let path_exists = self.project.update(cx, |project, cx| {
project.entry_for_path(&path, cx).is_some()
});
if !path_exists {
return;
}
// TODO maybe move all of this into project?
cx.emit(Event::OpenedEntry { path });
}
/// Commit all staged changes /// Commit all staged changes
fn commit_changes( fn commit(&mut self, _: &git::Commit, window: &mut Window, cx: &mut Context<Self>) {
&mut self, let editor = self.commit_editor.read(cx);
_: &git::CommitChanges, if editor.is_empty(cx) {
name_and_email: Option<(SharedString, SharedString)>, if !editor.focus_handle(cx).contains_focused(window, cx) {
window: &mut Window, editor.focus_handle(cx).focus(window);
cx: &mut Context<Self>, return;
) { }
let Some(active_repository) = self.active_repository.clone() else {
return;
};
if !self.has_staged_changes() {
self.commit_tracked_changes(&Default::default(), name_and_email, window, cx);
return;
} }
let message = self.commit_editor.read(cx).text(cx);
if message.trim().is_empty() {
return;
}
self.commit_pending = true;
let commit_editor = self.commit_editor.clone();
self.commit_task = cx.spawn_in(window, |git_panel, mut cx| async move {
let commit = active_repository.update(&mut cx, |active_repository, _| {
active_repository.commit(SharedString::from(message), name_and_email)
})?;
let result = maybe!(async {
commit.await??;
cx.update(|window, cx| {
commit_editor.update(cx, |editor, cx| editor.clear(window, cx));
})
})
.await;
git_panel.update(&mut cx, |git_panel, cx| { self.commit_changes(window, cx)
git_panel.commit_pending = false;
result
.map_err(|e| {
git_panel.show_err_toast(e, cx);
})
.ok();
})
});
} }
/// Commit all changes, regardless of whether they are staged or not fn commit_changes(&mut self, window: &mut Window, cx: &mut Context<Self>) {
fn commit_tracked_changes(
&mut self,
_: &git::CommitAllChanges,
name_and_email: Option<(SharedString, SharedString)>,
window: &mut Window,
cx: &mut Context<Self>,
) {
let Some(active_repository) = self.active_repository.clone() else { let Some(active_repository) = self.active_repository.clone() else {
return; return;
}; };
if !self.has_staged_changes() || !self.has_tracked_changes() { let error_spawn = |message, window: &mut Window, cx: &mut App| {
let prompt = window.prompt(PromptLevel::Warning, message, None, &["Ok"], cx);
cx.spawn(|_| async move {
prompt.await.ok();
})
.detach();
};
if self.has_unstaged_conflicts() {
error_spawn(
"There are still conflicts. You must stage these before committing",
window,
cx,
);
return; return;
} }
let message = self.commit_editor.read(cx).text(cx); let message = self.commit_editor.read(cx).text(cx);
if message.trim().is_empty() { if message.trim().is_empty() {
self.commit_editor.read(cx).focus_handle(cx).focus(window);
return; return;
} }
self.commit_pending = true;
let commit_editor = self.commit_editor.clone();
let tracked_files = self
.entries
.iter()
.filter_map(|entry| entry.status_entry())
.filter(|status_entry| {
!status_entry.status.is_created() && !status_entry.is_staged.unwrap_or(false)
})
.map(|status_entry| status_entry.repo_path.clone())
.collect::<Vec<_>>();
self.commit_task = cx.spawn_in(window, |git_panel, mut cx| async move { let task = if self.has_staged_changes() {
let result = maybe!(async { // Repository serializes all git operations, so we can just send a commit immediately
cx.update(|_, cx| active_repository.read(cx).stage_entries(tracked_files))? let commit_task = active_repository.read(cx).commit(message.into(), None);
.await??; cx.background_executor()
cx.update(|_, cx| { .spawn(async move { commit_task.await? })
active_repository } else {
.read(cx) let changed_files = self
.commit(SharedString::from(message), name_and_email) .entries
})? .iter()
.await??; .filter_map(|entry| entry.status_entry())
Ok(()) .filter(|status_entry| !status_entry.status.is_created())
}) .map(|status_entry| status_entry.repo_path.clone())
.await; .collect::<Vec<_>>();
cx.update(|window, cx| match result {
Ok(_) => commit_editor.update(cx, |editor, cx| {
editor.clear(window, cx);
}),
Err(e) => { if changed_files.is_empty() {
git_panel error_spawn("No changes to commit", window, cx);
.update(cx, |git_panel, cx| { return;
git_panel.show_err_toast(e, cx); }
})
.ok(); let stage_task = active_repository.read(cx).stage_entries(changed_files);
cx.spawn(|_, mut cx| async move {
stage_task.await??;
let commit_task = active_repository
.update(&mut cx, |repo, _| repo.commit(message.into(), None))?;
commit_task.await?
})
};
let task = cx.spawn_in(window, |this, mut cx| async move {
let result = task.await;
this.update_in(&mut cx, |this, window, cx| {
this.pending_commit.take();
match result {
Ok(()) => {
this.commit_editor
.update(cx, |editor, cx| editor.clear(window, cx));
}
Err(e) => this.show_err_toast(e, cx),
} }
})?;
git_panel.update(&mut cx, |git_panel, _| {
git_panel.commit_pending = false;
}) })
.ok();
}); });
self.pending_commit = Some(task);
} }
fn fill_co_authors(&mut self, _: &FillCoAuthors, window: &mut Window, cx: &mut Context<Self>) { fn fill_co_authors(&mut self, _: &FillCoAuthors, window: &mut Window, cx: &mut Context<Self>) {
@ -1057,13 +1030,19 @@ impl GitPanel {
} }
fn has_staged_changes(&self) -> bool { fn has_staged_changes(&self) -> bool {
self.tracked_staged_count > 0 || self.new_staged_count > 0 self.tracked_staged_count > 0
|| self.new_staged_count > 0
|| self.conflicted_staged_count > 0
} }
fn has_tracked_changes(&self) -> bool { fn has_tracked_changes(&self) -> bool {
self.tracked_count > 0 self.tracked_count > 0
} }
fn has_unstaged_conflicts(&self) -> bool {
self.conflicted_count > 0 && self.conflicted_count != self.conflicted_staged_count
}
fn header_state(&self, header_type: Section) -> ToggleState { fn header_state(&self, header_type: Section) -> ToggleState {
let (staged_count, count) = match header_type { let (staged_count, count) = match header_type {
Section::New => (self.new_staged_count, self.new_count), Section::New => (self.new_staged_count, self.new_count),
@ -1084,6 +1063,7 @@ impl GitPanel {
return; return;
}; };
let notif_id = NotificationId::Named("git-operation-error".into()); let notif_id = NotificationId::Named("git-operation-error".into());
let message = e.to_string(); let message = e.to_string();
workspace.update(cx, |workspace, cx| { workspace.update(cx, |workspace, cx| {
let toast = Toast::new(notif_id, message).on_click("Open Zed Log", |window, cx| { let toast = Toast::new(notif_id, message).on_click("Open Zed Log", |window, cx| {
@ -1092,9 +1072,7 @@ impl GitPanel {
workspace.show_toast(toast, cx); workspace.show_toast(toast, cx);
}); });
} }
}
impl GitPanel {
pub fn panel_button( pub fn panel_button(
&self, &self,
id: impl Into<SharedString>, id: impl Into<SharedString>,
@ -1200,14 +1178,13 @@ impl GitPanel {
) )
} }
pub fn render_commit_editor( pub fn render_commit_editor(&self, cx: &Context<Self>) -> impl IntoElement {
&self,
name_and_email: Option<(SharedString, SharedString)>,
cx: &Context<Self>,
) -> impl IntoElement {
let editor = self.commit_editor.clone(); let editor = self.commit_editor.clone();
let can_commit = let can_commit = (self.has_staged_changes() || self.has_tracked_changes())
(self.has_staged_changes() || self.has_tracked_changes()) && !self.commit_pending; && self.pending_commit.is_none()
&& !editor.read(cx).is_empty(cx)
&& !self.has_unstaged_conflicts()
&& self.has_write_access(cx);
let editor_focus_handle = editor.read(cx).focus_handle(cx).clone(); let editor_focus_handle = editor.read(cx).focus_handle(cx).clone();
let focus_handle_1 = self.focus_handle(cx).clone(); let focus_handle_1 = self.focus_handle(cx).clone();
@ -1226,14 +1203,11 @@ impl GitPanel {
.panel_button("commit-changes", title) .panel_button("commit-changes", title)
.tooltip(move |window, cx| { .tooltip(move |window, cx| {
let focus_handle = focus_handle_1.clone(); let focus_handle = focus_handle_1.clone();
Tooltip::for_action_in(tooltip, &CommitChanges, &focus_handle, window, cx) Tooltip::for_action_in(tooltip, &Commit, &focus_handle, window, cx)
}) })
.disabled(!can_commit) .disabled(!can_commit)
.on_click({ .on_click({
let name_and_email = name_and_email.clone(); cx.listener(move |this, _: &ClickEvent, window, cx| this.commit_changes(window, cx))
cx.listener(move |this, _: &ClickEvent, window, cx| {
this.commit_changes(&CommitChanges, name_and_email.clone(), window, cx)
})
}); });
div().w_full().h(px(140.)).px_2().pt_1().pb_2().child( div().w_full().h(px(140.)).px_2().pt_1().pb_2().child(
@ -1488,9 +1462,10 @@ impl GitPanel {
ix: usize, ix: usize,
header: &GitHeaderEntry, header: &GitHeaderEntry,
has_write_access: bool, has_write_access: bool,
_window: &Window, window: &Window,
cx: &Context<Self>, cx: &Context<Self>,
) -> AnyElement { ) -> AnyElement {
let selected = self.selected_entry == Some(ix);
let header_state = if self.has_staged_changes() { let header_state = if self.has_staged_changes() {
self.header_state(header.header) self.header_state(header.header)
} else { } else {
@ -1499,34 +1474,46 @@ impl GitPanel {
Section::New => ToggleState::Unselected, Section::New => ToggleState::Unselected,
} }
}; };
let checkbox = Checkbox::new(header.title(), header_state)
let checkbox = Checkbox::new(("checkbox", ix), header_state)
.disabled(!has_write_access) .disabled(!has_write_access)
.placeholder(!self.has_staged_changes())
.fill() .fill()
.elevation(ElevationIndex::Surface); .placeholder(!self.has_staged_changes())
let selected = self.selected_entry == Some(ix); .elevation(ElevationIndex::Surface)
.on_click({
let header = header.clone();
cx.listener(move |this, _, window, cx| {
this.toggle_staged_for_entry(&GitListEntry::Header(header.clone()), window, cx);
cx.stop_propagation();
})
});
let start_slot = h_flex()
.id(("start-slot", ix))
.gap(DynamicSpacing::Base04.rems(cx))
.child(checkbox)
.tooltip(|window, cx| Tooltip::for_action("Stage File", &ToggleStaged, window, cx))
.on_mouse_down(MouseButton::Left, |_, _, cx| {
// prevent the list item active state triggering when toggling checkbox
cx.stop_propagation();
});
div() div()
.w_full() .w_full()
.child( .child(
ListHeader::new(header.title()) ListItem::new(ix)
.start_slot(checkbox) .spacing(ListItemSpacing::Sparse)
.start_slot(start_slot)
.toggle_state(selected) .toggle_state(selected)
.on_toggle({ .focused(selected && self.focus_handle.is_focused(window))
let header = header.clone(); .disabled(!has_write_access)
cx.listener(move |this, _, window, cx| { .on_click({
if !has_write_access { cx.listener(move |this, _, _, cx| {
return;
}
this.selected_entry = Some(ix); this.selected_entry = Some(ix);
this.toggle_staged_for_entry( cx.notify();
&GitListEntry::Header(header.clone()),
window,
cx,
)
}) })
}) })
.inset(true), .child(h_flex().child(self.entry_label(header.title(), Color::Muted))),
) )
.into_any_element() .into_any_element()
} }
@ -1614,7 +1601,6 @@ impl GitPanel {
div() div()
.w_full() .w_full()
.px_0p5()
.child( .child(
ListItem::new(id) ListItem::new(id)
.indent_level(1) .indent_level(1)
@ -1622,17 +1608,13 @@ impl GitPanel {
.spacing(ListItemSpacing::Sparse) .spacing(ListItemSpacing::Sparse)
.start_slot(start_slot) .start_slot(start_slot)
.toggle_state(selected) .toggle_state(selected)
.focused(selected && self.focus_handle.is_focused(window))
.disabled(!has_write_access) .disabled(!has_write_access)
.on_click({ .on_click({
let entry = entry.clone();
cx.listener(move |this, _, window, cx| { cx.listener(move |this, _, window, cx| {
this.selected_entry = Some(ix); this.selected_entry = Some(ix);
let Some(workspace) = this.workspace.upgrade() else { cx.notify();
return; this.open_selected(&Default::default(), window, cx);
};
workspace.update(cx, |workspace, cx| {
ProjectDiff::deploy_at(workspace, Some(entry.clone()), window, cx);
})
}) })
}) })
.child( .child(
@ -1660,13 +1642,7 @@ impl GitPanel {
} }
fn has_write_access(&self, cx: &App) -> bool { fn has_write_access(&self, cx: &App) -> bool {
let room = self !self.project.read(cx).is_read_only(cx)
.workspace
.upgrade()
.and_then(|workspace| workspace.read(cx).active_call()?.read(cx).room().cloned());
room.as_ref()
.map_or(true, |room| room.read(cx).local_participant().can_write())
} }
} }
@ -1684,43 +1660,14 @@ impl Render for GitPanel {
.upgrade() .upgrade()
.and_then(|workspace| workspace.read(cx).active_call()?.read(cx).room().cloned()); .and_then(|workspace| workspace.read(cx).active_call()?.read(cx).room().cloned());
let has_write_access = room let has_write_access = self.has_write_access(cx);
.as_ref()
.map_or(true, |room| room.read(cx).local_participant().can_write());
let (can_commit, name_and_email) = match &room {
Some(room) => {
if project.is_via_collab() {
if has_write_access {
let name_and_email =
room.read(cx).local_participant_user(cx).and_then(|user| {
let email = SharedString::from(user.email.clone()?);
let name = user
.name
.clone()
.map(SharedString::from)
.unwrap_or(SharedString::from(user.github_login.clone()));
Some((name, email))
});
(name_and_email.is_some(), name_and_email)
} else {
(false, None)
}
} else {
(has_write_access, None)
}
}
None => (has_write_access, None),
};
let can_commit = !self.commit_pending && can_commit;
let has_co_authors = can_commit let has_co_authors = room.map_or(false, |room| {
&& has_write_access room.read(cx)
&& room.map_or(false, |room| { .remote_participants()
room.read(cx) .values()
.remote_participants() .any(|remote_participant| remote_participant.can_write())
.values() });
.any(|remote_participant| remote_participant.can_write())
});
v_flex() v_flex()
.id("git_panel") .id("git_panel")
@ -1731,31 +1678,7 @@ impl Render for GitPanel {
this.on_action(cx.listener(|this, &ToggleStaged, window, cx| { this.on_action(cx.listener(|this, &ToggleStaged, window, cx| {
this.toggle_staged_for_selected(&ToggleStaged, window, cx) this.toggle_staged_for_selected(&ToggleStaged, window, cx)
})) }))
.when(can_commit, |git_panel| { .on_action(cx.listener(GitPanel::commit))
git_panel
.on_action({
let name_and_email = name_and_email.clone();
cx.listener(move |git_panel, &CommitChanges, window, cx| {
git_panel.commit_changes(
&CommitChanges,
name_and_email.clone(),
window,
cx,
)
})
})
.on_action({
let name_and_email = name_and_email.clone();
cx.listener(move |git_panel, &CommitAllChanges, window, cx| {
git_panel.commit_tracked_changes(
&CommitAllChanges,
name_and_email.clone(),
window,
cx,
)
})
})
})
}) })
.when(self.is_focused(window, cx), |this| { .when(self.is_focused(window, cx), |this| {
this.on_action(cx.listener(Self::select_first)) this.on_action(cx.listener(Self::select_first))
@ -1768,7 +1691,7 @@ impl Render for GitPanel {
.on_action(cx.listener(Self::focus_changes_list)) .on_action(cx.listener(Self::focus_changes_list))
.on_action(cx.listener(Self::focus_editor)) .on_action(cx.listener(Self::focus_editor))
.on_action(cx.listener(Self::toggle_staged_for_selected)) .on_action(cx.listener(Self::toggle_staged_for_selected))
.when(has_co_authors, |git_panel| { .when(has_write_access && has_co_authors, |git_panel| {
git_panel.on_action(cx.listener(Self::fill_co_authors)) git_panel.on_action(cx.listener(Self::fill_co_authors))
}) })
// .on_action(cx.listener(|this, &OpenSelected, cx| this.open_selected(&OpenSelected, cx))) // .on_action(cx.listener(|this, &OpenSelected, cx| this.open_selected(&OpenSelected, cx)))
@ -1791,7 +1714,7 @@ impl Render for GitPanel {
} else { } else {
self.render_empty_state(cx).into_any_element() self.render_empty_state(cx).into_any_element()
}) })
.child(self.render_commit_editor(name_and_email, cx)) .child(self.render_commit_editor(cx))
} }
} }

View file

@ -235,7 +235,7 @@ impl ProjectDiff {
.update(cx, |workspace, cx| { .update(cx, |workspace, cx| {
if let Some(git_panel) = workspace.panel::<GitPanel>(cx) { if let Some(git_panel) = workspace.panel::<GitPanel>(cx) {
git_panel.update(cx, |git_panel, cx| { git_panel.update(cx, |git_panel, cx| {
git_panel.set_focused_path(project_path.into(), window, cx) git_panel.select_entry_by_path(project_path.into(), window, cx)
}) })
} }
}) })

View file

@ -368,6 +368,7 @@ enum ErrorCode {
DevServerProjectPathDoesNotExist = 16; DevServerProjectPathDoesNotExist = 16;
RemoteUpgradeRequired = 17; RemoteUpgradeRequired = 17;
RateLimitExceeded = 18; RateLimitExceeded = 18;
CommitFailed = 19;
reserved 6; reserved 6;
reserved 14 to 15; reserved 14 to 15;
} }