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

187 lines
6.4 KiB
Rust

use serde::{Deserialize, Serialize};
use crate::{
bare::GitBare,
cmd::{
commit::CommitSignature, oid::ObjectId, parse::format_git_timestamp,
},
errors::{GitError, GitResult},
};
#[derive(Clone, Deserialize, Debug, Serialize)]
pub struct CommitCherryPickParams {
pub cherrypick_oid: ObjectId,
pub author: CommitSignature,
pub committer: CommitSignature,
pub message: Option<String>,
pub mainline: u32,
pub update_ref: Option<String>,
}
#[derive(Clone, Deserialize, Debug, Serialize)]
pub struct CommitCherryPickSequence {
pub cherrypick_oids: Vec<ObjectId>,
pub author: CommitSignature,
pub committer: CommitSignature,
pub update_ref: Option<String>,
}
impl GitBare {
pub fn commit_pick(
&self,
params: CommitCherryPickParams,
) -> GitResult<ObjectId> {
let cherry_info = self.commit_info(params.cherrypick_oid.clone())?;
let their_tree = cherry_info.tree_id;
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 head_info = self.commit_info(head_oid.clone())?;
let our_tree = head_info.tree_id;
let base_result =
self.merge_base(head_oid.clone(), params.cherrypick_oid.clone());
let base_tree = match base_result {
Ok(base_oid) => {
let base_info = self.commit_info(base_oid)?;
base_info.tree_id.as_str().to_string()
}
Err(_) => {
let empty_tree = "4b825dc642cb6eb9a060e54bf899d15363c725d7";
let empty_tree_gix_id: gix::hash::ObjectId =
gix::hash::ObjectId::from_hex(empty_tree.as_bytes())
.map_err(|e| GitError::Gix(e.to_string()))?;
if repo.has_object(empty_tree_gix_id) {
empty_tree.to_string()
} else {
let mktree_output =
self.git_command_trusted_stdout(vec![
"mktree".to_string(),
])?;
mktree_output.trim().to_string()
}
}
};
let merge_tree_args = vec![
"merge-tree".to_string(),
"--write-tree".to_string(),
"--merge-base".to_string(),
base_tree.clone(),
our_tree.as_str().to_string(),
their_tree.as_str().to_string(),
];
let merge_output = self.git_command_trusted(merge_tree_args);
let merged_tree_id = match merge_output {
Ok(output) => {
if output.success {
ObjectId::new(output.stdout_lossy().trim())
} else {
return Err(GitError::CommandFailed {
status_code: output.status_code,
stderr: output.stderr_lossy(),
});
}
}
Err(err) => return Err(err),
};
let message = params
.message
.clone()
.unwrap_or(cherry_info.message.clone());
let mut commit_args = vec![
"commit-tree".to_string(),
merged_tree_id.as_str().to_string(),
"-p".to_string(),
head_oid.as_str().to_string(),
];
if cherry_info.parent_ids.len() > 1 {
commit_args.push("-m".to_string());
commit_args.push(format!(
"cherry picked from commit {}",
params.cherrypick_oid.as_str()
));
if params.mainline > 0 {
commit_args.push(format!("(mainline {})", params.mainline));
}
}
let cmd_params =
crate::cmd::command::GitCommandParams::new(commit_args)
.trusted()
.with_env(
"GIT_AUTHOR_NAME".to_string(),
params.author.name.clone(),
)
.with_env(
"GIT_AUTHOR_EMAIL".to_string(),
params.author.email.clone(),
)
.with_env(
"GIT_AUTHOR_DATE".to_string(),
format_git_timestamp(
params.author.time_secs,
params.author.offset_minutes,
),
)
.with_env(
"GIT_COMMITTER_NAME".to_string(),
params.committer.name.clone(),
)
.with_env(
"GIT_COMMITTER_EMAIL".to_string(),
params.committer.email.clone(),
)
.with_env(
"GIT_COMMITTER_DATE".to_string(),
format_git_timestamp(
params.committer.time_secs,
params.committer.offset_minutes,
),
)
.with_stdin(message.as_bytes().to_vec());
let commit_output = self.git_command_with(cmd_params)?;
if !commit_output.success {
return Err(GitError::CommandFailed {
status_code: commit_output.status_code,
stderr: commit_output.stderr_lossy(),
});
}
let new_oid = ObjectId::new(commit_output.stdout_lossy().trim());
if let Some(ref_name) = &params.update_ref {
self.git_command_trusted(vec![
"update-ref".to_string(),
ref_name.clone(),
new_oid.as_str().to_string(),
])?;
}
Ok(new_oid)
}
pub fn commit_cherry_pick_sequence(
&self,
params: CommitCherryPickSequence,
) -> GitResult<ObjectId> {
let mut last_oid: Option<ObjectId> = None;
for cherry_oid in params.cherrypick_oids {
let pick_params = CommitCherryPickParams {
cherrypick_oid: cherry_oid,
author: params.author.clone(),
committer: params.committer.clone(),
message: None, // Use original commit message
mainline: 1, // Default mainline
update_ref: params.update_ref.clone(),
};
last_oid = Some(self.commit_pick(pick_params)?);
}
last_oid.ok_or_else(|| {
GitError::ParseError("cherry-pick sequence was empty".to_string())
})
}
}