use git2::{Oid, RebaseOptions, Repository};
use super::{
commit::signature_allow_undefined_name,
repo,
utils::{bytes2string, get_head_refname, get_head_repo},
CommitId, RepoPath,
};
use crate::error::{Error, Result};
pub fn reword(
repo_path: &RepoPath,
commit: CommitId,
message: &str,
) -> Result<CommitId> {
let repo = repo(repo_path)?;
let config = repo.config()?;
if config.get_bool("commit.gpgsign").unwrap_or(false) {
use crate::sync::utils::undo_last_commit;
let head = get_head_repo(&repo)?;
if head == commit {
let parent = repo.find_commit(head.into())?;
let tree = parent.tree()?;
if repo
.diff_tree_to_index(Some(&tree), None, None)?
.deltas()
.len() == 0
{
undo_last_commit(repo_path)?;
return super::commit(repo_path, message);
}
return Err(Error::SignRewordLastCommitStaged);
}
return Err(Error::SignRewordNonLastCommit);
}
let cur_branch_ref = get_head_refname(&repo)?;
match reword_internal(&repo, commit.get_oid(), message) {
Ok(id) => Ok(id.into()),
Err(e) => {
if let Ok(mut rebase) = repo.open_rebase(None) {
rebase.abort()?;
repo.set_head(&cur_branch_ref)?;
repo.checkout_head(None)?;
}
Err(e)
}
}
}
fn get_current_branch(
repo: &Repository,
) -> Result<Option<git2::Branch>> {
for b in repo.branches(None)? {
let branch = b?.0;
if branch.is_head() {
return Ok(Some(branch));
}
}
Ok(None)
}
fn reword_internal(
repo: &Repository,
commit: Oid,
message: &str,
) -> Result<Oid> {
let sig = signature_allow_undefined_name(repo)?;
let parent_commit_oid = repo
.find_commit(commit)?
.parent(0)
.map_or(None, |parent_commit| Some(parent_commit.id()));
let commit_to_change = if let Some(pc_oid) = parent_commit_oid {
repo.find_annotated_commit(pc_oid)?
} else {
return Err(Error::NoParent);
};
if let Ok(Some(branch)) = get_current_branch(repo) {
let cur_branch_ref = bytes2string(branch.get().name_bytes())?;
let cur_branch_name = bytes2string(branch.name_bytes()?)?;
let top_branch_commit = repo.find_annotated_commit(
branch.get().peel_to_commit()?.id(),
)?;
let mut rebase = repo.rebase(
Some(&top_branch_commit),
Some(&commit_to_change),
None,
Some(&mut RebaseOptions::default()),
)?;
let mut target;
rebase.next();
if parent_commit_oid.is_none() {
return Err(Error::NoParent);
}
target = rebase.commit(None, &sig, Some(message))?;
let reworded_commit = target;
while rebase.next().is_some() {
target = rebase.commit(None, &sig, None)?;
}
rebase.finish(None)?;
repo.branch(
&cur_branch_name,
&repo.find_commit(target)?,
true,
)?;
repo.set_head(&cur_branch_ref)?;
repo.checkout_head(None)?;
return Ok(reworded_commit);
}
Err(Error::NoBranch)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sync::{
get_commit_info,
tests::{repo_init_empty, write_commit_file},
};
use pretty_assertions::assert_eq;
#[test]
fn test_reword() {
let (_td, repo) = repo_init_empty().unwrap();
let root = repo.path().parent().unwrap();
let repo_path: &RepoPath =
&root.as_os_str().to_str().unwrap().into();
write_commit_file(&repo, "foo", "a", "commit1");
let oid2 = write_commit_file(&repo, "foo", "ab", "commit2");
let branch =
repo.branches(None).unwrap().next().unwrap().unwrap().0;
let branch_ref = branch.get();
let commit_ref = branch_ref.peel_to_commit().unwrap();
let message = commit_ref.message().unwrap();
assert_eq!(message, "commit2");
let reworded =
reword(repo_path, oid2, "NewCommitMessage").unwrap();
let branch =
repo.branches(None).unwrap().next().unwrap().unwrap().0;
let branch_ref = branch.get();
let commit_ref_new = branch_ref.peel_to_commit().unwrap();
let message_new = commit_ref_new.message().unwrap();
assert_eq!(message_new, "NewCommitMessage");
assert_eq!(
message_new,
get_commit_info(repo_path, &reworded).unwrap().message
);
}
}