gitdataai/lib/git/cmd/merge/squash_commit.rs
2026-05-30 01:38:40 +08:00

121 lines
4.2 KiB
Rust

use serde::{Deserialize, Serialize};
use crate::{
bare::GitBare,
cmd::{merge::MergeOptions, oid::ObjectId, parse::format_git_timestamp},
errors::{GitError, GitResult},
};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SquashCommitParams {
pub their_commit: ObjectId,
pub options: Option<MergeOptions>,
}
impl GitBare {
pub fn squash_commit(
&self,
params: SquashCommitParams,
) -> GitResult<ObjectId> {
let repo = self.gix_repo()?;
let head_id =
repo.head_id().map_err(|e| GitError::Gix(e.to_string()))?;
let head_oid = ObjectId::new(head_id.detach().to_hex().to_string());
let mut merge_tree_args =
vec!["merge-tree".to_string(), "--write-tree".to_string()];
if let Some(opts) = &params.options {
if opts.find_renames {
merge_tree_args.push("--find-renames".to_string());
if opts.rename_threshold > 0 {
merge_tree_args.push(format!(
"--rename-threshold={}",
opts.rename_threshold
));
}
}
if opts.fail_on_conflict {
merge_tree_args.push("--fail-on-conflict".to_string());
}
}
merge_tree_args.push(head_oid.as_str().to_string());
merge_tree_args.push(params.their_commit.as_str().to_string());
let merge_tree_output = self.git_command_with(
crate::cmd::command::GitCommandParams::new(merge_tree_args)
.unchecked(),
)?;
let stdout = merge_tree_output.stdout_lossy();
let tree_oid_str = stdout.lines().next().unwrap_or("").trim();
if tree_oid_str.is_empty() {
if !merge_tree_output.success {
return Err(GitError::CommandFailed {
status_code: merge_tree_output.status_code,
stderr: merge_tree_output.stderr_lossy(),
});
}
return Err(GitError::ParseError(
"merge-tree produced no tree OID".to_string(),
));
}
let tree_oid = ObjectId::new(tree_oid_str);
let their_info = self.commit_info(params.their_commit.clone())?;
let squash_message = their_info.message.clone();
let commit_tree_args = vec![
"commit-tree".to_string(),
tree_oid.as_str().to_string(),
"-p".to_string(),
head_oid.as_str().to_string(),
"-F".to_string(),
"-".to_string(),
];
let author_timestamp = format_git_timestamp(
their_info.author.time_secs,
their_info.author.offset_minutes,
);
let committer_timestamp = format_git_timestamp(
their_info.committer.time_secs,
their_info.committer.offset_minutes,
);
let commit_output = self.git_command_with(
crate::cmd::command::GitCommandParams::new(commit_tree_args)
.with_stdin(squash_message.as_bytes().to_vec())
.with_env(
"GIT_AUTHOR_NAME".to_string(),
their_info.author.name.clone(),
)
.with_env(
"GIT_AUTHOR_EMAIL".to_string(),
their_info.author.email.clone(),
)
.with_env("GIT_AUTHOR_DATE".to_string(), author_timestamp)
.with_env(
"GIT_COMMITTER_NAME".to_string(),
their_info.committer.name.clone(),
)
.with_env(
"GIT_COMMITTER_EMAIL".to_string(),
their_info.committer.email.clone(),
)
.with_env(
"GIT_COMMITTER_DATE".to_string(),
committer_timestamp,
),
)?;
let stdout_str = commit_output.stdout_lossy();
let commit_oid_str = stdout_str.trim();
if commit_oid_str.is_empty() {
return Err(GitError::CommandFailed {
status_code: commit_output.status_code,
stderr: commit_output.stderr_lossy(),
});
}
Ok(ObjectId::new(commit_oid_str))
}
}