gitdataai/libs/git/ssh/branch_protect.rs
ZhenYi 0703816482 feat(ssh): enhance ref update handling and add push queue
- Add post-receive refs tracking with mutex-protected storage
- Improve branch protection error messages with actionable guidance
- Add push queue slot waiting mechanism for concurrent push control
- Support for checking push queue availability before push operations
2026-05-15 11:48:40 +08:00

67 lines
2.5 KiB
Rust

use crate::ssh::ref_update::RefUpdate;
use models::repos::repo_branch_protect;
/// Ref name matches a protection rule exactly, or as a directory prefix
/// (e.g. "refs/heads/main" matches "refs/heads/main" and "refs/heads/main/*"
/// but NOT "refs/heads/main-v2").
fn ref_matches_protection(ref_name: &str, protection_branch: &str) -> bool {
ref_name == protection_branch || ref_name.starts_with(&format!("{}/", protection_branch))
}
/// Granular branch protection check (same logic as HTTP handler).
/// Returns `Some(error_message)` if the push should be rejected.
pub fn check_branch_protection(
branch_protects: &[repo_branch_protect::Model],
r#ref: &RefUpdate,
) -> Option<String> {
for protection in branch_protects {
if !ref_matches_protection(&r#ref.name, &protection.branch) {
continue;
}
// Check deletion (new_oid is all zeros)
if r#ref.new_oid == "0000000000000000000000000000000000000000" {
if protection.forbid_deletion {
return Some(format!(
"GitData: 🛡️ protected branch rejected. Deletion of '{}' is forbidden. Create a PR or ask a maintainer to update branch protection.",
r#ref.name
));
}
continue;
}
// Check tag push
if r#ref.name.starts_with("refs/tags/") {
if protection.forbid_tag_push {
return Some(format!(
"GitData: 🛡️ protected ref rejected. Tag push to '{}' is forbidden by branch protection.",
r#ref.name
));
}
continue;
}
// Check force push: old != new AND old is non-zero (non-fast-forward)
let is_new_branch = r#ref.old_oid == "0000000000000000000000000000000000000000";
if !is_new_branch
&& r#ref.old_oid != r#ref.new_oid
&& r#ref.name.starts_with("refs/heads/")
&& protection.forbid_force_push
{
return Some(format!(
"GitData: 🛡️ protected branch rejected. Force push to '{}' is forbidden. Create a PR instead of rewriting protected history.",
r#ref.name
));
}
// Check push
if protection.forbid_push {
return Some(format!(
"GitData: 🛡️ protected branch rejected. Direct push to '{}' is forbidden. Please push to a feature branch and create a PR.",
r#ref.name
));
}
}
None
}