---
name: git-administration
description: Set up and administer Git infrastructure and behavior — self-hosting a Git server (protocol choice, SSH/git-daemon/Smart HTTP setup, access restriction), server-side config, Git hooks (client and server), enforcing policies like commit-message formats and per-path ACLs, and repository/team configuration such as line endings, whitespace, attributes (binary diffs, clean/smudge filters, export rules, per-file merge strategy), and credential storage. Use when the user wants to host repos themselves, add hooks, block force pushes, enforce commit rules, fix CRLF/whitespace issues, diff binary files, or configure Git for a team — even if they only say "our pushes should be rejected when..." or "line endings are a mess". Not for GitHub/GitLab account-level UI administration.
---

# Git administration: servers, hooks, policy, configuration

Stand up Git infrastructure and make Git enforce team rules.
Derived from Pro Git, 2nd ed. (Chacon & Straub).

## Self-hosting a server

Remotes are **bare** repositories (a `.git` directory with no working tree,
named `project.git` by convention). Read **references/server-setup.md** when
actually installing a server — it has the exact procedures (SSH accounts,
git-shell restriction, git daemon systemd unit, Smart HTTP via
git-http-backend, GitWeb/GitLab).

### Choosing protocols

| Protocol | Auth | Use when | Cautions |
|---|---|---|---|
| SSH | authenticated, encrypted | default for self-hosted write access; SSH infra usually exists | no anonymous read — pair with HTTP/git:// for public fetch |
| Smart HTTP(S) | user/pass or anonymous on one URL | the most popular option; firewall-friendly; easiest for users | slightly trickier server setup than SSH |
| Git (`git://`, port 9418) | none | fastest transfer for high-volume public read | **no auth or crypto — cloning over `git://` (and `http://`) is vulnerable to MITM code injection; avoid unless you know what you're doing.** `https://` is safe; SSH is safe if the host key fingerprint is verified. Push effectively never enabled |
| Local/shared filesystem | filesystem perms | same-host or NFS teams; quick grabs from a colleague's repo | no protection against users corrupting the repo; NFS often slower than SSH to the same box |

Consider hosted options when you don't want to maintain a server; even
self-hosting teams often mirror open source publicly.

### Server-side config to consider on any push server

- `receive.fsckObjects true` — verify object integrity on every push
  (guards against corrupt/malicious clients; costs time on big pushes).
- `receive.denyNonFastForwards true` — no force pushes.
- `receive.denyDeletes true` — closes the delete-and-repush loophole; branch
  removal then requires manual ref deletion on the server.
- Per-user/per-branch versions of these rules need hooks instead (below).

## Hooks

Location: `.git/hooks/<name>`, executable, no extension (samples ship as
`*.sample`; any language works). **Client-side hooks are not copied by
`git clone` — never rely on them alone to enforce policy; enforce on the
server and give clients matching hooks as a courtesy.**

Client-side (commit flow): `pre-commit` (inspect snapshot, run lint/tests;
non-zero aborts; bypassed by `git commit --no-verify`), `prepare-commit-msg`
(edit auto-generated messages), `commit-msg` (validate the message; non-zero
aborts), `post-commit` (notify only).
Email flow (`git am`): `applypatch-msg`, `pre-applypatch` (after apply, before
commit), `post-applypatch`.
Other: `pre-rebase` (sample refuses rebasing pushed commits), `post-rewrite`
(after amend/rebase), `post-checkout`, `post-merge`, `pre-push` (refs on stdin;
non-zero aborts push), `pre-auto-gc`.

Server-side: `pre-receive` (all pushed refs on stdin; non-zero rejects the
whole push), `update` (once **per branch**; args refname, old SHA, new SHA;
rejects just that ref), `post-receive` (notify CI/chat/tickets; cannot stop the
push; the client waits until it finishes). Anything hooks print goes back to
the pusher's terminal.

Read **references/policy-enforcement.md** when implementing an enforced policy
(commit-message format checks, per-path ACLs, blocking rebases of pushed
commits) — it contains the book's worked update-hook + client-hook pattern.

## Team configuration essentials

Config levels: `--system` (`[path]/etc/gitconfig`) < `--global` (`~/.gitconfig`
or `~/.config/git/config`) < `--local` (`.git/config`, the default) — the most
specific wins. Debug surprising values with
`git config --list --show-origin` or `git config --show-origin <key>`.

- **Line endings** (`core.autocrlf`): Windows devs `true` (CRLF in checkout, LF
  in repo); macOS/Linux `input` (fix CRLF on commit, never convert on
  checkout); Windows-only projects `false`. 
- **Whitespace policy** (`core.whitespace`): on by default `blank-at-eol`,
  `blank-at-eof`, `space-before-tab`; opt-in `indent-with-non-tab`,
  `tab-in-indent`, `cr-at-eol` (prefix `-` to disable one). Fix incoming
  patches with `git apply --whitespace=fix` and unpushed commits with
  `git rebase --whitespace=fix`.
- Useful defaults: `commit.template` (team message format file),
  `core.excludesfile` (global gitignore for `*~`, `.DS_Store`, `.*.swp`),
  `core.editor`, `help.autocorrect` (tenths of a second before auto-running
  the corrected command), `pull.rebase` (silence Git ≥2.27 warning: `false` =
  merge default, `true` = rebase).
- External merge/diff tools: `merge.tool` + `mergetool.<tool>.cmd` with
  `$BASE $LOCAL $REMOTE $MERGED`, `mergetool.<tool>.trustExitCode`,
  `diff.external`; list built-ins with `git mergetool --tool-help`.
- **Credentials over HTTP(S)**: `credential.helper` — `cache` (memory, 15 min
  default, `--timeout <s>`), `store` (**plaintext** `~/.git-credentials`;
  prefer OS-backed helpers), `osxkeychain`, Git Credential Manager on Windows
  (also serves WSL). Multiple helpers are queried in order. Any executable
  named `git-credential-<name>` answering get/store/erase can be a custom
  helper.

## Attributes (per-path rules)

Set in a committed `.gitattributes` (or uncommitted `.git/info/attributes`):

- `*.pbxproj binary` — treat as binary: no CRLF conversion, no text diff.
- Diff binary formats via text conversion: `*.docx diff=word` +
  `git config diff.word.textconv docx2txt`; images: `*.png diff=exif` +
  `exiftool`. 
- `clean`/`smudge` filters transform content on stage/checkout
  (`*.c filter=indent`; `git config filter.indent.clean indent`,
  `... .smudge cat`). **Warning:** `.gitattributes` travels with the repo but
  filter drivers don't — design filters to fail gracefully.
- Archive control: `test/ export-ignore` (exclude from `git archive`),
  `LAST_COMMIT export-subst` with `$Format:%cd by %aN$` placeholders expanded
  at archive time.
- Per-file merge strategy: `database.xml merge=ours` +
  `git config --global merge.ours.driver true` keeps your version of that file
  in every merge.

## Gotchas

- Restricting the shared `git` SSH account: set its shell to `git-shell` (add
  to `/etc/shells` first) and prepend
  `no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty` to each
  `authorized_keys` entry — otherwise key holders get shell access and
  port-forwarding through your server.
- `git daemon` serves only repositories containing a `git-daemon-export-ok`
  file; without `GIT_HTTP_EXPORT_ALL`, `git-http-backend` behaves the same.
- `post-receive` that runs long blocks the pusher's terminal.
- In hook/automation scripts, prefer long option names (`--force` over `-f`)
  for future readers.
