Git & GitHub
Git is a distributed version control system: it records the full history of a project, lets many people work on it at once, and keeps a complete copy of that history on every machine. Almost every confusing thing about Git becomes obvious once you understand its data model, so this page starts there rather than with commands.
What “distributed” means
Section titled “What “distributed” means”In older systems (Subversion, CVS, Perforce) there is one server that owns the history. You “check out” files from it, and you need it online to commit, view history, or branch.
Git has no privileged server. When you clone a repository you get the entire history, not just
the latest files. Committing, branching, diffing, searching history, and reverting all happen
locally with no network. Sharing is a separate, explicit step: you push your commits to another
copy, or fetch theirs.
GitHub is not Git. GitHub is a hosting service that stores one copy of a repository and wraps it in a website (pull requests, issues, reviews, CI). That copy is technically no more authoritative than yours — teams just agree to treat it as the source of truth.
The data model: snapshots, not diffs
Section titled “The data model: snapshots, not diffs”This is the single most important idea, and most people learn it backwards.
Many version control systems store a file’s history as an original plus a list of changes: “line 12 changed, line 40 deleted”. Git does not. Every commit stores a complete snapshot of the entire project tree. When you view a diff, Git computes it on the fly by comparing two snapshots.
Git avoids wasting space because it is content-addressed. Every object is stored under the
SHA-1 hash of its own contents (newer repositories can use SHA-256, but SHA-1 is still the default
in Git 2.x). Identical content produces an identical hash, so if a file is unchanged between two
commits, both snapshots point at the exact same stored object. A thousand commits that never touch
LICENSE store LICENSE exactly once.
There are four object types:
| Object | What it holds |
|---|---|
| blob | The raw bytes of one file. No name, no permissions, no history. |
| tree | A directory listing: names, modes, and the hashes of the blobs/trees inside it. |
| commit | A pointer to one root tree, plus parent commit(s), author, committer, date, message. |
| tag | An annotated tag: a name, a target object, a tagger, and a message. |
So a commit is: snapshot + parent(s) + metadata, addressed by a hash.
You can look at the raw objects yourself:
git cat-file -p HEADtree 8d9f2b1c3e4a5b6c7d8e9f0a1b2c3d4e5f6a7b8cparent 4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3aauthor Ada Lovelace <ada@example.com> 1712345678 +0100committer Ada Lovelace <ada@example.com> 1712345678 +0100
Add parser for config filesThat is the entire commit object. Note what is not there: no diff, no file list, no branch name. Just a tree hash and a parent hash.
git cat-file -p HEAD^{tree}100644 blob a1b2c3d4... README.md100644 blob e5f6a7b8... package.json040000 tree 9c0d1e2f... srcWhy the graph matters
Section titled “Why the graph matters”Because each commit names its parent, commits form a DAG — a directed acyclic graph. Follow parents backwards and you get history. A commit with two parents is a merge. A commit with no parent is the first commit in the repository.
A ── B ── C ── D (main) \ E ── F (feature)Everything else in Git is a label on this graph. A branch is a movable pointer to one commit. A tag
is a fixed pointer. HEAD points at whichever branch you have checked out. There is no separate
“branch storage” — deleting a branch just removes a pointer; the commits are untouched until
garbage collection eventually removes anything unreachable.
Installing and configuring Git
Section titled “Installing and configuring Git”Check whether you have it:
git --version# => git version 2.43.0Install if needed:
# Debian / Ubuntusudo apt install git
# macOS (Xcode command line tools, or Homebrew)xcode-select --installbrew install git
# Windowswinget install --id Git.GitFirst-time configuration
Section titled “First-time configuration”Git stamps your name and email into every commit. Set them once, globally:
git config --global user.name "Ada Lovelace"git config --global user.email "ada@example.com"Set the default branch name for new repositories. Git’s built-in default is master; most projects
and hosts now use main:
git config --global init.defaultBranch mainSet the editor used for commit messages, interactive rebases, and merge conflict prompts:
git config --global core.editor "nvim"# or: "code --wait", "vim", "nano", "emacs"The --wait flag matters for GUI editors — without it the editor returns immediately and Git
thinks you saved an empty message.
A couple of quality-of-life settings that are almost always worth it:
# Colour outputgit config --global color.ui auto
# Refuse to guess what `git pull` should do; you must choose merge or rebasegit config --global pull.rebase false # always merge (the classic default)# orgit config --global pull.rebase true # always rebase your local commits on top
# Automatically set up the remote branch on first pushgit config --global push.autoSetupRemote true # Git 2.37+Inspect what is set and where it came from:
git config --list --show-origingit config user.email # the effective value hereConfiguration levels
Section titled “Configuration levels”Git reads config from three files, in increasing priority:
| Level | Flag | Location |
|---|---|---|
| System | --system |
/etc/gitconfig |
| Global (per user) | --global |
~/.gitconfig or ~/.config/git/config |
| Local (per repo) | --local |
.git/config in the repository |
Local wins over global, which wins over system. This is how you use a work email in one repository and a personal one everywhere else:
cd ~/work/projectgit config --local user.email "ada@work-corp.com"The three areas
Section titled “The three areas”Every Git command moves content between three places. Learning these names removes most of the
confusion around add, reset, and restore.
| Area | Also called | What it is |
|---|---|---|
| Working directory | working tree | The actual files on disk that you edit. |
| Staging area | index, cache | A file (.git/index) listing exactly what the next commit will contain. |
| Repository | object database | .git/objects — every commit, tree, and blob ever created. |
working directory staging area repository (files you edit) (.git/index) (.git/objects) │ │ │ │ git add ───────────►│ │ │ │ git commit ───────►│ │◄────────── git restore│ │ │◄──────────────────── git restore --source=… │The staging area is Git’s distinctive feature and its most useful one. It lets you build a commit deliberately instead of committing whatever happens to be in your editor. You can change ten files and commit three of them, or even commit only some hunks of one file.
# Edit files...git status # see what changed and what is stagedgit add src/parser.js # stage one filegit add -p src/api.js # stage selected hunks of anothergit commit -m "Fix off-by-one in header parsing"Files in the working directory are in one of these states:
- Untracked — Git has never seen this path.
- Unmodified — matches the last commit.
- Modified — differs from the last commit, not staged.
- Staged — the current version is recorded in the index, ready to commit.
Creating a repository
Section titled “Creating a repository”Two ways in. Either turn an existing directory into a repository:
mkdir my-project && cd my-projectgit init# => Initialized empty Git repository in /home/ada/my-project/.git/…or copy an existing one:
git clone https://github.com/user/repo.gitgit clone git@github.com:user/repo.git # SSHgit clone https://github.com/user/repo.git dir # into a specific directorygit init creates a single directory, .git/, containing the entire repository. Everything else in
the folder is just your working directory. Delete .git/ and you have a plain folder with no
history; copy .git/ and you have copied the whole project history.
git clone does four things: creates the directory, runs init, adds a remote named origin
pointing at the source, fetches all objects, then checks out the default branch.
Your first commit
Section titled “Your first commit”cd my-projectecho "# My Project" > README.md
git status# => Untracked files: README.md
git add README.mdgit commit -m "Initial commit"# => [main (root-commit) 3f2a1b0] Initial commit# => 1 file changed, 1 insertion(+)Look at what you made:
git log --oneline# => 3f2a1b0 (HEAD -> main) Initial commit
git show --stat HEADWhat lives in .git
Section titled “What lives in .git”You will rarely touch these directly, but knowing they exist demystifies a lot:
| Path | Contents |
|---|---|
.git/objects/ |
All blobs, trees, commits, tags — the actual history. |
.git/refs/heads/ |
One small file per branch, containing a commit hash. |
.git/refs/tags/ |
One file per tag. |
.git/HEAD |
Usually the text ref: refs/heads/main. |
.git/index |
The staging area (a binary file). |
.git/config |
Repository-local configuration and remotes. |
cat .git/HEADcat .git/refs/heads/main# => 3f2a1b04c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0A branch really is a 41-byte file. That is why creating and deleting branches is instant.
The mental model, condensed
Section titled “The mental model, condensed”Hold these five sentences and the rest of Git follows:
- A commit is an immutable snapshot of the whole tree, plus its parent(s), identified by a hash of its own contents.
- Commits form a DAG; history is “follow the parent pointers”.
- Branches and tags are just labels pointing at commits — cheap to make, cheap to delete.
HEADis where you are; it usually points at a branch, which points at a commit.- Content flows working directory → index → repository, and every “undo” command is really “move a pointer” or “copy content backwards between those three areas”.
What each page covers
Section titled “What each page covers”- Core workflow — status, add, commit, diff, log,
.gitignore, and every flavour of undo. - Branching and merging — branches, merges, conflicts, rebase, stash, cherry-pick.
- Remotes and GitHub — fetch vs pull, push, tracking branches, authentication, forks and pull requests.
- History and recovery — interactive rebase, reflog, bisect, detached HEAD, safely rewriting history.
- Collaboration and best practices — branching strategies, commit and PR hygiene, common mistakes and their fixes.
Key points
Section titled “Key points”- Git stores snapshots, not diffs; diffs are computed on demand.
- Objects are addressed by the hash of their content, which is why unchanged files cost nothing and why any edit to a commit produces a new commit.
- Configure
user.name,user.email,init.defaultBranch, andcore.editorbefore your first commit. - The three areas — working directory, index, repository — explain the entire command surface.
.git/is the repository; everything outside it is a working copy you can regenerate.