By admin , 28 April 2026

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.