track.rs
1 use std::ffi::OsString; 2 use std::time; 3 4 use anyhow::anyhow; 5 6 use radicle::node; 7 use radicle::node::tracking::{Alias, Scope}; 8 use radicle::node::{Handle, NodeId}; 9 use radicle::{prelude::*, Node}; 10 11 use crate::commands::rad_sync as sync; 12 use crate::terminal as term; 13 use crate::terminal::args::{Args, Error, Help}; 14 15 pub const HELP: Help = Help { 16 name: "track", 17 description: "Manage repository and node tracking policy", 18 version: env!("CARGO_PKG_VERSION"), 19 usage: r#" 20 Usage 21 22 rad track <nid> [--alias <name>] [<option>...] 23 rad track <rid> [--[no-]fetch] [--scope <scope>] [<option>...] 24 25 The `track` command takes either an NID or an RID. Based on the argument, it will 26 either update the tracking policy of a node (NID), or a repository (RID). 27 28 When tracking a repository, a scope can be specified: this can be either `all` or 29 `trusted`. When using `all`, all remote nodes will be tracked for that repository. 30 On the other hand, with `trusted`, only the repository delegates will be tracked, 31 plus any remote that is explicitly tracked via `rad track <nid>`. 32 33 Options 34 35 --alias <name> Associate an alias to a tracked node 36 --[no-]fetch Fetch refs after tracking 37 --scope <scope> Node (remote) tracking scope for a repository 38 --verbose, -v Verbose output 39 --help Print help 40 "#, 41 }; 42 43 #[derive(Debug)] 44 pub enum Operation { 45 TrackNode { nid: NodeId, alias: Option<Alias> }, 46 TrackRepo { rid: Id, scope: Scope }, 47 } 48 49 #[derive(Debug)] 50 pub struct Options { 51 pub op: Operation, 52 pub fetch: bool, 53 pub verbose: bool, 54 } 55 56 impl Args for Options { 57 fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> { 58 use lexopt::prelude::*; 59 60 let mut parser = lexopt::Parser::from_args(args); 61 let mut op: Option<Operation> = None; 62 let mut fetch = true; 63 let mut verbose = false; 64 65 while let Some(arg) = parser.next()? { 66 match (&arg, &mut op) { 67 (Value(val), None) => { 68 if let Ok(rid) = term::args::rid(val) { 69 op = Some(Operation::TrackRepo { 70 rid, 71 scope: Scope::default(), 72 }); 73 } else if let Ok(did) = term::args::did(val) { 74 op = Some(Operation::TrackNode { 75 nid: did.into(), 76 alias: None, 77 }); 78 } else if let Ok(nid) = term::args::nid(val) { 79 op = Some(Operation::TrackNode { nid, alias: None }); 80 } 81 } 82 (Long("alias"), Some(Operation::TrackNode { alias, .. })) => { 83 let name = parser.value()?; 84 let name = term::args::alias(&name)?; 85 86 *alias = Some(name.to_owned()); 87 } 88 (Long("scope"), Some(Operation::TrackRepo { scope, .. })) => { 89 let val = parser.value()?; 90 91 *scope = term::args::parse_value("scope", val)?; 92 } 93 (Long("fetch"), Some(Operation::TrackRepo { .. })) => fetch = true, 94 (Long("no-fetch"), Some(Operation::TrackRepo { .. })) => fetch = false, 95 (Long("verbose") | Short('v'), _) => verbose = true, 96 (Long("help") | Short('h'), _) => { 97 return Err(Error::Help.into()); 98 } 99 _ => { 100 return Err(anyhow!(arg.unexpected())); 101 } 102 } 103 } 104 105 Ok(( 106 Options { 107 op: op.ok_or_else(|| anyhow!("either a NID or an RID must be specified"))?, 108 fetch, 109 verbose, 110 }, 111 vec![], 112 )) 113 } 114 } 115 116 pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { 117 let profile = ctx.profile()?; 118 let mut node = radicle::Node::new(profile.socket()); 119 120 match options.op { 121 Operation::TrackNode { nid, alias } => { 122 if let Err(node::Error::Connect(_)) = track_node(nid, alias, &mut node) { 123 anyhow::bail!("to track another node, your node must be running. To start it, run `rad node start`"); 124 } 125 } 126 Operation::TrackRepo { rid, scope } => { 127 if let Err(node::Error::Connect(_)) = track_repo(rid, scope, &mut node) { 128 anyhow::bail!("to track a repository, your node must be running. To start it, run `rad node start`"); 129 } 130 131 if options.fetch { 132 sync::fetch( 133 rid, 134 sync::RepoSync::default(), 135 time::Duration::from_secs(6), 136 &mut node, 137 )?; 138 } 139 } 140 } 141 Ok(()) 142 } 143 144 pub fn track_repo(rid: Id, scope: Scope, node: &mut Node) -> Result<(), node::Error> { 145 let tracked = node.track_repo(rid, scope)?; 146 let outcome = if tracked { "updated" } else { "exists" }; 147 148 term::success!( 149 "Tracking policy {outcome} for {} with scope '{scope}'", 150 term::format::tertiary(rid), 151 ); 152 153 Ok(()) 154 } 155 156 pub fn track_node(nid: NodeId, alias: Option<Alias>, node: &mut Node) -> Result<(), node::Error> { 157 let tracked = node.track_node(nid, alias.clone())?; 158 let outcome = if tracked { "updated" } else { "exists" }; 159 160 if let Some(alias) = alias { 161 term::success!( 162 "Tracking policy {outcome} for {} ({alias})", 163 term::format::tertiary(nid), 164 ); 165 } else { 166 term::success!( 167 "Tracking policy {outcome} for {}", 168 term::format::tertiary(nid), 169 ); 170 } 171 172 Ok(()) 173 }