use itertools::Itertools;
use std::collections::HashSet;
use std::error::Error;
use std::path::{Path, PathBuf};
use std::str;
use crate::kernel::Settings;
use crate::kernel::settings::GitInputSelector;
use crate::model::{TxnData, Txns};
use crate::parser::tackler_parser;
use crate::tackler;
use gix as git;
use gix::date::time::CustomFormat;
use gix::hash as gix_hash;
use gix::objs::tree::EntryKind;
use tackler_api::metadata::items::{GitInputReference, MetadataItem};
pub fn string_to_txns(
input: &mut &str,
settings: &mut Settings,
) -> Result<TxnData, tackler::Error> {
let txns = tackler_parser::txns_text(input, settings)?;
TxnData::try_from(None, txns, settings)
}
pub fn paths_to_txns(
paths: &[PathBuf],
settings: &mut Settings,
) -> Result<TxnData, tackler::Error> {
let txns: Result<Txns, tackler::Error> = paths
.iter()
.map(|p| tackler_parser::txns_file(p, settings))
.flatten_ok()
.collect();
TxnData::try_from(None, txns?, settings)
}
#[allow(clippy::too_many_lines)]
pub fn git_to_txns(
repo_path: &Path,
dir: &str,
extension: &str,
input_selector: GitInputSelector,
settings: &mut Settings,
) -> Result<TxnData, tackler::Error> {
let repo = git::open(repo_path)?;
let (object, reference) = match input_selector {
GitInputSelector::CommitId(id) => {
let mut candidates = Some(HashSet::default());
let prefix = match gix_hash::Prefix::try_from(id.as_str()) {
Ok(v) => v,
Err(err) => {
let msg = format!("Invalid commit id '{id}': {err}");
return Err(msg.into());
}
};
let res = repo.objects.lookup_prefix(prefix, candidates.as_mut())?;
let object_id = match res {
Some(Ok(id)) => id,
Some(Err(())) => return Err(format!("Ambiguous abbreviated commit id {id}").into()),
None => return Err(format!("Unknown commit id '{id}'").into()),
};
(repo.find_object(object_id)?.try_into_commit()?, None)
}
GitInputSelector::Reference(ref_str) => {
let id_res = repo.rev_parse_single(ref_str.as_bytes());
let id = match id_res {
Ok(id) => id,
Err(err) => {
let msg = if let Some(source) = err.source() {
format!("{source}")
} else {
format!("{err}")
};
return Err(msg.into());
}
};
let reference = if id.to_string().starts_with(ref_str.as_str()) {
None
} else {
Some(ref_str.clone())
};
(id.object()?.peel_to_commit()?, reference)
}
};
let signature = object.author()?;
let author = format!("{} <{}>", signature.name, signature.email);
let date = signature
.time()?
.format(CustomFormat::new("%Y-%m-%d %H:%M:%S %z"))?
.clone();
let gitmd = GitInputReference {
commit: object.id.to_string(),
reference,
dir: dir.to_string(),
extension: extension.to_string(),
subject: object.message()?.summary().to_string(),
author,
date,
};
let dir = if dir.ends_with('/') {
dir.to_string()
} else {
format!("{dir}/")
};
let ext = format!(".{extension}");
let tree = object.tree()?;
let txns: Result<Txns, tackler::Error> = tree
.traverse()
.breadthfirst
.files()?
.iter()
.map(|entry| {
use git::objs::tree::EntryKind::{Blob, Link};
match EntryKind::from(entry.mode) {
Blob => {
if entry.filepath.starts_with(str::as_bytes(dir.as_str()))
&& entry.filepath.ends_with(str::as_bytes(ext.as_str()))
{
let obj = repo.find_object(entry.oid)?;
let par_res =
tackler_parser::txns_text(&mut str::from_utf8(&obj.data)?, settings);
match par_res {
Ok(txns) => Ok(txns),
Err(err) => {
let msg = format!(
"\
GIT: Error while processing git object\n\
\x20 commit id: {}\n\
\x20 object id: {}\n\
\x20 path: {}\n\
\x20 msg: {}\
",
object.id, obj.id, entry.filepath, err
);
Err(msg.into())
}
}
} else {
Ok(Vec::default())
}
}
Link => {
let obj = repo.find_object(entry.oid)?;
let msg = format!(
"\
GIT: Error while processing git object\n\
\x20 commit id: {}\n\
\x20 object id: {}\n\
\x20 path: {}\n\
\x20 msg: {}\
",
object.id,
obj.id,
entry.filepath,
"Links inside repository are not supported"
);
Err(msg.into())
}
_ => Ok(Vec::default()),
}
})
.flatten_ok()
.collect::<Result<Txns, tackler::Error>>();
TxnData::try_from(
Some(MetadataItem::GitInputReference(gitmd)),
txns?,
settings,
)
}