GitRepository Structure

Repository Structure

A Git repository looks like an ordinary folder on disk — but it is hiding a complete database in a subfolder called .git/. Understanding that subfolder demystifies almost everything Git does. You do not need to memorise its contents, but a quick tour gives you a feel for what is real and what is just commands.

A repo at a glance

A typical project after a few commits

Text
my-project/
├── .git/              ← Git's database — the actual repository
├── .gitignore         ← (optional) which files to ignore
├── README.md
├── package.json
├── src/
│   └── index.js
└── tests/
    └── index.test.js

Everything outside .git/ is your working tree — the files you edit. Everything inside .git/ is the repository itself. Delete .git/ and you have an ordinary folder with no history; delete the rest and Git can recreate it from history.

Inside .git/

What lives in .git

Text
.git/
├── HEAD              ← pointer to the current branch
├── ORIG_HEAD         ← previous HEAD before a destructive op
├── FETCH_HEAD        ← latest fetched commit
├── config            ← repo-level git config
├── description       ← only used by GitWeb
├── index             ← the staging area (binary file)
├── packed-refs       ← compressed ref store
├── hooks/            ← scripts that run on Git events
├── info/
│   └── exclude       ← like .gitignore but private to this clone
├── logs/             ← reflogs for branches and HEAD
│   └── HEAD
├── objects/          ← THE DATABASE — every commit, tree, and blob
│   ├── 1f/
│   │   └── 9ab2c...
│   ├── pack/
│   └── info/
└── refs/
    ├── heads/        ← local branch pointers
    │   └── main
    ├── tags/         ← tag pointers
    └── remotes/
        └── origin/
            └── main
The interesting files explained
  • HEAD — a tiny text file containing ref: refs/heads/main (or whichever branch you are on). When you cat .git/HEAD you see exactly which branch is current.

  • index — a binary file that is the staging area. When you git add, Git updates the index. When you git commit, Git turns the index into a new commit.

  • config — INI-format file with repo-level settings (the remote URLs, user name overrides, etc.).

  • objects/ — the heart of Git. Every commit, every directory snapshot, and every file content lives here as a content-addressed blob.

  • refs/heads/ — one tiny file per local branch. The file content is a 40-character commit hash — the tip of that branch.

  • refs/tags/ — same idea, but for tags.

  • refs/remotes/ — pointers to where Git thinks each remote branch is. Updated by git fetch.

  • hooks/ — sample scripts you can rename and make executable to run on commit, push, etc.

  • logs/ — the reflog — a per-branch log of every move HEAD or each branch has made. This is how git reflog recovers lost commits.

How a branch is stored

Look inside .git

Bash
cat .git/HEAD
# ref: refs/heads/main

cat .git/refs/heads/main
# 1f9ab2c0e8b4...   (a 40-char SHA-1)

# That is literally the entire 'main' branch:
# - HEAD points to refs/heads/main
# - refs/heads/main points to a commit hash
# - the commit lives in .git/objects/1f/9ab2c0e8b4...

Branches really are just 41-byte files. That is why creating one is instantaneous.

The four object types
  • blob — the contents of a file (no name, no permissions, just bytes).

  • tree — a snapshot of a directory: a list of names mapping to blobs and other trees, with permissions.

  • commit — a snapshot reference (one tree), plus parent commit(s), plus metadata (author, date, message).

  • tag — an annotated tag — a named pointer to a commit with its own message.

Peek at any object

Bash
# What type is this object?
git cat-file -t 1f9ab2c
# commit

# Show its contents
git cat-file -p 1f9ab2c
# tree 5b88e9...
# parent c204c1...
# author You <you@example.com> 1700000000 +0000
# committer You <you@example.com> 1700000000 +0000
#
# Initial commit
The working tree vs the repository
  • Working tree — your real files. Edit them in your editor.

  • Repository (.git/) — Git’s database. Modified only by Git commands.

  • Files only become part of history once you git add them (into the index) and git commit them (into the objects database).

The staging area / index

The index (the file .git/index) is a curated picture of what your next commit will look like. It sits between the working tree and the repo.

Three places at once

Text
┌────────────┐   git add   ┌────────────┐   git commit   ┌────────────┐
│  Working   │ ──────────▶ │   Index    │ ─────────────▶ │ Repository │
│   Tree     │ ◀────────── │ (staging)  │ ◀───────────── │   (.git)   │
└────────────┘ git restore └────────────┘                └────────────┘
Bare repositories look different

A bare repo (e.g., the one GitHub hosts on its servers) has no working tree. Its top-level layout is just the contents of .git/:

A bare repo

Text
my-project.git/
├── HEAD
├── config
├── description
├── hooks/
├── info/
├── objects/
└── refs/
Inspecting your repo

Useful inspections

Bash
# Top-level repo location
git rev-parse --show-toplevel

# Where is .git for this repo?
git rev-parse --git-dir

# How big is the repo?
git count-objects -vH

# Are we inside a worktree or a bare repo?
git rev-parse --is-inside-work-tree
git rev-parse --is-bare-repository
.git is just a folder
On any system you can `cd .git/` and look around with normal commands (`ls`, `cat`, `find`). It is plain files — no magic beyond Git knowing how to read them.
Warning
Never edit anything inside `.git/` by hand. The only safe operations are: open a file, read it, then walk away. Direct edits can corrupt history in ways that are very hard to recover.
Tip
After a week of using Git, run find .git -type f | head -20 inside one of your repos. The file names will start to make sense. After two weeks they will feel obvious. That moment is when Git really clicks.