refactor(cache): redesign cache system with structured keys and improved performance

- Add repo_path parameter to cached_response and cached_vec_response functions
- Implement structured cache key format with namespace, repo_path, and request proto
- Replace global cache with Moka in-memory cache using weight-based eviction
- Set 256MB memory cap with 10-minute TTL and 2-minute TTI policy
- Add metrics collection for cache operations and evictions
- Implement efficient repo-scoped invalidation using key structure
- Add detailed documentation comments explaining cache architecture
- Remove outdated dependencies and update dependency versions
- Add error handling for encoding failures in cache operations
- Optimize Vec responses with length-delimited encoding and pre-allocation
This commit is contained in:
zhenyi
2026-06-12 12:53:23 +08:00
parent a40da90ef9
commit 934858bebf
82 changed files with 1273 additions and 4969 deletions
+33 -5
View File
@@ -11,9 +11,7 @@ use crate::error::GitResult;
/// Git disallows: space, `~`, `^`, `:`, `?`, `*`, `[`, `\`, and all ASCII
/// control characters (bytes 031 and 127). The control characters are
/// checked separately via `is_ascii_control()`.
const FORBIDDEN_REF_CHARS: &[char] = &[
'~', '^', ':', '?', '*', '[', '\\', ' ',
];
const FORBIDDEN_REF_CHARS: &[char] = &['~', '^', ':', '?', '*', '[', '\\', ' '];
/// Returns true if `c` is an ASCII control character (bytes 031, 127).
fn is_ascii_control(c: char) -> bool {
@@ -30,6 +28,24 @@ fn is_ascii_control(c: char) -> bool {
/// - Cannot contain '..'
/// - Cannot contain '@{'
/// - Cannot be empty
pub fn validate_oid_hex(hex: &str) -> GitResult<()> {
if hex.is_empty() {
return Err(GitError::InvalidArgument("oid hex cannot be empty".into()));
}
if !(4..=64).contains(&hex.len()) {
return Err(GitError::InvalidArgument(format!(
"oid hex length must be 4..=64 chars: {}",
hex.len()
)));
}
if !hex.chars().all(|c| c.is_ascii_hexdigit()) {
return Err(GitError::InvalidArgument(format!(
"oid hex contains non-hex character: {hex}"
)));
}
Ok(())
}
pub fn validate_ref_name(name: &str) -> GitResult<()> {
if name.is_empty() {
return Err(GitError::InvalidArgument("ref name cannot be empty".into()));
@@ -253,8 +269,10 @@ pub fn validate_config_key(key: &str) -> GitResult<()> {
for pattern in DANGEROUS_CONFIG_KEYS {
if pattern.contains('*') {
// e.g. "remote.*.url" — match any "remote.<something>.url"
let (prefix, suffix) = pattern.split_once('*').unwrap();
if key.starts_with(prefix) && key.ends_with(suffix) {
if let Some((prefix, suffix)) = pattern.split_once('*')
&& key.starts_with(prefix)
&& key.ends_with(suffix)
{
return Err(GitError::InvalidArgument(format!(
"config key '{key}' matches dangerous pattern '{pattern}'"
)));
@@ -347,6 +365,16 @@ pub fn validate_relative_path(path: &str) -> GitResult<()> {
"relative_path must be relative, not absolute".into(),
));
}
if path.contains('\0') {
return Err(GitError::InvalidArgument(
"relative_path cannot contain null byte".into(),
));
}
if path.len() > 4096 {
return Err(GitError::InvalidArgument(
"relative_path too long (max 4096 chars)".into(),
));
}
if path.contains("..") {
return Err(GitError::InvalidArgument(format!(
"path traversal detected: relative_path contains '..': {path}"