git: Add ability to pass --signoff (#29874)

This adds an option for `--signoff` to the git panel and commit modal.
It allows users to enable the [`--signoff`
flag](https://git-scm.com/docs/git-commit#Documentation/git-commit.txt-code--signoffcode)
when committing through Zed. The option is added to the context menu of
the commit button (following the style of the "Editor Controls").

To support this, the commit+amend experience was revamped (following the
ideas of [this
comment](https://github.com/zed-industries/zed/pull/29874#issuecomment-2950848000)).
Amending is now also a toggle in the commit button's dropdown menu. I've
kept some of the original experience such as the changed button text and
ability to cancel outside the context menu.

The tooltip of the commit buttons now also includes the flags that will
be used based on the amending and signoff status (which I couldn't
capture in screenshots unfortunately). So, by default the tooltip will
say `git commit` and if you toggle, e.g., amending on it will say `git
commit --amend`.

| What | Panel | Modal |
| --- | --- | --- |
| Not amending, dropdown | ![git modal preview, not amending,
dropdown](https://github.com/user-attachments/assets/82c2b338-b3b5-418c-97bf-98c33202d7dd)
| ![commit modal preview, not amending,
dropdown](https://github.com/user-attachments/assets/f7a6f2fb-902d-447d-a473-2efb4ba0f444)
|
| Amending, dropdown | ![git modal preview, amending,
dropdown](https://github.com/user-attachments/assets/9e755975-4a27-43f0-aa62-be002ecd3a92)
| ![commit modal preview, amending,
dropdown](https://github.com/user-attachments/assets/cad03817-14e1-46f6-ba39-8ccc7dd12161)
|
| Amending | ![git modal preview,
amending](https://github.com/user-attachments/assets/e1ec4eba-174e-4e5f-9659-5867d6b0fdc2)
| - |

The initial implementation was based on the changeset of
https://github.com/zed-industries/zed/pull/28187.

Closes https://github.com/zed-industries/zed/discussions/26114

Release Notes:

- Added git `--signoff` support.
- Update the git `--amend` experience.
- Improved git panel to persist width as well as amend and signoff on a
per-workspace basis.
This commit is contained in:
Eric Cornelissen 2025-07-17 05:39:54 +02:00 committed by GitHub
parent 1ce384bbda
commit 1d72fa8e9e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 342 additions and 337 deletions

View file

@ -77,6 +77,8 @@ actions!(
Commit,
/// Amends the last commit with staged changes.
Amend,
/// Enable the --signoff option.
Signoff,
/// Cancels the current git operation.
Cancel,
/// Expands the commit message editor.

View file

@ -96,6 +96,7 @@ impl Upstream {
#[derive(Clone, Copy, Default)]
pub struct CommitOptions {
pub amend: bool,
pub signoff: bool,
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
@ -1209,6 +1210,10 @@ impl GitRepository for RealGitRepository {
cmd.arg("--amend");
}
if options.signoff {
cmd.arg("--signoff");
}
if let Some((name, email)) = name_and_email {
cmd.arg("--author").arg(&format!("{name} <{email}>"));
}

View file

@ -1,8 +1,8 @@
use crate::branch_picker::{self, BranchList};
use crate::git_panel::{GitPanel, commit_message_editor};
use git::repository::CommitOptions;
use git::{Amend, Commit, GenerateCommitMessage};
use panel::{panel_button, panel_editor_style, panel_filled_button};
use git::{Amend, Commit, GenerateCommitMessage, Signoff};
use panel::{panel_button, panel_editor_style};
use ui::{
ContextMenu, KeybindingHint, PopoverMenu, PopoverMenuHandle, SplitButton, Tooltip, prelude::*,
};
@ -273,14 +273,51 @@ impl CommitModal {
.child(Icon::new(IconName::ChevronDownSmall).size(IconSize::XSmall)),
),
)
.menu(move |window, cx| {
Some(ContextMenu::build(window, cx, |context_menu, _, _| {
context_menu
.when_some(keybinding_target.clone(), |el, keybinding_target| {
el.context(keybinding_target.clone())
})
.action("Amend", Amend.boxed_clone())
}))
.menu({
let git_panel_entity = self.git_panel.clone();
move |window, cx| {
let git_panel = git_panel_entity.read(cx);
let amend_enabled = git_panel.amend_pending();
let signoff_enabled = git_panel.signoff_enabled();
let has_previous_commit = git_panel.head_commit(cx).is_some();
Some(ContextMenu::build(window, cx, |context_menu, _, _| {
context_menu
.when_some(keybinding_target.clone(), |el, keybinding_target| {
el.context(keybinding_target.clone())
})
.when(has_previous_commit, |this| {
this.toggleable_entry(
"Amend",
amend_enabled,
IconPosition::Start,
Some(Box::new(Amend)),
{
let git_panel = git_panel_entity.clone();
move |window, cx| {
git_panel.update(cx, |git_panel, cx| {
git_panel.toggle_amend_pending(&Amend, window, cx);
})
}
},
)
})
.toggleable_entry(
"Signoff",
signoff_enabled,
IconPosition::Start,
Some(Box::new(Signoff)),
{
let git_panel = git_panel_entity.clone();
move |window, cx| {
git_panel.update(cx, |git_panel, cx| {
git_panel.toggle_signoff_enabled(&Signoff, window, cx);
})
}
},
)
}))
}
})
.with_handle(self.commit_menu_handle.clone())
.anchor(Corner::TopRight)
@ -295,7 +332,7 @@ impl CommitModal {
generate_commit_message,
active_repo,
is_amend_pending,
has_previous_commit,
is_signoff_enabled,
) = self.git_panel.update(cx, |git_panel, cx| {
let (can_commit, tooltip) = git_panel.configure_commit_button(cx);
let title = git_panel.commit_button_title();
@ -303,10 +340,7 @@ impl CommitModal {
let generate_commit_message = git_panel.render_generate_commit_message_button(cx);
let active_repo = git_panel.active_repository.clone();
let is_amend_pending = git_panel.amend_pending();
let has_previous_commit = active_repo
.as_ref()
.and_then(|repo| repo.read(cx).head_commit.as_ref())
.is_some();
let is_signoff_enabled = git_panel.signoff_enabled();
(
can_commit,
tooltip,
@ -315,7 +349,7 @@ impl CommitModal {
generate_commit_message,
active_repo,
is_amend_pending,
has_previous_commit,
is_signoff_enabled,
)
});
@ -396,126 +430,59 @@ impl CommitModal {
.px_1()
.gap_4()
.children(close_kb_hint)
.when(is_amend_pending, |this| {
let focus_handle = focus_handle.clone();
this.child(
panel_filled_button(commit_label)
.tooltip(move |window, cx| {
if can_commit {
Tooltip::for_action_in(
tooltip,
&Amend,
&focus_handle,
window,
cx,
)
} else {
Tooltip::simple(tooltip, cx)
}
})
.disabled(!can_commit)
.on_click(cx.listener(move |this, _: &ClickEvent, window, cx| {
telemetry::event!("Git Amended", source = "Git Modal");
this.git_panel.update(cx, |git_panel, cx| {
git_panel.set_amend_pending(false, cx);
git_panel.commit_changes(
CommitOptions { amend: true },
window,
cx,
);
});
cx.emit(DismissEvent);
})),
.child(SplitButton::new(
ui::ButtonLike::new_rounded_left(ElementId::Name(
format!("split-button-left-{}", commit_label).into(),
))
.layer(ui::ElevationIndex::ModalSurface)
.size(ui::ButtonSize::Compact)
.child(
div()
.child(Label::new(commit_label).size(LabelSize::Small))
.mr_0p5(),
)
})
.when(!is_amend_pending, |this| {
this.when(has_previous_commit, |this| {
this.child(SplitButton::new(
ui::ButtonLike::new_rounded_left(ElementId::Name(
format!("split-button-left-{}", commit_label).into(),
))
.layer(ui::ElevationIndex::ModalSurface)
.size(ui::ButtonSize::Compact)
.child(
div()
.child(Label::new(commit_label).size(LabelSize::Small))
.mr_0p5(),
.on_click(cx.listener(move |this, _: &ClickEvent, window, cx| {
telemetry::event!("Git Committed", source = "Git Modal");
this.git_panel.update(cx, |git_panel, cx| {
git_panel.commit_changes(
CommitOptions {
amend: is_amend_pending,
signoff: is_signoff_enabled,
},
window,
cx,
)
.on_click(cx.listener(move |this, _: &ClickEvent, window, cx| {
telemetry::event!("Git Committed", source = "Git Modal");
this.git_panel.update(cx, |git_panel, cx| {
git_panel.commit_changes(
CommitOptions { amend: false },
window,
cx,
)
});
cx.emit(DismissEvent);
}))
.disabled(!can_commit)
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
if can_commit {
Tooltip::with_meta_in(
tooltip,
Some(&git::Commit),
"git commit",
&focus_handle.clone(),
window,
cx,
)
} else {
Tooltip::simple(tooltip, cx)
}
}
}),
self.render_git_commit_menu(
ElementId::Name(
format!("split-button-right-{}", commit_label).into(),
),
Some(focus_handle.clone()),
)
.into_any_element(),
))
})
.when(!has_previous_commit, |this| {
this.child(
panel_filled_button(commit_label)
.tooltip(move |window, cx| {
if can_commit {
Tooltip::with_meta_in(
tooltip,
Some(&git::Commit),
"git commit",
&focus_handle,
window,
cx,
)
} else {
Tooltip::simple(tooltip, cx)
}
})
.disabled(!can_commit)
.on_click(cx.listener(
move |this, _: &ClickEvent, window, cx| {
telemetry::event!(
"Git Committed",
source = "Git Modal"
);
this.git_panel.update(cx, |git_panel, cx| {
git_panel.commit_changes(
CommitOptions { amend: false },
window,
cx,
)
});
cx.emit(DismissEvent);
},
)),
)
})
}),
});
cx.emit(DismissEvent);
}))
.disabled(!can_commit)
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
if can_commit {
Tooltip::with_meta_in(
tooltip,
Some(&git::Commit),
format!(
"git commit{}{}",
if is_amend_pending { " --amend" } else { "" },
if is_signoff_enabled { " --signoff" } else { "" }
),
&focus_handle.clone(),
window,
cx,
)
} else {
Tooltip::simple(tooltip, cx)
}
}
}),
self.render_git_commit_menu(
ElementId::Name(format!("split-button-right-{}", commit_label).into()),
Some(focus_handle.clone()),
)
.into_any_element(),
)),
)
}
@ -534,7 +501,14 @@ impl CommitModal {
}
telemetry::event!("Git Committed", source = "Git Modal");
self.git_panel.update(cx, |git_panel, cx| {
git_panel.commit_changes(CommitOptions { amend: false }, window, cx)
git_panel.commit_changes(
CommitOptions {
amend: false,
signoff: git_panel.signoff_enabled(),
},
window,
cx,
)
});
cx.emit(DismissEvent);
}
@ -559,7 +533,14 @@ impl CommitModal {
telemetry::event!("Git Amended", source = "Git Modal");
self.git_panel.update(cx, |git_panel, cx| {
git_panel.set_amend_pending(false, cx);
git_panel.commit_changes(CommitOptions { amend: true }, window, cx);
git_panel.commit_changes(
CommitOptions {
amend: true,
signoff: git_panel.signoff_enabled(),
},
window,
cx,
);
});
cx.emit(DismissEvent);
}

View file

@ -25,7 +25,7 @@ use git::repository::{
UpstreamTrackingStatus, get_git_committer,
};
use git::status::StageStatus;
use git::{Amend, ToggleStaged, repository::RepoPath, status::FileStatus};
use git::{Amend, Signoff, ToggleStaged, repository::RepoPath, status::FileStatus};
use git::{ExpandCommitEditor, RestoreTrackedFiles, StageAll, TrashUntrackedFiles, UnstageAll};
use gpui::{
Action, Animation, AnimationExt as _, AsyncApp, AsyncWindowContext, Axis, ClickEvent, Corner,
@ -61,8 +61,8 @@ use std::{collections::HashSet, sync::Arc, time::Duration, usize};
use strum::{IntoEnumIterator, VariantNames};
use time::OffsetDateTime;
use ui::{
Checkbox, ContextMenu, ElevationIndex, PopoverMenu, Scrollbar, ScrollbarState, SplitButton,
Tooltip, prelude::*,
Checkbox, ContextMenu, ElevationIndex, IconPosition, Label, LabelSize, PopoverMenu, Scrollbar,
ScrollbarState, SplitButton, Tooltip, prelude::*,
};
use util::{ResultExt, TryFutureExt, maybe};
@ -174,6 +174,10 @@ pub enum Event {
#[derive(Serialize, Deserialize)]
struct SerializedGitPanel {
width: Option<Pixels>,
#[serde(default)]
amend_pending: bool,
#[serde(default)]
signoff_enabled: bool,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
@ -337,6 +341,7 @@ pub struct GitPanel {
pending: Vec<PendingOperation>,
pending_commit: Option<Task<()>>,
amend_pending: bool,
signoff_enabled: bool,
pending_serialization: Task<Option<()>>,
pub(crate) project: Entity<Project>,
scroll_handle: UniformListScrollHandle,
@ -512,6 +517,7 @@ impl GitPanel {
pending: Vec::new(),
pending_commit: None,
amend_pending: false,
signoff_enabled: false,
pending_serialization: Task::ready(None),
single_staged_entry: None,
single_tracked_entry: None,
@ -690,14 +696,38 @@ impl GitPanel {
cx.notify();
}
fn serialization_key(workspace: &Workspace) -> Option<String> {
workspace
.database_id()
.map(|id| i64::from(id).to_string())
.or(workspace.session_id())
.map(|id| format!("{}-{:?}", GIT_PANEL_KEY, id))
}
fn serialize(&mut self, cx: &mut Context<Self>) {
let width = self.width;
let amend_pending = self.amend_pending;
let signoff_enabled = self.signoff_enabled;
let Some(serialization_key) = self
.workspace
.read_with(cx, |workspace, _| Self::serialization_key(workspace))
.ok()
.flatten()
else {
return;
};
self.pending_serialization = cx.background_spawn(
async move {
KEY_VALUE_STORE
.write_kvp(
GIT_PANEL_KEY.into(),
serde_json::to_string(&SerializedGitPanel { width })?,
serialization_key,
serde_json::to_string(&SerializedGitPanel {
width,
amend_pending,
signoff_enabled,
})?,
)
.await?;
anyhow::Ok(())
@ -1432,7 +1462,14 @@ impl GitPanel {
.contains_focused(window, cx)
{
telemetry::event!("Git Committed", source = "Git Panel");
self.commit_changes(CommitOptions { amend: false }, window, cx)
self.commit_changes(
CommitOptions {
amend: false,
signoff: self.signoff_enabled,
},
window,
cx,
)
} else {
cx.propagate();
}
@ -1444,19 +1481,21 @@ impl GitPanel {
.focus_handle(cx)
.contains_focused(window, cx)
{
if self
.active_repository
.as_ref()
.and_then(|repo| repo.read(cx).head_commit.as_ref())
.is_some()
{
if self.head_commit(cx).is_some() {
if !self.amend_pending {
self.set_amend_pending(true, cx);
self.load_last_commit_message_if_empty(cx);
} else {
telemetry::event!("Git Amended", source = "Git Panel");
self.set_amend_pending(false, cx);
self.commit_changes(CommitOptions { amend: true }, window, cx);
self.commit_changes(
CommitOptions {
amend: true,
signoff: self.signoff_enabled,
},
window,
cx,
);
}
}
} else {
@ -1464,21 +1503,21 @@ impl GitPanel {
}
}
pub fn head_commit(&self, cx: &App) -> Option<CommitDetails> {
self.active_repository
.as_ref()
.and_then(|repo| repo.read(cx).head_commit.as_ref())
.cloned()
}
pub fn load_last_commit_message_if_empty(&mut self, cx: &mut Context<Self>) {
if !self.commit_editor.read(cx).is_empty(cx) {
return;
}
let Some(active_repository) = self.active_repository.as_ref() else {
return;
};
let Some(recent_sha) = active_repository
.read(cx)
.head_commit
.as_ref()
.map(|commit| commit.sha.to_string())
else {
let Some(head_commit) = self.head_commit(cx) else {
return;
};
let recent_sha = head_commit.sha.to_string();
let detail_task = self.load_commit_details(recent_sha, cx);
cx.spawn(async move |this, cx| {
if let Ok(message) = detail_task.await.map(|detail| detail.message) {
@ -1495,12 +1534,6 @@ impl GitPanel {
.detach();
}
fn cancel(&mut self, _: &git::Cancel, _: &mut Window, cx: &mut Context<Self>) {
if self.amend_pending {
self.set_amend_pending(false, cx);
}
}
fn custom_or_suggested_commit_message(
&self,
window: &mut Window,
@ -3003,14 +3036,35 @@ impl GitPanel {
.child(Icon::new(IconName::ChevronDownSmall).size(IconSize::XSmall)),
),
)
.menu(move |window, cx| {
Some(ContextMenu::build(window, cx, |context_menu, _, _| {
context_menu
.when_some(keybinding_target.clone(), |el, keybinding_target| {
el.context(keybinding_target.clone())
})
.action("Amend", Amend.boxed_clone())
}))
.menu({
let has_previous_commit = self.head_commit(cx).is_some();
let amend = self.amend_pending();
let signoff = self.signoff_enabled;
move |window, cx| {
Some(ContextMenu::build(window, cx, |context_menu, _, _| {
context_menu
.when_some(keybinding_target.clone(), |el, keybinding_target| {
el.context(keybinding_target.clone())
})
.when(has_previous_commit, |this| {
this.toggleable_entry(
"Amend",
amend,
IconPosition::Start,
Some(Box::new(Amend)),
move |window, cx| window.dispatch_action(Box::new(Amend), cx),
)
})
.toggleable_entry(
"Signoff",
signoff,
IconPosition::Start,
Some(Box::new(Signoff)),
move |window, cx| window.dispatch_action(Box::new(Signoff), cx),
)
}))
}
})
.anchor(Corner::TopRight)
}
@ -3187,7 +3241,6 @@ impl GitPanel {
let editor_is_long = self.commit_editor.update(cx, |editor, cx| {
editor.max_point(cx).row().0 >= MAX_PANEL_EDITOR_LINES as u32
});
let has_previous_commit = head_commit.is_some();
let footer = v_flex()
.child(PanelRepoFooter::new(
@ -3231,7 +3284,7 @@ impl GitPanel {
h_flex()
.gap_0p5()
.children(enable_coauthors)
.child(self.render_commit_button(has_previous_commit, cx)),
.child(self.render_commit_button(cx)),
),
)
.child(
@ -3280,14 +3333,12 @@ impl GitPanel {
Some(footer)
}
fn render_commit_button(
&self,
has_previous_commit: bool,
cx: &mut Context<Self>,
) -> impl IntoElement {
fn render_commit_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
let (can_commit, tooltip) = self.configure_commit_button(cx);
let title = self.commit_button_title();
let commit_tooltip_focus_handle = self.commit_editor.focus_handle(cx);
let amend = self.amend_pending();
let signoff = self.signoff_enabled;
div()
.id("commit-wrapper")
@ -3296,165 +3347,86 @@ impl GitPanel {
*hovered && !this.has_staged_changes() && !this.has_unstaged_conflicts();
cx.notify()
}))
.when(self.amend_pending, {
|this| {
this.h_flex()
.gap_1()
.child(
panel_filled_button("Cancel")
.tooltip({
let handle = commit_tooltip_focus_handle.clone();
move |window, cx| {
Tooltip::for_action_in(
"Cancel amend",
&git::Cancel,
&handle,
window,
cx,
)
}
})
.on_click(move |_, window, cx| {
window.dispatch_action(Box::new(git::Cancel), cx);
}),
)
.child(
panel_filled_button(title)
.tooltip({
let handle = commit_tooltip_focus_handle.clone();
move |window, cx| {
if can_commit {
Tooltip::for_action_in(
tooltip, &Amend, &handle, window, cx,
)
} else {
Tooltip::simple(tooltip, cx)
}
}
})
.disabled(!can_commit || self.modal_open)
.on_click({
let git_panel = cx.weak_entity();
move |_, window, cx| {
telemetry::event!("Git Amended", source = "Git Panel");
git_panel
.update(cx, |git_panel, cx| {
git_panel.set_amend_pending(false, cx);
git_panel.commit_changes(
CommitOptions { amend: true },
window,
cx,
);
})
.ok();
}
}),
)
}
})
.when(!self.amend_pending, |this| {
this.when(has_previous_commit, |this| {
this.child(SplitButton::new(
ui::ButtonLike::new_rounded_left(ElementId::Name(
format!("split-button-left-{}", title).into(),
))
.layer(ui::ElevationIndex::ModalSurface)
.size(ui::ButtonSize::Compact)
.child(
div()
.child(Label::new(title).size(LabelSize::Small))
.mr_0p5(),
)
.on_click({
let git_panel = cx.weak_entity();
move |_, window, cx| {
telemetry::event!("Git Committed", source = "Git Panel");
git_panel
.update(cx, |git_panel, cx| {
git_panel.commit_changes(
CommitOptions { amend: false },
window,
cx,
);
})
.ok();
}
})
.disabled(!can_commit || self.modal_open)
.tooltip({
let handle = commit_tooltip_focus_handle.clone();
move |window, cx| {
if can_commit {
Tooltip::with_meta_in(
tooltip,
Some(&git::Commit),
"git commit",
&handle.clone(),
window,
cx,
)
} else {
Tooltip::simple(tooltip, cx)
}
}
}),
self.render_git_commit_menu(
ElementId::Name(format!("split-button-right-{}", title).into()),
Some(commit_tooltip_focus_handle.clone()),
cx,
)
.into_any_element(),
))
})
.when(!has_previous_commit, |this| {
this.child(
panel_filled_button(title)
.tooltip(move |window, cx| {
if can_commit {
Tooltip::with_meta_in(
tooltip,
Some(&git::Commit),
"git commit",
&commit_tooltip_focus_handle,
window,
cx,
)
} else {
Tooltip::simple(tooltip, cx)
}
.child(SplitButton::new(
ui::ButtonLike::new_rounded_left(ElementId::Name(
format!("split-button-left-{}", title).into(),
))
.layer(ui::ElevationIndex::ModalSurface)
.size(ui::ButtonSize::Compact)
.child(
div()
.child(Label::new(title).size(LabelSize::Small))
.mr_0p5(),
)
.on_click({
let git_panel = cx.weak_entity();
move |_, window, cx| {
telemetry::event!("Git Committed", source = "Git Panel");
git_panel
.update(cx, |git_panel, cx| {
git_panel.set_amend_pending(false, cx);
git_panel.commit_changes(
CommitOptions { amend, signoff },
window,
cx,
);
})
.disabled(!can_commit || self.modal_open)
.on_click({
let git_panel = cx.weak_entity();
move |_, window, cx| {
telemetry::event!("Git Committed", source = "Git Panel");
git_panel
.update(cx, |git_panel, cx| {
git_panel.commit_changes(
CommitOptions { amend: false },
window,
cx,
);
})
.ok();
}
}),
)
.ok();
}
})
})
.disabled(!can_commit || self.modal_open)
.tooltip({
let handle = commit_tooltip_focus_handle.clone();
move |window, cx| {
if can_commit {
Tooltip::with_meta_in(
tooltip,
Some(&git::Commit),
format!(
"git commit{}{}",
if amend { " --amend" } else { "" },
if signoff { " --signoff" } else { "" }
),
&handle.clone(),
window,
cx,
)
} else {
Tooltip::simple(tooltip, cx)
}
}
}),
self.render_git_commit_menu(
ElementId::Name(format!("split-button-right-{}", title).into()),
Some(commit_tooltip_focus_handle.clone()),
cx,
)
.into_any_element(),
))
}
fn render_pending_amend(&self, cx: &mut Context<Self>) -> impl IntoElement {
div()
.p_2()
h_flex()
.py_1p5()
.px_2()
.gap_1p5()
.justify_between()
.border_t_1()
.border_color(cx.theme().colors().border)
.border_color(cx.theme().colors().border.opacity(0.8))
.child(
Label::new(
"This will update your most recent commit. Cancel to make a new one instead.",
)
.size(LabelSize::Small),
div()
.flex_grow()
.overflow_hidden()
.max_w(relative(0.85))
.child(
Label::new("This will update your most recent commit.")
.size(LabelSize::Small)
.truncate(),
),
)
.child(panel_button("Cancel").size(ButtonSize::Default).on_click(
cx.listener(|this, _, window, cx| this.toggle_amend_pending(&Amend, window, cx)),
))
}
fn render_previous_commit(&self, cx: &mut Context<Self>) -> Option<impl IntoElement> {
@ -4218,17 +4190,56 @@ impl GitPanel {
cx.notify();
}
pub fn toggle_amend_pending(
&mut self,
_: &Amend,
_window: &mut Window,
cx: &mut Context<Self>,
) {
self.set_amend_pending(!self.amend_pending, cx);
self.serialize(cx);
}
pub fn signoff_enabled(&self) -> bool {
self.signoff_enabled
}
pub fn set_signoff_enabled(&mut self, value: bool, cx: &mut Context<Self>) {
self.signoff_enabled = value;
self.serialize(cx);
cx.notify();
}
pub fn toggle_signoff_enabled(
&mut self,
_: &Signoff,
_window: &mut Window,
cx: &mut Context<Self>,
) {
self.set_signoff_enabled(!self.signoff_enabled, cx);
}
pub async fn load(
workspace: WeakEntity<Workspace>,
mut cx: AsyncWindowContext,
) -> anyhow::Result<Entity<Self>> {
let serialized_panel = cx
.background_spawn(async move { KEY_VALUE_STORE.read_kvp(&GIT_PANEL_KEY) })
.await
.context("loading git panel")
.log_err()
let serialized_panel = match workspace
.read_with(&cx, |workspace, _| Self::serialization_key(workspace))
.ok()
.flatten()
.and_then(|panel| serde_json::from_str::<SerializedGitPanel>(&panel).log_err());
{
Some(serialization_key) => cx
.background_spawn(async move { KEY_VALUE_STORE.read_kvp(&serialization_key) })
.await
.context("loading git panel")
.log_err()
.flatten()
.map(|panel| serde_json::from_str::<SerializedGitPanel>(&panel))
.transpose()
.log_err()
.flatten(),
None => None,
};
workspace.update_in(&mut cx, |workspace, window, cx| {
let panel = GitPanel::new(workspace, window, cx);
@ -4236,6 +4247,8 @@ impl GitPanel {
if let Some(serialized_panel) = serialized_panel {
panel.update(cx, |panel, cx| {
panel.width = serialized_panel.width;
panel.amend_pending = serialized_panel.amend_pending;
panel.signoff_enabled = serialized_panel.signoff_enabled;
cx.notify();
})
}
@ -4320,7 +4333,8 @@ impl Render for GitPanel {
.on_action(cx.listener(Self::stage_range))
.on_action(cx.listener(GitPanel::commit))
.on_action(cx.listener(GitPanel::amend))
.on_action(cx.listener(GitPanel::cancel))
.on_action(cx.listener(GitPanel::toggle_amend_pending))
.on_action(cx.listener(GitPanel::toggle_signoff_enabled))
.on_action(cx.listener(Self::stage_all))
.on_action(cx.listener(Self::unstage_all))
.on_action(cx.listener(Self::stage_selected))

View file

@ -1738,6 +1738,7 @@ impl GitStore {
name.zip(email),
CommitOptions {
amend: options.amend,
signoff: options.signoff,
},
cx,
)
@ -3488,6 +3489,7 @@ impl Repository {
email: email.map(String::from),
options: Some(proto::commit::CommitOptions {
amend: options.amend,
signoff: options.signoff,
}),
})
.await

View file

@ -298,6 +298,7 @@ message Commit {
message CommitOptions {
bool amend = 1;
bool signoff = 2;
}
}