Chapter 17Lesson 02~155 minutes

Hooks, Aliases, Templates, Attributes, and Local Automation: Guided Hands-On Workflow and Core Operations

Create a disposable automation lab with Git aliases, a safe commit-msg hook, core.hooksPath, attributes, ignore diagnostics, local attribute overrides, init templates, and a local bare server receive hook.

Hands-on automationcommit-msgcheck-attrpre-receive

Learning objectives

  • Configure and inspect local Git aliases and core.hooksPath.
  • Observe commit-msg arguments/exit status and prove staged state survives rejection.
  • Use git check-attr, git check-ignore, and git ls-files --eol to inspect path behavior.
  • Seed harmless repository metadata with an explicit init template.
  • Observe a pre-receive hook consuming ref updates on stdin and rejecting one ref.

1. Create a disposable policy-automation lab

The scenario uses a tracked .githooks directory so the hook script can be reviewed in normal history, but activation remains local configuration. It also uses a local bare repository later to demonstrate an authoritative receive hook without any hosted account.

Git Bash, Bash, or zsh

mkdir git-local-automation-lab
cd git-local-automation-lab
git init -b trunk project
cd project
git config user.name "Automation Lab"
git config user.email "automation-lab@example.invalid"

mkdir -p .githooks docs assets scripts
printf "# Automation Policy Lab\n" > README.md
printf "# Internal notes\n" > docs/internal.md
printf "echo hello\n" > scripts/build.sh
printf "fake-png-payload\n" > assets/logo.png
printf "*.log\n" > .gitignore

cat > .gitattributes <<'EOF'
* text=auto
*.sh text eol=lf
*.png binary
docs/internal.md export-ignore
EOF

PowerShell setup alternative

New-Item -ItemType Directory git-local-automation-lab | Out-Null
Set-Location git-local-automation-lab
git init -b trunk project
Set-Location project
git config user.name "Automation Lab"
git config user.email "automation-lab@example.invalid"

'.githooks','docs','assets','scripts' | ForEach-Object { New-Item -ItemType Directory -Force $_ | Out-Null }
Set-Content README.md '# Automation Policy Lab'
Set-Content docs/internal.md '# Internal notes'
Set-Content scripts/build.sh 'echo hello'
Set-Content assets/logo.png 'fake-png-payload'
Set-Content .gitignore '*.log'
@('* text=auto','*.sh text eol=lf','*.png binary','docs/internal.md export-ignore') | Set-Content .gitattributes

2. Create a non-destructive commit-msg hook

cat > .githooks/commit-msg <<'EOF'
#!/bin/sh
msgfile=$1
gitdir=$(git rev-parse --git-dir)
printf 'hook=commit-msg argc=%s msgfile=%s\n' "$#" "$msgfile" >> "$gitdir/hook-observations.log"

subject=$(sed -n '1p' "$msgfile")
case "$subject" in
  "DEMO: "*) exit 0 ;;
  *)
    echo "commit-msg: subject must start with DEMO: " >&2
    exit 1
    ;;
esac
EOF

chmod +x .githooks/commit-msg
git config --local core.hooksPath .githooks
git config --show-origin --get core.hooksPath

The POSIX chmod command is for filesystems/environments that expose executable permission bits. Git's hook contract requires an executable hook; Windows/Git-for-Windows execution details depend on the filesystem/runtime, so verify actual invocation rather than assuming POSIX permission behavior maps perfectly.

3. Observe a rejected commit without losing staged content

git add .
git status --short
git commit -m "initial policy lab"
echo "commit exit=$?"

git status --short
cat .git/hook-observations.log

Expected: the hook exits 1, so no commit is created. The staged snapshot remains staged. The observation log shows that commit-msg received one argument: the proposed message-file path.

4. Create a valid commit and prove the ref moved once

git commit -m "DEMO: establish policy automation lab"
git log -1 --oneline --decorate
git status --short --branch
cat .git/hook-observations.log

The branch now points to the new commit. The hook script and .gitattributes are normal tracked content; core.hooksPath and the observation log remain local metadata.

5. Demonstrate why client hooks are not an enforcement boundary

This bypass demonstration belongs only in the disposable lab. It proves an architectural limitation; it is not a recommended team workflow.
printf "local bypass demonstration\n" > bypass.txt
git add bypass.txt
git commit --no-verify -m "plain subject bypasses client hook"
git log -1 --oneline

The commit succeeds because commit-msg is one of the hooks that --no-verify can bypass. This is the evidence for moving mandatory policy to CI/server governance.

6. Configure and inspect simple Git aliases

git config --local alias.st 'status --short --branch'
git config --local alias.last 'log -1 --oneline --decorate'

git config --get-regexp '^alias\.'
git st
git last

These aliases live in this repository's local config. They do not create commits or change refs. They are interactive ergonomics, not project history.

7. Ask Git which attributes apply instead of inferring from patterns

git check-attr --all -- scripts/build.sh
git check-attr --all -- assets/logo.png
git check-attr --all -- docs/internal.md
git ls-files --eol -- scripts/build.sh

Expected: the shell script is text with LF working-tree intent; the PNG path has the built-in binary macro effects; the internal document is marked export-ignore. ls-files --eol helps inspect index/worktree line-ending state.

8. Debug ignore rules separately from attributes

printf "temporary log\n" > debug.log
git check-ignore -v -- debug.log
git check-attr --all -- debug.log
git status --short

check-ignore -v identifies the source file and matching pattern. The attribute query may show only broad text policy. One mechanism decides untracked visibility; the other assigns path behavior.

9. Demonstrate a repository-local attribute override

printf "local-only.dat -text custom=local\n" > .git/info/attributes
git check-attr text custom -- local-only.dat

The info/attributes rule is not tracked and has high precedence for this repository. It is appropriate for one developer's local behavior but not for shared policy.

10. Seed harmless metadata through an init template

cd ..
mkdir -p template-seed
printf "Created from the training template.\n" > template-seed/policy-notice.txt

git init --template=template-seed -b trunk templated
test -f templated/.git/policy-notice.txt
cat templated/.git/policy-notice.txt

The template file is copied into the new repository's .git directory at initialization. It is not a tracked working-tree file and does not establish ongoing synchronization with template-seed.

11. Create a local bare remote with a receive hook that consumes stdin

git init --bare central.git
cat > central.git/hooks/pre-receive <<'EOF'
#!/bin/sh
reject=0
while read old_oid new_oid ref_name
do
  printf 'old=%s new=%s ref=%s\n' "$old_oid" "$new_oid" "$ref_name" >> pre-receive.log
  if test "$ref_name" = "refs/heads/blocked-demo"
  then
    echo "server policy: blocked-demo is rejected" >&2
    reject=1
  fi
done
exit "$reject"
EOF
chmod +x central.git/hooks/pre-receive

git -C project remote add origin ../central.git
git -C project push -u origin trunk
cat central.git/pre-receive.log

Receive hooks run in $GIT_DIR of the bare repository. Each proposed ref update arrives on standard input as old object ID, new object ID, and full ref name. Exit 0 allows the accepted updates.

12. Observe authoritative rejection

git -C project branch blocked-demo
git -C project push origin blocked-demo
echo "push exit=$?"
tail -n 2 central.git/pre-receive.log

Expected: the push is rejected because the server-side hook exits non-zero for that ref. A client-side --no-verify cannot disable a hook that executes on the remote server.

13. Challenge — choose the correct customization layer

  1. A shorthand is only for your interactive terminal. Git alias, tracked script, or server hook?
  2. Every clone should know that *.png is binary. Where should that policy live?
  3. Every developer should get fast commit-message feedback, but the rule must also be mandatory. Which two layers should cooperate?
  4. You want every newly initialized internal repository to start with a local metadata notice. Which mechanism?
  5. A path is unexpectedly ignored. Which diagnostic command identifies the matching rule source?

14. Cleanup

Confirm the disposable lab path before recursive deletion.

Git Bash / Bash / zsh

cd ..
pwd
rm -rf git-local-automation-lab

PowerShell

Set-Location ..
Get-Location
Remove-Item -Recurse -Force git-local-automation-lab

15. Knowledge check

Question 1. What state remains after a commit-msg hook rejects a commit?

Question 2. Where are alias.st and core.hooksPath stored in this lab?

Question 3. What does git check-ignore -v add over ordinary status?

Question 4. Why can a pre-receive hook enforce a remote rule more strongly than commit-msg?

Question 5. What did the init template copy?

16. Summary

You activated a tracked client hook through local configuration, proved its bypass boundary, configured aliases, inspected attributes and ignore patterns, created a local-only attribute override, seeded repository metadata through a template, and observed a server receive hook's stdin/exit contract.

Next

Turn the mechanisms into portable team design choices

Lesson 3 examines configuration scope, hook distribution, template precedence, attribute precedence, line-ending normalization, and custom diff/merge-driver trust.

Authoritative references

 githooks
 git-config
 git-check-attr
 git-check-ignore
 git-init

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.