187 lines
6.4 KiB
Rust
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) = ¶ms.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())
|
|
})
|
|
}
|
|
}
|