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/drop.rs | 205 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 src/cmd/drop.rs (limited to 'src/cmd/drop.rs') diff --git a/src/cmd/drop.rs b/src/cmd/drop.rs new file mode 100644 index 0000000..208dbd6 --- /dev/null +++ b/src/cmd/drop.rs @@ -0,0 +1,205 @@ +// Copyright © 2022 Kim Altintop +// SPDX-License-Identifier: GPL-2.0-only WITH openvpn-openssl-exception + +use std::{ + ops::Deref, + path::PathBuf, +}; + +use anyhow::{ + ensure, + Context, +}; +use clap::ValueHint; +use either::Either::Left; + +use crate::{ + cmd, + metadata::{ + self, + git::{ + FromGit, + META_FILE_ALTERNATES, + META_FILE_MIRRORS, + }, + IdentityId, + Signed, + }, + patches::REF_HEADS_PATCHES, +}; + +mod bundles; +pub use bundles::{ + sync, + Bundles, + Sync, +}; + +mod edit; +pub use edit::{ + edit, + Edit, +}; + +mod init; +pub use init::{ + init, + Init, +}; + +mod serve; +pub use serve::{ + serve, + Serve, +}; + +mod snapshot; +pub use snapshot::{ + snapshot, + Snapshot, +}; + +mod show; +pub use show::{ + show, + Show, +}; + +mod unbundle; +pub use unbundle::{ + unbundle, + Unbundle, +}; + +#[derive(Debug, clap::Subcommand)] +#[allow(clippy::large_enum_variant)] +pub enum Cmd { + /// Initialise a drop + Init(Init), + /// Display the drop metadata + Show(Show), + /// Serve bundles and patch submission over HTTP + Serve(Serve), + /// Edit the drop metadata + Edit(Edit), + /// Manage patch bundles + #[clap(subcommand)] + Bundles(Bundles), + /// Take a snapshot of the patches received so far + Snapshot(Snapshot), + /// Unbundle the entire drop history + Unbundle(Unbundle), +} + +impl Cmd { + pub fn run(self) -> cmd::Result { + match self { + Self::Init(args) => init(args).map(cmd::IntoOutput::into_output), + Self::Show(args) => show(args).map(cmd::IntoOutput::into_output), + Self::Serve(args) => serve(args).map(cmd::IntoOutput::into_output), + Self::Edit(args) => edit(args).map(cmd::IntoOutput::into_output), + Self::Bundles(cmd) => cmd.run(), + Self::Snapshot(args) => snapshot(args).map(cmd::IntoOutput::into_output), + Self::Unbundle(args) => unbundle(args).map(cmd::IntoOutput::into_output), + } + } +} + +#[derive(Debug, clap::Args)] +struct Common { + /// Path to the drop repository + #[clap(from_global)] + git_dir: PathBuf, + /// A list of paths to search for identity repositories + #[clap( + long, + value_parser, + value_name = "PATH", + env = "IT_ID_PATH", + default_value_t, + value_hint = ValueHint::DirPath, + )] + id_path: cmd::util::args::IdSearchPath, +} + +fn find_id( + repo: &git2::Repository, + id_path: &[git2::Repository], + id: &IdentityId, +) -> cmd::Result> { + let signed = metadata::Identity::from_search_path(id_path, cmd::id::identity_ref(Left(id))?)? + .meta + .signed; + + let verified_id = signed + .verify(cmd::find_parent(repo)) + .with_context(|| format!("invalid identity {id}"))?; + ensure!( + &verified_id == id, + "ids do not match after verification: expected {id}, found {verified_id}", + ); + + Ok(signed) +} + +#[derive(serde::Serialize, serde::Deserialize)] +struct Editable { + description: metadata::drop::Description, + roles: metadata::drop::Roles, + custom: metadata::Custom, +} + +impl From for Editable { + fn from( + metadata::Drop { + description, + roles, + custom, + .. + }: metadata::Drop, + ) -> Self { + Self { + description, + roles, + custom, + } + } +} + +impl TryFrom for metadata::Drop { + type Error = crate::Error; + + fn try_from( + Editable { + description, + roles, + custom, + }: Editable, + ) -> Result { + ensure!(!roles.root.ids.is_empty(), "drop role cannot be empty"); + ensure!( + !roles.snapshot.ids.is_empty(), + "snapshot roles cannot be empty" + ); + ensure!( + !roles.branches.is_empty(), + "at least one branch role is required" + ); + for (name, ann) in &roles.branches { + ensure!( + !ann.role.ids.is_empty(), + "branch role {name} cannot be empty" + ); + ensure!(name.starts_with("refs/heads/"), "not a branch {name}"); + ensure!(name.deref() != REF_HEADS_PATCHES, "reserved branch {name}"); + } + + Ok(Self { + spec_version: crate::SPEC_VERSION, + description, + prev: None, + roles, + custom, + }) + } +} -- cgit v1.2.3