diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/cli.rs | 44 | ||||
| -rw-r--r-- | src/git.rs | 2 | ||||
| -rw-r--r-- | src/git_remote_helper.rs | 156 | ||||
| -rw-r--r-- | src/main.rs | 45 | ||||
| -rw-r--r-- | src/sub_commands/fetch.rs | 2 | ||||
| -rw-r--r-- | src/sub_commands/init.rs | 2 | ||||
| -rw-r--r-- | src/sub_commands/login.rs | 2 | ||||
| -rw-r--r-- | src/sub_commands/push.rs | 2 | ||||
| -rw-r--r-- | src/sub_commands/send.rs | 2 |
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 @@ | |||
| 1 | use clap::{Parser, Subcommand}; | ||
| 2 | |||
| 3 | use crate::sub_commands; | ||
| 4 | |||
| 5 | #[derive(Parser)] | ||
| 6 | #[command(author, version, about, long_about = None)] | ||
| 7 | #[command(propagate_version = true)] | ||
| 8 | pub 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)] | ||
| 29 | pub 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 | } | ||
| @@ -10,7 +10,7 @@ use nostr_sdk::hashes::{sha1::Hash as Sha1Hash, Hash}; | |||
| 10 | use crate::sub_commands::list::{get_commit_id_from_patch, tag_value}; | 10 | use crate::sub_commands::list::{get_commit_id_from_patch, tag_value}; |
| 11 | 11 | ||
| 12 | pub struct Repo { | 12 | pub struct Repo { |
| 13 | git_repo: git2::Repository, | 13 | pub git_repo: git2::Repository, |
| 14 | } | 14 | } |
| 15 | 15 | ||
| 16 | impl Repo { | 16 | impl 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 | |||
| 7 | use core::str; | ||
| 8 | use std::{ | ||
| 9 | collections::HashSet, | ||
| 10 | env, | ||
| 11 | io::{self}, | ||
| 12 | path::PathBuf, | ||
| 13 | }; | ||
| 14 | |||
| 15 | use anyhow::{bail, Context, Result}; | ||
| 16 | #[cfg(not(test))] | ||
| 17 | use client::Connect; | ||
| 18 | use client::{fetching_with_report, get_repo_ref_from_cache}; | ||
| 19 | use git::RepoActions; | ||
| 20 | use nostr::nips::nip01::Coordinate; | ||
| 21 | use nostr_sdk::Url; | ||
| 22 | |||
| 23 | #[cfg(not(test))] | ||
| 24 | use crate::client::Client; | ||
| 25 | #[cfg(test)] | ||
| 26 | use crate::client::MockConnect; | ||
| 27 | use crate::git::Repo; | ||
| 28 | |||
| 29 | mod cli; | ||
| 30 | mod cli_interactor; | ||
| 31 | mod client; | ||
| 32 | mod config; | ||
| 33 | mod git; | ||
| 34 | mod key_handling; | ||
| 35 | mod login; | ||
| 36 | mod repo_ref; | ||
| 37 | mod sub_commands; | ||
| 38 | |||
| 39 | #[tokio::main] | ||
| 40 | async 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. | ||
| 134 | pub(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 | |||
| 147 | fn 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 | ||
| 5 | use anyhow::Result; | 5 | use anyhow::Result; |
| 6 | use clap::{Parser, Subcommand}; | 6 | use clap::Parser; |
| 7 | use cli::{Cli, Commands}; | ||
| 7 | 8 | ||
| 9 | mod cli; | ||
| 8 | mod cli_interactor; | 10 | mod cli_interactor; |
| 9 | mod client; | 11 | mod client; |
| 10 | mod config; | 12 | mod config; |
| @@ -14,47 +16,6 @@ mod login; | |||
| 14 | mod repo_ref; | 16 | mod repo_ref; |
| 15 | mod sub_commands; | 17 | mod sub_commands; |
| 16 | 18 | ||
| 17 | #[derive(Parser)] | ||
| 18 | #[command(author, version, about, long_about = None)] | ||
| 19 | #[command(propagate_version = true)] | ||
| 20 | pub 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)] | ||
| 41 | enum 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] |
| 59 | async fn main() -> Result<()> { | 20 | async 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)] |
| 10 | use crate::client::MockConnect; | 10 | use crate::client::MockConnect; |
| 11 | use crate::{ | 11 | use 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)] |
| 11 | use crate::client::MockConnect; | 11 | use crate::client::MockConnect; |
| 12 | use crate::{ | 12 | use 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; | |||
| 5 | use crate::client::Client; | 5 | use crate::client::Client; |
| 6 | #[cfg(test)] | 6 | #[cfg(test)] |
| 7 | use crate::client::MockConnect; | 7 | use crate::client::MockConnect; |
| 8 | use crate::{client::Connect, git::Repo, login, Cli}; | 8 | use crate::{cli::Cli, client::Connect, git::Repo, login}; |
| 9 | 9 | ||
| 10 | #[derive(clap::Args)] | 10 | #[derive(clap::Args)] |
| 11 | pub struct SubCommandArgs { | 11 | pub 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)] |
| 6 | use crate::client::MockConnect; | 6 | use crate::client::MockConnect; |
| 7 | use crate::{ | 7 | use 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)] |
| 21 | use crate::client::MockConnect; | 21 | use crate::client::MockConnect; |
| 22 | use crate::{ | 22 | use 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)] |