feat(commit): 添加提交消息模板支持
- 移除 config 命令中未使用的 List 子命令及相关显示字段 - 统一 ChangelogCommand 和 CommitCommand 的 ContentGenerator 初始化方式
This commit is contained in:
@@ -124,6 +124,11 @@ impl GitProfile {
|
||||
.or_else(|| self.gpg.as_ref().map(|g| g.key_id.as_str()))
|
||||
}
|
||||
|
||||
/// Get the commit template if set
|
||||
pub fn commit_template(&self) -> Option<&str> {
|
||||
self.settings.commit_template.as_deref()
|
||||
}
|
||||
|
||||
/// Add a token to the profile
|
||||
pub fn add_token(&mut self, service: String, token: TokenConfig) {
|
||||
self.tokens.insert(service, token);
|
||||
@@ -159,78 +164,120 @@ impl GitProfile {
|
||||
pub fn apply_to_repo(&self, repo: &git2::Repository) -> Result<()> {
|
||||
let mut config = repo.config()?;
|
||||
|
||||
config.set_str("user.name", &self.user_name)?;
|
||||
config.set_str("user.email", &self.user_email)?;
|
||||
|
||||
if let Some(key) = self.signing_key() {
|
||||
config.set_str("user.signingkey", key)?;
|
||||
|
||||
if self.settings.auto_sign_commits {
|
||||
config.set_bool("commit.gpgsign", true)?;
|
||||
}
|
||||
|
||||
if self.settings.auto_sign_tags {
|
||||
config.set_bool("tag.gpgsign", true)?;
|
||||
}
|
||||
// Clean up old managed keys that the new profile won't set
|
||||
if self.ssh.as_ref().and_then(|s| s.git_ssh_command()).is_none() {
|
||||
let _ = config.remove("core.sshCommand");
|
||||
}
|
||||
if self.signing_key().is_none() {
|
||||
let _ = config.remove("user.signingkey");
|
||||
let _ = config.remove("commit.gpgsign");
|
||||
let _ = config.remove("tag.gpgsign");
|
||||
}
|
||||
if self.gpg.is_none() {
|
||||
let _ = config.remove("gpg.program");
|
||||
}
|
||||
|
||||
if let Some(ref ssh) = self.ssh
|
||||
&& let Some(ref key_path) = ssh.private_key_path
|
||||
{
|
||||
let path_str = key_path.display().to_string();
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
config.set_str(
|
||||
"core.sshCommand",
|
||||
&format!("ssh -i \"{}\"", path_str.replace('\\', "/")),
|
||||
)?;
|
||||
// Apply new values; track whether we've written past name/email for rollback
|
||||
let mut wrote_optional = false;
|
||||
let result = (|| -> Result<()> {
|
||||
config.set_str("user.name", &self.user_name)?;
|
||||
config.set_str("user.email", &self.user_email)?;
|
||||
|
||||
if let Some(ref gpg) = self.gpg {
|
||||
config.set_str("gpg.program", &gpg.program)?;
|
||||
wrote_optional = true;
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
config.set_str("core.sshCommand", &format!("ssh -i '{}'", path_str))?;
|
||||
|
||||
if let Some(key) = self.signing_key() {
|
||||
config.set_str("user.signingkey", key)?;
|
||||
if self.settings.auto_sign_commits {
|
||||
config.set_bool("commit.gpgsign", true)?;
|
||||
}
|
||||
if self.settings.auto_sign_tags {
|
||||
config.set_bool("tag.gpgsign", true)?;
|
||||
}
|
||||
wrote_optional = true;
|
||||
}
|
||||
|
||||
if let Some(ref ssh) = self.ssh {
|
||||
if let Some(ssh_cmd) = ssh.git_ssh_command() {
|
||||
config.set_str("core.sshCommand", &ssh_cmd)?;
|
||||
wrote_optional = true;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})();
|
||||
|
||||
if result.is_err() && wrote_optional {
|
||||
let _ = config.remove("core.sshCommand");
|
||||
let _ = config.remove("user.signingkey");
|
||||
let _ = config.remove("commit.gpgsign");
|
||||
let _ = config.remove("tag.gpgsign");
|
||||
let _ = config.remove("gpg.program");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
result
|
||||
}
|
||||
|
||||
/// Apply this profile globally
|
||||
pub fn apply_global(&self) -> Result<()> {
|
||||
let mut config = git2::Config::open_default()?;
|
||||
|
||||
config.set_str("user.name", &self.user_name)?;
|
||||
config.set_str("user.email", &self.user_email)?;
|
||||
|
||||
if let Some(key) = self.signing_key() {
|
||||
config.set_str("user.signingkey", key)?;
|
||||
|
||||
if self.settings.auto_sign_commits {
|
||||
config.set_bool("commit.gpgsign", true)?;
|
||||
}
|
||||
|
||||
if self.settings.auto_sign_tags {
|
||||
config.set_bool("tag.gpgsign", true)?;
|
||||
}
|
||||
// Clean up old managed keys that the new profile won't set
|
||||
if self.ssh.as_ref().and_then(|s| s.git_ssh_command()).is_none() {
|
||||
let _ = config.remove("core.sshCommand");
|
||||
}
|
||||
if self.signing_key().is_none() {
|
||||
let _ = config.remove("user.signingkey");
|
||||
let _ = config.remove("commit.gpgsign");
|
||||
let _ = config.remove("tag.gpgsign");
|
||||
}
|
||||
if self.gpg.is_none() {
|
||||
let _ = config.remove("gpg.program");
|
||||
}
|
||||
|
||||
if let Some(ref ssh) = self.ssh
|
||||
&& let Some(ref key_path) = ssh.private_key_path
|
||||
{
|
||||
let path_str = key_path.display().to_string();
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
config.set_str(
|
||||
"core.sshCommand",
|
||||
&format!("ssh -i \"{}\"", path_str.replace('\\', "/")),
|
||||
)?;
|
||||
// Apply new values; track whether we've written past name/email for rollback
|
||||
let mut wrote_optional = false;
|
||||
let result = (|| -> Result<()> {
|
||||
config.set_str("user.name", &self.user_name)?;
|
||||
config.set_str("user.email", &self.user_email)?;
|
||||
|
||||
if let Some(ref gpg) = self.gpg {
|
||||
config.set_str("gpg.program", &gpg.program)?;
|
||||
wrote_optional = true;
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
config.set_str("core.sshCommand", &format!("ssh -i '{}'", path_str))?;
|
||||
|
||||
if let Some(key) = self.signing_key() {
|
||||
config.set_str("user.signingkey", key)?;
|
||||
if self.settings.auto_sign_commits {
|
||||
config.set_bool("commit.gpgsign", true)?;
|
||||
}
|
||||
if self.settings.auto_sign_tags {
|
||||
config.set_bool("tag.gpgsign", true)?;
|
||||
}
|
||||
wrote_optional = true;
|
||||
}
|
||||
|
||||
if let Some(ref ssh) = self.ssh {
|
||||
if let Some(ssh_cmd) = ssh.git_ssh_command() {
|
||||
config.set_str("core.sshCommand", &ssh_cmd)?;
|
||||
wrote_optional = true;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})();
|
||||
|
||||
if result.is_err() && wrote_optional {
|
||||
let _ = config.remove("core.sshCommand");
|
||||
let _ = config.remove("user.signingkey");
|
||||
let _ = config.remove("commit.gpgsign");
|
||||
let _ = config.remove("tag.gpgsign");
|
||||
let _ = config.remove("gpg.program");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
result
|
||||
}
|
||||
|
||||
/// Compare with current git configuration
|
||||
@@ -349,25 +396,72 @@ impl SshConfig {
|
||||
bail!("SSH public key does not exist: {:?}", path);
|
||||
}
|
||||
|
||||
if let Some(ref path) = self.known_hosts_file
|
||||
&& !path.exists()
|
||||
{
|
||||
bail!("SSH known_hosts file does not exist: {:?}", path);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get SSH command for git
|
||||
/// Get the effective public key path, deriving from private key if not explicitly set
|
||||
pub fn effective_public_key_path(&self) -> Option<std::path::PathBuf> {
|
||||
self.public_key_path.clone().or_else(|| {
|
||||
self.private_key_path.as_ref().map(|pk| {
|
||||
let mut pub_path = pk.clone();
|
||||
let ext = pk
|
||||
.extension()
|
||||
.map(|e| format!("{}.pub", e.to_string_lossy()))
|
||||
.unwrap_or_else(|| "pub".to_string());
|
||||
pub_path.set_extension(&ext);
|
||||
pub_path
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the effective SSH command for git config
|
||||
///
|
||||
/// Priority: custom `ssh_command` > constructed from key/agent/known_hosts
|
||||
pub fn git_ssh_command(&self) -> Option<String> {
|
||||
if let Some(ref cmd) = self.ssh_command {
|
||||
Some(cmd.clone())
|
||||
} else if let Some(ref key_path) = self.private_key_path {
|
||||
return Some(cmd.clone());
|
||||
}
|
||||
|
||||
let mut parts: Vec<String> = vec!["ssh".to_string()];
|
||||
|
||||
if self.agent_forwarding {
|
||||
parts.push("-A".to_string());
|
||||
}
|
||||
|
||||
if let Some(ref key_path) = self.private_key_path {
|
||||
let path_str = key_path.display().to_string();
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
Some(format!("ssh -i \"{}\"", path_str.replace('\\', "/")))
|
||||
parts.push(format!("-i \"{}\"", path_str.replace('\\', "/")));
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
Some(format!("ssh -i '{}'", path_str))
|
||||
parts.push(format!("-i '{}'", path_str));
|
||||
}
|
||||
} else {
|
||||
}
|
||||
|
||||
if let Some(ref known_hosts) = self.known_hosts_file {
|
||||
let kh_str = known_hosts.display().to_string();
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
parts.push(format!("-o UserKnownHostsFile=\"{}\"", kh_str.replace('\\', "/")));
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
parts.push(format!("-o UserKnownHostsFile='{}'", kh_str));
|
||||
}
|
||||
}
|
||||
|
||||
if parts.len() == 1 {
|
||||
None
|
||||
} else {
|
||||
Some(parts.join(" "))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user