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
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
use crate::bare::GitBare;
|
||||
use crate::error::{GitError, GitResult};
|
||||
use crate::pb::{CreateTagRequest, GetTagRequest, Tag};
|
||||
|
||||
impl GitBare {
|
||||
pub fn create_tag(&self, request: CreateTagRequest) -> GitResult<Tag> {
|
||||
let target = match request.target.and_then(|s| s.selector) {
|
||||
Some(crate::pb::object_selector::Selector::Oid(oid)) => oid.hex,
|
||||
Some(crate::pb::object_selector::Selector::Revision(name)) => name.revision,
|
||||
None => "HEAD".into(),
|
||||
};
|
||||
let mut args = vec![
|
||||
"--git-dir".to_string(),
|
||||
self.bare_dir.to_string_lossy().into_owned(),
|
||||
"tag".to_string(),
|
||||
];
|
||||
if request.force {
|
||||
args.push("--force".into());
|
||||
}
|
||||
if request.annotated {
|
||||
args.push("--annotate".into());
|
||||
if !request.message.is_empty() {
|
||||
args.push("-m".into());
|
||||
args.push(request.message.clone());
|
||||
}
|
||||
}
|
||||
args.push(request.name.clone());
|
||||
args.push(target);
|
||||
let result = duct::cmd("git", &args)
|
||||
.stdout_capture()
|
||||
.stderr_capture()
|
||||
.unchecked()
|
||||
.run()?;
|
||||
if !result.status.success() {
|
||||
return Err(GitError::CommandFailed {
|
||||
status_code: result.status.code(),
|
||||
stderr: String::from_utf8_lossy(&result.stderr).into_owned(),
|
||||
});
|
||||
}
|
||||
self.get_tag(GetTagRequest {
|
||||
repository: request.repository,
|
||||
name: request.name,
|
||||
include_raw: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
use crate::bare::GitBare;
|
||||
use crate::error::{GitError, GitResult};
|
||||
use crate::pb::DeleteTagRequest;
|
||||
|
||||
impl GitBare {
|
||||
pub fn delete_tag(&self, request: DeleteTagRequest) -> GitResult<()> {
|
||||
let result = duct::cmd(
|
||||
"git",
|
||||
[
|
||||
"--git-dir",
|
||||
self.bare_dir.to_string_lossy().as_ref(),
|
||||
"tag",
|
||||
"-d",
|
||||
&request.name,
|
||||
],
|
||||
)
|
||||
.stdout_capture()
|
||||
.stderr_capture()
|
||||
.unchecked()
|
||||
.run()?;
|
||||
if !result.status.success() {
|
||||
return Err(GitError::CommandFailed {
|
||||
status_code: result.status.code(),
|
||||
stderr: String::from_utf8_lossy(&result.stderr).into_owned(),
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
use gix::bstr::ByteSlice;
|
||||
|
||||
use crate::bare::GitBare;
|
||||
use crate::error::GitResult;
|
||||
use crate::pb::{GetTagRequest, ObjectType, Tag};
|
||||
|
||||
impl GitBare {
|
||||
pub fn get_tag(&self, request: GetTagRequest) -> GitResult<Tag> {
|
||||
let repo = self.gix_repo()?;
|
||||
let refname = format!("refs/tags/{}", request.name);
|
||||
let mut r = repo.find_reference(refname.as_str())?;
|
||||
let full_ref = r.name().as_bstr().to_string();
|
||||
|
||||
let raw_target_hex = r.target().try_id().map(|id| id.to_string());
|
||||
let peeled_hex = r.peel_to_id()?.to_string();
|
||||
let is_annotated = raw_target_hex
|
||||
.as_ref()
|
||||
.is_some_and(|raw| *raw != peeled_hex);
|
||||
|
||||
let mut tag = Tag {
|
||||
name: request.name,
|
||||
full_ref,
|
||||
target_oid: Some(self.oid_to_pb(peeled_hex)),
|
||||
target_type: ObjectType::Commit as i32,
|
||||
tag_oid: None,
|
||||
annotated: false,
|
||||
tagger: None,
|
||||
message: String::new(),
|
||||
signature: None,
|
||||
raw: Vec::new(),
|
||||
};
|
||||
|
||||
if is_annotated
|
||||
&& let Some(ref raw_hex) = raw_target_hex
|
||||
&& let Ok(id) = gix::hash::ObjectId::from_hex(raw_hex.as_bytes())
|
||||
{
|
||||
tag.tag_oid = Some(self.oid_to_pb(raw_hex.clone()));
|
||||
if let Ok(obj) = repo.find_object(id)
|
||||
&& let Ok(tag_obj) = obj.try_into_tag()
|
||||
{
|
||||
tag.annotated = true;
|
||||
if let Ok(Some(tagger)) = tag_obj.tagger() {
|
||||
tag.tagger = Some(crate::pb::Signature {
|
||||
identity: Some(crate::pb::Identity {
|
||||
name: tagger.name.to_string(),
|
||||
email: tagger.email.to_string(),
|
||||
}),
|
||||
when: None,
|
||||
timezone_offset: 0,
|
||||
});
|
||||
}
|
||||
if let Ok(decoded) = tag_obj.decode() {
|
||||
tag.message = String::from_utf8_lossy(decoded.message.trim()).into_owned();
|
||||
}
|
||||
if request.include_raw {
|
||||
tag.raw = tag_obj.data.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(tag)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
use crate::bare::GitBare;
|
||||
use crate::error::GitResult;
|
||||
use crate::paginate;
|
||||
use crate::pb::{ListTagsRequest, ListTagsResponse, Tag};
|
||||
|
||||
impl GitBare {
|
||||
pub fn list_tags(&self, request: ListTagsRequest) -> GitResult<ListTagsResponse> {
|
||||
let repo = self.gix_repo()?;
|
||||
let mut tags = Vec::new();
|
||||
for r in repo.references()?.tags()? {
|
||||
let mut r = r.map_err(|e| crate::error::GitError::Gix(e.to_string()))?;
|
||||
let name = r.name().shorten().to_string();
|
||||
if !request.pattern.is_empty() && !name.contains(&request.pattern) {
|
||||
continue;
|
||||
}
|
||||
let hex = r
|
||||
.peel_to_id()
|
||||
.ok()
|
||||
.map(|id| id.to_string())
|
||||
.unwrap_or_default();
|
||||
tags.push(Tag {
|
||||
name: name.clone(),
|
||||
full_ref: r.name().to_string(),
|
||||
target_oid: Some(self.oid_to_pb(hex)),
|
||||
target_type: crate::pb::ObjectType::Commit as i32,
|
||||
tag_oid: None,
|
||||
annotated: false,
|
||||
tagger: None,
|
||||
message: String::new(),
|
||||
signature: None,
|
||||
raw: Vec::new(),
|
||||
});
|
||||
}
|
||||
paginate::apply_sort(&mut tags, request.sort_direction);
|
||||
let (tags, page_info) = paginate::paginate(&tags, request.pagination.as_ref());
|
||||
Ok(ListTagsResponse {
|
||||
tags,
|
||||
page_info: Some(page_info),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
pub mod create_tag;
|
||||
pub mod delete_tag;
|
||||
pub mod get_tag;
|
||||
pub mod list_tags;
|
||||
pub mod verify_tag;
|
||||
@@ -0,0 +1,35 @@
|
||||
use crate::bare::GitBare;
|
||||
use crate::error::GitResult;
|
||||
use crate::pb::{VerifiedSignature, VerifyTagRequest};
|
||||
|
||||
impl GitBare {
|
||||
pub fn verify_tag(&self, request: VerifyTagRequest) -> GitResult<VerifiedSignature> {
|
||||
let result = duct::cmd(
|
||||
"git",
|
||||
[
|
||||
"--git-dir",
|
||||
self.bare_dir.to_string_lossy().as_ref(),
|
||||
"tag",
|
||||
"-v",
|
||||
&request.name,
|
||||
],
|
||||
)
|
||||
.stdout_capture()
|
||||
.stderr_capture()
|
||||
.unchecked()
|
||||
.run()?;
|
||||
let verified = result.status.success();
|
||||
Ok(VerifiedSignature {
|
||||
verified,
|
||||
reason: if verified {
|
||||
crate::pb::verified_signature::Reason::Valid as i32
|
||||
} else {
|
||||
crate::pb::verified_signature::Reason::GpgverifyError as i32
|
||||
},
|
||||
signature: String::new(),
|
||||
payload: String::new(),
|
||||
key_fingerprint: String::new(),
|
||||
signer: String::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user