git: match native agent workflows
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
//! Single-commit cherry-pick uses Git's CHERRY_PICK_HEAD and MERGE_MSG for continuation.
|
||||
use crate::{Result, branches, checkout, local, output, repo::error, staging};
|
||||
use gix::bstr::ByteSlice;
|
||||
|
||||
pub(crate) fn merge_trees(
|
||||
repo: &gix::Repository,
|
||||
base: gix::ObjectId,
|
||||
ours: gix::ObjectId,
|
||||
theirs: gix::ObjectId,
|
||||
current: &str,
|
||||
other: &str,
|
||||
) -> Result<(gix::ObjectId, gix::index::State, Vec<String>)> {
|
||||
let local = crate::merge::merge_repository(repo)?;
|
||||
let labels = gix::merge::blob::builtin_driver::text::Labels {
|
||||
ancestor: None,
|
||||
current: Some(current.into()),
|
||||
other: Some(other.into()),
|
||||
};
|
||||
let mut outcome = local
|
||||
.merge_trees(
|
||||
base,
|
||||
ours,
|
||||
theirs,
|
||||
labels,
|
||||
local
|
||||
.tree_merge_options()
|
||||
.map_err(|e| error("merge options", e))?,
|
||||
)
|
||||
.map_err(|e| error("merge trees", e))?;
|
||||
let tree = outcome
|
||||
.tree
|
||||
.write()
|
||||
.map_err(|e| error("merge tree", e))?
|
||||
.detach();
|
||||
let mut index = gix::index::State::from_tree(&tree, &repo.objects, Default::default())
|
||||
.map_err(|e| error("merge index", e))?;
|
||||
outcome.index_changed_after_applying_conflicts(
|
||||
&mut index,
|
||||
Default::default(),
|
||||
gix::merge::tree::apply_index_entries::RemovalMode::Prune,
|
||||
);
|
||||
index.sort_entries();
|
||||
let conflicts = index
|
||||
.entries()
|
||||
.iter()
|
||||
.filter(|e| e.stage_raw() != 0)
|
||||
.map(|e| e.path(&index).to_str_lossy().into_owned())
|
||||
.collect::<std::collections::BTreeSet<_>>()
|
||||
.into_iter()
|
||||
.collect();
|
||||
Ok((tree, index, conflicts))
|
||||
}
|
||||
fn clear(repo: &gix::Repository) -> Result<()> {
|
||||
for name in ["CHERRY_PICK_HEAD", "MERGE_MSG"] {
|
||||
let path = repo.git_dir().join(name);
|
||||
if path.exists() {
|
||||
std::fs::remove_file(path).map_err(|e| error("cherry-pick cleanup", e))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub(crate) fn run(
|
||||
repo: &gix::Repository,
|
||||
revision: Option<String>,
|
||||
resume: bool,
|
||||
abort: bool,
|
||||
quiet: bool,
|
||||
) -> Result<i32> {
|
||||
if abort {
|
||||
if !repo.git_dir().join("CHERRY_PICK_HEAD").exists() {
|
||||
return Err(error("cherry-pick abort", "no cherry-pick in progress"));
|
||||
}
|
||||
let old = std::fs::read_to_string(repo.git_dir().join("ORIG_HEAD"))
|
||||
.map_err(|e| error("cherry-pick abort", e))?;
|
||||
local::reset(repo, old.trim(), &[], false, true, true)?;
|
||||
clear(repo)?;
|
||||
return Ok(0);
|
||||
}
|
||||
let id = if resume {
|
||||
let id = std::fs::read_to_string(repo.git_dir().join("CHERRY_PICK_HEAD"))
|
||||
.map_err(|e| error("cherry-pick continue", e))?;
|
||||
branches::resolve(repo, id.trim())?
|
||||
} else {
|
||||
crate::merge::ensure_idle(repo)?;
|
||||
crate::merge::clean(repo)?;
|
||||
branches::resolve(repo, &crate::required(revision, "cherry-pick revision")?)?
|
||||
};
|
||||
let old = branches::resolve(repo, "HEAD")?;
|
||||
let original = repo
|
||||
.find_commit(id)
|
||||
.map_err(|e| error("cherry-pick commit", e))?;
|
||||
let decoded = original
|
||||
.decode()
|
||||
.map_err(|e| error("cherry-pick decode", e))?;
|
||||
let subject = decoded
|
||||
.message
|
||||
.to_str_lossy()
|
||||
.lines()
|
||||
.next()
|
||||
.unwrap_or("")
|
||||
.to_owned();
|
||||
let tree = if resume {
|
||||
staging::tree_from_index(repo, &staging::load(repo)?)?
|
||||
} else {
|
||||
let base = match decoded.parents.first() {
|
||||
Some(id) => local::tree(repo, &id.to_string())?,
|
||||
None => repo
|
||||
.write_object(&gix::objs::Tree { entries: vec![] })
|
||||
.map_err(|e| error("empty tree", e))?
|
||||
.detach(),
|
||||
};
|
||||
let ours = local::tree(repo, "HEAD")?;
|
||||
let (tree, index, conflicts) = merge_trees(
|
||||
repo,
|
||||
base,
|
||||
ours,
|
||||
decoded.tree(),
|
||||
"HEAD",
|
||||
&format!("{} ({subject})", &id.to_string()[..7]),
|
||||
)?;
|
||||
checkout::write_tree_to_worktree(repo, Some(ours), tree, false)?;
|
||||
staging::publish(repo, index)?;
|
||||
if !conflicts.is_empty() {
|
||||
std::fs::write(repo.git_dir().join("CHERRY_PICK_HEAD"), format!("{id}\n"))
|
||||
.map_err(|e| error("cherry-pick state", e))?;
|
||||
std::fs::write(repo.git_dir().join("ORIG_HEAD"), format!("{old}\n"))
|
||||
.map_err(|e| error("cherry-pick state", e))?;
|
||||
std::fs::write(repo.git_dir().join("MERGE_MSG"), decoded.message.as_bytes())
|
||||
.map_err(|e| error("cherry-pick state", e))?;
|
||||
output::conflicts(&conflicts, false);
|
||||
return Ok(1);
|
||||
}
|
||||
tree
|
||||
};
|
||||
let commit = gix::objs::Commit {
|
||||
tree,
|
||||
parents: vec![old].into_iter().collect(),
|
||||
author: decoded
|
||||
.author()
|
||||
.map_err(|e| error("pick author", e))?
|
||||
.to_owned()
|
||||
.map_err(|e| error("pick author", e))?,
|
||||
committer: staging::signature(None, None)?,
|
||||
encoding: decoded.encoding.map(ToOwned::to_owned),
|
||||
message: decoded.message.to_owned(),
|
||||
extra_headers: vec![],
|
||||
};
|
||||
let new = repo
|
||||
.write_object(&commit)
|
||||
.map_err(|e| error("cherry-pick write", e))?
|
||||
.detach();
|
||||
staging::update_head(
|
||||
repo,
|
||||
Some(old),
|
||||
new,
|
||||
&staging::signature(None, None)?,
|
||||
&format!("cherry-pick: {subject}"),
|
||||
)?;
|
||||
clear(repo)?;
|
||||
if !quiet {
|
||||
let head = crate::head_info(repo)?;
|
||||
println!(
|
||||
"[{} {}] {subject}",
|
||||
head.branch
|
||||
.unwrap_or_else(|| "detached HEAD".into())
|
||||
.trim_start_matches("refs/heads/"),
|
||||
&new.to_string()[..7]
|
||||
);
|
||||
output::author_date(&repo.find_commit(new).map_err(|e| error("pick output", e))?)?;
|
||||
output::commit_stat(&crate::changes::diff(crate::changes::DiffParams {
|
||||
repo: repo.workdir().unwrap().to_string_lossy().into_owned(),
|
||||
from: Some(old.to_string()),
|
||||
to: Some(new.to_string()),
|
||||
staged: None,
|
||||
path: None,
|
||||
context: None,
|
||||
})?);
|
||||
}
|
||||
Ok(0)
|
||||
}
|
||||
Reference in New Issue
Block a user