upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/sub_commands
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2024-02-23 13:57:23 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2024-02-23 13:57:23 +0000
commitd5284b758661c491e6a206570763f2982424b70a (patch)
treecc7d8cee7b535999dc99e466a17127b1a94aee97 /src/sub_commands
parenta9e4c6772a378fd28edbca9b9267d2e7d08bee2c (diff)
feat(init): add customisation and defaults
- allow more cli input options - allow customisation of more fields in interface - change default identifer from shorthand root commit to short name - defaults to existing repo event (users or other) or maintainers.yaml
Diffstat (limited to 'src/sub_commands')
-rw-r--r--src/sub_commands/init.rs256
-rw-r--r--src/sub_commands/list.rs1
-rw-r--r--src/sub_commands/pull.rs1
-rw-r--r--src/sub_commands/push.rs1
-rw-r--r--src/sub_commands/send.rs1
5 files changed, 221 insertions, 39 deletions
diff --git a/src/sub_commands/init.rs b/src/sub_commands/init.rs
index 3a0ff55..54b6156 100644
--- a/src/sub_commands/init.rs
+++ b/src/sub_commands/init.rs
@@ -1,4 +1,5 @@
1use anyhow::{Context, Result}; 1use anyhow::{Context, Result};
2use nostr::{secp256k1::XOnlyPublicKey, FromBech32, ToBech32};
2 3
3use super::send::send_events; 4use super::send::send_events;
4#[cfg(not(test))] 5#[cfg(not(test))]
@@ -10,7 +11,7 @@ use crate::{
10 client::Connect, 11 client::Connect,
11 git::{Repo, RepoActions}, 12 git::{Repo, RepoActions},
12 login, 13 login,
13 repo_ref::{extract_pks, get_repo_config_from_yaml, save_repo_config_to_yaml, RepoRef}, 14 repo_ref::{self, extract_pks, get_repo_config_from_yaml, save_repo_config_to_yaml, RepoRef},
14 Cli, 15 Cli,
15}; 16};
16 17
@@ -22,14 +23,27 @@ pub struct SubCommandArgs {
22 #[clap(short, long)] 23 #[clap(short, long)]
23 /// optional description 24 /// optional description
24 description: Option<String>, 25 description: Option<String>,
26 #[clap(long)]
27 /// git server url users can clone from
28 clone_url: Option<String>,
25 #[clap(short, long, value_parser, num_args = 1..)] 29 #[clap(short, long, value_parser, num_args = 1..)]
26 /// homepage 30 /// homepage
27 web: Vec<String>, 31 web: Vec<String>,
28 #[clap(short, long, value_parser, num_args = 1..)] 32 #[clap(short, long, value_parser, num_args = 1..)]
29 /// relays contributors push patches and comments to 33 /// relays contributors push patches and comments to
30 relays: Vec<String>, 34 relays: Vec<String>,
35 #[clap(short, long, value_parser, num_args = 1..)]
36 /// npubs of other maintainers
37 other_maintainers: Vec<String>,
38 #[clap(long)]
39 /// usually root commit but will be more recent commit for forks
40 earliest_unique_commit: Option<String>,
41 #[clap(short, long)]
42 /// shortname with no spaces or special characters
43 identifier: Option<String>,
31} 44}
32 45
46#[allow(clippy::too_many_lines)]
33pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> { 47pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {
34 let git_repo = Repo::discover().context("cannot find a git repository")?; 48 let git_repo = Repo::discover().context("cannot find a git repository")?;
35 49
@@ -40,29 +54,95 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {
40 // TODO: check for empty repo 54 // TODO: check for empty repo
41 // TODO: check for existing maintaiers file 55 // TODO: check for existing maintaiers file
42 56
57 #[cfg(not(test))]
58 let mut client = Client::default();
59 #[cfg(test)]
60 let mut client = <MockConnect as std::default::Default>::default();
61
62 let (keys, user_ref) = login::launch(&cli_args.nsec, &cli_args.password, Some(&client)).await?;
63
64 client.set_keys(&keys).await;
65
66 let repo_ref = if let Ok(rep_ref) = repo_ref::fetch(
67 &git_repo,
68 root_commit.to_string(),
69 &client,
70 user_ref.relays.write(),
71 false,
72 )
73 .await
74 {
75 Some(rep_ref)
76 } else {
77 None
78 };
79
43 let repo_config_result = get_repo_config_from_yaml(&git_repo); 80 let repo_config_result = get_repo_config_from_yaml(&git_repo);
44 // TODO: check for other claims 81 // TODO: check for other claims
45 82
46 let identifier = root_commit.to_string()[..7].to_string();
47
48 let name = match &args.title { 83 let name = match &args.title {
49 Some(t) => t.clone(), 84 Some(t) => t.clone(),
50 None => Interactor::default().input(PromptInputParms::default().with_prompt("name"))?, 85 None => Interactor::default().input(
86 PromptInputParms::default()
87 .with_prompt("name")
88 .with_default(if let Some(repo_ref) = &repo_ref {
89 repo_ref.name.clone()
90 } else {
91 String::new()
92 }),
93 )?,
94 };
95
96 let identifier = match &args.identifier {
97 Some(t) => t.clone(),
98 None => Interactor::default().input(
99 PromptInputParms::default()
100 .with_prompt("identifier")
101 .with_default(if let Some(repo_ref) = &repo_ref {
102 repo_ref.identifier.clone()
103 } else {
104 name.clone()
105 .replace(' ', "-")
106 .chars()
107 .map(|c| {
108 if c.is_ascii_alphanumeric() || c.eq(&'/') {
109 c
110 } else {
111 '-'
112 }
113 })
114 .collect()
115 }),
116 )?,
51 }; 117 };
52 118
53 let description = match &args.description { 119 let description = match &args.description {
54 Some(t) => t.clone(), 120 Some(t) => t.clone(),
55 None => { 121 None => Interactor::default().input(
56 Interactor::default().input(PromptInputParms::default().with_prompt("description"))? 122 PromptInputParms::default()
57 } 123 .with_prompt("description")
124 .with_default(if let Some(repo_ref) = &repo_ref {
125 repo_ref.description.clone()
126 } else {
127 String::new()
128 }),
129 )?,
58 }; 130 };
59 131
60 let git_server = git_repo 132 let git_server = match &args.clone_url {
61 .get_origin_url() 133 Some(t) => t.clone(),
62 .context( 134 None => Interactor::default().input(
63 "to claim the repository it must be available on a publically accessable git server", 135 PromptInputParms::default()
64 ) 136 .with_prompt("clone url")
65 .context("no git remote origin configured")?; 137 .with_default(if let Some(repo_ref) = &repo_ref {
138 repo_ref.git_server.clone()
139 } else if let Ok(git_repo) = git_repo.get_origin_url() {
140 git_repo
141 } else {
142 String::new()
143 }),
144 )?,
145 };
66 146
67 let web: Vec<String> = if args.web.is_empty() { 147 let web: Vec<String> = if args.web.is_empty() {
68 Interactor::default() 148 Interactor::default()
@@ -70,7 +150,11 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {
70 PromptInputParms::default() 150 PromptInputParms::default()
71 .with_prompt("web") 151 .with_prompt("web")
72 .optional() 152 .optional()
73 .with_default(format!("https://gitworkshop.dev/repo/{}", &identifier)), 153 .with_default(if let Some(repo_ref) = &repo_ref {
154 repo_ref.web.clone().join(" ")
155 } else {
156 format!("https://gitworkshop.dev/repo/{}", &identifier)
157 }),
74 )? 158 )?
75 .split(' ') 159 .split(' ')
76 .map(std::string::ToString::to_string) 160 .map(std::string::ToString::to_string)
@@ -78,41 +162,132 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {
78 } else { 162 } else {
79 args.web.clone() 163 args.web.clone()
80 }; 164 };
81 #[cfg(not(test))]
82 let mut client = Client::default();
83 #[cfg(test)]
84 let mut client = <MockConnect as std::default::Default>::default();
85 165
86 let (keys, user_ref) = login::launch(&cli_args.nsec, &cli_args.password, Some(&client)).await?; 166 let maintainers: Vec<XOnlyPublicKey> = {
87 167 let mut dont_ask = !args.other_maintainers.is_empty();
88 client.set_keys(&keys).await; 168 let mut maintainers_string = if !args.other_maintainers.is_empty() {
89 169 [args.other_maintainers.clone()].concat().join(" ")
90 let mut maintainers = vec![keys.public_key()]; 170 } else if repo_ref.is_none() && repo_config_result.is_err() {
171 keys.public_key().to_bech32()?
172 } else {
173 let maintainers = if let Ok(config) = &repo_config_result {
174 config.maintainers.clone()
175 } else if let Some(repo_ref) = &repo_ref {
176 repo_ref
177 .maintainers
178 .clone()
179 .iter()
180 .map(|k| k.to_bech32().unwrap())
181 .collect()
182 } else {
183 //unreachable
184 vec![keys.public_key().to_bech32()?]
185 };
186 // add current user if not present
187 if maintainers.iter().any(|m| {
188 if let Ok(m_pubkey) = XOnlyPublicKey::from_bech32(m) {
189 user_ref.public_key.eq(&m_pubkey)
190 } else {
191 false
192 }
193 }) {
194 maintainers.join(" ")
195 } else {
196 [maintainers, vec![keys.public_key().to_bech32()?]]
197 .concat()
198 .join(" ")
199 }
200 };
201 'outer: loop {
202 if !dont_ask {
203 maintainers_string = Interactor::default()
204 .input(
205 PromptInputParms::default()
206 .with_prompt("maintainers")
207 .with_default(maintainers_string),
208 )?
209 .split(' ')
210 .map(std::string::ToString::to_string)
211 .collect();
212 }
213 let mut maintainers: Vec<XOnlyPublicKey> = vec![];
214 for m in maintainers_string.split(' ') {
215 if let Ok(m_pubkey) = XOnlyPublicKey::from_bech32(m) {
216 maintainers.push(m_pubkey);
217 } else {
218 println!("not a valid set of npubs seperated by a space");
219 dont_ask = false;
220 continue 'outer;
221 }
222 }
223 // add current user incase removed
224 if !maintainers.iter().any(|m| user_ref.public_key.eq(m)) {
225 maintainers.push(keys.public_key());
226 }
227 break maintainers;
228 }
229 };
91 230
92 let repo_relays: Vec<String> = if !args.relays.is_empty() { 231 // TODO: check if relays are free to post to so contributors can submit patches
93 args.relays.clone() 232 // TODO: recommend some reliable free ones
94 } else if let Ok(config) = &repo_config_result { 233 let relays: Vec<String> = if args.relays.is_empty() {
95 config.relays.clone() 234 Interactor::default()
235 .input(
236 PromptInputParms::default()
237 .with_prompt("relays")
238 .with_default(if let Ok(config) = &repo_config_result {
239 config.relays.clone().join(" ")
240 } else if let Some(repo_ref) = &repo_ref {
241 repo_ref.relays.clone().join(" ")
242 } else {
243 user_ref.relays.write().join(" ")
244 }),
245 )?
246 .split(' ')
247 .map(std::string::ToString::to_string)
248 .collect()
96 } else { 249 } else {
97 // TODO: choice input defaulting to user relay list filtered by non paid relays 250 args.relays.clone()
98 // TODO: allow manual input for more relays
99 // TODO: reccommend some free relays
100 user_ref.relays.write()
101 }; 251 };
102 252
103 if let Ok(config) = &repo_config_result { 253 let earliest_unique_commit = match &args.earliest_unique_commit {
104 maintainers = extract_pks(config.maintainers.clone())?; 254 Some(t) => t.clone(),
105 } 255 None => {
256 let mut earliest_unique_commit = if let Some(repo_ref) = &repo_ref {
257 repo_ref.root_commit.clone()
258 } else {
259 root_commit.to_string()
260 };
261 loop {
262 earliest_unique_commit = Interactor::default().input(
263 PromptInputParms::default()
264 .with_prompt("earliest unique commit")
265 .with_default(earliest_unique_commit.clone()),
266 )?;
267 if let Ok(exists) = git_repo.does_commit_exist(&earliest_unique_commit) {
268 if exists {
269 break earliest_unique_commit;
270 }
271 println!("commit does not exist on current repository");
272 } else {
273 println!("commit id not formatted correctly");
274 }
275 if earliest_unique_commit.len().ne(&40) {
276 println!("commit id must be 40 characters long");
277 }
278 }
279 }
280 };
106 281
107 // if yaml file doesnt exist or needs updating 282 // if yaml file doesnt exist or needs updating
108 if match &repo_config_result { 283 if match &repo_config_result {
109 Ok(config) => { 284 Ok(config) => {
110 !(extract_pks(config.maintainers.clone())?.eq(&maintainers) 285 !(extract_pks(config.maintainers.clone())?.eq(&maintainers)
111 && config.relays.eq(&repo_relays)) 286 && config.relays.eq(&relays))
112 } 287 }
113 Err(_) => true, 288 Err(_) => true,
114 } { 289 } {
115 save_repo_config_to_yaml(&git_repo, maintainers.clone(), repo_relays.clone())?; 290 save_repo_config_to_yaml(&git_repo, maintainers.clone(), relays.clone())?;
116 println!( 291 println!(
117 "maintainers.yaml {}. commit and push.", 292 "maintainers.yaml {}. commit and push.",
118 if repo_config_result.is_err() { 293 if repo_config_result.is_err() {
@@ -121,6 +296,9 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {
121 "updated" 296 "updated"
122 } 297 }
123 ); 298 );
299 println!(
300 "this enables existing contributors to automatically fetch your repo event (instead of one from a pubkey pretending to be the maintainer)"
301 );
124 } 302 }
125 303
126 println!("publishing repostory reference..."); 304 println!("publishing repostory reference...");
@@ -129,10 +307,10 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {
129 identifier, 307 identifier,
130 name, 308 name,
131 description, 309 description,
132 root_commit: root_commit.to_string(), 310 root_commit: earliest_unique_commit,
133 git_server, 311 git_server,
134 web, 312 web,
135 relays: repo_relays.clone(), 313 relays: relays.clone(),
136 maintainers, 314 maintainers,
137 } 315 }
138 .to_event(&keys)?; 316 .to_event(&keys)?;
@@ -141,7 +319,7 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {
141 &client, 319 &client,
142 vec![repo_event], 320 vec![repo_event],
143 user_ref.relays.write(), 321 user_ref.relays.write(),
144 repo_relays, 322 relays,
145 !cli_args.disable_cli_spinners, 323 !cli_args.disable_cli_spinners,
146 ) 324 )
147 .await?; 325 .await?;
diff --git a/src/sub_commands/list.rs b/src/sub_commands/list.rs
index 666c4bf..f7397f1 100644
--- a/src/sub_commands/list.rs
+++ b/src/sub_commands/list.rs
@@ -48,6 +48,7 @@ pub async fn launch(_cli_args: &Cli, _args: &SubCommandArgs) -> Result<()> {
48 root_commit.to_string(), 48 root_commit.to_string(),
49 &client, 49 &client,
50 client.get_fallback_relays().clone(), 50 client.get_fallback_relays().clone(),
51 true,
51 ) 52 )
52 .await?; 53 .await?;
53 54
diff --git a/src/sub_commands/pull.rs b/src/sub_commands/pull.rs
index 9b74719..daae37f 100644
--- a/src/sub_commands/pull.rs
+++ b/src/sub_commands/pull.rs
@@ -44,6 +44,7 @@ pub async fn launch() -> Result<()> {
44 root_commit.to_string(), 44 root_commit.to_string(),
45 &client, 45 &client,
46 client.get_fallback_relays().clone(), 46 client.get_fallback_relays().clone(),
47 true,
47 ) 48 )
48 .await?; 49 .await?;
49 50
diff --git a/src/sub_commands/push.rs b/src/sub_commands/push.rs
index 06c3e50..bcac178 100644
--- a/src/sub_commands/push.rs
+++ b/src/sub_commands/push.rs
@@ -60,6 +60,7 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {
60 root_commit.to_string(), 60 root_commit.to_string(),
61 &client, 61 &client,
62 client.get_fallback_relays().clone(), 62 client.get_fallback_relays().clone(),
63 true,
63 ) 64 )
64 .await?; 65 .await?;
65 66
diff --git a/src/sub_commands/send.rs b/src/sub_commands/send.rs
index b5ab78f..857bc60 100644
--- a/src/sub_commands/send.rs
+++ b/src/sub_commands/send.rs
@@ -151,6 +151,7 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {
151 .to_string(), 151 .to_string(),
152 &client, 152 &client,
153 user_ref.relays.write(), 153 user_ref.relays.write(),
154 true,
154 ) 155 )
155 .await?; 156 .await?;
156 157