use super::*;
use std::fs::File;
use std::io::BufReader;
#[derive(clap::Args, Debug)]
#[command(after_long_help = "\
Examples:
mnem import notes.car # import into current repo
cat notes.car | mnem import - # read from stdin
mnem init ~/restored && mnem -R ~/restored import notes.car
")]
pub(crate) struct Args {
pub path: String,
}
pub(crate) fn run(override_path: Option<&Path>, args: Args) -> Result<()> {
let data_dir = repo::locate_data_dir(override_path).or_else(|_| {
let cwd = std::env::current_dir().context("cwd unreadable")?;
let dir = cwd.join(repo::MNEM_DIR);
std::fs::create_dir_all(&dir).with_context(|| format!("creating {}", dir.display()))?;
Ok::<_, anyhow::Error>(dir)
})?;
let (bs, ohs) = repo::create_or_open_stores(&data_dir)?;
let normalized = super::normalize_cli_path(&args.path);
let stats = if normalized == "-" {
let stdin = std::io::stdin();
let mut lock = stdin.lock();
mnem_transport::import(&mut lock, &*bs).with_context(|| {
"reading CAR from stdin\n\
hint: see docs/RUNBOOK.md#5-car-import-rejected for the error-variant \
taxonomy (malformed CAR, CID mismatch, size cap, missing root, ...)."
.to_string()
})?
} else {
let path = Path::new(&normalized);
let file = File::open(path).with_context(|| format!("opening {}", path.display()))?;
let mut r = BufReader::new(file);
mnem_transport::import(&mut r, &*bs).with_context(|| {
format!(
"reading CAR from {}\n\
hint: see docs/RUNBOOK.md#5-car-import-rejected for the error-variant \
taxonomy (malformed CAR, CID mismatch, size cap, missing root, ...).",
path.display()
)
})?
};
let roots_summary = if stats.roots.is_empty() {
"(no declared roots)".to_string()
} else {
stats
.roots
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(", ")
};
println!(
"imported {} blocks, {} bytes from {} (roots: {})",
stats.blocks, stats.bytes, normalized, roots_summary
);
let head_commit = find_head_commit_in_roots(&bs, &stats.roots)?;
if let Some(head_cid) = &head_commit {
let r_repo = ReadonlyRepo::init(bs.clone(), ohs.clone())?;
let cfg = config::load(&data_dir)?;
let author = config::author_string(&cfg);
r_repo.update_heads(head_cid.clone(), &author)?;
println!("Imported {head_cid}. HEAD advanced.");
} else if !stats.roots.is_empty() {
println!(
"note: no commit block found among CAR roots; HEAD not advanced. \
Run `mnem ref set HEAD <cid>` manually if needed."
);
}
Ok(())
}
fn find_head_commit_in_roots(
bs: &std::sync::Arc<dyn mnem_core::store::Blockstore>,
roots: &[mnem_core::id::Cid],
) -> Result<Option<mnem_core::id::Cid>> {
let mut best: Option<(u64, mnem_core::id::Cid)> = None;
for root_cid in roots {
let Some(bytes) = bs.get(root_cid)? else {
continue;
};
let Ok(Ipld::Map(m)) = from_canonical_bytes::<Ipld>(&bytes) else {
continue;
};
let Some(Ipld::String(kind)) = m.get("_kind") else {
continue;
};
if kind != "commit" {
continue;
}
let time = match m.get("time") {
Some(Ipld::Integer(n)) => u64::try_from(*n).unwrap_or(0),
_ => 0,
};
best = Some(match best {
None => (time, root_cid.clone()),
Some((t, _)) if time > t => (time, root_cid.clone()),
Some(prev) => prev,
});
}
Ok(best.map(|(_, c)| c))
}