upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/repo_ref.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/repo_ref.rs')
-rw-r--r--src/repo_ref.rs171
1 files changed, 166 insertions, 5 deletions
diff --git a/src/repo_ref.rs b/src/repo_ref.rs
index 8b34d2b..4952b16 100644
--- a/src/repo_ref.rs
+++ b/src/repo_ref.rs
@@ -1,8 +1,16 @@
1use std::{fs::File, io::BufReader, str::FromStr}; 1use std::{
2 collections::{HashMap, HashSet},
3 fs::File,
4 io::BufReader,
5 str::FromStr,
6};
2 7
3use anyhow::{bail, Context, Result}; 8use anyhow::{bail, Context, Result};
4use nostr::{nips::nip19::Nip19, FromBech32, PublicKey, Tag, TagStandard, ToBech32}; 9use nostr::{
5use nostr_sdk::NostrSigner; 10 nips::{nip01::Coordinate, nip19::Nip19},
11 FromBech32, PublicKey, Tag, TagStandard, ToBech32,
12};
13use nostr_sdk::{Kind, NostrSigner, Timestamp};
6use serde::{Deserialize, Serialize}; 14use serde::{Deserialize, Serialize};
7 15
8#[cfg(not(test))] 16#[cfg(not(test))]
@@ -11,7 +19,7 @@ use crate::client::Client;
11use crate::client::MockConnect; 19use crate::client::MockConnect;
12use crate::{ 20use crate::{
13 cli_interactor::{Interactor, InteractorPrompt, PromptInputParms}, 21 cli_interactor::{Interactor, InteractorPrompt, PromptInputParms},
14 client::{sign_event, Connect}, 22 client::{get_event_from_cache, get_event_from_global_cache, sign_event, Connect},
15 git::{Repo, RepoActions}, 23 git::{Repo, RepoActions},
16}; 24};
17 25
@@ -25,6 +33,7 @@ pub struct RepoRef {
25 pub web: Vec<String>, 33 pub web: Vec<String>,
26 pub relays: Vec<String>, 34 pub relays: Vec<String>,
27 pub maintainers: Vec<PublicKey>, 35 pub maintainers: Vec<PublicKey>,
36 pub events: HashMap<Coordinate, nostr::Event>,
28 // code languages and hashtags 37 // code languages and hashtags
29} 38}
30 39
@@ -88,7 +97,16 @@ impl TryFrom<nostr::Event> for RepoRef {
88 } else { 97 } else {
89 r.maintainers = vec![event.pubkey]; 98 r.maintainers = vec![event.pubkey];
90 } 99 }
91 100 r.events = HashMap::new();
101 r.events.insert(
102 Coordinate {
103 kind: event.kind,
104 identifier: event.identifier().unwrap().to_string(),
105 public_key: event.author(),
106 relays: vec![],
107 },
108 event,
109 );
92 Ok(r) 110 Ok(r)
93 } 111 }
94} 112}
@@ -160,6 +178,145 @@ impl RepoRef {
160 .await 178 .await
161 .context("failed to create repository reference event") 179 .context("failed to create repository reference event")
162 } 180 }
181 pub fn coordinates(&self) -> HashSet<Coordinate> {
182 let mut res = HashSet::new();
183 for m in &self.maintainers {
184 res.insert(Coordinate {
185 kind: Kind::Custom(REPO_REF_KIND),
186 public_key: *m,
187 identifier: self.identifier.clone(),
188 relays: vec![],
189 });
190 }
191 res
192 }
193 pub fn coordinates_with_timestamps(&self) -> Vec<(Coordinate, Option<Timestamp>)> {
194 self.coordinates()
195 .iter()
196 .map(|c| (c.clone(), self.events.get(c).map(|e| e.created_at)))
197 .collect::<Vec<(Coordinate, Option<Timestamp>)>>()
198 }
199}
200
201pub async fn get_repo_coordinates(
202 git_repo: &Repo,
203 #[cfg(test)] client: &crate::client::MockConnect,
204 #[cfg(not(test))] client: &Client,
205) -> Result<HashSet<Coordinate>> {
206 let mut repo_coordinates = HashSet::new();
207
208 if let Some(repo_override) = git_repo.get_git_config_item("nostr.repo", Some(false))? {
209 for s in repo_override.split(',') {
210 if let Ok(c) = Coordinate::parse(s) {
211 repo_coordinates.insert(c);
212 }
213 }
214 }
215
216 // TODO: when nostr remotes functionality is added, iterate on each remote and
217 // extract coordinates
218
219 if repo_coordinates.is_empty() {
220 if let Ok(repo_config) = get_repo_config_from_yaml(git_repo) {
221 let maintainers = {
222 let mut maintainers = HashSet::new();
223 for m in &repo_config.maintainers {
224 if let Ok(maintainer) = PublicKey::parse(m) {
225 maintainers.insert(maintainer);
226 }
227 }
228 maintainers
229 };
230 if let Some(identifier) = repo_config.identifier {
231 for public_key in maintainers {
232 repo_coordinates.insert(Coordinate {
233 kind: Kind::Custom(REPO_REF_KIND),
234 public_key,
235 identifier: identifier.clone(),
236 relays: vec![],
237 });
238 }
239 } else {
240 // if repo_config.identifier.is_empty() {
241 // this will only apply for a few repositories created before ngit v1.3
242 // that haven't updated their maintainers.yaml
243 if let Ok(Some(current_user_npub)) =
244 git_repo.get_git_config_item("nostr.npub", None)
245 {
246 if let Ok(current_user) = PublicKey::parse(current_user_npub) {
247 for m in &repo_config.maintainers {
248 if let Ok(maintainer) = PublicKey::parse(m) {
249 if current_user.eq(&maintainer) {
250 println!(
251 "please run `nigt init` to add the repo identifier to maintainers.yaml"
252 );
253 }
254 }
255 }
256 }
257 }
258 // look find all repo refs with root_commit. for identifier
259 let filter = nostr::Filter::default()
260 .kind(nostr::Kind::Custom(REPO_REF_KIND))
261 .reference(git_repo.get_root_commit()?.to_string())
262 .authors(maintainers.clone());
263 let mut events =
264 get_event_from_cache(git_repo.get_path()?, vec![filter.clone()]).await?;
265 if events.is_empty() {
266 events =
267 get_event_from_global_cache(git_repo.get_path()?, vec![filter.clone()])
268 .await?;
269 }
270 if events.is_empty() {
271 events = client
272 .get_events(client.get_fallback_relays().clone(), vec![filter.clone()])
273 .await?;
274 }
275 if let Some(e) = events.first() {
276 if let Some(identifier) = e.identifier() {
277 for m in &repo_config.maintainers {
278 if let Ok(maintainer) = PublicKey::parse(m) {
279 repo_coordinates.insert(Coordinate {
280 kind: Kind::Custom(REPO_REF_KIND),
281 public_key: maintainer,
282 identifier: identifier.to_string(),
283 relays: vec![],
284 });
285 }
286 }
287 }
288 } else {
289 let c = ask_for_naddr()?;
290 git_repo.save_git_config_item("nostr.repo", &c.to_bech32()?, false)?;
291 repo_coordinates.insert(c);
292 }
293 }
294 }
295 }
296
297 if repo_coordinates.is_empty() {
298 // TODO: present list of events filter by root_commit
299 // TODO: fallback to search based on identifier
300 let c = ask_for_naddr()?;
301 // PROBLEM: we are saving this before checking whether it actually exists, which
302 // means next time the user won't be prompted and may not know how to
303 // change the selected repo
304 git_repo.save_git_config_item("nostr.repo", &c.to_bech32()?, false)?;
305 repo_coordinates.insert(c);
306 }
307 Ok(repo_coordinates)
308}
309
310fn ask_for_naddr() -> Result<Coordinate> {
311 let mut prompt = "repository naddr";
312 Ok(loop {
313 if let Ok(c) = Coordinate::parse(
314 Interactor::default().input(PromptInputParms::default().with_prompt(prompt))?,
315 ) {
316 break c;
317 }
318 prompt = "repository valid naddr";
319 })
163} 320}
164 321
165pub async fn fetch( 322pub async fn fetch(
@@ -248,6 +405,7 @@ pub async fn fetch(
248 405
249#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq)] 406#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq)]
250pub struct RepoConfigYaml { 407pub struct RepoConfigYaml {
408 pub identifier: Option<String>,
251 pub maintainers: Vec<String>, 409 pub maintainers: Vec<String>,
252 pub relays: Vec<String>, 410 pub relays: Vec<String>,
253} 411}
@@ -277,6 +435,7 @@ pub fn extract_pks(pk_strings: Vec<String>) -> Result<Vec<PublicKey>> {
277 435
278pub fn save_repo_config_to_yaml( 436pub fn save_repo_config_to_yaml(
279 git_repo: &Repo, 437 git_repo: &Repo,
438 identifier: String,
280 maintainers: Vec<PublicKey>, 439 maintainers: Vec<PublicKey>,
281 relays: Vec<String>, 440 relays: Vec<String>,
282) -> Result<()> { 441) -> Result<()> {
@@ -301,6 +460,7 @@ pub fn save_repo_config_to_yaml(
301 serde_yaml::to_writer( 460 serde_yaml::to_writer(
302 file, 461 file,
303 &RepoConfigYaml { 462 &RepoConfigYaml {
463 identifier: Some(identifier),
304 maintainers: maintainers_npubs, 464 maintainers: maintainers_npubs,
305 relays, 465 relays,
306 }, 466 },
@@ -327,6 +487,7 @@ mod tests {
327 ], 487 ],
328 relays: vec!["ws://relay1.io".to_string(), "ws://relay2.io".to_string()], 488 relays: vec!["ws://relay1.io".to_string(), "ws://relay2.io".to_string()],
329 maintainers: vec![TEST_KEY_1_KEYS.public_key(), TEST_KEY_2_KEYS.public_key()], 489 maintainers: vec![TEST_KEY_1_KEYS.public_key(), TEST_KEY_2_KEYS.public_key()],
490 events: HashMap::new(),
330 } 491 }
331 .to_event(&TEST_KEY_1_SIGNER) 492 .to_event(&TEST_KEY_1_SIGNER)
332 .await 493 .await