refactor(bare): enhance security and performance optimizations

- Remove unnecessary sorting in advertise_refs for deterministic output
- Add path traversal detection and validation in bare_dir construction
- Implement symlink resolution checks to prevent security vulnerabilities
- Refactor cache system with CRC validation and improved metrics
- Integrate repo-specific cache invalidation using indexed keys
- Add comprehensive unit tests for commit operations and diff functionality
- Move configuration constants to centralized config module
- Optimize string operations in disk cache random value generation
- Enhance license detection algorithm with cleaner matching logic
- Streamline argument processing in various git operations
- Update dependencies including crc32fast and flate2 for performance
- Add signal handling capability to tokio runtime configuration
This commit is contained in:
zhenyi
2026-06-12 15:04:12 +08:00
parent e386f44ee2
commit 10a4398e81
41 changed files with 1373 additions and 365 deletions
+218
View File
@@ -519,3 +519,221 @@ async fn test_oid_binary_encoding() {
let hex_from_bytes: String = oid.value.iter().map(|b| format!("{b:02x}")).collect();
assert_eq!(hex_from_bytes, oid.hex);
}
#[test]
fn test_count_commits_head() {
let (_dir, gb) = common::setup_bare_repo();
let resp = gb.count_commits(CountCommitsRequest {
repository: Some(hdr()),
revision: String::new(),
path: String::new(),
since: String::new(),
until: String::new(),
}).unwrap();
assert_eq!(resp.count, 4);
}
#[test]
fn test_count_commits_with_revision() {
let (_dir, gb) = common::setup_bare_repo();
let resp = gb.count_commits(CountCommitsRequest {
repository: Some(hdr()),
revision: "feature".into(),
path: String::new(),
since: String::new(),
until: String::new(),
}).unwrap();
assert_eq!(resp.count, 1);
}
#[test]
fn test_count_commits_with_path() {
let (_dir, gb) = common::setup_bare_repo();
let resp = gb.count_commits(CountCommitsRequest {
repository: Some(hdr()),
revision: "main".into(),
path: "README.md".into(),
since: String::new(),
until: String::new(),
}).unwrap();
assert!(resp.count >= 1);
}
#[test]
fn test_count_diverging_commits() {
let (_dir, gb) = common::setup_bare_repo();
let resp = gb.count_diverging_commits(CountDivergingCommitsRequest {
repository: Some(hdr()),
left: "feature".into(),
right: "main".into(),
}).unwrap();
assert_eq!(resp.left_count, 0);
assert_eq!(resp.right_count, 3);
}
#[test]
fn test_find_commit_by_oid() {
let (_dir, gb) = common::setup_bare_repo();
let oid = common::get_main_oid(&gb);
let commit = gb.find_commit(FindCommitRequest {
repository: Some(hdr()),
revision: common::oid_selector(&oid),
include_stats: false,
}).unwrap();
assert!(!commit.oid.as_ref().unwrap().hex.is_empty());
}
#[test]
fn test_find_commit_by_revision() {
let (_dir, gb) = common::setup_bare_repo();
let commit = gb.find_commit(FindCommitRequest {
repository: Some(hdr()),
revision: common::rev_selector("main"),
include_stats: false,
}).unwrap();
assert!(!commit.oid.as_ref().unwrap().hex.is_empty());
}
#[test]
fn test_find_commit_default_head() {
let (_dir, gb) = common::setup_bare_repo();
let commit = gb.find_commit(FindCommitRequest {
repository: Some(hdr()),
revision: None,
include_stats: false,
}).unwrap();
assert!(!commit.oid.as_ref().unwrap().hex.is_empty());
}
#[test]
fn test_list_commits_by_oid() {
let (_dir, gb) = common::setup_bare_repo();
let oid = common::get_main_oid(&gb);
let oid_bytes = gitks::oid::hex_to_bytes(&oid).unwrap();
let resp = gb.list_commits_by_oid(ListCommitsByOidRequest {
repository: Some(hdr()),
oids: vec![oid_bytes],
include_stats: false,
}).unwrap();
assert_eq!(resp.commits.len(), 1);
}
#[test]
fn test_list_commits_by_oid_empty() {
let (_dir, gb) = common::setup_bare_repo();
let resp = gb.list_commits_by_oid(ListCommitsByOidRequest {
repository: Some(hdr()),
oids: vec![],
include_stats: false,
}).unwrap();
assert!(resp.commits.is_empty());
}
#[test]
fn test_commits_by_message_basic() {
let (_dir, gb) = common::setup_bare_repo();
let resp = gb.commits_by_message(CommitsByMessageRequest {
repository: Some(hdr()),
query: "initial".into(),
revision: String::new(),
limit: 10,
offset: 0,
case_insensitive: false,
}).unwrap();
assert_eq!(resp.commits.len(), 1);
}
#[test]
fn test_commits_by_message_case_insensitive() {
let (_dir, gb) = common::setup_bare_repo();
let resp = gb.commits_by_message(CommitsByMessageRequest {
repository: Some(hdr()),
query: "INITIAL".into(),
revision: String::new(),
limit: 10,
offset: 0,
case_insensitive: true,
}).unwrap();
assert_eq!(resp.commits.len(), 1);
}
#[test]
fn test_commits_by_message_no_match() {
let (_dir, gb) = common::setup_bare_repo();
let resp = gb.commits_by_message(CommitsByMessageRequest {
repository: Some(hdr()),
query: "zzzznonexistent".into(),
revision: String::new(),
limit: 10,
offset: 0,
case_insensitive: false,
}).unwrap();
assert!(resp.commits.is_empty());
}
#[test]
fn test_check_objects_exist() {
let (_dir, gb) = common::setup_bare_repo();
let oid = common::get_main_oid(&gb);
let resp = gb.check_objects_exist(CheckObjectsExistRequest {
repository: Some(hdr()),
revisions: vec![oid.clone(), "HEAD".into(), "nonexistent-branch".into()],
}).unwrap();
assert_eq!(resp.revisions.len(), 3);
assert!(resp.revisions[0].exists);
assert!(resp.revisions[1].exists);
assert!(!resp.revisions[2].exists);
}
#[test]
fn test_get_commit_stats() {
let (_dir, gb) = common::setup_bare_repo();
let oid = common::get_main_oid(&gb);
let stats = gb.get_commit_stats(GetCommitStatsRequest {
repository: Some(hdr()),
revision: common::oid_selector(&oid),
}).unwrap();
assert!(stats.changed_files >= 1);
}
#[test]
fn test_get_commit_stats_default() {
let (_dir, gb) = common::setup_bare_repo();
let stats = gb.get_commit_stats(GetCommitStatsRequest {
repository: Some(hdr()),
revision: None,
}).unwrap();
assert!(stats.changed_files >= 1);
}
#[test]
fn test_last_commit_for_path() {
let (_dir, gb) = common::setup_bare_repo();
let resp = gb.last_commit_for_path(LastCommitForPathRequest {
repository: Some(hdr()),
path: "README.md".into(),
revision: "main".into(),
literal_pathspec: false,
}).unwrap();
assert!(resp.commit.is_some());
assert_eq!(resp.path, "README.md");
}
#[test]
fn test_last_commit_for_path_nonexistent() {
let (_dir, gb) = common::setup_bare_repo();
let resp = gb.last_commit_for_path(LastCommitForPathRequest {
repository: Some(hdr()),
path: "nonexistent.txt".into(),
revision: "main".into(),
literal_pathspec: false,
}).unwrap();
assert!(resp.commit.is_none());
}
+49
View File
@@ -172,3 +172,52 @@ pub fn setup_bare_repo_with_conflict() -> (tempfile::TempDir, GitBare) {
(dir, GitBare::new(bare_dir))
}
#[allow(dead_code)]
pub fn get_oid(gb: &GitBare, rev: &str) -> String {
let output = std::process::Command::new("git")
.args([
"--git-dir",
gb.bare_dir.to_string_lossy().as_ref(),
"rev-parse",
rev,
])
.output()
.expect("git rev-parse");
assert!(output.status.success(), "git rev-parse {rev} failed");
String::from_utf8_lossy(&output.stdout).trim().to_string()
}
#[allow(dead_code)]
pub fn get_main_oid(gb: &GitBare) -> String {
get_oid(gb, "refs/heads/main")
}
#[allow(dead_code)]
pub fn get_feature_oid(gb: &GitBare) -> String {
get_oid(gb, "refs/heads/feature")
}
#[allow(dead_code)]
pub fn oid_selector(hex: &str) -> Option<gitks::pb::ObjectSelector> {
Some(gitks::pb::ObjectSelector {
selector: Some(gitks::pb::object_selector::Selector::Oid(
gitks::pb::Oid {
hex: hex.to_string(),
value: vec![],
format: 0,
},
)),
})
}
#[allow(dead_code)]
pub fn rev_selector(rev: &str) -> Option<gitks::pb::ObjectSelector> {
Some(gitks::pb::ObjectSelector {
selector: Some(gitks::pb::object_selector::Selector::Revision(
gitks::pb::ObjectName {
revision: rev.to_string(),
},
)),
})
}
+76
View File
@@ -266,3 +266,79 @@ async fn test_get_patch() {
.collect();
assert!(combined.contains("diff --git") || combined.contains("@@"));
}
#[test]
fn test_find_changed_paths() {
let (_dir, gb) = common::setup_bare_repo();
let feature_oid = common::get_feature_oid(&gb);
let main_oid = common::get_main_oid(&gb);
let resp = gb.find_changed_paths(FindChangedPathsRequest {
repository: Some(hdr()),
base: feature_oid,
head: main_oid,
paths: vec![],
}).unwrap();
assert!(!resp.paths.is_empty());
}
#[test]
fn test_find_changed_paths_same_ref() {
let (_dir, gb) = common::setup_bare_repo();
let oid = common::get_main_oid(&gb);
let resp = gb.find_changed_paths(FindChangedPathsRequest {
repository: Some(hdr()),
base: oid.clone(),
head: oid,
paths: vec![],
}).unwrap();
assert!(resp.paths.is_empty());
}
#[test]
fn test_raw_diff() {
let (_dir, gb) = common::setup_bare_repo();
let feature_oid = common::get_feature_oid(&gb);
let main_oid = common::get_main_oid(&gb);
let chunks = gb.raw_diff(RawDiffRequest {
repository: Some(hdr()),
base: feature_oid,
head: main_oid,
options: None,
}).unwrap();
assert!(!chunks.is_empty());
let combined: Vec<u8> = chunks.iter().flat_map(|c| c.data.clone()).collect();
let text = String::from_utf8_lossy(&combined);
assert!(text.contains("diff"));
}
#[test]
fn test_raw_patch() {
let (_dir, gb) = common::setup_bare_repo();
let feature_oid = common::get_feature_oid(&gb);
let main_oid = common::get_main_oid(&gb);
let chunks = gb.raw_patch(RawPatchRequest {
repository: Some(hdr()),
base: feature_oid,
head: main_oid,
}).unwrap();
assert!(!chunks.is_empty());
let combined: Vec<u8> = chunks.iter().flat_map(|c| c.data.clone()).collect();
let text = String::from_utf8_lossy(&combined);
assert!(text.contains("From"));
}
#[test]
fn test_get_raw_changes() {
let (_dir, gb) = common::setup_bare_repo();
let feature_oid = common::get_feature_oid(&gb);
let main_oid = common::get_main_oid(&gb);
let resp = gb.get_raw_changes(GetRawChangesRequest {
repository: Some(hdr()),
base: feature_oid,
head: main_oid,
}).unwrap();
assert!(!resp.changes.is_empty());
}
+99
View File
@@ -63,3 +63,102 @@ fn test_list_refs_direct() {
assert_eq!(oid.hex.len(), 40, "SHA-1 hex should be 40 chars");
}
}
#[test]
fn test_write_ref_and_ref_exists() {
let (_dir, gb) = common::setup_bare_repo();
let oid = common::get_main_oid(&gb);
let resp = gb.write_ref(gitks::pb::WriteRefRequest {
repository: Some(hdr()),
ref_name: "refs/heads/test-write".into(),
new_oid: oid,
old_oid: String::new(),
force: false,
}).unwrap();
assert!(resp.ok, "write_ref failed: {}", resp.error);
let exists = gb.ref_exists(gitks::pb::RefExistsRequest {
repository: Some(hdr()),
ref_name: "refs/heads/test-write".into(),
}).unwrap();
assert!(exists.exists);
let not_exists = gb.ref_exists(gitks::pb::RefExistsRequest {
repository: Some(hdr()),
ref_name: "refs/heads/nonexistent".into(),
}).unwrap();
assert!(!not_exists.exists);
}
#[test]
fn test_update_references_batch() {
let (_dir, gb) = common::setup_bare_repo();
let oid = common::get_main_oid(&gb);
let resp = gb.update_references(gitks::pb::UpdateReferencesRequest {
repository: Some(hdr()),
updates: vec![
gitks::pb::RefUpdateEntry {
ref_name: "refs/heads/batch-a".into(),
new_oid: oid.clone(),
old_oid: String::new(),
},
gitks::pb::RefUpdateEntry {
ref_name: "refs/heads/batch-b".into(),
new_oid: oid,
old_oid: String::new(),
},
],
}).unwrap();
assert!(resp.failed_refs.is_empty(), "error: {}", resp.error);
let a = gb.ref_exists(gitks::pb::RefExistsRequest {
repository: Some(hdr()),
ref_name: "refs/heads/batch-a".into(),
}).unwrap();
assert!(a.exists);
}
#[test]
fn test_update_references_empty() {
let (_dir, gb) = common::setup_bare_repo();
let resp = gb.update_references(gitks::pb::UpdateReferencesRequest {
repository: Some(hdr()),
updates: vec![],
}).unwrap();
assert!(resp.failed_refs.is_empty());
}
#[test]
fn test_delete_refs() {
let (_dir, gb) = common::setup_bare_repo();
let oid = common::get_main_oid(&gb);
gb.write_ref(gitks::pb::WriteRefRequest {
repository: Some(hdr()),
ref_name: "refs/heads/to-delete".into(),
new_oid: oid,
old_oid: String::new(),
force: false,
}).unwrap();
let resp = gb.delete_refs(gitks::pb::DeleteRefsRequest {
repository: Some(hdr()),
ref_names: vec!["refs/heads/to-delete".into()],
}).unwrap();
assert!(resp.failed_refs.is_empty());
let exists = gb.ref_exists(gitks::pb::RefExistsRequest {
repository: Some(hdr()),
ref_name: "refs/heads/to-delete".into(),
}).unwrap();
assert!(!exists.exists);
}
#[test]
fn test_find_default_branch_name() {
let (_dir, gb) = common::setup_bare_repo();
let resp = gb.find_default_branch_name().unwrap();
assert_eq!(resp.name, "main");
}
+190
View File
@@ -326,3 +326,193 @@ async fn test_exists_nonexistent_repo() {
.into_inner();
assert!(!result.exists);
}
#[test]
fn test_find_merge_base() {
let (_dir, gb) = common::setup_bare_repo();
let main_oid = common::get_main_oid(&gb);
let feature_oid = common::get_feature_oid(&gb);
let resp = gb.find_merge_base(FindMergeBaseRequest {
repository: Some(header(&gb)),
revisions: vec![
main_oid.as_bytes().to_vec(),
feature_oid.as_bytes().to_vec(),
],
}).unwrap();
assert!(!resp.base_oid.is_empty());
}
#[test]
fn test_find_merge_base_empty() {
let (_dir, gb) = common::setup_bare_repo();
let resp = gb.find_merge_base(FindMergeBaseRequest {
repository: Some(header(&gb)),
revisions: vec![],
}).unwrap();
assert!(resp.base_oid.is_empty());
}
#[test]
fn test_find_merge_base_single() {
let (_dir, gb) = common::setup_bare_repo();
let oid = common::get_main_oid(&gb);
let resp = gb.find_merge_base(FindMergeBaseRequest {
repository: Some(header(&gb)),
revisions: vec![oid.as_bytes().to_vec()],
}).unwrap();
assert!(!resp.base_oid.is_empty());
}
#[test]
fn test_commit_is_ancestor() {
let (_dir, gb) = common::setup_bare_repo();
let feature_oid = common::get_feature_oid(&gb);
let main_oid = common::get_main_oid(&gb);
let resp = gb.commit_is_ancestor(CommitIsAncestorRequest {
repository: Some(header(&gb)),
ancestor_oid: feature_oid,
descendant_oid: main_oid,
}).unwrap();
assert!(resp.is_ancestor);
}
#[test]
fn test_commit_is_ancestor_false() {
let (_dir, gb) = common::setup_bare_repo();
let main_oid = common::get_main_oid(&gb);
let feature_oid = common::get_feature_oid(&gb);
let resp = gb.commit_is_ancestor(CommitIsAncestorRequest {
repository: Some(header(&gb)),
ancestor_oid: main_oid,
descendant_oid: feature_oid,
}).unwrap();
assert!(!resp.is_ancestor);
}
#[test]
fn test_objects_size() {
let (_dir, gb) = common::setup_bare_repo();
let oid = common::get_main_oid(&gb);
let resp = gb.objects_size(ObjectsSizeRequest {
repository: Some(header(&gb)),
oids: vec![oid.clone(), "0000000000000000000000000000000000000000".into()],
}).unwrap();
assert_eq!(resp.sizes.len(), 2);
assert!(resp.sizes[0].found);
assert!(resp.sizes[0].size > 0);
}
#[test]
fn test_objects_size_empty() {
let (_dir, gb) = common::setup_bare_repo();
let resp = gb.objects_size(ObjectsSizeRequest {
repository: Some(header(&gb)),
oids: vec![],
}).unwrap();
assert!(resp.sizes.is_empty());
}
#[test]
fn test_repository_size() {
let (_dir, gb) = common::setup_bare_repo();
let resp = gb.repository_size().unwrap();
assert!(resp.size_bytes > 0);
}
#[test]
fn test_find_license_no_license() {
let (_dir, gb) = common::setup_bare_repo();
let resp = gb.find_license().unwrap();
assert!(resp.license_spdx.is_empty());
}
#[test]
fn test_optimize_repository_heuristic() {
let (_dir, gb) = common::setup_bare_repo();
let resp = gb.optimize_repository(OptimizeRepositoryRequest {
repository: Some(header(&gb)),
strategy: OptimizeStrategy::Heuristic as i32,
}).unwrap();
assert!(resp.ok);
}
#[test]
fn test_optimize_repository_incremental() {
let (_dir, gb) = common::setup_bare_repo();
let resp = gb.optimize_repository(OptimizeRepositoryRequest {
repository: Some(header(&gb)),
strategy: OptimizeStrategy::Incremental as i32,
}).unwrap();
assert!(resp.ok);
}
#[test]
fn test_search_files_by_content() {
let (_dir, gb) = common::setup_bare_repo();
let resp = gb.search_files_by_content(SearchFilesByContentRequest {
repository: Some(header(&gb)),
query: "Test".into(),
revision: "main".into(),
max_results: 10,
case_sensitive: true,
}).unwrap();
assert!(!resp.results.is_empty());
}
#[test]
fn test_search_files_by_content_no_match() {
let (_dir, gb) = common::setup_bare_repo();
let resp = gb.search_files_by_content(SearchFilesByContentRequest {
repository: Some(header(&gb)),
query: "zzzznonexistentzzzz".into(),
revision: "main".into(),
max_results: 10,
case_sensitive: true,
}).unwrap();
assert!(resp.results.is_empty());
}
#[test]
fn test_search_files_by_content_empty_query() {
let (_dir, gb) = common::setup_bare_repo();
let resp = gb.search_files_by_content(SearchFilesByContentRequest {
repository: Some(header(&gb)),
query: String::new(),
revision: "main".into(),
max_results: 10,
case_sensitive: true,
});
assert!(resp.is_err());
}
#[test]
fn test_search_files_by_name() {
let (_dir, gb) = common::setup_bare_repo();
let resp = gb.search_files_by_name(SearchFilesByNameRequest {
repository: Some(header(&gb)),
query: "README".into(),
revision: "main".into(),
max_results: 10,
recursive: true,
}).unwrap();
assert!(!resp.results.is_empty());
assert!(resp.results.iter().any(|r| r.path.contains("README")));
}
#[test]
fn test_search_files_by_name_no_match() {
let (_dir, gb) = common::setup_bare_repo();
let resp = gb.search_files_by_name(SearchFilesByNameRequest {
repository: Some(header(&gb)),
query: "zzzznonexistentzzzz".into(),
revision: "main".into(),
max_results: 10,
recursive: true,
}).unwrap();
assert!(resp.results.is_empty());
}
+182 -5
View File
@@ -1,6 +1,5 @@
use gitks::sanitize::*;
// ==================== validate_ref_name tests ====================
#[test]
fn test_validate_ref_name_accepts_valid_names() {
@@ -69,7 +68,6 @@ fn test_validate_ref_name_rejects_too_long() {
assert!(validate_ref_name(&max_valid_name).is_ok());
}
// ==================== validate_revision tests ====================
#[test]
fn test_validate_revision_accepts_empty() {
@@ -149,7 +147,6 @@ fn test_validate_revision_accepts_valid_branch_names() {
assert!(validate_revision("v1.0.0").is_ok());
}
// ==================== validate_file_path tests ====================
#[test]
fn test_validate_file_path_accepts_valid_paths() {
@@ -216,7 +213,6 @@ fn test_validate_file_path_rejects_windows_reserved_names() {
assert!(validate_file_path("CON.txt").is_err());
}
// ==================== validate_relative_path tests ====================
#[test]
fn test_validate_relative_path_accepts_valid_paths() {
@@ -244,7 +240,6 @@ fn test_validate_relative_path_rejects_traversal() {
assert!(validate_relative_path("path/..").is_err());
}
// ==================== validate_config_key tests ====================
#[test]
fn test_validate_config_key_accepts_safe_keys() {
@@ -281,3 +276,185 @@ fn test_validate_config_key_rejects_invalid_chars() {
assert!(validate_config_key("key$(command)").is_err());
assert!(validate_config_key("key`command`").is_err());
}
/// Ensure no input causes panic in validate_ref_name.
#[test]
fn fuzz_validate_ref_name_no_panic() {
let long_name = "x".repeat(300);
let test_inputs: Vec<&str> = vec![
"",
"\0",
"\0\0\0",
"\x7f",
"\x01\x02\x03",
"~^:?*[]\\ ",
"../../../etc/passwd",
"a/b/c/d/e/f/g/h",
&long_name,
"branch@{upstream}",
"HEAD~99999999999",
"HEAD^99999999999",
"ref:HEAD",
"ref:refs/heads/main",
"; rm -rf /",
"$(echo pwned)",
"`echo pwned`",
"\n\r\t",
];
for input in test_inputs {
let _ = validate_ref_name(input);
}
}
/// Ensure no input causes panic in validate_revision.
#[test]
fn fuzz_validate_revision_no_panic() {
let test_inputs: Vec<&str> = vec![
"",
"HEAD",
"HEAD~0",
"HEAD~99999999",
"HEAD^0",
"HEAD^99999999",
"HEAD^{tree}",
"HEAD^{commit}",
"HEAD^{object}",
"abcdef01",
"abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789",
"0000",
"zzzz",
"ref:HEAD",
"ref:refs/heads/main",
"\0",
"branch~abc",
"branch^abc",
"branch~",
"branch^",
"a~10001",
"a^10001",
];
for input in test_inputs {
let _ = validate_revision(input);
}
}
/// Ensure no input causes panic in validate_file_path.
#[test]
fn fuzz_validate_file_path_no_panic() {
let long_path = "x".repeat(5000);
let medium_path = "a".repeat(100);
let test_inputs: Vec<&str> = vec![
"",
"/etc/passwd",
"../escape",
"a/../b",
".git",
".git/config",
"src/.git/HEAD",
"a/b/.git",
"\0",
"\0\0\0",
&long_path,
"path/with\x00null",
"path/with\nnewline",
"normal/path.txt",
&medium_path,
];
for input in test_inputs {
let _ = validate_file_path(input);
}
}
/// Ensure no input causes panic in validate_remote_url.
#[test]
fn fuzz_validate_remote_url_no_panic() {
let long_url = "x".repeat(5000);
let test_inputs: Vec<&str> = vec![
"",
"https://github.com/user/repo",
"http://localhost:3000/repo",
"ssh://git@host/repo",
"git://host/repo",
"git+ssh://git@host/repo",
"file:///etc/passwd",
"ext::sh -c 'rm -rf /'",
"ftp://host/repo",
"https://user:pass@host/repo",
"\0",
"https://host\0injection",
&long_url,
];
for input in test_inputs {
let _ = validate_remote_url(input);
}
}
/// Ensure no input causes panic in validate_oid_hex.
#[test]
fn fuzz_validate_oid_hex_no_panic() {
let long_hex = "x".repeat(65);
let exact_hex = "x".repeat(64);
let test_inputs: Vec<&str> = vec![
"",
"abc",
"abcd",
"0123456789abcdef",
"ZZZZ",
"g000",
"0000000000000000000000000000000000000000",
&long_hex,
&exact_hex,
"\0",
" ",
"\n",
];
for input in test_inputs {
let _ = validate_oid_hex(input);
}
}
/// Ensure no input causes panic in validate_relative_path.
#[test]
fn fuzz_validate_relative_path_no_panic() {
let long_path = "x".repeat(5000);
let test_inputs: Vec<&str> = vec![
"",
"/absolute",
"relative/path",
"../escape",
"path/../escape",
"\0",
&long_path,
".",
"..",
"...",
"a/b/c",
];
for input in test_inputs {
let _ = validate_relative_path(input);
}
}
/// Ensure no input causes panic in validate_refspec.
#[test]
fn fuzz_validate_refspec_no_panic() {
let long_refspec = "x".repeat(2000);
let test_inputs: Vec<&str> = vec![
"",
"+refs/heads/*:refs/heads/*",
"refs/heads/main",
"; rm -rf /",
"$(evil)",
"`evil`",
"| pipe",
"& bg",
"< redirect",
"> redirect",
"\0",
&long_refspec,
];
for input in test_inputs {
let _ = validate_refspec(input);
}
}