refactor(actor): implement Raft consensus algorithm for cluster leader election

- Add voting mechanism with term tracking and vote persistence
- Implement election triggering logic with majority vote counting
- Add primary/replica role transition handling with state management
- Integrate health check failure detection for automatic elections
- Refactor actor messaging system for distributed coordination
- Update repository registration to query cluster for existing primary
- Add broadcast mechanism for role change notifications
- Implement proper term comparison and duplicate request filtering
- Upgrade dependency versions including tokio-util for async utilities
- Optimize code formatting and line wrapping for improved readability
- Remove redundant blank lines and improve code structure consistency
- Enhance error logging and trace information for debugging purposes
This commit is contained in:
zhenyi
2026-06-10 12:35:10 +08:00
parent ab32e8826e
commit 9a0c26e5f6
40 changed files with 1184 additions and 449 deletions
+13 -3
View File
@@ -5,7 +5,11 @@ use crate::pb::*;
impl GitBare {
/// Count commits in a revision range or path.
pub fn count_commits(&self, request: CountCommitsRequest) -> GitResult<CountCommitsResponse> {
let revision = if request.revision.is_empty() { "HEAD" } else { &request.revision };
let revision = if request.revision.is_empty() {
"HEAD"
} else {
&request.revision
};
crate::sanitize::validate_revision(revision)?;
let mut args = vec![
@@ -48,7 +52,10 @@ impl GitBare {
}
/// Count diverging commits between two branches (left vs right).
pub fn count_diverging_commits(&self, request: CountDivergingCommitsRequest) -> GitResult<CountDivergingCommitsResponse> {
pub fn count_diverging_commits(
&self,
request: CountDivergingCommitsRequest,
) -> GitResult<CountDivergingCommitsResponse> {
crate::sanitize::validate_revision(&request.left)?;
crate::sanitize::validate_revision(&request.right)?;
@@ -75,6 +82,9 @@ impl GitBare {
let left_count = parts.first().and_then(|s| s.parse().ok()).unwrap_or(0);
let right_count = parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
Ok(CountDivergingCommitsResponse { left_count, right_count })
Ok(CountDivergingCommitsResponse {
left_count,
right_count,
})
}
}
+21 -6
View File
@@ -13,18 +13,27 @@ impl GitBare {
crate::sanitize::validate_revision(&revision)?;
let repo = self.gix_repo()?;
let oid = repo.rev_parse_single(revision.as_str())
let oid = repo
.rev_parse_single(revision.as_str())
.map_err(|e| GitError::Gix(e.to_string()))?;
let commit = oid.object()
let commit = oid
.object()
.map_err(|e| GitError::Gix(e.to_string()))?
.try_into_commit()
.map_err(|e| GitError::Gix(format!("not a commit: {e}")))?;
Ok(crate::commit::get_commit::commit_to_pb(self, &commit, request.include_stats))
Ok(crate::commit::get_commit::commit_to_pb(
self,
&commit,
request.include_stats,
))
}
/// Batch lookup commits by OID list.
pub fn list_commits_by_oid(&self, request: ListCommitsByOidRequest) -> GitResult<ListCommitsByOidResponse> {
pub fn list_commits_by_oid(
&self,
request: ListCommitsByOidRequest,
) -> GitResult<ListCommitsByOidResponse> {
let repo = self.gix_repo()?;
let mut commits = Vec::new();
@@ -33,11 +42,17 @@ impl GitBare {
if let Ok(oid) = gix::ObjectId::from_hex(hex.as_bytes()) {
if let Ok(obj) = repo.find_object(oid) {
if let Ok(commit) = obj.try_into_commit() {
commits.push(crate::commit::get_commit::commit_to_pb(self, &commit, request.include_stats));
commits.push(crate::commit::get_commit::commit_to_pb(
self,
&commit,
request.include_stats,
));
}
}
}
if commits.len() >= 100 { break; }
if commits.len() >= 100 {
break;
}
}
Ok(ListCommitsByOidResponse { commits })
+5 -2
View File
@@ -19,7 +19,10 @@ impl GitBare {
pub(crate) fn commit_to_pb(gb: &GitBare, commit: &gix::Commit<'_>, include_raw: bool) -> Commit {
let hex = commit.id.to_string();
let tree_hex = commit.tree_id().map(|t| t.to_string()).unwrap_or_default();
let message = commit.message_raw().map(|m| m.to_string()).unwrap_or_default();
let message = commit
.message_raw()
.map(|m| m.to_string())
.unwrap_or_default();
let (subject, body) = message
.split_once('\n')
.map(|(s, b)| (s.to_string(), b.trim_start_matches('\n').to_string()))
@@ -74,4 +77,4 @@ pub(crate) fn gix_sig_to_pb(sig: &gix::actor::SignatureRef<'_>) -> crate::pb::Si
}),
timezone_offset: time.map(|t| t.offset / 60).unwrap_or(0),
}
}
}
+46 -13
View File
@@ -4,11 +4,22 @@ use crate::pb::*;
impl GitBare {
/// Search commits by message content.
pub fn commits_by_message(&self, request: CommitsByMessageRequest) -> GitResult<CommitsByMessageResponse> {
let revision = if request.revision.is_empty() { "HEAD" } else { &request.revision };
pub fn commits_by_message(
&self,
request: CommitsByMessageRequest,
) -> GitResult<CommitsByMessageResponse> {
let revision = if request.revision.is_empty() {
"HEAD"
} else {
&request.revision
};
crate::sanitize::validate_revision(revision)?;
let limit = if request.limit == 0 { 20 } else { request.limit.min(200) };
let limit = if request.limit == 0 {
20
} else {
request.limit.min(200)
};
let mut args = vec![
"--git-dir".to_string(),
@@ -50,7 +61,9 @@ impl GitBare {
if let Ok(oid) = gix::ObjectId::from_hex(hex.as_bytes()) {
if let Ok(obj) = repo.find_object(oid) {
if let Ok(commit) = obj.try_into_commit() {
commits.push(crate::commit::get_commit::commit_to_pb(self, &commit, false));
commits.push(crate::commit::get_commit::commit_to_pb(
self, &commit, false,
));
}
}
}
@@ -60,7 +73,10 @@ impl GitBare {
}
/// Batch check if objects/revisions exist.
pub fn check_objects_exist(&self, request: CheckObjectsExistRequest) -> GitResult<CheckObjectsExistResponse> {
pub fn check_objects_exist(
&self,
request: CheckObjectsExistRequest,
) -> GitResult<CheckObjectsExistResponse> {
let repo = self.gix_repo()?;
let mut revisions = Vec::new();
@@ -119,13 +135,24 @@ impl GitBare {
}
}
Ok(CommitStats { additions, deletions, changed_files })
Ok(CommitStats {
additions,
deletions,
changed_files,
})
}
/// Get the last commit for a given path.
pub fn last_commit_for_path(&self, request: LastCommitForPathRequest) -> GitResult<LastCommitForPathResponse> {
pub fn last_commit_for_path(
&self,
request: LastCommitForPathRequest,
) -> GitResult<LastCommitForPathResponse> {
crate::sanitize::validate_file_path(&request.path)?;
let revision = if request.revision.is_empty() { "HEAD" } else { &request.revision };
let revision = if request.revision.is_empty() {
"HEAD"
} else {
&request.revision
};
crate::sanitize::validate_revision(revision)?;
let args = vec![
@@ -155,20 +182,26 @@ impl GitBare {
let hex = stdout.lines().next().unwrap_or("").trim().to_string();
if hex.is_empty() {
return Ok(LastCommitForPathResponse { commit: None, path: request.path });
return Ok(LastCommitForPathResponse {
commit: None,
path: request.path,
});
}
let repo = self.gix_repo()?;
let commit = if let Ok(oid) = gix::ObjectId::from_hex(hex.as_bytes()) {
repo.find_object(oid).ok().and_then(|obj| {
obj.try_into_commit().ok().map(|c| {
crate::commit::get_commit::commit_to_pb(self, &c, false)
})
obj.try_into_commit()
.ok()
.map(|c| crate::commit::get_commit::commit_to_pb(self, &c, false))
})
} else {
None
};
Ok(LastCommitForPathResponse { commit, path: request.path })
Ok(LastCommitForPathResponse {
commit,
path: request.path,
})
}
}