diff options
Diffstat (limited to 'src/git/mod.rs')
| -rw-r--r-- | src/git/mod.rs | 127 |
1 files changed, 127 insertions, 0 deletions
diff --git a/src/git/mod.rs b/src/git/mod.rs new file mode 100644 index 0000000..bd3b9e8 --- /dev/null +++ b/src/git/mod.rs | |||
| @@ -0,0 +1,127 @@ | |||
| 1 | //! Git Smart HTTP Backend | ||
| 2 | //! | ||
| 3 | //! This module implements Git Smart HTTP protocol support for ngit-grasp. | ||
| 4 | //! It provides handlers for clone, fetch, and push operations over HTTP. | ||
| 5 | //! | ||
| 6 | //! # Architecture | ||
| 7 | //! | ||
| 8 | //! - `protocol` - Git pkt-line format parsing and utilities | ||
| 9 | //! - `subprocess` - Git process spawning and management | ||
| 10 | //! - `handlers` - HTTP request handlers for Git operations | ||
| 11 | //! | ||
| 12 | //! # URL Patterns | ||
| 13 | //! | ||
| 14 | //! The following URL patterns are supported: | ||
| 15 | //! - `GET /<npub>/<identifier>.git/info/refs?service=git-upload-pack` - Clone/fetch advertisement | ||
| 16 | //! - `GET /<npub>/<identifier>.git/info/refs?service=git-receive-pack` - Push advertisement | ||
| 17 | //! - `POST /<npub>/<identifier>.git/git-upload-pack` - Clone/fetch operation | ||
| 18 | //! - `POST /<npub>/<identifier>.git/git-receive-pack` - Push operation | ||
| 19 | |||
| 20 | pub mod handlers; | ||
| 21 | pub mod protocol; | ||
| 22 | pub mod subprocess; | ||
| 23 | |||
| 24 | use std::path::PathBuf; | ||
| 25 | |||
| 26 | /// Parse a Git repository path from URL components | ||
| 27 | /// | ||
| 28 | /// Converts /<npub>/<identifier>.git/* to a filesystem path | ||
| 29 | /// | ||
| 30 | /// # Arguments | ||
| 31 | /// * `git_data_path` - Base directory for Git repositories | ||
| 32 | /// * `npub` - The npub (Nostr public key in bech32 format) | ||
| 33 | /// * `identifier` - The repository identifier | ||
| 34 | /// | ||
| 35 | /// # Returns | ||
| 36 | /// Path to the bare Git repository | ||
| 37 | pub fn resolve_repo_path(git_data_path: &str, npub: &str, identifier: &str) -> PathBuf { | ||
| 38 | // Remove .git suffix if present | ||
| 39 | let identifier = identifier.strip_suffix(".git").unwrap_or(identifier); | ||
| 40 | |||
| 41 | PathBuf::from(git_data_path) | ||
| 42 | .join(npub) | ||
| 43 | .join(format!("{}.git", identifier)) | ||
| 44 | } | ||
| 45 | |||
| 46 | /// Extract npub and identifier from a Git URL path | ||
| 47 | /// | ||
| 48 | /// Parses paths like `/<npub>/<identifier>.git/info/refs` | ||
| 49 | /// | ||
| 50 | /// Returns (npub, identifier, subpath) where subpath is the part after .git/ | ||
| 51 | pub fn parse_git_url(path: &str) -> Option<(&str, &str, &str)> { | ||
| 52 | // Remove leading slash | ||
| 53 | let path = path.strip_prefix('/').unwrap_or(path); | ||
| 54 | |||
| 55 | // Split into components | ||
| 56 | let parts: Vec<&str> = path.splitn(3, '/').collect(); | ||
| 57 | |||
| 58 | if parts.len() < 3 { | ||
| 59 | return None; | ||
| 60 | } | ||
| 61 | |||
| 62 | let npub = parts[0]; | ||
| 63 | let repo_part = parts[1]; | ||
| 64 | let subpath = parts[2]; | ||
| 65 | |||
| 66 | // Extract identifier (remove .git suffix if present for the middle part) | ||
| 67 | let identifier = if repo_part.ends_with(".git") { | ||
| 68 | &repo_part[..repo_part.len() - 4] | ||
| 69 | } else { | ||
| 70 | repo_part | ||
| 71 | }; | ||
| 72 | |||
| 73 | Some((npub, identifier, subpath)) | ||
| 74 | } | ||
| 75 | |||
| 76 | #[cfg(test)] | ||
| 77 | mod tests { | ||
| 78 | use super::*; | ||
| 79 | |||
| 80 | #[test] | ||
| 81 | fn test_resolve_repo_path() { | ||
| 82 | let path = resolve_repo_path( | ||
| 83 | "/data/git", | ||
| 84 | "npub1abc123", | ||
| 85 | "my-repo" | ||
| 86 | ); | ||
| 87 | assert_eq!( | ||
| 88 | path, | ||
| 89 | PathBuf::from("/data/git/npub1abc123/my-repo.git") | ||
| 90 | ); | ||
| 91 | } | ||
| 92 | |||
| 93 | #[test] | ||
| 94 | fn test_resolve_repo_path_with_git_suffix() { | ||
| 95 | let path = resolve_repo_path( | ||
| 96 | "/data/git", | ||
| 97 | "npub1abc123", | ||
| 98 | "my-repo.git" | ||
| 99 | ); | ||
| 100 | assert_eq!( | ||
| 101 | path, | ||
| 102 | PathBuf::from("/data/git/npub1abc123/my-repo.git") | ||
| 103 | ); | ||
| 104 | } | ||
| 105 | |||
| 106 | #[test] | ||
| 107 | fn test_parse_git_url_info_refs() { | ||
| 108 | let (npub, id, subpath) = parse_git_url("/npub1abc/repo.git/info/refs").unwrap(); | ||
| 109 | assert_eq!(npub, "npub1abc"); | ||
| 110 | assert_eq!(id, "repo"); | ||
| 111 | assert_eq!(subpath, "info/refs"); | ||
| 112 | } | ||
| 113 | |||
| 114 | #[test] | ||
| 115 | fn test_parse_git_url_upload_pack() { | ||
| 116 | let (npub, id, subpath) = parse_git_url("/npub1abc/repo.git/git-upload-pack").unwrap(); | ||
| 117 | assert_eq!(npub, "npub1abc"); | ||
| 118 | assert_eq!(id, "repo"); | ||
| 119 | assert_eq!(subpath, "git-upload-pack"); | ||
| 120 | } | ||
| 121 | |||
| 122 | #[test] | ||
| 123 | fn test_parse_git_url_invalid() { | ||
| 124 | assert!(parse_git_url("/npub1abc").is_none()); | ||
| 125 | assert!(parse_git_url("/npub1abc/repo").is_none()); | ||
| 126 | } | ||
| 127 | } \ No newline at end of file | ||