Compare commits

...

1 Commits

Author SHA1 Message Date
Miguel Raz Guzmán Macedo
fc9c3456e7 draft: Add git commit --cleanup=strip/--cleanup=whitespace option 2025-11-20 11:49:30 -06:00
6 changed files with 89 additions and 62 deletions

View File

@@ -12,7 +12,7 @@ use gpui::{AppContext as _, AsyncApp, BackgroundExecutor, SharedString, Task};
use parking_lot::Mutex; use parking_lot::Mutex;
use rope::Rope; use rope::Rope;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::Deserialize; use serde::{Deserialize, Serialize};
use smol::io::{AsyncBufReadExt, AsyncReadExt, BufReader}; use smol::io::{AsyncBufReadExt, AsyncReadExt, BufReader};
use std::ffi::{OsStr, OsString}; use std::ffi::{OsStr, OsString};
use std::process::{ExitStatus, Stdio}; use std::process::{ExitStatus, Stdio};
@@ -137,10 +137,18 @@ impl Upstream {
} }
} }
#[derive(Clone, Copy, Debug, Default)]
pub enum CommitMessageCleanup {
#[default]
Strip,
Whitespace,
}
#[derive(Clone, Copy, Default)] #[derive(Clone, Copy, Default)]
pub struct CommitOptions { pub struct CommitOptions {
pub amend: bool, pub amend: bool,
pub signoff: bool, pub signoff: bool,
pub cleanup: CommitMessageCleanup,
} }
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
@@ -1637,12 +1645,16 @@ impl GitRepository for RealGitRepository {
let git_binary_path = self.any_git_binary_path.clone(); let git_binary_path = self.any_git_binary_path.clone();
let executor = self.executor.clone(); let executor = self.executor.clone();
async move { async move {
let cleanup_kind = match options.cleanup {
CommitMessageCleanup::Strip => "--cleanup=strip",
CommitMessageCleanup::Whitespace => "--cleanup=whitespace",
};
let mut cmd = new_smol_command(git_binary_path); let mut cmd = new_smol_command(git_binary_path);
cmd.current_dir(&working_directory?) cmd.current_dir(&working_directory?)
.envs(env.iter()) .envs(env.iter())
.args(["commit", "--quiet", "-m"]) .args(["commit", "--quiet", "-m"])
.arg(&message.to_string()) .arg(&message.to_string())
.arg("--cleanup=strip") .arg(cleanup_kind)
.stdout(smol::process::Stdio::piped()) .stdout(smol::process::Stdio::piped())
.stderr(smol::process::Stdio::piped()); .stderr(smol::process::Stdio::piped());

View File

@@ -1,6 +1,5 @@
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::repository::CommitOptions;
use git::{Amend, Commit, GenerateCommitMessage, Signoff}; use git::{Amend, Commit, GenerateCommitMessage, Signoff};
use panel::{panel_button, panel_editor_style}; use panel::{panel_button, panel_editor_style};
use project::DisableAiSettings; use project::DisableAiSettings;
@@ -439,10 +438,8 @@ impl CommitModal {
telemetry::event!("Git Committed", source = "Git Modal"); telemetry::event!("Git Committed", source = "Git Modal");
this.git_panel.update(cx, |git_panel, cx| { this.git_panel.update(cx, |git_panel, cx| {
git_panel.commit_changes( git_panel.commit_changes(
CommitOptions { is_amend_pending,
amend: is_amend_pending, is_signoff_enabled,
signoff: is_signoff_enabled,
},
window, window,
cx, cx,
) )
@@ -498,14 +495,7 @@ impl CommitModal {
} }
telemetry::event!("Git Committed", source = "Git Modal"); telemetry::event!("Git Committed", source = "Git Modal");
self.git_panel.update(cx, |git_panel, cx| { self.git_panel.update(cx, |git_panel, cx| {
git_panel.commit_changes( git_panel.commit_changes(false, git_panel.signoff_enabled(), window, cx)
CommitOptions {
amend: false,
signoff: git_panel.signoff_enabled(),
},
window,
cx,
)
}); });
cx.emit(DismissEvent); cx.emit(DismissEvent);
} }
@@ -530,14 +520,7 @@ impl CommitModal {
telemetry::event!("Git Amended", source = "Git Modal"); telemetry::event!("Git Amended", source = "Git Modal");
self.git_panel.update(cx, |git_panel, cx| { self.git_panel.update(cx, |git_panel, cx| {
git_panel.set_amend_pending(false, cx); git_panel.set_amend_pending(false, cx);
git_panel.commit_changes( git_panel.commit_changes(true, git_panel.signoff_enabled(), window, cx);
CommitOptions {
amend: true,
signoff: git_panel.signoff_enabled(),
},
window,
cx,
);
}); });
cx.emit(DismissEvent); cx.emit(DismissEvent);
} }

View File

@@ -1538,14 +1538,7 @@ impl GitPanel {
.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( self.commit_changes(false, self.signoff_enabled, window, cx)
CommitOptions {
amend: false,
signoff: self.signoff_enabled,
},
window,
cx,
)
} else { } else {
cx.propagate(); cx.propagate();
} }
@@ -1563,14 +1556,7 @@ impl GitPanel {
self.load_last_commit_message_if_empty(cx); self.load_last_commit_message_if_empty(cx);
} else { } else {
telemetry::event!("Git Amended", source = "Git Panel"); telemetry::event!("Git Amended", source = "Git Panel");
self.commit_changes( self.commit_changes(true, self.signoff_enabled, window, cx);
CommitOptions {
amend: true,
signoff: self.signoff_enabled,
},
window,
cx,
);
} }
} }
} else { } else {
@@ -1657,7 +1643,8 @@ impl GitPanel {
pub(crate) fn commit_changes( pub(crate) fn commit_changes(
&mut self, &mut self,
options: CommitOptions, amend: bool,
signoff: bool,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
@@ -1696,7 +1683,7 @@ 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 = active_repository.update(cx, |repo, cx| { let commit_task = active_repository.update(cx, |repo, cx| {
repo.commit(message.into(), None, options, askpass, cx) repo.commit(message.into(), None, amend, signoff, askpass, cx)
}); });
cx.background_spawn(async move { commit_task.await? }) cx.background_spawn(async move { commit_task.await? })
} else { } else {
@@ -1708,7 +1695,7 @@ impl GitPanel {
.map(|status_entry| status_entry.repo_path.clone()) .map(|status_entry| status_entry.repo_path.clone())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if changed_files.is_empty() && !options.amend { if changed_files.is_empty() && !amend {
error_spawn("No changes to commit", window, cx); error_spawn("No changes to commit", window, cx);
return; return;
} }
@@ -1718,7 +1705,7 @@ impl GitPanel {
cx.spawn(async move |_, cx| { cx.spawn(async move |_, cx| {
stage_task.await?; stage_task.await?;
let commit_task = active_repository.update(cx, |repo, cx| { let commit_task = active_repository.update(cx, |repo, cx| {
repo.commit(message.into(), None, options, askpass, cx) repo.commit(message.into(), None, amend, signoff, askpass, cx)
})?; })?;
commit_task.await? commit_task.await?
}) })
@@ -1740,7 +1727,7 @@ impl GitPanel {
}); });
self.pending_commit = Some(task); self.pending_commit = Some(task);
if options.amend { if amend {
self.set_amend_pending(false, cx); self.set_amend_pending(false, cx);
} }
} }
@@ -3646,11 +3633,7 @@ impl GitPanel {
telemetry::event!("Git Committed", source = "Git Panel"); telemetry::event!("Git Committed", source = "Git Panel");
git_panel git_panel
.update(cx, |git_panel, cx| { .update(cx, |git_panel, cx| {
git_panel.commit_changes( git_panel.commit_changes(amend, signoff, window, cx);
CommitOptions { amend, signoff },
window,
cx,
);
}) })
.ok(); .ok();
} }

View File

@@ -6,6 +6,7 @@ pub mod pending_op;
use crate::{ use crate::{
ProjectEnvironment, ProjectItem, ProjectPath, ProjectEnvironment, ProjectItem, ProjectPath,
buffer_store::{BufferStore, BufferStoreEvent}, buffer_store::{BufferStore, BufferStoreEvent},
project_settings::{GitSettings, ProjectSettings},
worktree_store::{WorktreeStore, WorktreeStoreEvent}, worktree_store::{WorktreeStore, WorktreeStoreEvent},
}; };
use anyhow::{Context as _, Result, anyhow, bail}; use anyhow::{Context as _, Result, anyhow, bail};
@@ -55,7 +56,7 @@ use rpc::{
proto::{self, git_reset, split_repository_update}, proto::{self, git_reset, split_repository_update},
}; };
use serde::Deserialize; use serde::Deserialize;
use settings::WorktreeId; use settings::{Settings as _, WorktreeId};
use std::{ use std::{
cmp::Ordering, cmp::Ordering,
collections::{BTreeSet, HashSet, VecDeque}, collections::{BTreeSet, HashSet, VecDeque},
@@ -2009,10 +2010,8 @@ impl GitStore {
repository_handle.commit( repository_handle.commit(
message, message,
name.zip(email), name.zip(email),
CommitOptions { options.amend,
amend: options.amend, options.signoff,
signoff: options.signoff,
},
askpass, askpass,
cx, cx,
) )
@@ -4266,21 +4265,41 @@ impl Repository {
&mut self, &mut self,
message: SharedString, message: SharedString,
name_and_email: Option<(SharedString, SharedString)>, name_and_email: Option<(SharedString, SharedString)>,
options: CommitOptions, amend: bool,
signoff: bool,
askpass: AskPassDelegate, askpass: AskPassDelegate,
_cx: &mut App, cx: &mut App,
) -> oneshot::Receiver<Result<()>> { ) -> oneshot::Receiver<Result<()>> {
let id = self.id; let id = self.id;
let askpass_delegates = self.askpass_delegates.clone(); let askpass_delegates = self.askpass_delegates.clone();
let askpass_id = util::post_inc(&mut self.latest_askpass_id); let askpass_id = util::post_inc(&mut self.latest_askpass_id);
let project_path =
self.send_job(Some("git commit".into()), move |git_repo, _cx| async move { self.repo_path_to_project_path(&RepoPath::from_rel_path(RelPath::empty()), cx);
self.send_job(Some("git commit".into()), move |git_repo, cx| async move {
match git_repo { match git_repo {
RepositoryState::Local { RepositoryState::Local {
backend, backend,
environment, environment,
.. ..
} => { } => {
let cleanup = cx.update(|cx| {
ProjectSettings::get(
project_path
.as_ref()
.map(|project_path| settings::SettingsLocation {
worktree_id: project_path.worktree_id,
path: &project_path.path,
}),
cx,
)
.git
.commit_cleanup
})?;
let options = CommitOptions {
amend,
signoff,
cleanup,
};
backend backend
.commit(message, name_and_email, options, askpass, environment) .commit(message, name_and_email, options, askpass, environment)
.await .await
@@ -4299,10 +4318,7 @@ 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 { options: Some(proto::commit::CommitOptions { amend, signoff }),
amend: options.amend,
signoff: options.signoff,
}),
askpass_id, askpass_id,
}) })
.await .await

View File

@@ -4,6 +4,7 @@ use context_server::ContextServerCommand;
use dap::adapters::DebugAdapterName; use dap::adapters::DebugAdapterName;
use fs::Fs; use fs::Fs;
use futures::StreamExt as _; use futures::StreamExt as _;
use git::repository::CommitMessageCleanup;
use gpui::{AsyncApp, BorrowAppContext, Context, Entity, EventEmitter, Subscription, Task}; use gpui::{AsyncApp, BorrowAppContext, Context, Entity, EventEmitter, Subscription, Task};
use lsp::LanguageServerName; use lsp::LanguageServerName;
use paths::{ use paths::{
@@ -352,6 +353,10 @@ pub struct GitSettings {
/// ///
/// Default: file_name_first /// Default: file_name_first
pub path_style: GitPathStyle, pub path_style: GitPathStyle,
/// How commit cleanup is handled in the commit message.
///
/// Default: strip
pub commit_cleanup: CommitMessageCleanup,
} }
#[derive(Clone, Copy, Debug, PartialEq, Default)] #[derive(Clone, Copy, Debug, PartialEq, Default)]
@@ -522,6 +527,10 @@ impl Settings for ProjectSettings {
}, },
hunk_style: git.hunk_style.unwrap(), hunk_style: git.hunk_style.unwrap(),
path_style: git.path_style.unwrap().into(), path_style: git.path_style.unwrap().into(),
commit_cleanup: match git.commit_cleanup.unwrap_or_default() {
settings::CommitMessageCleanup::Strip => CommitMessageCleanup::Strip,
settings::CommitMessageCleanup::Whitespace => CommitMessageCleanup::Whitespace,
},
}; };
Self { Self {
context_servers: project context_servers: project

View File

@@ -315,6 +315,10 @@ pub struct GitSettings {
/// ///
/// Default: file_name_first /// Default: file_name_first
pub path_style: Option<GitPathStyle>, pub path_style: Option<GitPathStyle>,
/// How to cleanup a git commit message.
///
/// Default: strip
pub commit_cleanup: Option<CommitMessageCleanup>,
} }
#[derive( #[derive(
@@ -432,6 +436,26 @@ pub enum GitPathStyle {
FilePathFirst, FilePathFirst,
} }
#[derive(
Copy,
Clone,
Debug,
PartialEq,
Default,
Serialize,
Deserialize,
JsonSchema,
MergeFrom,
strum::VariantArray,
strum::VariantNames,
)]
#[serde(rename_all = "snake_case")]
pub enum CommitMessageCleanup {
#[default]
Strip,
Whitespace,
}
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct DiagnosticsSettingsContent { pub struct DiagnosticsSettingsContent {