upleb.uk

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

summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2026-01-12 14:05:51 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-01-12 14:05:51 +0000
commit817ce37a5ee8d6279a44cf8cce3cc6a1e4bab576 (patch)
tree9fd5a6d3969afc33baa900bdab25bff81c5a83a4 /tests
parentf25eea8cc3b940cbcaa96224485826bfaae82449 (diff)
feat: add uploadpack.allowFilter support for GRASP-01 compliance
Add mandatory uploadpack.allowFilter capability to support partial clones and fetches as required by GRASP-01 specification. This enables efficient git operations for bandwidth-constrained clients (e.g., browser-based git clients like git-natural-api). Changes: - Add uploadpack.allowFilter=true to git subprocess configuration - Update SmartGitServer test helper with filter support - Add integration tests for filter capability advertisement and functionality - Update documentation to reflect filter as required capability Tests verify: - Filter capability is advertised in info/refs - Filtered clones with blob:none work correctly - Filtered fetches with tree:0 work correctly
Diffstat (limited to 'tests')
-rw-r--r--tests/common/git_server.rs4
-rw-r--r--tests/test_filter_support.rs158
2 files changed, 162 insertions, 0 deletions
diff --git a/tests/common/git_server.rs b/tests/common/git_server.rs
index 3190901..7634968 100644
--- a/tests/common/git_server.rs
+++ b/tests/common/git_server.rs
@@ -764,6 +764,8 @@ async fn handle_info_refs_upload_pack(
764 .arg("uploadpack.allowReachableSHA1InWant=true") 764 .arg("uploadpack.allowReachableSHA1InWant=true")
765 .arg("-c") 765 .arg("-c")
766 .arg("uploadpack.allowTipSHA1InWant=true") 766 .arg("uploadpack.allowTipSHA1InWant=true")
767 .arg("-c")
768 .arg("uploadpack.allowFilter=true")
767 .arg("upload-pack") 769 .arg("upload-pack")
768 .arg("--advertise-refs") 770 .arg("--advertise-refs")
769 .arg("--stateless-rpc"); 771 .arg("--stateless-rpc");
@@ -854,6 +856,8 @@ async fn handle_upload_pack(
854 .arg("uploadpack.allowReachableSHA1InWant=true") 856 .arg("uploadpack.allowReachableSHA1InWant=true")
855 .arg("-c") 857 .arg("-c")
856 .arg("uploadpack.allowTipSHA1InWant=true") 858 .arg("uploadpack.allowTipSHA1InWant=true")
859 .arg("-c")
860 .arg("uploadpack.allowFilter=true")
857 .arg("upload-pack") 861 .arg("upload-pack")
858 .arg("--stateless-rpc"); 862 .arg("--stateless-rpc");
859 863
diff --git a/tests/test_filter_support.rs b/tests/test_filter_support.rs
new file mode 100644
index 0000000..58c6352
--- /dev/null
+++ b/tests/test_filter_support.rs
@@ -0,0 +1,158 @@
1//! Integration test for git filter support (--filter=blob:none)
2
3use tempfile::TempDir;
4use tokio::process::Command;
5
6mod common;
7use common::purgatory_helpers::{create_test_repo_with_commit, CommitVariant};
8use common::SmartGitServer;
9
10/// Test that the server advertises filter capability
11#[tokio::test]
12async fn test_filter_capability_advertised() {
13 // Create a test repo
14 let temp_dir = TempDir::new().expect("Failed to create temp dir");
15 create_test_repo_with_commit(temp_dir.path(), CommitVariant::StateTest)
16 .expect("Failed to create test repo");
17
18 // Start smart git server
19 let server = SmartGitServer::start(temp_dir.path()).await;
20
21 // Run git ls-remote to see advertised capabilities
22 let output = Command::new("git")
23 .env("GIT_TRACE_PACKET", "1")
24 .args(["ls-remote", server.url()])
25 .output()
26 .await
27 .expect("Failed to run git ls-remote");
28
29 // Capture stderr which contains GIT_TRACE_PACKET output
30 let stderr = String::from_utf8_lossy(&output.stderr);
31
32 // Check for filter capability in the advertisement
33 // The capability is advertised as "filter" in the pkt-line
34 assert!(
35 stderr.contains("filter") || stderr.contains("allow"),
36 "Expected to find 'filter' capability in git protocol advertisement.\nStderr:\n{}",
37 stderr
38 );
39
40 // Also verify the command succeeded
41 assert!(
42 output.status.success(),
43 "git ls-remote failed: {}",
44 String::from_utf8_lossy(&output.stderr)
45 );
46
47 server.stop().await;
48}
49
50/// Test that filtered clones work (--filter=blob:none)
51#[tokio::test]
52async fn test_filtered_clone_succeeds() {
53 // Create a test repo with files
54 let temp_dir = TempDir::new().expect("Failed to create temp dir");
55 create_test_repo_with_commit(temp_dir.path(), CommitVariant::StateTest)
56 .expect("Failed to create test repo");
57
58 // Start smart git server
59 let server = SmartGitServer::start(temp_dir.path()).await;
60
61 // Create a clone destination
62 let clone_dir = TempDir::new().expect("Failed to create clone dir");
63 let clone_path = clone_dir.path().join("cloned-repo");
64
65 // Attempt a filtered clone
66 let output = Command::new("git")
67 .args([
68 "clone",
69 "--filter=blob:none",
70 server.url(),
71 clone_path.to_str().unwrap(),
72 ])
73 .output()
74 .await
75 .expect("Failed to run git clone");
76
77 // Check if clone succeeded
78 if !output.status.success() {
79 eprintln!("git clone --filter=blob:none failed!");
80 eprintln!("Stdout: {}", String::from_utf8_lossy(&output.stdout));
81 eprintln!("Stderr: {}", String::from_utf8_lossy(&output.stderr));
82 panic!("Filtered clone failed");
83 }
84
85 // Verify the clone worked
86 assert!(clone_path.exists(), "Clone directory should exist");
87 assert!(
88 clone_path.join(".git").exists(),
89 "Cloned repo should have .git directory"
90 );
91
92 // In a filtered clone, we should be able to list files
93 let ls_output = Command::new("git")
94 .current_dir(&clone_path)
95 .args(["ls-files"])
96 .output()
97 .await
98 .expect("Failed to list files");
99
100 assert!(
101 ls_output.status.success(),
102 "Should be able to list files in filtered clone"
103 );
104
105 let files = String::from_utf8_lossy(&ls_output.stdout);
106 assert!(
107 !files.trim().is_empty(),
108 "Should have files in the filtered clone"
109 );
110
111 server.stop().await;
112}
113
114/// Test that filtered fetches work
115#[tokio::test]
116async fn test_filtered_fetch_succeeds() {
117 // Create a test repo
118 let temp_dir = TempDir::new().expect("Failed to create temp dir");
119 create_test_repo_with_commit(temp_dir.path(), CommitVariant::StateTest)
120 .expect("Failed to create test repo");
121
122 // Start smart git server
123 let server = SmartGitServer::start(temp_dir.path()).await;
124
125 // First, clone normally (to set up tracking)
126 let clone_dir = TempDir::new().expect("Failed to create clone dir");
127 let clone_path = clone_dir.path().join("repo");
128
129 let clone_output = Command::new("git")
130 .args(["clone", server.url(), clone_path.to_str().unwrap()])
131 .output()
132 .await
133 .expect("Failed to run git clone");
134
135 assert!(
136 clone_output.status.success(),
137 "Initial clone failed: {}",
138 String::from_utf8_lossy(&clone_output.stderr)
139 );
140
141 // Now try a filtered fetch
142 let fetch_output = Command::new("git")
143 .current_dir(&clone_path)
144 .args(["fetch", "--filter=blob:none", "origin"])
145 .output()
146 .await
147 .expect("Failed to run git fetch");
148
149 // Check if fetch succeeded
150 if !fetch_output.status.success() {
151 eprintln!("git fetch --filter=blob:none failed!");
152 eprintln!("Stdout: {}", String::from_utf8_lossy(&fetch_output.stdout));
153 eprintln!("Stderr: {}", String::from_utf8_lossy(&fetch_output.stderr));
154 panic!("Filtered fetch failed");
155 }
156
157 server.stop().await;
158}