upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2024-07-26 10:29:33 +0100
committerDanConwayDev <DanConwayDev@protonmail.com>2024-07-26 10:36:58 +0100
commit0134ab8eb413b8b81ec8e179897ddb8ea63e134e (patch)
tree1d3ef8786fa806a4d39378286c68cda722cc017d /src
parent52f9efa50f81142da013c4da5f3cd3091e07916b (diff)
feat(remote): add nostr git remote helper
as a simple proxy to the first git server listed in announcement parse clone url as `nostr://naddr123...`
Diffstat (limited to 'src')
-rw-r--r--src/cli.rs44
-rw-r--r--src/git.rs2
-rw-r--r--src/git_remote_helper.rs156
-rw-r--r--src/main.rs45
-rw-r--r--src/sub_commands/fetch.rs2
-rw-r--r--src/sub_commands/init.rs2
-rw-r--r--src/sub_commands/login.rs2
-rw-r--r--src/sub_commands/push.rs2
-rw-r--r--src/sub_commands/send.rs2
9 files changed, 209 insertions, 48 deletions
diff --git a/src/cli.rs b/src/cli.rs
new file mode 100644
index 0000000..d0f934e
--- /dev/null
+++ b/src/cli.rs
@@ -0,0 +1,44 @@
1use clap::{Parser, Subcommand};
2
3use crate::sub_commands;
4
5#[derive(Parser)]
6#[command(author, version, about, long_about = None)]
7#[command(propagate_version = true)]
8pub struct Cli {
9 #[command(subcommand)]
10 pub command: Commands,
11 /// remote signer address
12 #[arg(long, global = true)]
13 pub bunker_uri: Option<String>,
14 /// remote signer app secret key
15 #[arg(long, global = true)]
16 pub bunker_app_key: Option<String>,
17 /// nsec or hex private key
18 #[arg(short, long, global = true)]
19 pub nsec: Option<String>,
20 /// password to decrypt nsec
21 #[arg(short, long, global = true)]
22 pub password: Option<String>,
23 /// disable spinner animations
24 #[arg(long, action)]
25 pub disable_cli_spinners: bool,
26}
27
28#[derive(Subcommand)]
29pub enum Commands {
30 /// update cache with latest updates from nostr
31 Fetch(sub_commands::fetch::SubCommandArgs),
32 /// signal you are this repo's maintainer accepting proposals via nostr
33 Init(sub_commands::init::SubCommandArgs),
34 /// issue commits as a proposal
35 Send(sub_commands::send::SubCommandArgs),
36 /// list proposals; checkout, apply or download selected
37 List,
38 /// send proposal revision
39 Push(sub_commands::push::SubCommandArgs),
40 /// fetch and apply new proposal commits / revisions linked to branch
41 Pull,
42 /// run with --nsec flag to change npub
43 Login(sub_commands::login::SubCommandArgs),
44}
diff --git a/src/git.rs b/src/git.rs
index c13b46d..eaea512 100644
--- a/src/git.rs
+++ b/src/git.rs
@@ -10,7 +10,7 @@ use nostr_sdk::hashes::{sha1::Hash as Sha1Hash, Hash};
10use crate::sub_commands::list::{get_commit_id_from_patch, tag_value}; 10use crate::sub_commands::list::{get_commit_id_from_patch, tag_value};
11 11
12pub struct Repo { 12pub struct Repo {
13 git_repo: git2::Repository, 13 pub git_repo: git2::Repository,
14} 14}
15 15
16impl Repo { 16impl Repo {
diff --git a/src/git_remote_helper.rs b/src/git_remote_helper.rs
new file mode 100644
index 0000000..6050d1a
--- /dev/null
+++ b/src/git_remote_helper.rs
@@ -0,0 +1,156 @@
1#![cfg_attr(not(test), warn(clippy::pedantic))]
2#![allow(clippy::large_futures)]
3// better solution to dead_code error on multiple binaries than https://stackoverflow.com/a/66196291
4#![allow(dead_code)]
5#![cfg_attr(not(test), warn(clippy::expect_used))]
6
7use core::str;
8use std::{
9 collections::HashSet,
10 env,
11 io::{self},
12 path::PathBuf,
13};
14
15use anyhow::{bail, Context, Result};
16#[cfg(not(test))]
17use client::Connect;
18use client::{fetching_with_report, get_repo_ref_from_cache};
19use git::RepoActions;
20use nostr::nips::nip01::Coordinate;
21use nostr_sdk::Url;
22
23#[cfg(not(test))]
24use crate::client::Client;
25#[cfg(test)]
26use crate::client::MockConnect;
27use crate::git::Repo;
28
29mod cli;
30mod cli_interactor;
31mod client;
32mod config;
33mod git;
34mod key_handling;
35mod login;
36mod repo_ref;
37mod sub_commands;
38
39#[tokio::main]
40async fn main() -> Result<()> {
41 let args = env::args();
42 let args = args.skip(1).take(2).collect::<Vec<_>>();
43
44 let ([_, url] | [url]) = args.as_slice() else {
45 bail!("invalid arguments - no url");
46 };
47 if env::args().nth(1).as_deref() == Some("--version") {
48 println!("v0.0.1");
49 }
50
51 let git_repo = Repo::from_path(&PathBuf::from(
52 std::env::var("GIT_DIR").context("git should set GIT_DIR when remote helper is called")?,
53 ))?;
54 let git_repo_path = git_repo.get_path()?;
55
56 #[cfg(not(test))]
57 let client = Client::default();
58 #[cfg(test)]
59 let client = <MockConnect as std::default::Default>::default();
60
61 let repo_coordinates = nostr_git_url_to_repo_coordinates(url).context("invalid nostr url")?;
62
63 fetching_with_report(git_repo_path, &client, &repo_coordinates).await?;
64
65 let repo_ref = get_repo_ref_from_cache(git_repo_path, &repo_coordinates).await?;
66
67 let stdin = io::stdin();
68 let mut line = String::new();
69
70 let temp_remote_url = repo_ref
71 .git_server
72 .first()
73 .context("no git server listed in nostr repository announcement")?;
74
75 let mut temp_remote = git_repo.git_repo.remote_anonymous(temp_remote_url)?;
76
77 loop {
78 let tokens = read_line(&stdin, &mut line)?;
79
80 match tokens.as_slice() {
81 ["capabilities"] => {
82 println!("option");
83 println!("push");
84 println!("fetch");
85 println!();
86 }
87 ["option", "verbosity"] => {
88 println!("ok");
89 }
90 ["option", ..] => {
91 println!("unsupported");
92 }
93 ["fetch", _oid, refstr] => {
94 temp_remote.connect(git2::Direction::Fetch)?;
95 temp_remote.download(&[refstr], None)?;
96 temp_remote.disconnect()?;
97 println!();
98 }
99 ["push", refspec] => {
100 temp_remote.connect(git2::Direction::Push)?;
101 temp_remote.push(&[refspec], None)?;
102 temp_remote.disconnect()?;
103 println!();
104 }
105 ["list"] => {
106 temp_remote.connect(git2::Direction::Fetch)?;
107 for head in temp_remote.list()? {
108 println!("{} {}", head.oid(), head.name());
109 }
110 temp_remote.disconnect()?;
111 println!();
112 }
113 ["list", "for-push"] => {
114 temp_remote.connect(git2::Direction::Fetch)?;
115 for head in temp_remote.list()? {
116 if head.name() != "HEAD" {
117 println!("{} {}", head.oid(), head.name());
118 }
119 }
120 temp_remote.disconnect()?;
121 println!();
122 }
123 [] => {
124 return Ok(());
125 }
126 _ => {
127 bail!(format!("unknown command: {}", line.trim().to_owned()));
128 }
129 }
130 }
131}
132
133/// Read one line from stdin, and split it into tokens.
134pub(crate) fn read_line<'a>(stdin: &io::Stdin, line: &'a mut String) -> io::Result<Vec<&'a str>> {
135 line.clear();
136
137 let read = stdin.read_line(line)?;
138 if read == 0 {
139 return Ok(vec![]);
140 }
141 let line = line.trim();
142 let tokens = line.split(' ').filter(|t| !t.is_empty()).collect();
143
144 Ok(tokens)
145}
146
147fn nostr_git_url_to_repo_coordinates(url: &str) -> Result<HashSet<Coordinate>> {
148 let mut repo_coordinattes = HashSet::new();
149 let coordinate = Coordinate::parse(Url::parse(url)?.domain().context("no naddr")?)?;
150 if coordinate.kind.eq(&nostr_sdk::Kind::GitRepoAnnouncement) {
151 repo_coordinattes.insert(coordinate);
152 } else {
153 bail!("naddr doesnt point to a git repository announcement");
154 }
155 Ok(repo_coordinattes)
156}
diff --git a/src/main.rs b/src/main.rs
index 81eaf2f..add26f1 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3,8 +3,10 @@
3#![cfg_attr(not(test), warn(clippy::expect_used))] 3#![cfg_attr(not(test), warn(clippy::expect_used))]
4 4
5use anyhow::Result; 5use anyhow::Result;
6use clap::{Parser, Subcommand}; 6use clap::Parser;
7use cli::{Cli, Commands};
7 8
9mod cli;
8mod cli_interactor; 10mod cli_interactor;
9mod client; 11mod client;
10mod config; 12mod config;
@@ -14,47 +16,6 @@ mod login;
14mod repo_ref; 16mod repo_ref;
15mod sub_commands; 17mod sub_commands;
16 18
17#[derive(Parser)]
18#[command(author, version, about, long_about = None)]
19#[command(propagate_version = true)]
20pub struct Cli {
21 #[command(subcommand)]
22 command: Commands,
23 /// remote signer address
24 #[arg(long, global = true)]
25 bunker_uri: Option<String>,
26 /// remote signer app secret key
27 #[arg(long, global = true)]
28 bunker_app_key: Option<String>,
29 /// nsec or hex private key
30 #[arg(short, long, global = true)]
31 nsec: Option<String>,
32 /// password to decrypt nsec
33 #[arg(short, long, global = true)]
34 password: Option<String>,
35 /// disable spinner animations
36 #[arg(long, action)]
37 disable_cli_spinners: bool,
38}
39
40#[derive(Subcommand)]
41enum Commands {
42 /// update cache with latest updates from nostr
43 Fetch(sub_commands::fetch::SubCommandArgs),
44 /// signal you are this repo's maintainer accepting proposals via nostr
45 Init(sub_commands::init::SubCommandArgs),
46 /// issue commits as a proposal
47 Send(sub_commands::send::SubCommandArgs),
48 /// list proposals; checkout, apply or download selected
49 List,
50 /// send proposal revision
51 Push(sub_commands::push::SubCommandArgs),
52 /// fetch and apply new proposal commits / revisions linked to branch
53 Pull,
54 /// run with --nsec flag to change npub
55 Login(sub_commands::login::SubCommandArgs),
56}
57
58#[tokio::main] 19#[tokio::main]
59async fn main() -> Result<()> { 20async fn main() -> Result<()> {
60 let cli = Cli::parse(); 21 let cli = Cli::parse();
diff --git a/src/sub_commands/fetch.rs b/src/sub_commands/fetch.rs
index ab6e0fc..b1e83c5 100644
--- a/src/sub_commands/fetch.rs
+++ b/src/sub_commands/fetch.rs
@@ -9,10 +9,10 @@ use crate::client::Client;
9#[cfg(test)] 9#[cfg(test)]
10use crate::client::MockConnect; 10use crate::client::MockConnect;
11use crate::{ 11use crate::{
12 cli::Cli,
12 client::{fetching_with_report, Connect}, 13 client::{fetching_with_report, Connect},
13 git::{Repo, RepoActions}, 14 git::{Repo, RepoActions},
14 repo_ref::get_repo_coordinates, 15 repo_ref::get_repo_coordinates,
15 Cli,
16}; 16};
17 17
18#[derive(clap::Args)] 18#[derive(clap::Args)]
diff --git a/src/sub_commands/init.rs b/src/sub_commands/init.rs
index 28ba21b..ba188c9 100644
--- a/src/sub_commands/init.rs
+++ b/src/sub_commands/init.rs
@@ -10,6 +10,7 @@ use crate::client::Client;
10#[cfg(test)] 10#[cfg(test)]
11use crate::client::MockConnect; 11use crate::client::MockConnect;
12use crate::{ 12use crate::{
13 cli::Cli,
13 cli_interactor::{Interactor, InteractorPrompt, PromptInputParms}, 14 cli_interactor::{Interactor, InteractorPrompt, PromptInputParms},
14 client::{fetching_with_report, get_repo_ref_from_cache, Connect}, 15 client::{fetching_with_report, get_repo_ref_from_cache, Connect},
15 git::{Repo, RepoActions}, 16 git::{Repo, RepoActions},
@@ -18,7 +19,6 @@ use crate::{
18 extract_pks, get_repo_config_from_yaml, save_repo_config_to_yaml, 19 extract_pks, get_repo_config_from_yaml, save_repo_config_to_yaml,
19 try_and_get_repo_coordinates, RepoRef, 20 try_and_get_repo_coordinates, RepoRef,
20 }, 21 },
21 Cli,
22}; 22};
23 23
24#[derive(Debug, clap::Args)] 24#[derive(Debug, clap::Args)]
diff --git a/src/sub_commands/login.rs b/src/sub_commands/login.rs
index 6f49ba8..77fecdd 100644
--- a/src/sub_commands/login.rs
+++ b/src/sub_commands/login.rs
@@ -5,7 +5,7 @@ use clap;
5use crate::client::Client; 5use crate::client::Client;
6#[cfg(test)] 6#[cfg(test)]
7use crate::client::MockConnect; 7use crate::client::MockConnect;
8use crate::{client::Connect, git::Repo, login, Cli}; 8use crate::{cli::Cli, client::Connect, git::Repo, login};
9 9
10#[derive(clap::Args)] 10#[derive(clap::Args)]
11pub struct SubCommandArgs { 11pub struct SubCommandArgs {
diff --git a/src/sub_commands/push.rs b/src/sub_commands/push.rs
index acd91f0..56927fe 100644
--- a/src/sub_commands/push.rs
+++ b/src/sub_commands/push.rs
@@ -5,6 +5,7 @@ use crate::client::Client;
5#[cfg(test)] 5#[cfg(test)]
6use crate::client::MockConnect; 6use crate::client::MockConnect;
7use crate::{ 7use crate::{
8 cli::Cli,
8 client::{fetching_with_report, get_repo_ref_from_cache, Connect}, 9 client::{fetching_with_report, get_repo_ref_from_cache, Connect},
9 git::{str_to_sha1, Repo, RepoActions}, 10 git::{str_to_sha1, Repo, RepoActions},
10 login, 11 login,
@@ -21,7 +22,6 @@ use crate::{
21 identify_ahead_behind, send_events, 22 identify_ahead_behind, send_events,
22 }, 23 },
23 }, 24 },
24 Cli,
25}; 25};
26 26
27#[derive(Debug, clap::Args)] 27#[derive(Debug, clap::Args)]
diff --git a/src/sub_commands/send.rs b/src/sub_commands/send.rs
index 73c980b..07eb343 100644
--- a/src/sub_commands/send.rs
+++ b/src/sub_commands/send.rs
@@ -20,6 +20,7 @@ use crate::client::Client;
20#[cfg(test)] 20#[cfg(test)]
21use crate::client::MockConnect; 21use crate::client::MockConnect;
22use crate::{ 22use crate::{
23 cli::Cli,
23 cli_interactor::{ 24 cli_interactor::{
24 Interactor, InteractorPrompt, PromptConfirmParms, PromptInputParms, PromptMultiChoiceParms, 25 Interactor, InteractorPrompt, PromptConfirmParms, PromptInputParms, PromptMultiChoiceParms,
25 }, 26 },
@@ -29,7 +30,6 @@ use crate::{
29 git::{Repo, RepoActions}, 30 git::{Repo, RepoActions},
30 login, 31 login,
31 repo_ref::{get_repo_coordinates, RepoRef}, 32 repo_ref::{get_repo_coordinates, RepoRef},
32 Cli,
33}; 33};
34 34
35#[derive(Debug, clap::Args)] 35#[derive(Debug, clap::Args)]