use std::collections::BTreeMap;
use std::os::raw::c_uint;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex;
use bstr::ByteSlice;
use once_cell::sync::Lazy;
use crate::hg_data::{GitAuthorship, HgAuthorship};
use crate::libgit::{lookup_replace_commit, rev_list, CommitId, RawCommit, TreeId};
use crate::progress::Progress;
use crate::store::{
has_metadata, GeneratedGitChangesetMetadata, GitChangesetId, HgChangesetId, RawHgChangeset,
};
extern "C" {
fn replace_map_size() -> c_uint;
}
pub fn grafted() -> bool {
unsafe { replace_map_size() != 0 }
}
static DID_SOMETHING: AtomicBool = AtomicBool::new(false);
static GRAFT_TREES: Lazy<Mutex<BTreeMap<TreeId, Vec<CommitId>>>> =
Lazy::new(|| Mutex::new(BTreeMap::new()));
pub fn graft_finish() -> Option<bool> {
Lazy::get(&GRAFT_TREES).map(|_| grafted() || DID_SOMETHING.load(Ordering::Relaxed))
}
pub fn init_graft() {
let mut args = vec![
"--full-history",
"--exclude=refs/cinnabar/*",
"--exclude=refs/notes/cinnabar",
"--exclude=refs/original/*",
"--all",
];
if has_metadata() {
args.push("--not");
args.push("refs/cinnabar/metadata^");
}
let mut graft_trees = GRAFT_TREES.lock().unwrap();
for cid in rev_list(&args).progress(|n| format!("Reading {} graft candidates", n)) {
let c = RawCommit::read(&cid).unwrap();
let c = c.parse().unwrap();
let cids_for_tree = graft_trees.entry(c.tree().clone()).or_default();
cids_for_tree.push(cid);
}
}
#[derive(Debug)]
pub enum GraftError {
Ambiguous(Box<[CommitId]>),
NoGraft,
}
pub fn graft(
changeset_id: &HgChangesetId,
raw_changeset: &RawHgChangeset,
tree: &TreeId,
parents: &[GitChangesetId],
) -> Result<Option<CommitId>, GraftError> {
if Lazy::get(&GRAFT_TREES).is_none() {
return Ok(None);
}
let changeset = raw_changeset.parse().unwrap();
let mut graft_trees = GRAFT_TREES.lock().unwrap();
let graft_trees_entry = graft_trees.get_mut(tree).ok_or(GraftError::NoGraft)?;
let candidates = graft_trees_entry
.iter()
.map(|c| {
let raw = RawCommit::read(c).unwrap();
(c.clone(), raw)
})
.collect::<Vec<_>>();
let candidates = candidates
.iter()
.map(|(cid, c)| (cid, c.parse().unwrap()))
.filter(|(_, c)| {
if &*HgAuthorship::from(GitAuthorship(c.author())).timestamp != changeset.timestamp() {
return false;
}
if c.parents()
.iter()
.zip(parents)
.all(|(commit_parent, changeset_parent)| {
lookup_replace_commit(commit_parent) == lookup_replace_commit(changeset_parent)
})
{
return true;
}
!grafted()
})
.collect::<Vec<_>>();
let mut candidates = candidates.iter().collect::<Vec<_>>();
if candidates.len() > 1 {
let cs_subject = ByteSlice::lines(changeset.body()).next();
let mut possible_candidates = candidates.clone();
possible_candidates.retain(|(_, c)| ByteSlice::lines(c.body()).next() == cs_subject);
if possible_candidates.len() > 1 {
possible_candidates.retain(|(_, c)| {
&*HgAuthorship::from(GitAuthorship(c.author())).author == changeset.author()
});
}
if possible_candidates.len() == 1 {
candidates = possible_candidates;
}
}
if candidates.len() > 1 {
candidates.retain(|(_, c)| {
GeneratedGitChangesetMetadata::generate(c, changeset_id, raw_changeset)
.unwrap()
.patch()
.is_some()
});
}
match candidates.len() {
1 => {
let (commit, _) = candidates[0];
graft_trees_entry.retain(|c| c != *commit);
DID_SOMETHING.store(true, Ordering::Relaxed);
Ok(Some((*commit).clone()))
}
0 => Err(GraftError::NoGraft),
_ => Err(GraftError::Ambiguous(
candidates
.into_iter()
.map(|(cid, _)| (*cid).clone())
.collect::<Vec<_>>()
.into(),
)),
}
}