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