Git Fundamentals

Git is a distributed version control system. Every clone is a full copy of the history. Everyone commits locally first; sharing is a separate step. Understanding Git’s mental model is worth more than memorising commands.

What problem Git solves

Two problems, really:

  1. History — “what did this file look like last Tuesday? why was this line added? who changed it?”
  2. Collaboration — “five people are editing the same code; how do we avoid stomping on each other?”

Git answers both by tracking every change as an immutable snapshot, each with a parent, forming a graph of history.

The mental model

A commit is a snapshot, not a diff

A common misconception: “a commit stores the changes I made.” Actually, a commit stores a complete snapshot of every tracked file at that moment, along with:

  • A parent commit (what came before)
  • The author, committer, timestamp
  • A message
  • A unique 40-character SHA-1 hash

When you look at “the diff for this commit,” Git is computing snapshot_N - snapshot_N-1 on the fly.

Three places your work lives

Working directory     →  Staging area (index)  →  Local repository (.git)
  (your files)              (what you                (committed history)
                             `git add`ed)
  • Working directory — the files on disk. Editable.
  • Staging area — a list of changes you’re preparing to commit. git add moves things here.
  • Local repository — committed history. git commit turns what’s staged into a new commit.

Then there’s a fourth place: remote (GitHub, GitLab, etc.), which git push and git pull sync with.

Branches are just pointers

A branch is a movable label that points to a commit. That’s it. main, develop, feature/x — all the same kind of thing. Creating a branch is O(1); switching is fast. This is why Git encourages many short-lived branches.

           feature/x  ←── your current work
              ↓
   A ── B ── C ── D
              ↑
             main

When you git commit, the branch pointer moves forward to the new commit.

Commands you actually use

Setup

git init                            # start a repo here
git clone <url>                     # copy a remote repo locally
git config --global user.name "..." # first-time setup
git config --global user.email "..."

Daily loop

git status                          # what's changed?
git diff                            # show the changes
git add <file>                      # stage a file for commit
git add -A                          # stage everything
git commit -m "fix: thing"          # create a commit
git log                             # see history
git log --oneline --graph           # compact graph view

Branching

git branch                          # list branches
git checkout -b feature/x           # create and switch to new branch
git switch feature/x                # (newer, clearer) switch to existing
git merge feature/x                 # merge feature/x into current branch
git branch -d feature/x             # delete local branch

Remote

git remote -v                       # show configured remotes
git push                            # send commits to remote
git push -u origin feature/x        # first push of a new branch
git pull                            # fetch + merge from remote
git fetch                           # just fetch, don't merge

The critical distinction: merge vs rebase

You’ve been working on feature/x for two days. Meanwhile, main has moved forward. Before merging back, you need to integrate the new main changes. Two options:

Merge (git merge main)

Creates a merge commit that has two parents — yours and main. History becomes a graph.

  A ── B ── C ── D ── M    (main with merge commit M)
         \          /
          E ── F ──          (feature/x)
  • Preserves actual history
  • Easy, safe, reversible
  • Can make git log noisy on busy repos

Rebase (git rebase main)

Rewrites your commits on top of the new main, as if you’d started from there.

  A ── B ── C ── D ── E' ── F'    (clean linear history)
  • Clean, linear history
  • But: you rewrote commits. If they were already pushed, other collaborators see different history → conflicts.
  • Rule: never rebase commits that have been pushed and shared.

Most teams adopt one style: merge (easier) or rebase (cleaner). Either is fine; mixing them sloppily is the problem.

Undoing things

What you wantCommand
Unstage a file you addedgit restore --staged <file>
Discard uncommitted changes in a filegit restore <file> (⚠️ destructive)
Change the last commit messagegit commit --amend
Add a forgotten file to the last commitgit add <file>; git commit --amend --no-edit
Move HEAD back but keep changesgit reset HEAD~1
Nuke last commit and changesgit reset --hard HEAD~1 (⚠️)
Revert a pushed commit (safely)git revert <sha> — creates an “undo” commit

Rule: reset --hard and push --force are the two commands that lose work. Understand them before using them.

.gitignore

A file listing patterns for files Git should not track:

# node
node_modules/

# Python
__pycache__/
*.pyc
.venv/

# Secrets — never commit
.env
*.pem
credentials.json

# OS / editor
.DS_Store
.idea/
.vscode/

Never commit secrets. If you do, rotate them — git rm doesn’t actually remove them from history. Tools: git-secrets, gitleaks, trufflehog.

Git in IT / ops

You’ll use Git for:

  • IaC repos — Terraform, Ansible, Kubernetes manifests
  • Config backups — router/switch configs pushed to a repo as backup and audit trail
  • Documentation — this vault, runbooks, dotfiles
  • Scripts — your personal toolkit

The pattern you’ll see everywhere is GitOps: git is the source of truth, tooling watches it and applies changes.

Beyond the basics

Topics to learn after you’re fluent with the above:

  • Rewriting history: rebase -i, cherry-pick, reflog
  • Stashing: git stash for “I need to switch context but not commit”
  • Tags: marking releases
  • Submodules / subtrees: including other repos
  • Hooks: scripts that run on commit/push (pre-commit framework is widely used)

See also