upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/lib/git/mod.rs
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2024-09-04 11:32:05 +0100
committerDanConwayDev <DanConwayDev@protonmail.com>2024-09-04 14:23:54 +0100
commit771f944af447c202eba045936a36dee71ab797ac (patch)
treee691de4ebc8dde7ac4855e139881ff923bc254ce /src/lib/git/mod.rs
parent949c6459aa7683453a7160423b689ceadb08954b (diff)
refactor: fix imports, etc based on restructure
move some functions out of ngit and into lib/mod and lib/git_events remove MockConnect from binaries so it is only used in the library. this was done: * mainly because automocks were not being imported from lib into each binary * but also because the these functions were being tested with MockConnect
Diffstat (limited to 'src/lib/git/mod.rs')
-rw-r--r--src/lib/git/mod.rs266
1 files changed, 7 insertions, 259 deletions
diff --git a/src/lib/git/mod.rs b/src/lib/git/mod.rs
index 5919667..f92272f 100644
--- a/src/lib/git/mod.rs
+++ b/src/lib/git/mod.rs
@@ -1,18 +1,16 @@
1use std::{ 1use std::{
2 collections::HashSet,
3 env::current_dir, 2 env::current_dir,
4 path::{Path, PathBuf}, 3 path::{Path, PathBuf},
5}; 4};
6 5
7use anyhow::{bail, Context, Result}; 6use anyhow::{bail, Context, Result};
8use git2::{DiffOptions, Oid, Revwalk}; 7use git2::{DiffOptions, Oid, Revwalk};
9use nostr::nips::nip01::Coordinate; 8pub use identify_ahead_behind::identify_ahead_behind;
10use nostr_sdk::{ 9use nostr_sdk::hashes::{sha1::Hash as Sha1Hash, Hash};
11 hashes::{sha1::Hash as Sha1Hash, Hash},
12 PublicKey, Url,
13};
14 10
15use crate::sub_commands::list::{get_commit_id_from_patch, tag_value}; 11use crate::git_events::{get_commit_id_from_patch, tag_value};
12pub mod identify_ahead_behind;
13pub mod nostr_url;
16 14
17pub struct Repo { 15pub struct Repo {
18 pub git_repo: git2::Repository, 16 pub git_repo: git2::Repository,
@@ -835,188 +833,6 @@ fn extract_sig_from_patch_tags<'a>(
835 .context("failed to create git signature") 833 .context("failed to create git signature")
836} 834}
837 835
838#[derive(Debug, PartialEq)]
839pub enum ServerProtocol {
840 Ssh,
841 Https,
842 Http,
843 Git,
844}
845
846#[derive(Debug, PartialEq)]
847pub struct NostrUrlDecoded {
848 pub coordinates: HashSet<Coordinate>,
849 pub protocol: Option<ServerProtocol>,
850 pub user: Option<String>,
851}
852
853static INCORRECT_NOSTR_URL_FORMAT_ERROR: &str = "incorrect nostr git url format. try nostr://naddr123 or nostr://npub123/my-repo or nostr://ssh/npub123/relay.damus.io/my-repo";
854
855impl NostrUrlDecoded {
856 pub fn from_str(url: &str) -> Result<Self> {
857 let mut coordinates = HashSet::new();
858 let mut protocol = None;
859 let mut user = None;
860 let mut relays = vec![];
861
862 if !url.starts_with("nostr://") {
863 bail!("nostr git url must start with nostr://");
864 }
865 // process get url parameters if present
866 for (name, value) in Url::parse(url)?.query_pairs() {
867 if name.contains("relay") {
868 let mut decoded = urlencoding::decode(&value)
869 .context("could not parse relays in nostr git url")?
870 .to_string();
871 if !decoded.starts_with("ws://") && !decoded.starts_with("wss://") {
872 decoded = format!("wss://{decoded}");
873 }
874 let url =
875 Url::parse(&decoded).context("could not parse relays in nostr git url")?;
876 relays.push(url.to_string());
877 } else if name == "protocol" {
878 protocol = match value.as_ref() {
879 "ssh" => Some(ServerProtocol::Ssh),
880 "https" => Some(ServerProtocol::Https),
881 "http" => Some(ServerProtocol::Http),
882 "git" => Some(ServerProtocol::Git),
883 _ => None,
884 };
885 } else if name == "user" {
886 user = Some(value.to_string());
887 }
888 }
889
890 let mut parts: Vec<&str> = url[8..]
891 .split('?')
892 .next()
893 .unwrap_or("")
894 .split('/')
895 .collect();
896
897 // extract optional protocol
898 if protocol.is_none() {
899 let part = parts.first().context(INCORRECT_NOSTR_URL_FORMAT_ERROR)?;
900 let protocol_str = if let Some(at_index) = part.find('@') {
901 user = Some(part[..at_index].to_string());
902 &part[at_index + 1..]
903 } else {
904 part
905 };
906 protocol = match protocol_str {
907 "ssh" => Some(ServerProtocol::Ssh),
908 "https" => Some(ServerProtocol::Https),
909 "http" => Some(ServerProtocol::Http),
910 "git" => Some(ServerProtocol::Git),
911 _ => protocol,
912 };
913 if protocol.is_some() {
914 parts.remove(0);
915 }
916 }
917 // extract naddr npub/<optional-relays>/identifer
918 let part = parts.first().context(INCORRECT_NOSTR_URL_FORMAT_ERROR)?;
919 // naddr used
920 if let Ok(coordinate) = Coordinate::parse(part) {
921 if coordinate.kind.eq(&nostr_sdk::Kind::GitRepoAnnouncement) {
922 coordinates.insert(coordinate);
923 } else {
924 bail!("naddr doesnt point to a git repository announcement");
925 }
926 // npub/<optional-relays>/identifer used
927 } else if let Ok(public_key) = PublicKey::parse(part) {
928 parts.remove(0);
929 let identifier = parts
930 .pop()
931 .context("nostr url must have an identifier eg. nostr://npub123/repo-identifier")?
932 .to_string();
933 for relay in parts {
934 let mut decoded = urlencoding::decode(relay)
935 .context("could not parse relays in nostr git url")?
936 .to_string();
937 if !decoded.starts_with("ws://") && !decoded.starts_with("wss://") {
938 decoded = format!("wss://{decoded}");
939 }
940 let url =
941 Url::parse(&decoded).context("could not parse relays in nostr git url")?;
942 relays.push(url.to_string());
943 }
944 coordinates.insert(Coordinate {
945 identifier,
946 public_key,
947 kind: nostr_sdk::Kind::GitRepoAnnouncement,
948 relays,
949 });
950 } else {
951 bail!(INCORRECT_NOSTR_URL_FORMAT_ERROR);
952 }
953
954 Ok(Self {
955 coordinates,
956 protocol,
957 user,
958 })
959 }
960}
961
962/** produce error when using local repo or custom protocols */
963pub fn convert_clone_url_to_https(url: &str) -> Result<String> {
964 // Strip credentials if present
965 let stripped_url = strip_credentials(url);
966
967 // Check if the URL is already in HTTPS format
968 if stripped_url.starts_with("https://") {
969 return Ok(stripped_url);
970 }
971 // Convert http:// to https://
972 else if stripped_url.starts_with("http://") {
973 return Ok(stripped_url.replace("http://", "https://"));
974 }
975 // Check if the URL starts with SSH
976 else if stripped_url.starts_with("ssh://") {
977 // Convert SSH to HTTPS
978 let parts: Vec<&str> = stripped_url
979 .trim_start_matches("ssh://")
980 .split('/')
981 .collect();
982 if parts.len() >= 2 {
983 // Construct the HTTPS URL
984 return Ok(format!("https://{}/{}", parts[0], parts[1..].join("/")));
985 }
986 bail!("Invalid SSH URL format: {}", url);
987 }
988 // Convert ftp:// to https://
989 else if stripped_url.starts_with("ftp://") {
990 return Ok(stripped_url.replace("ftp://", "https://"));
991 }
992 // Convert git:// to https://
993 else if stripped_url.starts_with("git://") {
994 return Ok(stripped_url.replace("git://", "https://"));
995 }
996
997 // If the URL is neither HTTPS, SSH, nor git@, return an error
998 bail!("Unsupported URL protocol: {}", url);
999}
1000
1001// Function to strip username and password from the URL
1002fn strip_credentials(url: &str) -> String {
1003 if let Some(pos) = url.find("://") {
1004 let (protocol, rest) = url.split_at(pos + 3); // Split at "://"
1005 let rest_parts: Vec<&str> = rest.split('@').collect();
1006 if rest_parts.len() > 1 {
1007 // If there are credentials, return the URL without them
1008 return format!("{}{}", protocol, rest_parts[1]);
1009 }
1010 } else if let Some(at_pos) = url.find('@') {
1011 // Handle user@host:path format
1012 let (_, rest) = url.split_at(at_pos);
1013 // This is a git@ syntax
1014 let host_and_repo = &rest[1..]; // Skip the ':'
1015 return format!("ssh://{}", host_and_repo.replace(':', "/"));
1016 }
1017 url.to_string() // Return the original URL if no credentials are found
1018}
1019
1020#[cfg(test)] 836#[cfg(test)]
1021mod tests { 837mod tests {
1022 use std::fs; 838 use std::fs;
@@ -1813,7 +1629,7 @@ mod tests {
1813 use test_utils::TEST_KEY_1_SIGNER; 1629 use test_utils::TEST_KEY_1_SIGNER;
1814 1630
1815 use super::*; 1631 use super::*;
1816 use crate::{repo_ref::RepoRef, sub_commands::send::generate_patch_event}; 1632 use crate::{git_events::generate_patch_event, repo_ref::RepoRef};
1817 1633
1818 async fn generate_patch_from_head_commit(test_repo: &GitTestRepo) -> Result<nostr::Event> { 1634 async fn generate_patch_from_head_commit(test_repo: &GitTestRepo) -> Result<nostr::Event> {
1819 let original_oid = test_repo.git_repo.head()?.peel_to_commit()?.id(); 1635 let original_oid = test_repo.git_repo.head()?.peel_to_commit()?.id();
@@ -1959,9 +1775,7 @@ mod tests {
1959 use test_utils::TEST_KEY_1_SIGNER; 1775 use test_utils::TEST_KEY_1_SIGNER;
1960 1776
1961 use super::*; 1777 use super::*;
1962 use crate::{ 1778 use crate::{git_events::generate_cover_letter_and_patch_events, repo_ref::RepoRef};
1963 repo_ref::RepoRef, sub_commands::send::generate_cover_letter_and_patch_events,
1964 };
1965 1779
1966 static BRANCH_NAME: &str = "add-example-feature"; 1780 static BRANCH_NAME: &str = "add-example-feature";
1967 // returns original_repo, cover_letter_event, patch_events 1781 // returns original_repo, cover_letter_event, patch_events
@@ -2497,70 +2311,4 @@ mod tests {
2497 Ok(()) 2311 Ok(())
2498 } 2312 }
2499 } 2313 }
2500 mod convert_clone_url_to_https {
2501 use super::*;
2502
2503 #[test]
2504 fn test_https_url() {
2505 let url = "https://github.com/user/repo.git";
2506 let result = convert_clone_url_to_https(url).unwrap();
2507 assert_eq!(result, "https://github.com/user/repo.git");
2508 }
2509
2510 #[test]
2511 fn test_http_url() {
2512 let url = "http://github.com/user/repo.git";
2513 let result = convert_clone_url_to_https(url).unwrap();
2514 assert_eq!(result, "https://github.com/user/repo.git");
2515 }
2516
2517 #[test]
2518 fn test_http_url_with_credentials() {
2519 let url = "http://username:password@github.com/user/repo.git";
2520 let result = convert_clone_url_to_https(url).unwrap();
2521 assert_eq!(result, "https://github.com/user/repo.git");
2522 }
2523
2524 #[test]
2525 fn test_git_at_url() {
2526 let url = "git@github.com:user/repo.git";
2527 let result = convert_clone_url_to_https(url).unwrap();
2528 assert_eq!(result, "https://github.com/user/repo.git");
2529 }
2530
2531 #[test]
2532 fn test_user_at_url() {
2533 let url = "user1@github.com:user/repo.git";
2534 let result = convert_clone_url_to_https(url).unwrap();
2535 assert_eq!(result, "https://github.com/user/repo.git");
2536 }
2537
2538 #[test]
2539 fn test_ssh_url() {
2540 let url = "ssh://github.com/user/repo.git";
2541 let result = convert_clone_url_to_https(url).unwrap();
2542 assert_eq!(result, "https://github.com/user/repo.git");
2543 }
2544
2545 #[test]
2546 fn test_ftp_url() {
2547 let url = "ftp://example.com/repo.git";
2548 let result = convert_clone_url_to_https(url).unwrap();
2549 assert_eq!(result, "https://example.com/repo.git");
2550 }
2551
2552 #[test]
2553 fn test_git_protocol_url() {
2554 let url = "git://example.com/repo.git";
2555 let result = convert_clone_url_to_https(url).unwrap();
2556 assert_eq!(result, "https://example.com/repo.git");
2557 }
2558
2559 #[test]
2560 fn test_invalid_url() {
2561 let url = "unsupported://example.com/repo.git";
2562 let result = convert_clone_url_to_https(url);
2563 assert!(result.is_err());
2564 }
2565 }
2566} 2314}