use crate::check::Outcome;
use crate::pushrefs::PushRef;
use crate::ui::{error_sign, highlight};
const PROTECTED: [&str; 2] = ["main", "master"];
fn protected_name(remote_ref: &str) -> Option<&'static str> {
let name = remote_ref.strip_prefix("refs/heads/")?;
PROTECTED.iter().copied().find(|p| *p == name)
}
fn is_delete(r: &PushRef) -> bool {
r.local_oid.chars().all(|c| c == '0')
}
pub fn run(refs: &[PushRef]) -> Outcome {
let mut blocked = Vec::new();
for r in refs {
if let Some(name) = protected_name(&r.remote_ref) {
blocked.push((name, is_delete(r)));
}
}
if blocked.is_empty() {
crate::hooks::common::ok("No push to a protected branch");
return Outcome::Passed;
}
for (name, deleting) in &blocked {
let what = if *deleting { "Deleting" } else { "Pushing to" };
println!(
"{} {what} branch {} is forbidden. Open a Pull Request.",
error_sign(),
highlight(name)
);
}
println!(
" (if you really mean it: {})",
highlight("git push --no-verify")
);
Outcome::Failed
}
#[cfg(test)]
mod tests {
use super::*;
fn r(local_oid: &str, remote_ref: &str) -> PushRef {
PushRef {
local_ref: "refs/heads/whatever".into(),
local_oid: local_oid.into(),
remote_ref: remote_ref.into(),
remote_oid: "b".into(),
}
}
#[test]
fn blocks_main_and_master() {
assert_eq!(run(&[r("a", "refs/heads/main")]), Outcome::Failed);
assert_eq!(run(&[r("a", "refs/heads/master")]), Outcome::Failed);
}
#[test]
fn allows_any_other_branch() {
assert_eq!(run(&[r("a", "refs/heads/feat/x")]), Outcome::Passed);
assert_eq!(run(&[r("a", "refs/heads/maintenance")]), Outcome::Passed);
assert_eq!(run(&[r("a", "refs/heads/mainline")]), Outcome::Passed);
}
#[test]
fn allows_tags_even_named_main() {
assert_eq!(run(&[r("a", "refs/tags/main")]), Outcome::Passed);
}
#[test]
fn a_renamed_push_to_main_is_still_blocked() {
let mut p = r("a", "refs/heads/main");
p.local_ref = "refs/heads/my-feature".into();
assert_eq!(run(&[p]), Outcome::Failed);
}
#[test]
fn a_branch_delete_is_blocked_too() {
assert_eq!(
run(&[r(
"0000000000000000000000000000000000000000",
"refs/heads/main"
)]),
Outcome::Failed
);
}
#[test]
fn no_refs_is_a_pass() {
assert_eq!(run(&[]), Outcome::Passed);
}
#[test]
fn a_mixed_push_is_blocked() {
assert_eq!(
run(&[r("a", "refs/heads/feat/x"), r("a", "refs/heads/main")]),
Outcome::Failed
);
}
}