25 KiB
25 KiB
Customizing Git
Purpose & scope
- Goal: make Git operate in a more customized fashion (personal/team/company needs)
- Main customization mechanisms covered
- Configuration settings (
git config) - Attributes (path-specific behavior via
.gitattributes/.git/info/attributes) - Hooks (event-driven scripts: client-side + server-side)
- Configuration settings (
Git Configuration
git config basics
- Used to read/write configuration values
- Common initial setup (examples)
git config --global user.name "John Doe"git config --global user.email johndoe@example.com
Configuration files (“levels”) & precedence
- System level
- File:
[path]/etc/gitconfig - Applies to: every user + all repositories on the system
git config --system …reads/writes here
- File:
- Global level (user)
- File:
~/.gitconfigor~/.config/git/config - Applies to: a specific user across repositories
git config --global …reads/writes here
- File:
- Local level (repo)
- File:
.git/config(inside current repository) - Applies to: current repository only
git config --local …reads/writes here- Default level if you don’t specify
--system/--global/--local
- File:
- Override rule
localoverridesglobaloverridessystem
- Editing note
- Config files are plain text; manual edits work
- Generally easier/safer to use
git config
Client-side vs server-side options
- Options fall into two categories
- Client-side (most options): personal working preferences
- Server-side (fewer): repository receiving/policy behaviors
- Discover all supported options
man git-config- Reference:
https://git-scm.com/docs/git-config
Basic client configuration (common & useful)
core.editor
- Purpose: editor used for commit/tag messages
- Default selection order
$VISUALor$EDITORenvironment variables- fallback:
vi
- Set example
git config --global core.editor emacs
commit.template
- Purpose: provide an initial commit message template
- Use cases
- Remind yourself/team of message structure and policy
- Encourage consistent subject length + body + ticket references
- Example template content (concepts)
- Subject line guidance (e.g., keep under ~50 chars for
git log --oneline) - Multi-line description
- Optional ticket marker (e.g.,
[Ticket: X])
- Subject line guidance (e.g., keep under ~50 chars for
- Set + behavior
git config --global commit.template ~/.gitmessage.txtgit commitopens editor pre-filled with the template + comment lines
core.pager
- Purpose: pager for long output (e.g.,
log,diff) - Default: usually
less - Disable paging
git config --global core.pager ''- Effect: output is printed directly (no pager), regardless of length
user.signingkey
- Purpose: simplify signing annotated tags (GPG)
- Set
git config --global user.signingkey <gpg-key-id>
- Use afterward
git tag -s <tag-name>(no need to specify key each time)
core.excludesfile
- Purpose: global ignore patterns (applies to all repositories for that user)
- Use cases (examples)
- macOS:
.DS_Store - editors: Emacs backups
*~, Vim swap files.*.swp
- macOS:
- Example workflow
- Create
~/.gitignore_globalwith patterns like*~.*.swp.DS_Store
- Configure
git config --global core.excludesfile ~/.gitignore_global
- Create
help.autocorrect
- Problem: mistyped commands are suggested but not run
- Set behavior: auto-run a likely intended command after a delay
- Setting semantics
- Integer in tenths of a second
1→ 0.1s delay50→ 5s delay
- Integer in tenths of a second
- Example
git config --global help.autocorrect 1
- Runtime behavior
- Shows warning + countdown-like delay, then runs corrected command
Colors in Git
color.ui (master switch)
- Purpose: enable/disable default colored terminal output
- Values
false→ no colorauto(default) → color only when writing to a terminal; no color codes when piped/redirectedalways→ always emit color codes (rarely desired)
- Per-command override
- Use
--colorflag on specific Git commands if you want forced coloring in redirected output
- Use
color.* (command-specific control)
- Per-area switches (each:
true,false, oralways)color.branchcolor.diffcolor.interactivecolor.status
- Fine-grained subsettings (override specific parts)
- Example: diff “meta” styling
git config --global color.diff.meta "blue black bold"
- Example: diff “meta” styling
- Supported colors
normal,black,red,green,yellow,blue,magenta,cyan,white
- Supported attributes
bold,dim,ul(underline),blink,reverse
External Merge and Diff Tools
Why use external tools
- Git has built-in diff/merge, but you can:
- Use external diff viewers
- Use GUI merge tools for conflict resolution
- Example tool used in chapter
- P4Merge (Perforce Visual Merge Tool): graphical + free + cross-platform
Wrapper-script approach (example: P4Merge)
- Platform note
- Example paths are macOS/Linux-style
- On Windows, replace
/usr/local/binwith an executable path in your environment
extMerge wrapper
- Purpose: call the GUI merge tool with all passed arguments
- Example content (conceptual)
- Shell script that runs:
p4merge $* - macOS example path to binary:
/Applications/p4merge.app/Contents/MacOS/p4merge $*
- Shell script that runs:
extDiff wrapper
- Purpose: adapt Git’s diff-program arguments to what your merge viewer needs
- Git passes 7 arguments to external diff programs (concept)
path old-file old-hex old-mode new-file new-hex new-mode
- Wrapper logic
- Ensure 7 args exist
- Invoke merge tool on the old file and new file only
- Uses
$2(old-file) and$5(new-file)
- Uses
Make wrappers executable
sudo chmod +x /usr/local/bin/extMergesudo chmod +x /usr/local/bin/extDiff
Configure Git to use wrappers
- Key settings involved
merge.tool→ selects merge tool namemergetool.<tool>.cmd→ how to invoke tool (with$BASE,$LOCAL,$REMOTE,$MERGED)mergetool.<tool>.trustExitCode→ whether tool’s exit code indicates successdiff.external→ command for external diffs
- Example config commands
git config --global merge.tool extMergegit config --global mergetool.extMerge.cmd 'extMerge "$BASE" "$LOCAL" "$REMOTE" "$MERGED"'git config --global mergetool.extMerge.trustExitCode falsegit config --global diff.external extDiff
- Equivalent
.gitconfigblocks (concept)[merge] tool = extMerge[mergetool "extMerge"] cmd = … ; trustExitCode = false[diff] external = extDiff
Using the configured tools
- External diff example
git diff <rev1> <rev2>opens GUI instead of printing to terminal- (Figure reference in chapter: P4Merge screenshot)
- Merge conflicts
git mergetoollaunches GUI tool to resolve conflicts
Switching tools easily
- Benefit of wrapper design
- Change the underlying tool by editing
extMerge extDiffcontinues callingextMerge
- Change the underlying tool by editing
- Example: switch to KDiff3 by changing the binary invoked by
extMerge
Built-in mergetool presets
- Git supports many merge tools without custom
cmd - List supported tools
git mergetool --tool-help
- Environment caveat
- Windowed tools require a GUI; terminal-only sessions may fail
Using a tool only for merges (not diffs)
- If tool command is in
PATH(example:kdiff3)git config --global merge.tool kdiff3
- Result
- Merge resolution uses KDiff3
- Diffs remain Git’s normal diff output
Formatting and Whitespace
Problems addressed
- Cross-platform line endings (Windows vs macOS/Linux)
- Subtle whitespace edits introduced by editors/tools
core.autocrlf (line ending normalization)
- Background
- Windows newline: CRLF (
\r\n) - macOS/Linux newline: LF (
\n)
- Windows newline: CRLF (
- Behavior: auto-convert at boundaries
- On add/commit: convert as configured into repository-friendly form
- On checkout: convert as configured into working-tree-friendly form
- Recommended settings by environment
- Windows + cross-platform collaboration
git config --global core.autocrlf true- Checkout uses CRLF; repo stores LF
- macOS/Linux (LF) but want to “clean up” accidental CRLF commits
git config --global core.autocrlf input- Convert CRLF→LF on commit; do not convert on checkout
- Windows-only project, want CRLF stored as-is
git config --global core.autocrlf false
- Windows + cross-platform collaboration
core.whitespace (detect/fix whitespace issues)
- Six primary whitespace issues
- Enabled by default (can be disabled)
blank-at-eol(spaces at end of line)blank-at-eof(blank lines at end of file)space-before-tab(spaces before tabs in indentation)
- Disabled by default (can be enabled)
indent-with-non-tab(indent begins with spaces; usestabwidth)tab-in-indent(tabs in indentation portion)cr-at-eol(treat CR at EOL as acceptable)
- How to set
- Comma-separated list
- Disable an option by prefixing with
- - Omit options to keep defaults
- Shorthand
trailing-space=blank-at-eol+blank-at-eof
- Example intent from chapter
- Enable most checks, disable
space-before-tab, and enable the three disabled-by-default checks
- Enable most checks, disable
- Where it’s used
git diffhighlights whitespace problemsgit applyuses it for patch application- Warn:
git apply --whitespace=warn <patch> - Fix:
git apply --whitespace=fix <patch>
- Warn:
git rebasecan also fix while rewriting patchesgit rebase --whitespace=fix
Server Configuration
General note
- Fewer server-side config options, but some are important for integrity and policy
receive.fsckObjects
- Purpose: validate object integrity during push reception
- Check SHA-1 checksums
- Ensure objects point to valid objects
- Tradeoff: expensive; can slow pushes (especially large repos/pushes)
- Enable
git config --system receive.fsckObjects true
- Benefit
- Helps prevent corrupt or malicious objects being introduced
receive.denyNonFastForwards
- Purpose: refuse non-fast-forward updates (blocks most force-pushes)
- Typical scenario
- Rebase already-pushed commits, then attempt to push rewritten history
- Enable
git config --system receive.denyNonFastForwards true
- Alternative/enhancement
- Server-side hooks can enforce this with per-user/per-ref logic
receive.denyDeletes
- Purpose: prevent deletion of branches/tags on the server
- Stops the “delete and recreate” workaround to bypass non-FF restrictions
- Enable
git config --system receive.denyDeletes true
- Effect
- No user can delete branches/tags via push
- Must remove ref files manually on server (or via ACLs/policy hooks)
Git Attributes
What attributes are
- Path-specific settings controlling Git behavior for subsets of files
- Where to define them
.gitattributes(committed, shared with the project).git/info/attributes(local-only, not committed)
- Typical uses
- Choose merge strategies per file/directory
- Teach Git how to diff “non-text” formats
- Filter content on check-in/check-out (clean/smudge filters)
Binary Files
Identifying binary-like files
- Motivation: some “text” is effectively binary for Git operations (diff/merge not meaningful)
- Example from chapter
- Xcode
*.pbxproj(UTF-8 text, but acts like machine-managed DB) - Diffs/merges are not helpful; conflicts are not realistically resolvable by humans
- Xcode
- Attribute
- In
.gitattributes:*.pbxproj binary
- In
- Effects
- Avoid CRLF conversions/fixes for those paths
- Avoid computing/printing diffs for those files
Diffing binary files via text conversion (textconv)
- Core idea
- Convert binary content to a text representation, then use normal diff on that representation
Microsoft Word (.docx) diffing
- Attribute mapping
.gitattributes:*.docx diff=word
- Define the
worddiff “driver” withtextconv- Install
docx2txt(chapter references SourceForge project + INSTALL instructions) - Create wrapper script named
docx2txtinPATH(concept)- Calls
docx2txt.pl "$1" -to emit text to stdout
- Calls
- Make executable (
chmod a+x docx2txt) - Configure Git
git config diff.word.textconv docx2txt
- Install
- Result
git diffshows added/removed text instead of “Binary files differ”
- Limitation noted
- Formatting-only changes may not be represented perfectly
Image metadata diffing (EXIF)
- Attribute mapping
.gitattributes:*.png diff=exif
- Tool
- Install
exiftool
- Install
- Configure Git
git config diff.exif.textconv exiftool
- Result
git diffshows textual metadata differences (e.g., file size, width/height)
Keyword Expansion (CVS/SVN-style substitutions)
Why it’s tricky in Git
- Git hashes file content (blobs); modifying file contents “after commit” would change the hash
- Solution pattern
- Inject content on checkout
- Remove/normalize before staging/commit
Built-in ident attribute ($Id$)
- Attribute
.gitattributes:*.txt ident
- Behavior
- On checkout, replaces
$Id$with$Id: <blob-sha1> $ - Note: uses blob SHA-1 (not commit SHA-1)
- On checkout, replaces
- Limitation
- Blob SHA-1 isn’t a human-friendly timestamp/ordering signal
Custom clean/smudge filters
- Terminology
- smudge: runs on checkout (into working directory)
- clean: runs when staging (into index)
- (Figure references in chapter: smudge-on-checkout and clean-on-stage diagrams)
Example: auto-format C code using indent
.gitattributes*.c filter=indent
- Config filter behavior
- Clean (before staging):
git config --global filter.indent.clean indent - Smudge (on checkout):
git config --global filter.indent.smudge cat(no-op)
- Clean (before staging):
- Effect
- Code is run through
indentbefore being committed
- Code is run through
Example: $Date$ expansion (RCS-like)
- Smudge script (concept)
- Reads stdin
- Computes last commit date:
git log --pretty=format:"%ad" -1 - Replaces
$Date$→$Date: <last_date>$ - Script name in chapter:
expand_date(Ruby), placed inPATH
- Configure the filter “driver” (named
dater)- Smudge:
git config filter.dater.smudge expand_date - Clean:
git config filter.dater.clean 'perl -pe "s/\\\$Date[^\\\$]*\\\$/\\\$Date\\\$/"'- Strips expanded date back to literal
$Date$before storing
- Strips expanded date back to literal
- Smudge:
- Apply to files
.gitattributes:date*.txt filter=dater
- Demonstrated workflow
- Create file containing
$Date$ - Commit
- Remove + checkout again
- Observe expanded date in working directory
- Create file containing
- Portability caveat
.gitattributesis shared with the repo- Filter scripts/config are not automatically shared
- Filters should fail gracefully so project still works without them
Exporting Your Repository (archives)
export-ignore
- Purpose: exclude files/dirs from
git archiveoutput while still tracking them in Git - Example
.gitattributes:test/ export-ignore
- Result
git archivetarball omitstest/
export-subst
- Purpose: apply
git logformatting/keyword-style substitutions duringgit archive - Mark file(s)
.gitattributes:LAST_COMMIT export-subst
- Embed placeholders in file content (concept)
- Example pattern:
$Format:%cd by %aN$
- Example pattern:
- Behavior on archive
git archiveinjects metadata (date/author/etc.) into exported file- Can include commit message, git notes, and word-wrapped formatting (chapter shows
%+w(...)usage)
- Important limitation
- Exported archive is suitable for deployment
- Not suitable for continued development like a full Git checkout
Merge Strategies (per-path)
- Goal: apply special merge behavior for specific files
- Example: keep “our” version of a config-like file
.gitattributes:database.xml merge=ours- Configure merge driver
git config --global merge.ours.driver true(dummy driver; always “succeeds” taking ours)
- Result when merging
- Git uses current branch version for that path, avoiding manual conflict resolution for that file
Git Hooks
What hooks are
- Custom scripts triggered by Git events
- Two groups
- Client-side: local operations (commit, rebase, merge, checkout, push initiation, etc.)
- Server-side: network operations (receiving pushes)
Installing a hook
- Location
.git/hooksin a repository
- Defaults
git initcreates example hook scripts (typically*.sample)
- Enabling a hook
- Create/rename a file with the proper hook name (no extension)
- Make it executable
- Implementation language
- Any executable script works (shell, Perl, Ruby, Python, …)
Client-side hooks
- Critical distribution note
- Client-side hooks are not transferred when cloning
- To enforce policy, prefer server-side hooks (client-side can only assist)
Committing-workflow hooks
pre-commit- Runs: before commit message entry
- Use: inspect staged snapshot, run tests/lint, detect trailing whitespace, verify docs, etc.
- Abort rule: non-zero exit cancels commit
- Bypass:
git commit --no-verify
prepare-commit-msg- Runs: after default message is created, before editor opens
- Inputs (parameters)
- Commit message file path
- Commit type
- Commit SHA-1 (for amended commits)
- Use: adjust auto-generated messages (merge commits, squashes, amended commits, template-based flows)
commit-msg- Runs: after message is written, before commit is finalized
- Input: commit message file path
- Use: validate message format / required patterns
post-commit- Runs: after commit completes
- No parameters
- Use: notifications; can identify last commit via
git log -1 HEAD
Email workflow hooks (for git am)
- Scope note
- Only relevant if using email patch workflows (
git format-patch→git am)
- Only relevant if using email patch workflows (
applypatch-msg- Runs: first
- Input: temp file with proposed commit message
- Abort rule: non-zero cancels patch application
- Use: validate/normalize commit messages (can edit file in place)
pre-applypatch- Runs: after patch applied, before commit is made
- Use: inspect snapshot; run tests; abort
git amif failures occur
post-applypatch- Runs: after commit is made
- Use: notify author/team that patch was applied
- Cannot stop the patching process
Other client hooks
pre-rebase- Runs: before rebase
- Abort rule: non-zero cancels rebase
- Use: prevent rebasing commits that have already been pushed (sample hook attempts this)
post-rewrite- Triggered by: commands that replace commits (
git commit --amend,git rebase; notgit filter-branch) - Input: argument naming the triggering command; rewrite list on stdin
- Use: similar to post-checkout/post-merge automation/notifications
- Triggered by: commands that replace commits (
post-checkout- Runs: after successful
git checkout - Use: project environment setup (populate large binaries not tracked, generate docs, etc.)
- Runs: after successful
post-merge- Runs: after successful merge
- Use: restore non-tracked working-tree data (e.g., permissions), validate external dependencies
pre-push- Runs: during
git pushafter remote refs updated but before objects transferred - Inputs
- Parameters: remote name + remote location
- stdin: refs to be updated
- Abort rule: non-zero cancels push
- Use: validate ref updates before transferring objects
- Runs: during
pre-auto-gc- Runs: before automatic garbage collection (
git gc --auto) - Use: notify user or abort GC if inconvenient
- Runs: before automatic garbage collection (
Server-side hooks
- Admin-focused: enforce policies on pushes
- Pre hooks can reject pushes
- Exit non-zero to reject
- Print message to stdout to show error to client
pre-receive
- Runs: first during push handling
- Input: list of refs on stdin
- Reject behavior
- Non-zero exit rejects all refs in the push
- Use cases
- Block non-fast-forward updates globally
- Access control across refs and paths being modified
update
- Similar role to
pre-receive, but:- Runs once per branch/ref being updated
- Inputs (arguments)
- Ref name
- Old SHA-1
- New SHA-1
- Reject behavior
- Non-zero exit rejects only that ref; other refs can still update
post-receive
- Runs: after push process completes
- Input: same stdin data as
pre-receive - Use cases
- Notify services/users (email lists, CI, ticket trackers)
- Parse commit messages for automation
- Performance note
- Cannot stop push; client waits until hook finishes
- Avoid long-running tasks or offload them
Hook scripting tip (from chapter)
- Prefer long-form command-line flags in scripts for readability/maintainability
An Example Git-Enforced Policy
Goals
- Enforce commit message format (must include a ticket/reference token)
- Enforce user-based access control (who can change which directories/files)
- Provide client-side hooks to warn users early (reduce rejected pushes)
Implementation language in chapter
- Ruby (chosen for readability), but any scripting language works
Server-side enforcement (in hooks/update)
Update hook inputs & environment
- Runs once per branch being pushed
- Arguments
refname(ref being updated)oldrev(old SHA-1)newrev(new SHA-1)
- User identification assumption
- User available in
$USER - SSH single-user setups may need a wrapper to map public keys to a user and set env var
- User available in
- Hook prints an “Enforcing Policies…” banner
- Anything printed to stdout is relayed to the pushing client
Policy 1: Enforce commit message format
- Requirement: each commit message must contain something like
[ref: 1234] - Identify commits included in the push
git rev-list oldrev..newrev(lists new commits by SHA-1)
- Extract commit message for each commit
git cat-file commit <sha>gives raw commit object- Message content begins after first blank line
- Use
sed '1,/^$/d'to print message portion
- Validate messages
- Regex (concept):
/\[ref: (\d+)\]/ - If any commit lacks the pattern
- Print policy message
exit 1→ reject push
- Regex (concept):
Policy 2: Enforce directory/file ACL (user-based permissions)
- ACL file location (server-side)
aclfile stored in the bare repository
- ACL format (CVS-like)
- Lines:
avail|user1,user2|path - Pipe
|delimits fields - Blank
pathmeans access to everything - (Example also mentions
unavail, but the sample enforcement only handlesavail)
- Lines:
- Example intent
- Admin users: full access
- Doc writers: only
doc/ - Limited dev: only
lib/andtests/
- Parse ACL into structure
- Map:
user -> [allowed_paths] nilpath denotes “allowed everywhere”
- Map:
- Determine what files are modified by pushed commits
- For each new commit:
git log -1 --name-only --pretty=format:'' <rev>
- For each new commit:
- Validate each changed path against user’s allowed paths
- Allowed if
- user has a
nilaccess path (full access), or - file path starts with an allowed directory prefix
- user has a
- Allowed if
- On violation
- Print
[POLICY] You do not have access to push to <path> exit 1to reject
- Print
Testing behavior (server-side)
- Enable hook:
chmod u+x .git/hooks/update - Pushing with a bad commit message
- Hook prints policy banner + error
- Git reports hook failure and rejects the ref update
- Pushing unauthorized file edits
- Similar rejection, specifying the disallowed path
- Outcome
- Repo never accepts commits missing the required reference pattern
- Users are sandboxed to allowed paths
Client-side helper hooks (reduce “last-minute” rejections)
Distribution limitation
- Hooks don’t clone with the repository
- Must distribute scripts separately and have users install them into
.git/hooks/and make executable
Client policy 1: commit message check (commit-msg hook)
- Runs before commit finalization
- Input: commit message file path (
ARGV[0]) - Enforces same regex pattern as server policy
- Behavior
- Non-matching message → print policy message → exit non-zero → commit aborted
- Matching message → commit proceeds
Client policy 2: ACL check before commit (pre-commit hook)
- Requires local copy of ACL file
- Expected at:
.git/acl
- Expected at:
- Key differences vs server-side ACL enforcement
- Uses staging area (index) instead of commit history
- File list command
git diff-index --cached --name-only HEAD
- Same core permission logic
- If staged changes include a disallowed path, abort commit
- Identity caveat
- Assumes local
$USERmatches the user used when pushing to the server; otherwise set user explicitly
- Assumes local
Client policy 3: prevent rebasing already-pushed commits (pre-rebase hook)
- Motivation
- Server likely already denies non-fast-forward updates (
receive.denyNonFastForwards) and deletes - Client hook helps prevent accidental rebases that rewrite already-pushed commits
- Server likely already denies non-fast-forward updates (
- Script logic (concept)
- Determine base branch + topic branch (
HEADdefault) - List commits to be rewritten:
git rev-list base..topic - List remote refs:
git branch -r - For each commit SHA, check if reachable from any remote ref
- Uses revision syntax
sha^@(all parents) - Uses
git rev-list ^<sha>^@ refs/remotes/<remote_ref>to test reachability
- Uses revision syntax
- If any commit already exists remotely, abort rebase with policy message
- Determine base branch + topic branch (
- Tradeoffs
- Can be slow
- Often unnecessary unless you were going to force-push
- Still a useful preventative exercise
Summary (chapter wrap-up)
- Customization categories mastered
- Config settings (client + server)
- Attributes (path-specific diff/merge/filter/export behavior)
- Hooks (client assistance + server enforcement)
- Practical outcome
- Git can be shaped to match nearly any workflow, including enforceable policies and automation