67 lines
2.2 KiB
Rust
67 lines
2.2 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!(
|
|
"Deletion of protected branch '{}' is forbidden",
|
|
r#ref.name
|
|
));
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Check tag push
|
|
if r#ref.name.starts_with("refs/tags/") {
|
|
if protection.forbid_tag_push {
|
|
return Some(format!(
|
|
"Tag push to protected branch '{}' is forbidden",
|
|
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!(
|
|
"Force push to protected branch '{}' is forbidden",
|
|
r#ref.name
|
|
));
|
|
}
|
|
|
|
// Check push
|
|
if protection.forbid_push {
|
|
return Some(format!(
|
|
"Push to protected branch '{}' is forbidden",
|
|
r#ref.name
|
|
));
|
|
}
|
|
}
|
|
None
|
|
}
|