use crate::destructive_pattern;
use crate::packs::{DestructivePattern, Pack, SafePattern};
#[must_use]
pub fn create_pack() -> Pack {
Pack {
id: "strict_git".to_string(),
name: "Strict Git",
description: "Stricter git protections: blocks force pushes, rebases, history \
rewriting, blind staging, and direct pushes to default branches",
keywords: &["git"],
safe_patterns: create_safe_patterns(),
destructive_patterns: create_destructive_patterns(),
keyword_matcher: None,
safe_regex_set: None,
safe_regex_set_is_complete: false,
}
}
fn create_safe_patterns() -> Vec<SafePattern> {
vec![]
}
fn create_destructive_patterns() -> Vec<DestructivePattern> {
vec![
destructive_pattern!(
"push-force-any",
r"git\s+push\s+.*(?:--force|--force-with-lease|-f\b)",
"Force push (even with --force-with-lease) can rewrite remote history. Disabled in strict mode."
),
destructive_pattern!(
"rebase",
r"git\s+rebase\b",
"git rebase rewrites commit history. Disabled in strict mode."
),
destructive_pattern!(
"commit-amend",
r"git\s+commit\s+.*--amend",
"git commit --amend rewrites the last commit. Disabled in strict mode."
),
destructive_pattern!(
"cherry-pick",
r"git\s+cherry-pick\b",
"git cherry-pick can introduce duplicate commits. Review carefully."
),
destructive_pattern!(
"filter-branch",
r"git\s+filter-branch\b",
"git filter-branch rewrites entire repository history. Extremely dangerous!"
),
destructive_pattern!(
"filter-repo",
r"git\s+filter-repo\b",
"git filter-repo rewrites repository history. Review carefully."
),
destructive_pattern!(
"reflog-expire",
r"git\s+reflog\s+expire",
"git reflog expire removes reflog entries needed for recovery."
),
destructive_pattern!(
"gc-aggressive",
r"git\s+gc\s+.*--(?:aggressive|prune)",
"git gc with aggressive/prune options can remove recoverable objects."
),
destructive_pattern!(
"worktree-remove",
r"git\s+worktree\s+remove",
"git worktree remove deletes a linked working tree."
),
destructive_pattern!(
"submodule-deinit",
r"git\s+submodule\s+deinit",
"git submodule deinit removes submodule configuration."
),
destructive_pattern!(
"add-all-dot",
r"git\s+add\s+\.(?:\s|$)",
"git add . stages everything including secrets, .env files, and build artifacts. Use 'git add <specific-files>' instead."
),
destructive_pattern!(
"add-all-flag",
r"git\s+add\s+(?:-A|--all)\b",
"git add -A/--all stages all changes including secrets, .env files, and build artifacts. Use 'git add <specific-files>' instead."
),
destructive_pattern!(
"push-master",
r"git\s+(?:\S+\s+)*push\s+(?:.*[\s:])?master(?:\s|$)",
"Direct push to master is blocked. Use a feature branch and open a Pull Request."
),
destructive_pattern!(
"push-main",
r"git\s+(?:\S+\s+)*push\s+(?:.*[\s:])?main(?:\s|$)",
"Direct push to main is blocked. Use a feature branch and open a Pull Request."
),
]
}
#[cfg(test)]
mod tests {
use super::*;
use crate::packs::test_helpers::*;
#[test]
fn test_add_all_dot() {
let pack = create_pack();
assert_blocks(&pack, "git add .", "stages everything");
assert_blocks(&pack, "git add . && echo done", "stages everything");
assert_blocks(&pack, "git add . ; git status", "stages everything");
assert_blocks(&pack, "git add . | cat", "stages everything");
assert_blocks(&pack, "git add . ", "stages everything");
assert_allows(&pack, "git add .gitignore");
assert_allows(&pack, "git add ./src/main.rs");
assert_allows(&pack, "git add .env.example");
}
#[test]
fn test_add_all_flag() {
let pack = create_pack();
assert_blocks(&pack, "git add -A", "stages all changes");
assert_blocks(&pack, "git add --all", "stages all changes");
assert_allows(&pack, "git add -p");
assert_allows(&pack, "git add --patch");
}
#[test]
fn test_push_master() {
let pack = create_pack();
assert_blocks(
&pack,
"git push origin master",
"Direct push to master is blocked",
);
assert_blocks(&pack, "git push master", "Direct push to master is blocked");
assert_blocks(
&pack,
"git push origin HEAD:master",
"Direct push to master is blocked",
);
assert_blocks(
&pack,
"git push origin master:master",
"Direct push to master is blocked",
);
assert_allows(&pack, "git push origin feature-master");
assert_allows(&pack, "git push origin master-fix");
}
#[test]
fn test_push_main() {
let pack = create_pack();
assert_blocks(
&pack,
"git push origin main",
"Direct push to main is blocked",
);
assert_blocks(&pack, "git push main", "Direct push to main is blocked");
assert_blocks(
&pack,
"git push origin HEAD:main",
"Direct push to main is blocked",
);
assert_blocks(
&pack,
"git push origin main:main",
"Direct push to main is blocked",
);
assert_allows(&pack, "git push origin feature-main");
assert_allows(&pack, "git push origin main-fix");
assert_allows(&pack, "git push origin maintain");
}
}