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, pub mainline: u32, pub update_ref: Option, } #[derive(Clone, Deserialize, Debug, Serialize)] pub struct CommitCherryPickSequence { pub cherrypick_oids: Vec, pub author: CommitSignature, pub committer: CommitSignature, pub update_ref: Option, } impl GitBare { pub fn commit_pick( &self, params: CommitCherryPickParams, ) -> GitResult { 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 { let mut last_oid: Option = 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()) }) } }