From d2f423521ec76406944ad83098ec33afe20c692b Mon Sep 17 00:00:00 2001 From: Kim Altintop Date: Mon, 9 Jan 2023 13:18:33 +0100 Subject: This is it Squashed commit of all the exploration history. Development starts here. Signed-off-by: Kim Altintop --- src/cmd/topic/unbundle.rs | 174 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 src/cmd/topic/unbundle.rs (limited to 'src/cmd/topic/unbundle.rs') diff --git a/src/cmd/topic/unbundle.rs b/src/cmd/topic/unbundle.rs new file mode 100644 index 0000000..3aab54b --- /dev/null +++ b/src/cmd/topic/unbundle.rs @@ -0,0 +1,174 @@ +// Copyright © 2022 Kim Altintop +// SPDX-License-Identifier: GPL-2.0-only WITH openvpn-openssl-exception + +use std::{ + collections::{ + BTreeMap, + BTreeSet, + }, + path::PathBuf, +}; + +use anyhow::anyhow; +use clap::ValueHint; + +use super::Common; +use crate::{ + cmd::{ + self, + ui::{ + debug, + info, + warn, + }, + Aborted, + }, + git::{ + self, + if_not_found_none, + refs, + Refname, + }, + metadata::{ + self, + git::FromGit, + }, + patches::{ + self, + iter::dropped, + Bundle, + Record, + Topic, + REF_IT_BUNDLES, + REF_IT_PATCHES, + TOPIC_MERGES, + TOPIC_SNAPSHOTS, + }, + paths, +}; + +// TODO: +// +// - don't require patch bundle to be present on-disk when snapshots would do + +#[derive(Debug, clap::Args)] +pub struct Unbundle { + #[clap(flatten)] + common: Common, + /// The directory where to write the bundle to + /// + /// Unless this is an absolute path, it is treated as relative to $GIT_DIR. + #[clap( + long, + value_parser, + value_name = "DIR", + default_value_os_t = paths::bundles().to_owned(), + value_hint = ValueHint::DirPath, + )] + bundle_dir: PathBuf, + /// The topic to unbundle + #[clap(value_parser)] + topic: Topic, + /// The drop history to find the topic in + #[clap(value_parser)] + drop: Option, +} + +#[derive(serde::Serialize)] +pub struct Output { + updated: BTreeMap, +} + +pub fn unbundle(args: Unbundle) -> cmd::Result { + let repo = git::repo::open(&args.common.git_dir)?; + let bundle_dir = if args.bundle_dir.is_relative() { + repo.path().join(args.bundle_dir) + } else { + args.bundle_dir + }; + let drop = match args.drop { + Some(rev) => if_not_found_none(repo.resolve_reference_from_short_name(&rev))? + .ok_or_else(|| anyhow!("no ref matching {rev} found"))? + .name() + .ok_or_else(|| anyhow!("invalid drop"))? + .to_owned(), + None => REF_IT_PATCHES.to_owned(), + }; + + let filter = [&args.topic, &TOPIC_MERGES, &TOPIC_SNAPSHOTS]; + let mut on_topic: Vec = Vec::new(); + let mut checkpoints: Vec = Vec::new(); + for row in dropped::topics(&repo, &drop) { + let (t, id) = row?; + + if filter.into_iter().any(|f| f == &t) { + let commit = repo.find_commit(id)?; + let record = Record::from_commit(&repo, &commit)?; + if t == args.topic { + on_topic.push(record); + continue; + } + + // Skip checkpoint which came after the most recent record on the topic + if !on_topic.is_empty() { + checkpoints.push(record); + } + } + } + + let odb = repo.odb()?; + + info!("Indexing checkpoints..."); + for rec in checkpoints.into_iter().rev() { + Bundle::from_stored(&bundle_dir, rec.bundle_info().as_expect())? + .packdata()? + .index(&odb)? + } + + let mut missing = BTreeSet::new(); + for oid in on_topic + .iter() + .flat_map(|rec| &rec.bundle_info().prerequisites) + { + let oid = git2::Oid::try_from(oid)?; + if !odb.exists(oid) { + missing.insert(oid); + } + } + + if !missing.is_empty() { + warn!("Unable to satisfy all prerequisites"); + info!("The following prerequisite commits are missing:\n"); + for oid in missing { + info!("{oid}"); + } + info!("\nYou may try to unbundle the entire drop history"); + cmd::abort!(); + } + + info!("Unbundling topic records..."); + let mut tx = refs::Transaction::new(&repo)?; + let topic_ref = tx.lock_ref(args.topic.as_refname())?; + let mut up = BTreeMap::new(); + for rec in on_topic.into_iter().rev() { + let hash = rec.bundle_hash(); + let bundle = Bundle::from_stored(&bundle_dir, rec.bundle_info().as_expect())?; + if bundle.is_encrypted() { + warn!("Skipping encrypted bundle {hash}"); + continue; + } + bundle.packdata()?.index(&odb)?; + debug!("{hash}: unbundle"); + let updated = patches::unbundle(&odb, &mut tx, REF_IT_BUNDLES, &rec)?; + for (name, oid) in updated { + up.insert(name, oid.into()); + } + debug!("{hash}: merge notes"); + let submitter = metadata::Identity::from_content_hash(&repo, &rec.meta.signature.signer)? + .verified(metadata::git::find_parent(&repo))?; + patches::merge_notes(&repo, &submitter, &topic_ref, &rec)?; + } + tx.commit()?; + + Ok(Output { updated: up }) +} -- cgit v1.2.3