Introduction
Everything Git knows about a repository lives in .git/. Knowing what each entry is removes the mystery from "what just happened?" diagnostics.
Top-level layout
.git/
HEAD # symbolic ref to current branch
config # repository-local configuration
description # used by gitweb
index # the staging area
packed-refs # packed branch and tag refs (optional)
hooks/ # client-side hook scripts
info/ # excludes file and other repo-local info
logs/ # reflogs
objects/ # all objects (loose + pack/)
refs/ # branches, tags, remote-tracking
worktrees/ # linked worktrees (if any)
modules/ # submodules' .git directories
HEAD and refs
cat .git/HEAD
ls .git/refs/heads
ls .git/refs/remotes/origin
ls .git/refs/tags
cat .git/packed-refs | head
objects/
Loose objects under objects/xx/, packed objects in objects/pack/. Don't touch by hand.
logs/
The reflog. logs/HEAD records every move of HEAD; logs/refs/heads/<branch> records each branch's history.
cat .git/logs/HEAD | head
hooks/
Sample hook scripts, all named *.sample by default. To enable, drop the suffix and make executable:
cd .git/hooks
cp pre-commit.sample pre-commit
chmod +x pre-commit
info/
info/exclude is a per-repo, per-clone ignore file (not committed). info/refs is generated by git update-server-info for dumb HTTP servers.
config
Local config in INI format. Edit via git config --local or git config -e:
cat .git/config
git config --local user.email "[email protected]"
worktrees/
Each linked worktree has a directory under .git/worktrees/<name>/ with its own HEAD, index, and logs.
modules/
For repos with submodules, each submodule's .git directory lives here, and the submodule's working tree contains a small .git file pointing at it.
Bare repositories
A bare repo has the same contents but at the top level (no .git/ wrapper) and no working tree. Convention: name the directory project.git.
git rev-parse for paths
Scripts that need to find paths inside .git/ should never assume the directory is literally named .git. Submodules use a .git file pointing elsewhere; worktrees have linked gitdirs; bare repos have no separate gitdir. Use git rev-parse:
git rev-parse --git-dir # actual gitdir for current repo
git rev-parse --git-common-dir # shared dir for worktrees
git rev-parse --show-toplevel # working tree root
git rev-parse --is-inside-git-dir
git rev-parse --is-inside-work-tree
These five questions cover almost every "where am I?" check a script needs.
Common mistakes
Editing files inside .git/ directly. Almost everything has a Git command that does it correctly, atomically, and with hooks. Backing up only the working tree and forgetting .git/ contains the entire history; back up the whole directory. Sharing .git/hooks/ assuming they propagate; hooks are not versioned. Use core.hooksPath or a tool like pre-commit to share them. Finally, deleting .git/index on impulse; rebuild it with git read-tree HEAD, but only after backing up.