use super::*;
use crate::commands::common_opts::RepoAndChannel;
use anyhow::bail;
use clap::Parser;
use pijul_core::{Base32, ChannelTxnT, Merkle, TxnT, pristine::TxnErr};
use pijul_remote as remote;
use std::path::Path;
#[derive(Parser, Debug)]
pub struct Approve {
#[clap(flatten)]
base: RepoAndChannel,
#[clap(value_name = "STATE")]
state: Option<String>,
#[clap(long = "to")]
to: Option<String>,
#[clap(long = "identity")]
identity: Option<String>,
}
impl Approve {
pub fn repository_path(&mut self) -> Option<&Path> {
self.base.repo_path()
}
pub async fn run(mut self, config: &pijul_config::Config) -> Result<(), anyhow::Error> {
let repo = self.base.find_root()?;
let identity_name = self
.identity
.take()
.unwrap_or(pijul_identity::choose_identity_name(config).await?);
let complete = pijul_identity::Complete::load(&identity_name)?;
let secret = complete.skey();
let (state, channel_name) = {
let txn = repo.pristine.txn_begin()?;
let channel_name = get_channel(self.base.channel(), &txn).0.to_string();
let (channel_ref, _) = load_channel(self.base.channel(), &txn)?;
let Some((state, _)) = get_state(&txn, &*channel_ref.read(), self.state.as_deref())?
else {
bail!("No such state");
};
(state, channel_name)
};
let sig = pijul_identity::sign_sshsig(&secret, &state.to_bytes()).await?;
let remote_name = if let Some(ref to) = self.to {
to.clone()
} else if let Some(ref def) = config.default_remote {
def.clone()
} else {
bail!("Missing remote: pass --to or set a default remote");
};
let mut remote = remote::repository(
config,
Some(&repo.path),
Some(&complete.config.author.username),
&remote_name,
&channel_name,
false,
true,
)
.await?;
remote.approve(&channel_name, state, &sig).await?;
println!("Approved {}", state.to_base32());
Ok(())
}
}
fn get_state<T: ChannelTxnT + TxnT>(
txn: &T,
channel: &T::Channel,
state: Option<&str>,
) -> Result<Option<(Merkle, u64)>, TxnErr<T::GraphError>> {
if let Some(state) = state {
if let Ok((st, n)) = txn.state_from_prefix(&txn.states(channel), &state) {
return Ok(Some((st.into(), n.into())));
}
} else if let Some(x) = txn
.rev_cursor_revchangeset(txn.rev_changes(&channel), None)?
.next()
{
let (n, p) = x?;
return Ok(Some((p.b.into(), (*n).into())));
}
Ok(None)
}