import { describe, test } from 'bun:test'; import { assertAllowed, assertBlocked } from './helpers.ts'; describe('git checkout', () => { test('git checkout -- blocked', () => { assertBlocked('git checkout -- file.txt', 'git checkout --'); }); test('git checkout -- multiple files blocked', () => { assertBlocked('git checkout -- file1.txt file2.txt', 'git checkout --'); }); test('git checkout -- . blocked', () => { assertBlocked('git checkout -- .', 'git checkout --'); }); test('git checkout ref -- blocked', () => { assertBlocked('git checkout HEAD -- file.txt', 'git checkout -- '); }); test('git checkout -b allowed', () => { assertAllowed('git checkout -b new-branch'); }); test('git checkout --orphan allowed', () => { assertAllowed('git checkout --orphan orphan-branch'); }); test('git checkout -bnew-branch allowed', () => { assertAllowed('git checkout -bnew-branch'); }); test('git checkout -Bnew-branch allowed', () => { assertAllowed('git checkout -Bnew-branch'); }); test('git checkout ref pathspec blocked', () => { assertBlocked('git checkout HEAD file.txt', 'multiple positional args'); }); test('git checkout ref multiple pathspecs blocked', () => { assertBlocked('git checkout main a.txt b.txt', 'multiple positional args'); }); test('git checkout branch only allowed', () => { assertAllowed('git checkout main'); }); test('git checkout -U3 main allowed', () => { assertAllowed('git checkout -U3 main'); }); test('git checkout - allowed', () => { assertAllowed('git checkout -'); }); test('git checkout --detach allowed', () => { assertAllowed('git checkout --detach main'); }); test('git checkout --recurse-submodules allowed', () => { assertAllowed('git checkout --recurse-submodules main'); }); test('git checkout --pathspec-from-file blocked', () => { assertBlocked( 'git checkout HEAD --pathspec-from-file=paths.txt', 'git checkout --pathspec-from-file', ); }); test('git checkout ref pathspec from file arg blocked', () => { assertBlocked( 'git checkout HEAD --pathspec-from-file paths.txt', 'git checkout --pathspec-from-file', ); }); test('git checkout --conflict=merge allowed', () => { assertAllowed('git checkout --conflict=merge main'); }); test('git checkout --conflict merge allowed', () => { assertAllowed('git checkout --conflict merge main'); }); test('git checkout -q ref pathspec blocked', () => { assertBlocked('git checkout -q main file.txt', 'multiple positional args'); }); test('git checkout --recurse-submodules=checkout allowed', () => { assertAllowed('git checkout --recurse-submodules=checkout main'); }); test('git checkout --recurse-submodules=on-demand allowed', () => { assertAllowed('git checkout --recurse-submodules=on-demand main'); }); test('git checkout --recurse-submodules ref pathspec blocked', () => { assertBlocked('git checkout --recurse-submodules main file.txt', 'multiple positional args'); }); test('git checkout --recurse-submodules without mode allowed', () => { assertAllowed('git checkout --recurse-submodules main'); }); test('git checkout --recurse-submodules without mode ref pathspec blocked', () => { assertBlocked('git checkout --recurse-submodules main file.txt', 'multiple positional args'); }); test('git checkout --track=direct allowed', () => { assertAllowed('git checkout --track=direct main'); }); test('git checkout --track=inherit allowed', () => { assertAllowed('git checkout --track=inherit main'); }); test('git checkout --track without mode ref pathspec blocked', () => { assertBlocked('git checkout --track main file.txt', 'multiple positional args'); }); test('git checkout --unified 3 allowed', () => { assertAllowed('git checkout --unified 3 main'); }); test('git checkout -U attached value allowed', () => { assertAllowed('git checkout -U3 main'); }); test('git checkout unknown long option consumes value allowed', () => { assertAllowed('git checkout --unknown main file.txt'); }); test('git checkout unknown long option does not consume option value allowed', () => { assertAllowed('git checkout --unknown -q main'); }); test('git checkout unknown long option equals allowed', () => { assertAllowed('git checkout --unknown=value main'); }); }); describe('git restore', () => { test('git restore file blocked', () => { assertBlocked('git restore file.txt', 'git restore'); }); test('git restore multiple files blocked', () => { assertBlocked('git restore a.txt b.txt', 'git restore'); }); test('git restore --worktree blocked', () => { assertBlocked('git restore --worktree file.txt', 'git restore --worktree'); }); test('git restore --staged allowed', () => { assertAllowed('git restore --staged file.txt'); }); test('git restore --staged . allowed', () => { assertAllowed('git restore --staged .'); }); test('git restore --help allowed', () => { assertAllowed('git restore --help'); }); }); describe('git reset', () => { test('git reset --hard blocked', () => { assertBlocked('git reset --hard', 'git reset --hard'); }); test('git reset --hard HEAD~1 blocked', () => { assertBlocked('git reset --hard HEAD~1', 'git reset --hard'); }); test('git reset -q --hard blocked', () => { assertBlocked('git reset -q --hard', 'git reset --hard'); }); test('echo ok | git reset --hard blocked', () => { assertBlocked('echo ok | git reset --hard', 'git reset --hard'); }); test('git -C repo reset --hard blocked', () => { assertBlocked('git -C repo reset --hard', 'git reset --hard'); }); test('git -Crepo reset --hard blocked', () => { assertBlocked('git -Crepo reset --hard', 'git reset --hard'); }); test('git reset --hard global option -C attached blocked', () => { assertBlocked('git -Crepo reset --hard', 'git reset --hard'); }); test('git --git-dir=repo/.git reset --hard blocked', () => { assertBlocked('git --git-dir=repo/.git reset --hard', 'git reset --hard'); }); test('git --git-dir repo/.git reset --hard blocked', () => { assertBlocked('git --git-dir repo/.git reset --hard', 'git reset --hard'); }); test('git --work-tree=repo reset --hard blocked', () => { assertBlocked('git --work-tree=repo reset --hard', 'git reset --hard'); }); test('git --no-pager reset --hard blocked', () => { assertBlocked('git --no-pager reset --hard', 'git reset --hard'); }); test('git -c foo=bar reset --hard blocked', () => { assertBlocked('git -c foo=bar reset --hard', 'git reset --hard'); }); test('git -- reset --hard blocked', () => { assertBlocked('git -- reset --hard', 'reset --hard'); }); test('git -cfoo=bar reset --hard blocked', () => { assertBlocked('git -cfoo=bar reset --hard', 'git reset --hard'); }); test('sudo env VAR=1 git reset --hard blocked', () => { assertBlocked('sudo env VAR=1 git reset --hard', 'git reset --hard'); }); test('env -- git reset --hard blocked', () => { assertBlocked('env -- git reset --hard', 'git reset --hard'); }); test('command -- git reset --hard blocked', () => { assertBlocked('command -- git reset --hard', 'git reset --hard'); }); test('env -u PATH git reset --hard blocked', () => { assertBlocked('env -u PATH git reset --hard', 'git reset --hard'); }); test('git reset --merge blocked', () => { assertBlocked('git reset --merge', 'git reset --merge'); }); test("sh -c 'git reset --hard' blocked", () => { assertBlocked("sh -c 'git reset --hard'", 'git reset --hard'); }); }); describe('git clean', () => { test('git clean -f blocked', () => { assertBlocked('git clean -f', 'git clean'); }); test('git clean --force blocked', () => { assertBlocked('git clean --force', 'git clean -f'); }); test('git clean -nf blocked', () => { assertBlocked('git clean -nf', 'git clean -f'); }); test('git clean -n && git clean -f blocked', () => { assertBlocked('git clean -n && git clean -f', 'git clean -f'); }); test('git clean -fd blocked', () => { assertBlocked('git clean -fd', 'git clean'); }); test('git clean -xf blocked', () => { assertBlocked('git clean -xf', 'git clean'); }); test('git clean -n allowed', () => { assertAllowed('git clean -n'); }); test('git clean --dry-run allowed', () => { assertAllowed('git clean --dry-run'); }); test('git clean -nd allowed', () => { assertAllowed('git clean -nd'); }); }); describe('git push', () => { test('git push --force blocked', () => { assertBlocked('git push --force', 'push --force'); }); test('git push --force origin main blocked', () => { assertBlocked('git push --force origin main', 'push --force'); }); test('git push -f blocked', () => { assertBlocked('git push -f', 'push --force'); }); test('git push -f origin main blocked', () => { assertBlocked('git push -f origin main', 'push --force'); }); test('git push --force-with-lease allowed', () => { assertAllowed('git push --force-with-lease'); }); test('git push --force-with-lease origin main allowed', () => { assertAllowed('git push --force-with-lease origin main'); }); test('git push --force-with-lease=refs/heads/main allowed', () => { assertAllowed('git push --force-with-lease=refs/heads/main'); }); test('git push --force --force-with-lease allowed', () => { assertAllowed('git push --force --force-with-lease'); }); test('git push -f --force-with-lease allowed', () => { assertAllowed('git push -f --force-with-lease'); }); test('git push origin main allowed', () => { assertAllowed('git push origin main'); }); }); describe('git worktree', () => { test('git worktree remove --force blocked', () => { assertBlocked('git worktree remove --force /tmp/wt', 'git worktree remove --force'); }); test('git worktree remove -f blocked', () => { assertBlocked('git worktree remove -f /tmp/wt', 'git worktree remove --force'); }); test('git worktree remove without force allowed', () => { assertAllowed('git worktree remove /tmp/wt'); }); test('git worktree remove -- -f allowed', () => { assertAllowed('git worktree remove -- -f'); }); }); describe('git branch', () => { test('git branch -D blocked', () => { assertBlocked('git branch -D feature', 'git branch -D'); }); test('git branch -Dv blocked', () => { assertBlocked('git branch -Dv feature', 'git branch -D'); }); test('git branch -d allowed', () => { assertAllowed('git branch -d feature'); }); }); describe('git stash', () => { test('git stash drop blocked', () => { assertBlocked('git stash drop', 'git stash drop'); }); test('git stash drop stash@{0} blocked', () => { assertBlocked('git stash drop stash@{0}', 'git stash drop'); }); test('git stash clear blocked', () => { assertBlocked('git stash clear', 'git stash clear'); }); test('git stash allowed', () => { assertAllowed('git stash'); }); test('git stash list allowed', () => { assertAllowed('git stash list'); }); test('git stash pop allowed', () => { assertAllowed('git stash pop'); }); }); describe('safe commands', () => { test('git allowed', () => { assertAllowed('git'); }); test('git --help allowed', () => { assertAllowed('git --help'); }); test('git status allowed', () => { assertAllowed('git status'); }); test('git -C repo status allowed', () => { assertAllowed('git -C repo status'); }); test('git status global option -C allowed', () => { assertAllowed('git -Crepo status'); }); test('sudo env VAR=1 git status allowed', () => { assertAllowed('sudo env VAR=1 git status'); }); test('git diff allowed', () => { assertAllowed('git diff'); }); test('git log --oneline -10 allowed', () => { assertAllowed('git log --oneline -10'); }); test('git add . allowed', () => { assertAllowed('git add .'); }); test("git commit -m 'test' allowed", () => { assertAllowed("git commit -m 'test'"); }); test('git pull allowed', () => { assertAllowed('git pull'); }); test("bash -c 'echo ok' allowed", () => { assertAllowed("bash -c 'echo ok'"); }); test('python -c "print(\'ok\')" allowed', () => { assertAllowed('python -c "print(\'ok\')"'); }); test('ls -la allowed', () => { assertAllowed('ls -la'); }); test('cat file.txt allowed', () => { assertAllowed('cat file.txt'); }); });