upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/git_remote_helper.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/git_remote_helper.rs')
-rw-r--r--src/git_remote_helper.rs156
1 files changed, 156 insertions, 0 deletions
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}