The Rule Was in a Hook, and One Word Walked Past It
Goal
You build a set of client-side hooks that are kept in the repository and shared, and confirm for yourself what they block and what they cannot block.
Why it matters
Hooks are introduced with the phrase "automate the rules", but what a client-side hook actually does is fast feedback. It is not enforcement. If you do not know this difference and hang rules only on the client, you go on for a long time in a state where you believe they are being followed. This lab breaks that belief yourself — you build three rules and get past all of them with one word. And you confirm why it does not work if pre-commit reads the working tree, by creating a state in which the secret is only in the index. The fact that what gets committed is the index and not the working tree is worth knowing even if you never use hooks.
Steps
- Create
/root/gitx9/repoand setcore.hooksPathto.githooks. - Enforce the subject format with
.githooks/commit-msg, and leave the record of the rejection innotes/reject.txt. - Block files over 100000 bytes with
.githooks/pre-commitand leave it innotes/bigfile.txt. - Fix that hook to look at the index and block strings starting with
AKIA, and leave innotes/index.txtwhy it gets bypassed if you look only at the working tree. - Make one commit that breaks the rules with
--no-verifyand leave it innotes/bypass.txt. - Take a clone at
/root/gitx9/cloneand leave innotes/share.txtwhat comes along and what does not. - Create
/root/gitx9/origin.git, and with.githooks/pre-pushblock a direct push tomain, leaving it innotes/prepush.txt. - Summarize the roles of client-side hooks and server-side hooks in
notes/report.md.
Notes
- Be sure to give the hook files execute permission (
chmod +x). Without it, git silently skips them. - This image has no python3. Write hooks with
shandgit,grepandawk. - The test string for step 4 is
AKIAIOSFODNN7EXAMPLE. Set the pattern asAKIAfollowed by 16 uppercase letters or digits. - Common mistake: creating the hooks and not committing them. They are shared only when they are in the repository.
- Common mistake: actually pushing
mainin step 7. You must confirm that it is blocked and push only a feature branch.
Decide to keep the hooks inside the repository
Create /root/gitx9/repo and set core.hooksPath to .githooks.
.git/hooks/ does not come along with a clone. Move the place where hooks are looked up to a directory that the repository tracks, with git config core.hooksPath .githooks. Create the directory beforehand too. You must also set user.email and user.name for each repository for commits to work.
Enforce the subject format
Enforce the subject format with .githooks/commit-msg, and leave the record of the rejection in notes/reject.txt.
The commit-msg hook receives the message file path as $1. Look only at the first line and check whether it has the format <타입>(<범위>): <설명> (type, scope, description), using grep -qE. For the type, feat, fix, docs, refactor, test, chore and build are enough. On a mismatch, say what is wrong on standard error and exit with a non-zero value. Do not forget chmod +x.
Block large files before the commit
Block files over 100000 bytes with .githooks/pre-commit and leave it in notes/bigfile.txt.
pre-commit takes no arguments. You must find out for yourself what is about to be committed with git diff --cached --name-only --diff-filter=ACM. If the size exceeds 100000, say which file is how many bytes and exit with a non-zero value. After confirming for yourself that it is blocked, take that large file out of the staging.
What gets committed is the index, not the working tree
Fix that hook to look at the index and block strings starting with AKIA, and leave in notes/index.txt why it gets bypassed if you look only at the working tree.
The blob that went into the index is the second column of git ls-files -s -- <경로>, and you read the content with git cat-file -p <blob> or git show :<경로>. Measure the size with git cat-file -s <blob> too. Then write AKIAIOSFODNN7EXAMPLE into a file and git add it, and try committing after fixing only the working-tree file clean. It is a commit that would have passed as it is with a hook that reads the working tree.
It all gets past with one word
Make one commit that breaks the rules with --no-verify and leave it in notes/bypass.txt.
git commit --no-verify skips pre-commit and commit-msg. Make one commit that breaks the subject rule and leave it in the history. And also write that this is a design and not a flaw, and so where you must hang a rule that needs enforcement.
The hook files come along but the setting does not
Take a clone at /root/gitx9/clone and leave in notes/share.txt what comes along and what does not.
Run git clone /root/gitx9/repo /root/gitx9/clone, and inside it look at three things — what is in .githooks/, what git config --local core.hooksPath outputs, and what is in .git/hooks/. After checking, turn on core.hooksPath in the clone too.
Try blocking a protected branch on the client
Create /root/gitx9/origin.git, and with .githooks/pre-push block a direct push to main, leaving it in notes/prepush.txt.
pre-push receives on standard input, one line per ref, 로컬참조 로컬해시 원격참조 원격해시 (local ref, local hash, remote ref, remote hash). Block if there is a line where 원격참조 (the remote ref) is refs/heads/main. Create the remote with git init --bare /root/gitx9/origin.git and attach it with git remote add origin. After confirming that the main push is blocked, push only a feature branch.
Which place tells you and which place stops you
Summarize the roles of client-side hooks and server-side hooks in notes/report.md.
One table and a few lines of rules are enough. You must write the three things a client-side hook cannot block, and finish with why you hang the same rule in two places. The practical advice that if a hook is slow, people make bypassing a habit is also valuable.