Files
gitks/bare.rs
T
zhenyi dcb0fb74c5 feat(core): implement Git repository operations with gRPC services
- Add advertise_refs functionality for Git protocol communication
- Implement archive service with TAR/ZIP format support and streaming
- Create blame service for Git file annotation with line tracking
- Add branch management including create, delete, rename and compare operations
- Implement merge checking with conflict detection and fast-forward handling
- Add cherry-pick functionality for applying commits between branches
- Integrate gix library for Git repository operations and object handling
- Add comprehensive test suite covering all Git operations
- Implement proper error handling and repository validation
- Add pagination support for large result sets
- Create protobuf definitions for all Git operations and data structures
- Add build system for gRPC code generation and dependency management
2026-06-04 13:05:38 +08:00

113 lines
4.2 KiB
Rust

use std::path::{Path, PathBuf};
use crate::error::{GitError, GitResult};
use crate::pb::RepositoryHeader;
pub struct GitBare {
pub bare_dir: PathBuf,
}
impl GitBare {
pub fn gix_repo(&self) -> GitResult<gix::Repository> {
gix::open(&self.bare_dir)
.map_err(|e| GitError::Internal(format!("failed to open gix repository: {e}")))
}
pub fn from_repository_header(header: &RepositoryHeader) -> GitResult<Self> {
let storage_path = header.storage_path.trim();
let relative_path = header.relative_path.trim();
let storage_name = header.storage_name.trim();
let _ = storage_name; // reserved for future sharding logic
// Build base path: storage_path if given, else relative_path alone
let base = if !storage_path.is_empty() {
let p = Path::new(storage_path);
if !p.is_absolute() {
return Err(GitError::InvalidArgument(
"storage_path must be an absolute path".into(),
));
}
PathBuf::from(p)
} else if !relative_path.is_empty() {
// relative_path alone is rejected unless absolute
return Err(GitError::InvalidArgument(
"relative_path requires storage_path to be set".into(),
));
} else {
return Err(GitError::InvalidArgument("empty repository path".into()));
};
// Join relative_path if provided
let bare_dir = if !relative_path.is_empty() && !storage_path.is_empty() {
let candidate = base.join(relative_path);
// Canonicalize to resolve any `..` / symlinks, then check still under base
let canonical = candidate
.canonicalize()
.unwrap_or_else(|_| candidate.clone());
// Path traversal check: canonical resolved dir must start with base
let base_canon = base.canonicalize().unwrap_or_else(|_| base.clone());
if !canonical.starts_with(&base_canon) {
return Err(GitError::InvalidArgument(format!(
"path traversal detected: {relative_path} escapes storage root"
)));
}
canonical
} else if !storage_path.is_empty() {
base.canonicalize().unwrap_or(base)
} else {
return Err(GitError::InvalidArgument("empty repository path".into()));
};
// Validate bare_dir exists, is a directory, and is readable
if !bare_dir.exists() {
return Err(GitError::RepoNotFound);
}
if !bare_dir.is_dir() {
return Err(GitError::InvalidArgument(format!(
"not a directory: {}",
bare_dir.display()
)));
}
// Accept either bare repos (HEAD file) or non-bare (HEAD + .git)
let head_path = bare_dir.join("HEAD");
if !head_path.exists() {
// Maybe it's a non-bare repo
let git_dir = bare_dir.join(".git");
if git_dir.is_dir() && git_dir.join("HEAD").exists() {
return Ok(Self { bare_dir: git_dir });
}
return Err(GitError::NotBareRepository);
}
Ok(Self { bare_dir })
}
/// Detect the repository's object format (SHA-1 or SHA-256).
pub fn object_format(&self) -> crate::pb::ObjectFormat {
let repo = self.gix_repo().ok();
let kind = repo
.map(|r| r.object_hash())
.unwrap_or(gix::hash::Kind::Sha1);
match kind {
gix::hash::Kind::Sha1 => crate::pb::ObjectFormat::Sha1,
gix::hash::Kind::Sha256 => crate::pb::ObjectFormat::Sha256,
_ => crate::pb::ObjectFormat::Unspecified,
}
}
/// Convert a hex object id to a protobuf Oid.
///
/// `Oid.value` is the binary hash bytes, while `Oid.hex` keeps the printable
/// lowercase representation for clients that prefer string IDs.
pub fn oid_to_pb(&self, hex: impl Into<String>) -> crate::pb::Oid {
let hex = hex.into().to_lowercase();
let format = self.object_format();
crate::pb::Oid {
value: crate::oid::hex_to_bytes(&hex).unwrap_or_default(),
hex,
format: format as i32,
}
}
}