git: Amend (#28187)
Adds git amend support. - [x] Turn existing commit button into split button - [x] Clean up + Handle shortcuts/focus cases - [x] Test remote Release Notes: - Added git amend support. --------- Co-authored-by: Cole Miller <cole@zed.dev>
This commit is contained in:
parent
ac8a4ba5d4
commit
78ecc3cef0
@ -782,6 +782,7 @@
|
|||||||
"shift-tab": "git_panel::FocusEditor",
|
"shift-tab": "git_panel::FocusEditor",
|
||||||
"escape": "git_panel::ToggleFocus",
|
"escape": "git_panel::ToggleFocus",
|
||||||
"ctrl-enter": "git::Commit",
|
"ctrl-enter": "git::Commit",
|
||||||
|
"ctrl-shift-enter": "git::Amend",
|
||||||
"alt-enter": "menu::SecondaryConfirm",
|
"alt-enter": "menu::SecondaryConfirm",
|
||||||
"delete": ["git::RestoreFile", { "skip_prompt": false }],
|
"delete": ["git::RestoreFile", { "skip_prompt": false }],
|
||||||
"backspace": ["git::RestoreFile", { "skip_prompt": false }],
|
"backspace": ["git::RestoreFile", { "skip_prompt": false }],
|
||||||
@ -790,12 +791,20 @@
|
|||||||
"ctrl-delete": ["git::RestoreFile", { "skip_prompt": false }]
|
"ctrl-delete": ["git::RestoreFile", { "skip_prompt": false }]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "GitPanel && CommitEditor",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"escape": "git::Cancel"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "GitCommit > Editor",
|
"context": "GitCommit > Editor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"escape": "menu::Cancel",
|
"escape": "menu::Cancel",
|
||||||
"enter": "editor::Newline",
|
"enter": "editor::Newline",
|
||||||
"ctrl-enter": "git::Commit",
|
"ctrl-enter": "git::Commit",
|
||||||
|
"ctrl-shift-enter": "git::Amend",
|
||||||
"alt-l": "git::GenerateCommitMessage"
|
"alt-l": "git::GenerateCommitMessage"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -817,6 +826,7 @@
|
|||||||
"context": "GitDiff > Editor",
|
"context": "GitDiff > Editor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-enter": "git::Commit",
|
"ctrl-enter": "git::Commit",
|
||||||
|
"ctrl-shift-enter": "git::Amend",
|
||||||
"ctrl-space": "git::StageAll",
|
"ctrl-space": "git::StageAll",
|
||||||
"ctrl-shift-space": "git::UnstageAll"
|
"ctrl-shift-space": "git::UnstageAll"
|
||||||
}
|
}
|
||||||
@ -835,6 +845,7 @@
|
|||||||
"shift-tab": "git_panel::FocusChanges",
|
"shift-tab": "git_panel::FocusChanges",
|
||||||
"enter": "editor::Newline",
|
"enter": "editor::Newline",
|
||||||
"ctrl-enter": "git::Commit",
|
"ctrl-enter": "git::Commit",
|
||||||
|
"ctrl-shift-enter": "git::Amend",
|
||||||
"alt-up": "git_panel::FocusChanges",
|
"alt-up": "git_panel::FocusChanges",
|
||||||
"alt-l": "git::GenerateCommitMessage"
|
"alt-l": "git::GenerateCommitMessage"
|
||||||
}
|
}
|
||||||
|
@ -855,17 +855,26 @@
|
|||||||
"shift-tab": "git_panel::FocusEditor",
|
"shift-tab": "git_panel::FocusEditor",
|
||||||
"escape": "git_panel::ToggleFocus",
|
"escape": "git_panel::ToggleFocus",
|
||||||
"cmd-enter": "git::Commit",
|
"cmd-enter": "git::Commit",
|
||||||
|
"cmd-shift-enter": "git::Amend",
|
||||||
"backspace": ["git::RestoreFile", { "skip_prompt": false }],
|
"backspace": ["git::RestoreFile", { "skip_prompt": false }],
|
||||||
"delete": ["git::RestoreFile", { "skip_prompt": false }],
|
"delete": ["git::RestoreFile", { "skip_prompt": false }],
|
||||||
"cmd-backspace": ["git::RestoreFile", { "skip_prompt": true }],
|
"cmd-backspace": ["git::RestoreFile", { "skip_prompt": true }],
|
||||||
"cmd-delete": ["git::RestoreFile", { "skip_prompt": true }]
|
"cmd-delete": ["git::RestoreFile", { "skip_prompt": true }]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "GitPanel && CommitEditor",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"escape": "git::Cancel"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "GitDiff > Editor",
|
"context": "GitDiff > Editor",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-enter": "git::Commit",
|
"cmd-enter": "git::Commit",
|
||||||
|
"cmd-shift-enter": "git::Amend",
|
||||||
"cmd-ctrl-y": "git::StageAll",
|
"cmd-ctrl-y": "git::StageAll",
|
||||||
"cmd-ctrl-shift-y": "git::UnstageAll"
|
"cmd-ctrl-shift-y": "git::UnstageAll"
|
||||||
}
|
}
|
||||||
@ -876,6 +885,7 @@
|
|||||||
"bindings": {
|
"bindings": {
|
||||||
"enter": "editor::Newline",
|
"enter": "editor::Newline",
|
||||||
"cmd-enter": "git::Commit",
|
"cmd-enter": "git::Commit",
|
||||||
|
"cmd-shift-enter": "git::Amend",
|
||||||
"tab": "git_panel::FocusChanges",
|
"tab": "git_panel::FocusChanges",
|
||||||
"shift-tab": "git_panel::FocusChanges",
|
"shift-tab": "git_panel::FocusChanges",
|
||||||
"alt-up": "git_panel::FocusChanges",
|
"alt-up": "git_panel::FocusChanges",
|
||||||
@ -905,6 +915,7 @@
|
|||||||
"enter": "editor::Newline",
|
"enter": "editor::Newline",
|
||||||
"escape": "menu::Cancel",
|
"escape": "menu::Cancel",
|
||||||
"cmd-enter": "git::Commit",
|
"cmd-enter": "git::Commit",
|
||||||
|
"cmd-shift-enter": "git::Amend",
|
||||||
"alt-tab": "git::GenerateCommitMessage"
|
"alt-tab": "git::GenerateCommitMessage"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -5,8 +5,8 @@ use futures::future::{self, BoxFuture};
|
|||||||
use git::{
|
use git::{
|
||||||
blame::Blame,
|
blame::Blame,
|
||||||
repository::{
|
repository::{
|
||||||
AskPassDelegate, Branch, CommitDetails, GitRepository, GitRepositoryCheckpoint,
|
AskPassDelegate, Branch, CommitDetails, CommitOptions, GitRepository,
|
||||||
PushOptions, Remote, RepoPath, ResetMode,
|
GitRepositoryCheckpoint, PushOptions, Remote, RepoPath, ResetMode,
|
||||||
},
|
},
|
||||||
status::{FileStatus, GitStatus, StatusCode, TrackedStatus, UnmergedStatus},
|
status::{FileStatus, GitStatus, StatusCode, TrackedStatus, UnmergedStatus},
|
||||||
};
|
};
|
||||||
@ -365,6 +365,7 @@ impl GitRepository for FakeGitRepository {
|
|||||||
&self,
|
&self,
|
||||||
_message: gpui::SharedString,
|
_message: gpui::SharedString,
|
||||||
_name_and_email: Option<(gpui::SharedString, gpui::SharedString)>,
|
_name_and_email: Option<(gpui::SharedString, gpui::SharedString)>,
|
||||||
|
_options: CommitOptions,
|
||||||
_env: Arc<HashMap<String, String>>,
|
_env: Arc<HashMap<String, String>>,
|
||||||
) -> BoxFuture<Result<()>> {
|
) -> BoxFuture<Result<()>> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
|
@ -50,6 +50,8 @@ actions!(
|
|||||||
Pull,
|
Pull,
|
||||||
Fetch,
|
Fetch,
|
||||||
Commit,
|
Commit,
|
||||||
|
Amend,
|
||||||
|
Cancel,
|
||||||
ExpandCommitEditor,
|
ExpandCommitEditor,
|
||||||
GenerateCommitMessage,
|
GenerateCommitMessage,
|
||||||
Init,
|
Init,
|
||||||
|
@ -74,6 +74,11 @@ impl Upstream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Default)]
|
||||||
|
pub struct CommitOptions {
|
||||||
|
pub amend: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
|
||||||
pub enum UpstreamTracking {
|
pub enum UpstreamTracking {
|
||||||
/// Remote ref not present in local repository.
|
/// Remote ref not present in local repository.
|
||||||
@ -252,6 +257,7 @@ pub trait GitRepository: Send + Sync {
|
|||||||
&self,
|
&self,
|
||||||
message: SharedString,
|
message: SharedString,
|
||||||
name_and_email: Option<(SharedString, SharedString)>,
|
name_and_email: Option<(SharedString, SharedString)>,
|
||||||
|
options: CommitOptions,
|
||||||
env: Arc<HashMap<String, String>>,
|
env: Arc<HashMap<String, String>>,
|
||||||
) -> BoxFuture<Result<()>>;
|
) -> BoxFuture<Result<()>>;
|
||||||
|
|
||||||
@ -368,8 +374,8 @@ impl RealGitRepository {
|
|||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct GitRepositoryCheckpoint {
|
pub struct GitRepositoryCheckpoint {
|
||||||
ref_name: String,
|
pub ref_name: String,
|
||||||
commit_sha: Oid,
|
pub commit_sha: Oid,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GitRepository for RealGitRepository {
|
impl GitRepository for RealGitRepository {
|
||||||
@ -957,6 +963,7 @@ impl GitRepository for RealGitRepository {
|
|||||||
&self,
|
&self,
|
||||||
message: SharedString,
|
message: SharedString,
|
||||||
name_and_email: Option<(SharedString, SharedString)>,
|
name_and_email: Option<(SharedString, SharedString)>,
|
||||||
|
options: CommitOptions,
|
||||||
env: Arc<HashMap<String, String>>,
|
env: Arc<HashMap<String, String>>,
|
||||||
) -> BoxFuture<Result<()>> {
|
) -> BoxFuture<Result<()>> {
|
||||||
let working_directory = self.working_directory();
|
let working_directory = self.working_directory();
|
||||||
@ -969,6 +976,10 @@ impl GitRepository for RealGitRepository {
|
|||||||
.arg(&message.to_string())
|
.arg(&message.to_string())
|
||||||
.arg("--cleanup=strip");
|
.arg("--cleanup=strip");
|
||||||
|
|
||||||
|
if options.amend {
|
||||||
|
cmd.arg("--amend");
|
||||||
|
}
|
||||||
|
|
||||||
if let Some((name, email)) = name_and_email {
|
if let Some((name, email)) = name_and_email {
|
||||||
cmd.arg("--author").arg(&format!("{name} <{email}>"));
|
cmd.arg("--author").arg(&format!("{name} <{email}>"));
|
||||||
}
|
}
|
||||||
@ -1765,6 +1776,7 @@ mod tests {
|
|||||||
repo.commit(
|
repo.commit(
|
||||||
"Initial commit".into(),
|
"Initial commit".into(),
|
||||||
None,
|
None,
|
||||||
|
CommitOptions::default(),
|
||||||
Arc::new(checkpoint_author_envs()),
|
Arc::new(checkpoint_author_envs()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@ -1793,6 +1805,7 @@ mod tests {
|
|||||||
repo.commit(
|
repo.commit(
|
||||||
"Commit after checkpoint".into(),
|
"Commit after checkpoint".into(),
|
||||||
None,
|
None,
|
||||||
|
CommitOptions::default(),
|
||||||
Arc::new(checkpoint_author_envs()),
|
Arc::new(checkpoint_author_envs()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
use crate::branch_picker::{self, BranchList};
|
use crate::branch_picker::{self, BranchList};
|
||||||
use crate::git_panel::{GitPanel, commit_message_editor};
|
use crate::git_panel::{GitPanel, commit_message_editor};
|
||||||
use git::{Commit, GenerateCommitMessage};
|
use git::repository::CommitOptions;
|
||||||
|
use git::{Amend, Commit, GenerateCommitMessage};
|
||||||
|
use language::Buffer;
|
||||||
use panel::{panel_button, panel_editor_style, panel_filled_button};
|
use panel::{panel_button, panel_editor_style, panel_filled_button};
|
||||||
use ui::{KeybindingHint, PopoverMenu, PopoverMenuHandle, Tooltip, prelude::*};
|
use ui::{
|
||||||
|
ContextMenu, KeybindingHint, PopoverMenu, PopoverMenuHandle, SplitButton, Tooltip, prelude::*,
|
||||||
|
};
|
||||||
|
|
||||||
use editor::{Editor, EditorElement};
|
use editor::{Editor, EditorElement};
|
||||||
use gpui::*;
|
use gpui::*;
|
||||||
@ -100,6 +104,9 @@ impl CommitModal {
|
|||||||
workspace.register_action(|workspace, _: &Commit, window, cx| {
|
workspace.register_action(|workspace, _: &Commit, window, cx| {
|
||||||
CommitModal::toggle(workspace, window, cx);
|
CommitModal::toggle(workspace, window, cx);
|
||||||
});
|
});
|
||||||
|
workspace.register_action(|workspace, _: &Amend, window, cx| {
|
||||||
|
CommitModal::toggle(workspace, window, cx);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle(workspace: &mut Workspace, window: &mut Window, cx: &mut Context<Workspace>) {
|
pub fn toggle(workspace: &mut Workspace, window: &mut Window, cx: &mut Context<Workspace>) {
|
||||||
@ -214,14 +221,56 @@ impl CommitModal {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_git_commit_menu(
|
||||||
|
&self,
|
||||||
|
id: impl Into<ElementId>,
|
||||||
|
keybinding_target: Option<FocusHandle>,
|
||||||
|
) -> impl IntoElement {
|
||||||
|
PopoverMenu::new(id.into())
|
||||||
|
.trigger(
|
||||||
|
ui::ButtonLike::new_rounded_right("commit-split-button-right")
|
||||||
|
.layer(ui::ElevationIndex::ModalSurface)
|
||||||
|
.size(ui::ButtonSize::None)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.px_1()
|
||||||
|
.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())
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
.anchor(Corner::TopRight)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn render_footer(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
pub fn render_footer(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let (can_commit, tooltip, commit_label, co_authors, generate_commit_message, active_repo) =
|
let (
|
||||||
self.git_panel.update(cx, |git_panel, cx| {
|
can_commit,
|
||||||
|
tooltip,
|
||||||
|
commit_label,
|
||||||
|
co_authors,
|
||||||
|
generate_commit_message,
|
||||||
|
active_repo,
|
||||||
|
is_amend_pending,
|
||||||
|
has_previous_commit,
|
||||||
|
) = self.git_panel.update(cx, |git_panel, cx| {
|
||||||
let (can_commit, tooltip) = git_panel.configure_commit_button(cx);
|
let (can_commit, tooltip) = git_panel.configure_commit_button(cx);
|
||||||
let title = git_panel.commit_button_title();
|
let title = git_panel.commit_button_title();
|
||||||
let co_authors = git_panel.render_co_authors(cx);
|
let co_authors = git_panel.render_co_authors(cx);
|
||||||
let generate_commit_message = git_panel.render_generate_commit_message_button(cx);
|
let generate_commit_message = git_panel.render_generate_commit_message_button(cx);
|
||||||
let active_repo = git_panel.active_repository.clone();
|
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).branch.as_ref())
|
||||||
|
.and_then(|branch| branch.most_recent_commit.as_ref())
|
||||||
|
.is_some();
|
||||||
(
|
(
|
||||||
can_commit,
|
can_commit,
|
||||||
tooltip,
|
tooltip,
|
||||||
@ -229,6 +278,8 @@ impl CommitModal {
|
|||||||
co_authors,
|
co_authors,
|
||||||
generate_commit_message,
|
generate_commit_message,
|
||||||
active_repo,
|
active_repo,
|
||||||
|
is_amend_pending,
|
||||||
|
has_previous_commit,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -277,21 +328,6 @@ impl CommitModal {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let commit_button = panel_filled_button(commit_label)
|
|
||||||
.tooltip({
|
|
||||||
let panel_editor_focus_handle = focus_handle.clone();
|
|
||||||
move |window, cx| {
|
|
||||||
Tooltip::for_action_in(tooltip, &Commit, &panel_editor_focus_handle, window, 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(window, cx));
|
|
||||||
cx.emit(DismissEvent);
|
|
||||||
}));
|
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.group("commit_editor_footer")
|
.group("commit_editor_footer")
|
||||||
.flex_none()
|
.flex_none()
|
||||||
@ -324,21 +360,188 @@ impl CommitModal {
|
|||||||
.px_1()
|
.px_1()
|
||||||
.gap_4()
|
.gap_4()
|
||||||
.children(close_kb_hint)
|
.children(close_kb_hint)
|
||||||
.child(commit_button),
|
.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(move |_, window, cx| {
|
||||||
|
window.dispatch_action(Box::new(git::Commit), cx);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.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(move |_, window, cx| {
|
||||||
|
window.dispatch_action(Box::new(git::Commit), cx);
|
||||||
|
})
|
||||||
|
.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(move |_, window, cx| {
|
||||||
|
window.dispatch_action(Box::new(git::Commit), cx);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dismiss(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
|
fn dismiss(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
if self.git_panel.read(cx).amend_pending() {
|
||||||
|
self.git_panel
|
||||||
|
.update(cx, |git_panel, _| git_panel.set_amend_pending(false));
|
||||||
|
cx.notify();
|
||||||
|
} else {
|
||||||
cx.emit(DismissEvent);
|
cx.emit(DismissEvent);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn commit_message_buffer(&self, cx: &App) -> Entity<Buffer> {
|
||||||
|
self.commit_editor
|
||||||
|
.read(cx)
|
||||||
|
.buffer()
|
||||||
|
.read(cx)
|
||||||
|
.as_singleton()
|
||||||
|
.unwrap()
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
|
||||||
fn commit(&mut self, _: &git::Commit, window: &mut Window, cx: &mut Context<Self>) {
|
fn commit(&mut self, _: &git::Commit, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
if self.git_panel.read(cx).amend_pending() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
telemetry::event!("Git Committed", source = "Git Modal");
|
telemetry::event!("Git Committed", source = "Git Modal");
|
||||||
self.git_panel
|
self.git_panel.update(cx, |git_panel, cx| {
|
||||||
.update(cx, |git_panel, cx| git_panel.commit_changes(window, cx));
|
git_panel.commit_changes(CommitOptions { amend: false }, window, cx)
|
||||||
|
});
|
||||||
cx.emit(DismissEvent);
|
cx.emit(DismissEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn amend(&mut self, _: &git::Amend, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
let Some(active_repository) = self.git_panel.read(cx).active_repository.as_ref() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(branch) = active_repository.read(cx).branch.as_ref() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(recent_sha) = branch
|
||||||
|
.most_recent_commit
|
||||||
|
.as_ref()
|
||||||
|
.map(|commit| commit.sha.to_string())
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if self
|
||||||
|
.commit_editor
|
||||||
|
.focus_handle(cx)
|
||||||
|
.contains_focused(window, cx)
|
||||||
|
{
|
||||||
|
if !self.git_panel.read(cx).amend_pending() {
|
||||||
|
self.git_panel.update(cx, |git_panel, _| {
|
||||||
|
git_panel.set_amend_pending(true);
|
||||||
|
});
|
||||||
|
cx.notify();
|
||||||
|
if self.commit_editor.read(cx).is_empty(cx) {
|
||||||
|
let detail_task = self.git_panel.update(cx, |git_panel, cx| {
|
||||||
|
git_panel.load_commit_details(recent_sha, cx)
|
||||||
|
});
|
||||||
|
cx.spawn(async move |this, cx| {
|
||||||
|
if let Ok(message) = detail_task.await.map(|detail| detail.message) {
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.commit_message_buffer(cx).update(cx, |buffer, cx| {
|
||||||
|
let insert_position = buffer.anchor_before(buffer.len());
|
||||||
|
buffer.edit(
|
||||||
|
[(insert_position..insert_position, message)],
|
||||||
|
None,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
telemetry::event!("Git Amended", source = "Git Panel");
|
||||||
|
self.git_panel.update(cx, |git_panel, cx| {
|
||||||
|
git_panel.set_amend_pending(false);
|
||||||
|
git_panel.commit_changes(CommitOptions { amend: true }, window, cx);
|
||||||
|
});
|
||||||
|
cx.emit(DismissEvent);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cx.propagate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn toggle_branch_selector(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
fn toggle_branch_selector(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
if self.branch_list_handle.is_focused(window, cx) {
|
if self.branch_list_handle.is_focused(window, cx) {
|
||||||
self.focus_handle(cx).focus(window)
|
self.focus_handle(cx).focus(window)
|
||||||
@ -361,6 +564,7 @@ impl Render for CommitModal {
|
|||||||
.key_context("GitCommit")
|
.key_context("GitCommit")
|
||||||
.on_action(cx.listener(Self::dismiss))
|
.on_action(cx.listener(Self::dismiss))
|
||||||
.on_action(cx.listener(Self::commit))
|
.on_action(cx.listener(Self::commit))
|
||||||
|
.on_action(cx.listener(Self::amend))
|
||||||
.on_action(cx.listener(|this, _: &GenerateCommitMessage, _, cx| {
|
.on_action(cx.listener(|this, _: &GenerateCommitMessage, _, cx| {
|
||||||
this.git_panel.update(cx, |panel, cx| {
|
this.git_panel.update(cx, |panel, cx| {
|
||||||
panel.generate_commit_message(cx);
|
panel.generate_commit_message(cx);
|
||||||
|
@ -21,11 +21,11 @@ use editor::{
|
|||||||
use futures::StreamExt as _;
|
use futures::StreamExt as _;
|
||||||
use git::blame::ParsedCommitMessage;
|
use git::blame::ParsedCommitMessage;
|
||||||
use git::repository::{
|
use git::repository::{
|
||||||
Branch, CommitDetails, CommitSummary, DiffType, PushOptions, Remote, RemoteCommandOutput,
|
Branch, CommitDetails, CommitOptions, CommitSummary, DiffType, PushOptions, Remote,
|
||||||
ResetMode, Upstream, UpstreamTracking, UpstreamTrackingStatus,
|
RemoteCommandOutput, ResetMode, Upstream, UpstreamTracking, UpstreamTrackingStatus,
|
||||||
};
|
};
|
||||||
use git::status::StageStatus;
|
use git::status::StageStatus;
|
||||||
use git::{Commit, ToggleStaged, repository::RepoPath, status::FileStatus};
|
use git::{Amend, ToggleStaged, repository::RepoPath, status::FileStatus};
|
||||||
use git::{ExpandCommitEditor, RestoreTrackedFiles, StageAll, TrashUntrackedFiles, UnstageAll};
|
use git::{ExpandCommitEditor, RestoreTrackedFiles, StageAll, TrashUntrackedFiles, UnstageAll};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Action, Animation, AnimationExt as _, Axis, ClickEvent, Corner, DismissEvent, Entity,
|
Action, Animation, AnimationExt as _, Axis, ClickEvent, Corner, DismissEvent, Entity,
|
||||||
@ -59,8 +59,8 @@ use std::{collections::HashSet, sync::Arc, time::Duration, usize};
|
|||||||
use strum::{IntoEnumIterator, VariantNames};
|
use strum::{IntoEnumIterator, VariantNames};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
use ui::{
|
use ui::{
|
||||||
Checkbox, ContextMenu, ElevationIndex, PopoverMenu, Scrollbar, ScrollbarState, Tooltip,
|
Checkbox, ContextMenu, ElevationIndex, PopoverMenu, Scrollbar, ScrollbarState, SplitButton,
|
||||||
prelude::*,
|
Tooltip, prelude::*,
|
||||||
};
|
};
|
||||||
use util::{ResultExt, TryFutureExt, maybe};
|
use util::{ResultExt, TryFutureExt, maybe};
|
||||||
use workspace::AppState;
|
use workspace::AppState;
|
||||||
@ -340,6 +340,7 @@ pub struct GitPanel {
|
|||||||
new_staged_count: usize,
|
new_staged_count: usize,
|
||||||
pending: Vec<PendingOperation>,
|
pending: Vec<PendingOperation>,
|
||||||
pending_commit: Option<Task<()>>,
|
pending_commit: Option<Task<()>>,
|
||||||
|
amend_pending: bool,
|
||||||
pending_serialization: Task<Option<()>>,
|
pending_serialization: Task<Option<()>>,
|
||||||
pub(crate) project: Entity<Project>,
|
pub(crate) project: Entity<Project>,
|
||||||
scroll_handle: UniformListScrollHandle,
|
scroll_handle: UniformListScrollHandle,
|
||||||
@ -492,6 +493,7 @@ impl GitPanel {
|
|||||||
new_staged_count: 0,
|
new_staged_count: 0,
|
||||||
pending: Vec::new(),
|
pending: Vec::new(),
|
||||||
pending_commit: None,
|
pending_commit: None,
|
||||||
|
amend_pending: false,
|
||||||
pending_serialization: Task::ready(None),
|
pending_serialization: Task::ready(None),
|
||||||
single_staged_entry: None,
|
single_staged_entry: None,
|
||||||
single_tracked_entry: None,
|
single_tracked_entry: None,
|
||||||
@ -1417,18 +1419,76 @@ impl GitPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn commit(&mut self, _: &git::Commit, window: &mut Window, cx: &mut Context<Self>) {
|
fn commit(&mut self, _: &git::Commit, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
if self.amend_pending {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if self
|
if self
|
||||||
.commit_editor
|
.commit_editor
|
||||||
.focus_handle(cx)
|
.focus_handle(cx)
|
||||||
.contains_focused(window, cx)
|
.contains_focused(window, cx)
|
||||||
{
|
{
|
||||||
telemetry::event!("Git Committed", source = "Git Panel");
|
telemetry::event!("Git Committed", source = "Git Panel");
|
||||||
self.commit_changes(window, cx)
|
self.commit_changes(CommitOptions { amend: false }, window, cx)
|
||||||
} else {
|
} else {
|
||||||
cx.propagate();
|
cx.propagate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn amend(&mut self, _: &git::Amend, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
let Some(active_repository) = self.active_repository.as_ref() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(branch) = active_repository.read(cx).branch.as_ref() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(recent_sha) = branch
|
||||||
|
.most_recent_commit
|
||||||
|
.as_ref()
|
||||||
|
.map(|commit| commit.sha.to_string())
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if self
|
||||||
|
.commit_editor
|
||||||
|
.focus_handle(cx)
|
||||||
|
.contains_focused(window, cx)
|
||||||
|
{
|
||||||
|
if !self.amend_pending {
|
||||||
|
self.amend_pending = true;
|
||||||
|
cx.notify();
|
||||||
|
if self.commit_editor.read(cx).is_empty(cx) {
|
||||||
|
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) {
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.commit_message_buffer(cx).update(cx, |buffer, cx| {
|
||||||
|
let start = buffer.anchor_before(0);
|
||||||
|
let end = buffer.anchor_after(buffer.len());
|
||||||
|
buffer.edit([(start..end, message)], None, cx);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
telemetry::event!("Git Amended", source = "Git Panel");
|
||||||
|
self.amend_pending = false;
|
||||||
|
self.commit_changes(CommitOptions { amend: true }, window, cx);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cx.propagate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cancel(&mut self, _: &git::Cancel, _: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
if self.amend_pending {
|
||||||
|
self.amend_pending = false;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn custom_or_suggested_commit_message(&self, cx: &mut Context<Self>) -> Option<String> {
|
fn custom_or_suggested_commit_message(&self, cx: &mut Context<Self>) -> Option<String> {
|
||||||
let message = self.commit_editor.read(cx).text(cx);
|
let message = self.commit_editor.read(cx).text(cx);
|
||||||
|
|
||||||
@ -1440,7 +1500,12 @@ impl GitPanel {
|
|||||||
.filter(|message| !message.trim().is_empty())
|
.filter(|message| !message.trim().is_empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn commit_changes(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
pub(crate) fn commit_changes(
|
||||||
|
&mut self,
|
||||||
|
options: CommitOptions,
|
||||||
|
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;
|
||||||
};
|
};
|
||||||
@ -1474,8 +1539,9 @@ impl GitPanel {
|
|||||||
|
|
||||||
let task = if self.has_staged_changes() {
|
let task = if self.has_staged_changes() {
|
||||||
// Repository serializes all git operations, so we can just send a commit immediately
|
// Repository serializes all git operations, so we can just send a commit immediately
|
||||||
let commit_task =
|
let commit_task = active_repository.update(cx, |repo, cx| {
|
||||||
active_repository.update(cx, |repo, cx| repo.commit(message.into(), None, cx));
|
repo.commit(message.into(), None, options, cx)
|
||||||
|
});
|
||||||
cx.background_spawn(async move { commit_task.await? })
|
cx.background_spawn(async move { commit_task.await? })
|
||||||
} else {
|
} else {
|
||||||
let changed_files = self
|
let changed_files = self
|
||||||
@ -1495,8 +1561,9 @@ impl GitPanel {
|
|||||||
active_repository.update(cx, |repo, cx| repo.stage_entries(changed_files, cx));
|
active_repository.update(cx, |repo, cx| repo.stage_entries(changed_files, cx));
|
||||||
cx.spawn(async move |_, cx| {
|
cx.spawn(async move |_, cx| {
|
||||||
stage_task.await?;
|
stage_task.await?;
|
||||||
let commit_task = active_repository
|
let commit_task = active_repository.update(cx, |repo, cx| {
|
||||||
.update(cx, |repo, cx| repo.commit(message.into(), None, cx))?;
|
repo.commit(message.into(), None, options, cx)
|
||||||
|
})?;
|
||||||
commit_task.await?
|
commit_task.await?
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
@ -2722,6 +2789,34 @@ impl GitPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_git_commit_menu(
|
||||||
|
&self,
|
||||||
|
id: impl Into<ElementId>,
|
||||||
|
keybinding_target: Option<FocusHandle>,
|
||||||
|
) -> impl IntoElement {
|
||||||
|
PopoverMenu::new(id.into())
|
||||||
|
.trigger(
|
||||||
|
ui::ButtonLike::new_rounded_right("commit-split-button-right")
|
||||||
|
.layer(ui::ElevationIndex::ModalSurface)
|
||||||
|
.size(ui::ButtonSize::None)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.px_1()
|
||||||
|
.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())
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
.anchor(Corner::TopRight)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn configure_commit_button(&self, cx: &mut Context<Self>) -> (bool, &'static str) {
|
pub fn configure_commit_button(&self, cx: &mut Context<Self>) -> (bool, &'static str) {
|
||||||
if self.has_unstaged_conflicts() {
|
if self.has_unstaged_conflicts() {
|
||||||
(false, "You must resolve conflicts before committing")
|
(false, "You must resolve conflicts before committing")
|
||||||
@ -2739,12 +2834,20 @@ impl GitPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn commit_button_title(&self) -> &'static str {
|
pub fn commit_button_title(&self) -> &'static str {
|
||||||
|
if self.amend_pending {
|
||||||
|
if self.has_staged_changes() {
|
||||||
|
"Amend"
|
||||||
|
} else {
|
||||||
|
"Amend Tracked"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if self.has_staged_changes() {
|
if self.has_staged_changes() {
|
||||||
"Commit"
|
"Commit"
|
||||||
} else {
|
} else {
|
||||||
"Commit Tracked"
|
"Commit Tracked"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn expand_commit_editor(
|
fn expand_commit_editor(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -2885,6 +2988,10 @@ impl GitPanel {
|
|||||||
let editor_is_long = self.commit_editor.update(cx, |editor, cx| {
|
let editor_is_long = self.commit_editor.update(cx, |editor, cx| {
|
||||||
editor.max_point(cx).row().0 >= MAX_PANEL_EDITOR_LINES as u32
|
editor.max_point(cx).row().0 >= MAX_PANEL_EDITOR_LINES as u32
|
||||||
});
|
});
|
||||||
|
let has_previous_commit = branch
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|branch| branch.most_recent_commit.as_ref())
|
||||||
|
.is_some();
|
||||||
|
|
||||||
let footer = v_flex()
|
let footer = v_flex()
|
||||||
.child(PanelRepoFooter::new(display_name, branch, Some(git_panel)))
|
.child(PanelRepoFooter::new(display_name, branch, Some(git_panel)))
|
||||||
@ -2920,13 +3027,121 @@ impl GitPanel {
|
|||||||
.unwrap_or_else(|| div().into_any_element()),
|
.unwrap_or_else(|| div().into_any_element()),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
h_flex().gap_0p5().children(enable_coauthors).child(
|
h_flex()
|
||||||
|
.gap_0p5()
|
||||||
|
.children(enable_coauthors)
|
||||||
|
.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(move |_, window, cx| {
|
||||||
|
window.dispatch_action(
|
||||||
|
Box::new(git::Amend),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.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(move |_, window, cx| {
|
||||||
|
window
|
||||||
|
.dispatch_action(Box::new(git::Commit), cx);
|
||||||
|
})
|
||||||
|
.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()),
|
||||||
|
)
|
||||||
|
.into_any_element(),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.when(
|
||||||
|
!has_previous_commit,
|
||||||
|
|this| {
|
||||||
|
this.child(
|
||||||
panel_filled_button(title)
|
panel_filled_button(title)
|
||||||
.tooltip(move |window, cx| {
|
.tooltip(move |window, cx| {
|
||||||
if can_commit {
|
if can_commit {
|
||||||
Tooltip::for_action_in(
|
Tooltip::with_meta_in(
|
||||||
tooltip,
|
tooltip,
|
||||||
&Commit,
|
Some(&git::Commit),
|
||||||
|
"git commit",
|
||||||
&commit_tooltip_focus_handle,
|
&commit_tooltip_focus_handle,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
@ -2936,16 +3151,16 @@ impl GitPanel {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.disabled(!can_commit || self.modal_open)
|
.disabled(!can_commit || self.modal_open)
|
||||||
.on_click({
|
.on_click(move |_, window, cx| {
|
||||||
cx.listener(move |this, _: &ClickEvent, window, cx| {
|
window.dispatch_action(
|
||||||
telemetry::event!(
|
Box::new(git::Commit),
|
||||||
"Git Committed",
|
cx,
|
||||||
source = "Git Panel"
|
|
||||||
);
|
);
|
||||||
this.commit_changes(window, cx)
|
|
||||||
})
|
|
||||||
}),
|
}),
|
||||||
),
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
@ -2994,6 +3209,17 @@ impl GitPanel {
|
|||||||
Some(footer)
|
Some(footer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_pending_amend(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
div()
|
||||||
|
.py_2()
|
||||||
|
.px(px(8.))
|
||||||
|
.border_color(cx.theme().colors().border)
|
||||||
|
.child(
|
||||||
|
Label::new("Your changes will modify your most recent commit. If you want to make these changes as a new commit, you can cancel the amend operation.")
|
||||||
|
.size(LabelSize::Small),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn render_previous_commit(&self, cx: &mut Context<Self>) -> Option<impl IntoElement> {
|
fn render_previous_commit(&self, cx: &mut Context<Self>) -> Option<impl IntoElement> {
|
||||||
let active_repository = self.active_repository.as_ref()?;
|
let active_repository = self.active_repository.as_ref()?;
|
||||||
let branch = active_repository.read(cx).branch.as_ref()?;
|
let branch = active_repository.read(cx).branch.as_ref()?;
|
||||||
@ -3448,7 +3674,7 @@ impl GitPanel {
|
|||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_commit_details(
|
pub fn load_commit_details(
|
||||||
&self,
|
&self,
|
||||||
sha: String,
|
sha: String,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
@ -3766,6 +3992,14 @@ impl GitPanel {
|
|||||||
fn has_write_access(&self, cx: &App) -> bool {
|
fn has_write_access(&self, cx: &App) -> bool {
|
||||||
!self.project.read(cx).is_read_only(cx)
|
!self.project.read(cx).is_read_only(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn amend_pending(&self) -> bool {
|
||||||
|
self.amend_pending
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_amend_pending(&mut self, value: bool) {
|
||||||
|
self.amend_pending = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn current_language_model(cx: &Context<'_, GitPanel>) -> Option<Arc<dyn LanguageModel>> {
|
fn current_language_model(cx: &Context<'_, GitPanel>) -> Option<Arc<dyn LanguageModel>> {
|
||||||
@ -3806,6 +4040,8 @@ impl Render for GitPanel {
|
|||||||
.when(has_write_access && !project.is_read_only(cx), |this| {
|
.when(has_write_access && !project.is_read_only(cx), |this| {
|
||||||
this.on_action(cx.listener(Self::toggle_staged_for_selected))
|
this.on_action(cx.listener(Self::toggle_staged_for_selected))
|
||||||
.on_action(cx.listener(GitPanel::commit))
|
.on_action(cx.listener(GitPanel::commit))
|
||||||
|
.on_action(cx.listener(GitPanel::amend))
|
||||||
|
.on_action(cx.listener(GitPanel::cancel))
|
||||||
.on_action(cx.listener(Self::stage_all))
|
.on_action(cx.listener(Self::stage_all))
|
||||||
.on_action(cx.listener(Self::unstage_all))
|
.on_action(cx.listener(Self::unstage_all))
|
||||||
.on_action(cx.listener(Self::stage_selected))
|
.on_action(cx.listener(Self::stage_selected))
|
||||||
@ -3852,7 +4088,12 @@ impl Render for GitPanel {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.children(self.render_footer(window, cx))
|
.children(self.render_footer(window, cx))
|
||||||
.children(self.render_previous_commit(cx))
|
.when(self.amend_pending, |this| {
|
||||||
|
this.child(self.render_pending_amend(cx))
|
||||||
|
})
|
||||||
|
.when(!self.amend_pending, |this| {
|
||||||
|
this.children(self.render_previous_commit(cx))
|
||||||
|
})
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
)
|
)
|
||||||
.children(self.context_menu.as_ref().map(|(menu, position, _)| {
|
.children(self.context_menu.as_ref().map(|(menu, position, _)| {
|
||||||
|
@ -368,6 +368,7 @@ mod remote_button {
|
|||||||
})
|
})
|
||||||
.anchor(Corner::TopRight)
|
.anchor(Corner::TopRight)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn split_button(
|
fn split_button(
|
||||||
id: SharedString,
|
id: SharedString,
|
||||||
|
@ -21,7 +21,7 @@ use git::{
|
|||||||
blame::Blame,
|
blame::Blame,
|
||||||
parse_git_remote_url,
|
parse_git_remote_url,
|
||||||
repository::{
|
repository::{
|
||||||
Branch, CommitDetails, CommitDiff, CommitFile, DiffType, GitRepository,
|
Branch, CommitDetails, CommitDiff, CommitFile, CommitOptions, DiffType, GitRepository,
|
||||||
GitRepositoryCheckpoint, PushOptions, Remote, RemoteCommandOutput, RepoPath, ResetMode,
|
GitRepositoryCheckpoint, PushOptions, Remote, RemoteCommandOutput, RepoPath, ResetMode,
|
||||||
UpstreamTrackingStatus,
|
UpstreamTrackingStatus,
|
||||||
},
|
},
|
||||||
@ -1656,10 +1656,18 @@ impl GitStore {
|
|||||||
let message = SharedString::from(envelope.payload.message);
|
let message = SharedString::from(envelope.payload.message);
|
||||||
let name = envelope.payload.name.map(SharedString::from);
|
let name = envelope.payload.name.map(SharedString::from);
|
||||||
let email = envelope.payload.email.map(SharedString::from);
|
let email = envelope.payload.email.map(SharedString::from);
|
||||||
|
let options = envelope.payload.options.unwrap_or_default();
|
||||||
|
|
||||||
repository_handle
|
repository_handle
|
||||||
.update(&mut cx, |repository_handle, cx| {
|
.update(&mut cx, |repository_handle, cx| {
|
||||||
repository_handle.commit(message, name.zip(email), cx)
|
repository_handle.commit(
|
||||||
|
message,
|
||||||
|
name.zip(email),
|
||||||
|
CommitOptions {
|
||||||
|
amend: options.amend,
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
)
|
||||||
})?
|
})?
|
||||||
.await??;
|
.await??;
|
||||||
Ok(proto::Ack {})
|
Ok(proto::Ack {})
|
||||||
@ -3248,6 +3256,7 @@ impl Repository {
|
|||||||
&mut self,
|
&mut self,
|
||||||
message: SharedString,
|
message: SharedString,
|
||||||
name_and_email: Option<(SharedString, SharedString)>,
|
name_and_email: Option<(SharedString, SharedString)>,
|
||||||
|
options: CommitOptions,
|
||||||
_cx: &mut App,
|
_cx: &mut App,
|
||||||
) -> oneshot::Receiver<Result<()>> {
|
) -> oneshot::Receiver<Result<()>> {
|
||||||
let id = self.id;
|
let id = self.id;
|
||||||
@ -3258,7 +3267,11 @@ impl Repository {
|
|||||||
backend,
|
backend,
|
||||||
environment,
|
environment,
|
||||||
..
|
..
|
||||||
} => backend.commit(message, name_and_email, environment).await,
|
} => {
|
||||||
|
backend
|
||||||
|
.commit(message, name_and_email, options, environment)
|
||||||
|
.await
|
||||||
|
}
|
||||||
RepositoryState::Remote { project_id, client } => {
|
RepositoryState::Remote { project_id, client } => {
|
||||||
let (name, email) = name_and_email.unzip();
|
let (name, email) = name_and_email.unzip();
|
||||||
client
|
client
|
||||||
@ -3268,6 +3281,9 @@ impl Repository {
|
|||||||
message: String::from(message),
|
message: String::from(message),
|
||||||
name: name.map(String::from),
|
name: name.map(String::from),
|
||||||
email: email.map(String::from),
|
email: email.map(String::from),
|
||||||
|
options: Some(proto::commit::CommitOptions {
|
||||||
|
amend: options.amend,
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.context("sending commit request")?;
|
.context("sending commit request")?;
|
||||||
|
@ -292,6 +292,11 @@ message Commit {
|
|||||||
optional string name = 4;
|
optional string name = 4;
|
||||||
optional string email = 5;
|
optional string email = 5;
|
||||||
string message = 6;
|
string message = 6;
|
||||||
|
optional CommitOptions options = 7;
|
||||||
|
|
||||||
|
message CommitOptions {
|
||||||
|
bool amend = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message OpenCommitMessageBuffer {
|
message OpenCommitMessageBuffer {
|
||||||
|
@ -20,6 +20,12 @@ pub struct SplitButton {
|
|||||||
pub right: AnyElement,
|
pub right: AnyElement,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SplitButton {
|
||||||
|
pub fn new(left: ButtonLike, right: AnyElement) -> Self {
|
||||||
|
Self { left, right }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl RenderOnce for SplitButton {
|
impl RenderOnce for SplitButton {
|
||||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
h_flex()
|
h_flex()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user