Files
mapas-mentales/mindmap/Customizing Git.md

25 KiB
Raw Blame History

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)

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
  • Global level (user)
    • File: ~/.gitconfig or ~/.config/git/config
    • Applies to: a specific user across repositories
    • git config --global … reads/writes here
  • Local level (repo)
    • File: .git/config (inside current repository)
    • Applies to: current repository only
    • git config --local … reads/writes here
    • Default level if you dont specify --system/--global/--local
  • Override rule
    • local overrides global overrides system
  • 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
    • $VISUAL or $EDITOR environment 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])
  • Set + behavior
    • git config --global commit.template ~/.gitmessage.txt
    • git commit opens 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
  • Example workflow
    • Create ~/.gitignore_global with patterns like
      • *~
      • .*.swp
      • .DS_Store
    • Configure
      • git config --global core.excludesfile ~/.gitignore_global

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 delay
      • 50 → 5s delay
  • 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 color
    • auto (default) → color only when writing to a terminal; no color codes when piped/redirected
    • always → always emit color codes (rarely desired)
  • Per-command override
    • Use --color flag on specific Git commands if you want forced coloring in redirected output

color.* (command-specific control)

  • Per-area switches (each: true, false, or always)
    • color.branch
    • color.diff
    • color.interactive
    • color.status
  • Fine-grained subsettings (override specific parts)
    • Example: diff “meta” styling
      • git config --global color.diff.meta "blue black bold"
  • 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/bin with 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 $*

extDiff wrapper

  • Purpose: adapt Gits 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)

Make wrappers executable

  • sudo chmod +x /usr/local/bin/extMerge
  • sudo chmod +x /usr/local/bin/extDiff

Configure Git to use wrappers

  • Key settings involved
    • merge.tool → selects merge tool name
    • mergetool.<tool>.cmd → how to invoke tool (with $BASE, $LOCAL, $REMOTE, $MERGED)
    • mergetool.<tool>.trustExitCode → whether tools exit code indicates success
    • diff.external → command for external diffs
  • Example config commands
    • git config --global merge.tool extMerge
    • git config --global mergetool.extMerge.cmd 'extMerge "$BASE" "$LOCAL" "$REMOTE" "$MERGED"'
    • git config --global mergetool.extMerge.trustExitCode false
    • git config --global diff.external extDiff
  • Equivalent .gitconfig blocks (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 mergetool launches GUI tool to resolve conflicts

Switching tools easily

  • Benefit of wrapper design
    • Change the underlying tool by editing extMerge
    • extDiff continues calling extMerge
  • 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 Gits 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)
  • 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

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; uses tabwidth)
    • 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
  • Where its used
    • git diff highlights whitespace problems
    • git apply uses it for patch application
      • Warn: git apply --whitespace=warn <patch>
      • Fix: git apply --whitespace=fix <patch>
    • git rebase can also fix while rewriting patches
      • git 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
  • Attribute
    • In .gitattributes: *.pbxproj binary
  • 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 word diff “driver” with textconv
    • Install docx2txt (chapter references SourceForge project + INSTALL instructions)
    • Create wrapper script named docx2txt in PATH (concept)
      • Calls docx2txt.pl "$1" - to emit text to stdout
    • Make executable (chmod a+x docx2txt)
    • Configure Git
      • git config diff.word.textconv docx2txt
  • Result
    • git diff shows 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
  • Configure Git
    • git config diff.exif.textconv exiftool
  • Result
    • git diff shows textual metadata differences (e.g., file size, width/height)

Keyword Expansion (CVS/SVN-style substitutions)

Why its 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)
  • Limitation
    • Blob SHA-1 isnt 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)
  • Effect
    • Code is run through indent before being committed
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 in PATH
  • 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
  • Apply to files
    • .gitattributes: date*.txt filter=dater
  • Demonstrated workflow
    • Create file containing $Date$
    • Commit
    • Remove + checkout again
    • Observe expanded date in working directory
  • Portability caveat
    • .gitattributes is 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 archive output while still tracking them in Git
  • Example
    • .gitattributes: test/ export-ignore
  • Result
    • git archive tarball omits test/

export-subst

  • Purpose: apply git log formatting/keyword-style substitutions during git archive
  • Mark file(s)
    • .gitattributes: LAST_COMMIT export-subst
  • Embed placeholders in file content (concept)
    • Example pattern: $Format:%cd by %aN$
  • Behavior on archive
    • git archive injects 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/hooks in a repository
  • Defaults
    • git init creates 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-patchgit am)
  • 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 am if 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; not git filter-branch)
    • Input: argument naming the triggering command; rewrite list on stdin
    • Use: similar to post-checkout/post-merge automation/notifications
  • post-checkout
    • Runs: after successful git checkout
    • Use: project environment setup (populate large binaries not tracked, generate docs, etc.)
  • post-merge
    • Runs: after successful merge
    • Use: restore non-tracked working-tree data (e.g., permissions), validate external dependencies
  • pre-push
    • Runs: during git push after 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
  • pre-auto-gc
    • Runs: before automatic garbage collection (git gc --auto)
    • Use: notify user or abort GC if inconvenient

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
  • 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

Policy 2: Enforce directory/file ACL (user-based permissions)

  • ACL file location (server-side)
    • acl file stored in the bare repository
  • ACL format (CVS-like)
    • Lines: avail|user1,user2|path
    • Pipe | delimits fields
    • Blank path means access to everything
    • (Example also mentions unavail, but the sample enforcement only handles avail)
  • Example intent
    • Admin users: full access
    • Doc writers: only doc/
    • Limited dev: only lib/ and tests/
  • Parse ACL into structure
    • Map: user -> [allowed_paths]
    • nil path denotes “allowed everywhere”
  • Determine what files are modified by pushed commits
    • For each new commit: git log -1 --name-only --pretty=format:'' <rev>
  • Validate each changed path against users allowed paths
    • Allowed if
      • user has a nil access path (full access), or
      • file path starts with an allowed directory prefix
  • On violation
    • Print [POLICY] You do not have access to push to <path>
    • exit 1 to reject

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 dont 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
  • 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 $USER matches the user used when pushing to the server; otherwise set user explicitly

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
  • Script logic (concept)
    • Determine base branch + topic branch (HEAD default)
    • 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
    • If any commit already exists remotely, abort rebase with policy message
  • 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