upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2025-10-20 12:50:37 +0100
committerDanConwayDev <DanConwayDev@protonmail.com>2025-10-20 14:36:24 +0100
commit54bbcc3c16b88a01a29bb0f9bd76e9174993e16e (patch)
tree70f5625f9d544fda133c12beb9358d094ccbf598
parentb61f48bcdad6a7d13259bb0c1dfe6c7564b357a1 (diff)
fix: grasp server detection
to ensure we dont try and fallback to ssh
-rw-r--r--src/bin/git_remote_nostr/push.rs4
-rw-r--r--src/bin/ngit/sub_commands/sync.rs2
-rw-r--r--src/lib/repo_ref.rs216
3 files changed, 205 insertions, 17 deletions
diff --git a/src/bin/git_remote_nostr/push.rs b/src/bin/git_remote_nostr/push.rs
index 1645ae8..e880f0d 100644
--- a/src/bin/git_remote_nostr/push.rs
+++ b/src/bin/git_remote_nostr/push.rs
@@ -22,7 +22,7 @@ use ngit::{
22 list::list_from_remotes, 22 list::list_from_remotes,
23 login::{self, user::UserRef}, 23 login::{self, user::UserRef},
24 push::{push_to_remote, select_servers_push_refs_and_generate_pr_or_pr_update_event}, 24 push::{push_to_remote, select_servers_push_refs_and_generate_pr_or_pr_update_event},
25 repo_ref::{self, get_repo_config_from_yaml, is_grasp_server_in_list}, 25 repo_ref::{self, get_repo_config_from_yaml, is_grasp_server_clone_url},
26 repo_state, 26 repo_state,
27 utils::{ 27 utils::{
28 find_proposal_and_patches_by_branch_name, get_all_proposals, get_remote_name_by_url, 28 find_proposal_and_patches_by_branch_name, get_all_proposals, get_remote_name_by_url,
@@ -159,7 +159,7 @@ pub async fn run_push(
159 &repo_ref.to_nostr_git_url(&None), 159 &repo_ref.to_nostr_git_url(&None),
160 &remote_refspecs, 160 &remote_refspecs,
161 &term, 161 &term,
162 is_grasp_server_in_list(&git_server_url, &repo_ref.grasp_servers()), 162 is_grasp_server_clone_url(&git_server_url),
163 ); 163 );
164 } 164 }
165 } 165 }
diff --git a/src/bin/ngit/sub_commands/sync.rs b/src/bin/ngit/sub_commands/sync.rs
index 0860cc4..b7eb812 100644
--- a/src/bin/ngit/sub_commands/sync.rs
+++ b/src/bin/ngit/sub_commands/sync.rs
@@ -185,7 +185,7 @@ pub async fn launch(args: &SubCommandArgs) -> Result<()> {
185 &decoded_nostr_url, 185 &decoded_nostr_url,
186 &refspecs, 186 &refspecs,
187 &term, 187 &term,
188 *is_grasp_server, 188 *is_grasp_server || is_grasp_server_clone_url(url),
189 ) { 189 ) {
190 Err(error) => { 190 Err(error) => {
191 term.write_line(&format!( 191 term.write_line(&format!(
diff --git a/src/lib/repo_ref.rs b/src/lib/repo_ref.rs
index eb0964f..9573238 100644
--- a/src/lib/repo_ref.rs
+++ b/src/lib/repo_ref.rs
@@ -736,26 +736,56 @@ pub fn extract_npub(s: &str) -> Result<&str> {
736 736
737pub fn is_grasp_server_in_list(url: &str, grasp_servers: &[String]) -> bool { 737pub fn is_grasp_server_in_list(url: &str, grasp_servers: &[String]) -> bool {
738 if !grasp_servers.is_empty() { 738 if !grasp_servers.is_empty() {
739 if let Ok(url) = normalize_grasp_server_url(url) { 739 grasp_servers
740 grasp_servers.iter().any(|s| { 740 .iter()
741 if let Ok(s) = normalize_grasp_server_url(s) { 741 .any(|s| s.trim_end_matches('/') == url.trim_end_matches('/'))
742 s == url
743 } else {
744 false
745 }
746 })
747 } else {
748 false
749 }
750 } else { 742 } else {
751 false 743 false
752 } 744 }
753} 745}
754 746
755pub fn is_grasp_server_clone_url(url: &str) -> bool { 747pub fn is_grasp_server_clone_url(url: &str) -> bool {
756 extract_npub(url).is_ok() 748 // Must start with http:// or https://
757 && (url.ends_with(".git") || url.ends_with(".git/")) 749 if !url.starts_with("http://") && !url.starts_with("https://") {
758 && url.starts_with("http") 750 return false;
751 }
752
753 // Must end with .git or .git/
754 if !url.ends_with(".git") && !url.ends_with(".git/") {
755 return false;
756 }
757
758 // Must contain a valid npub
759 let npub = match extract_npub(url) {
760 Ok(npub) => npub,
761 Err(_) => return false,
762 };
763
764 // Must have format: /{npub}/<repo-name>.git
765 // The npub must be followed by a slash and then a non-empty repo name
766 let npub_pattern = format!("/{}/", npub);
767 if let Some(npub_pos) = url.find(&npub_pattern) {
768 // Get the part after /{npub}/
769 let after_npub = &url[npub_pos + npub_pattern.len()..];
770
771 // Remove trailing slash if present
772 let after_npub = after_npub.trim_end_matches('/');
773
774 // Must have a non-empty repo name that ends with .git
775 if after_npub.is_empty() || after_npub == ".git" {
776 return false;
777 }
778
779 // Repo name must be at least 1 character before .git
780 if !after_npub.ends_with(".git") {
781 return false;
782 }
783
784 let repo_name = &after_npub[..after_npub.len() - 4]; // Remove .git
785 !repo_name.is_empty()
786 } else {
787 false
788 }
759} 789}
760 790
761pub fn format_grasp_server_url_as_relay_url(url: &str) -> Result<String> { 791pub fn format_grasp_server_url_as_relay_url(url: &str) -> Result<String> {
@@ -1100,4 +1130,162 @@ mod tests {
1100 } 1130 }
1101 Ok(()) 1131 Ok(())
1102 } 1132 }
1133
1134 mod is_grasp_server_in_list {
1135 use super::*;
1136
1137 #[test]
1138 fn detects_in_list() {
1139 assert!(is_grasp_server_in_list(
1140 "https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/example-repo.git",
1141 &[
1142 "https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/example-repo.git".to_string(),
1143 "https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/example-repo2.git".to_string(),
1144 ],
1145 ))
1146 }
1147
1148 #[test]
1149 fn ignores_not_in_list() {
1150 assert!(!is_grasp_server_in_list(
1151 "https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/example-repo3.git",
1152 &[
1153 "https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/example-repo.git".to_string(),
1154 "https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/example-repo2.git".to_string(),
1155 ],
1156 ))
1157 }
1158 }
1159
1160 mod is_grasp_server_clone_url {
1161 use super::*;
1162
1163 #[test]
1164 fn valid_https_url() {
1165 assert!(is_grasp_server_clone_url(
1166 "https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/my-repo.git"
1167 ));
1168 }
1169
1170 #[test]
1171 fn valid_http_url() {
1172 assert!(is_grasp_server_clone_url(
1173 "http://localhost:8080/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/test-repo.git"
1174 ));
1175 }
1176
1177 #[test]
1178 fn valid_with_trailing_slash() {
1179 assert!(is_grasp_server_clone_url(
1180 "https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/my-repo.git/"
1181 ));
1182 }
1183
1184 #[test]
1185 fn valid_with_nested_path() {
1186 assert!(is_grasp_server_clone_url(
1187 "https://relay.ngit.dev/path/to/server/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/my-repo.git"
1188 ));
1189 }
1190
1191 #[test]
1192 fn valid_with_port() {
1193 assert!(is_grasp_server_clone_url(
1194 "https://relay.ngit.dev:8080/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/my-repo.git"
1195 ));
1196 }
1197
1198 #[test]
1199 fn invalid_missing_git_extension() {
1200 assert!(!is_grasp_server_clone_url(
1201 "https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/my-repo"
1202 ));
1203 }
1204
1205 #[test]
1206 fn invalid_no_npub() {
1207 assert!(!is_grasp_server_clone_url(
1208 "https://relay.ngit.dev/my-repo.git"
1209 ));
1210 }
1211
1212 #[test]
1213 fn invalid_npub_not_in_path() {
1214 // npub exists but not in the path structure (e.g., in query string or fragment)
1215 assert!(!is_grasp_server_clone_url(
1216 "https://relay.ngit.dev/my-repo.git?npub=npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr"
1217 ));
1218 }
1219
1220 #[test]
1221 fn invalid_wrong_protocol() {
1222 assert!(!is_grasp_server_clone_url(
1223 "ftp://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/my-repo.git"
1224 ));
1225 }
1226
1227 #[test]
1228 fn invalid_no_protocol() {
1229 assert!(!is_grasp_server_clone_url(
1230 "relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/my-repo.git"
1231 ));
1232 }
1233
1234 #[test]
1235 fn invalid_wss_protocol() {
1236 assert!(!is_grasp_server_clone_url(
1237 "wss://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/my-repo.git"
1238 ));
1239 }
1240
1241 #[test]
1242 fn invalid_npub_not_followed_by_slash() {
1243 // npub must be followed by a slash before the repo name
1244 assert!(!is_grasp_server_clone_url(
1245 "https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejrmy-repo.git"
1246 ));
1247 }
1248
1249 #[test]
1250 fn invalid_no_repo_name_after_npub() {
1251 assert!(!is_grasp_server_clone_url(
1252 "https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/.git"
1253 ));
1254 }
1255
1256 #[test]
1257 fn invalid_empty_repo_name() {
1258 assert!(!is_grasp_server_clone_url(
1259 "https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr.git"
1260 ));
1261 }
1262
1263 #[test]
1264 fn invalid_malformed_npub() {
1265 assert!(!is_grasp_server_clone_url(
1266 "https://relay.ngit.dev/npub123invalid/my-repo.git"
1267 ));
1268 }
1269
1270 #[test]
1271 fn valid_repo_name_with_hyphens() {
1272 assert!(is_grasp_server_clone_url(
1273 "https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/my-awesome-repo.git"
1274 ));
1275 }
1276
1277 #[test]
1278 fn valid_repo_name_with_underscores() {
1279 assert!(is_grasp_server_clone_url(
1280 "https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/my_repo.git"
1281 ));
1282 }
1283
1284 #[test]
1285 fn valid_repo_name_with_numbers() {
1286 assert!(is_grasp_server_clone_url(
1287 "https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/repo123.git"
1288 ));
1289 }
1290 }
1103} 1291}