Overview
Git tracks a project's history as a graph of immutable commits, each one a full snapshot of the project plus a pointer back to its parent, rather than storing changes as a linear list of diffs. Getting from a change you've made on disk into that graph goes through three distinct areas: the working directory (the files as you're editing them), the staging area (what you've explicitly told Git to include in the next commit, via git add), and the commit history (what git commit actually records). Almost every command that confuses newcomers - why git status shows a file as both staged and modified, why git restore and git restore --staged do different things, why a rebase "loses" commits that a revert wouldn't - makes sense once that three-stage model is the lens you're looking through, rather than treating Git as a single "save my changes" button.
Quick Reference
| Area | What it holds | Command that moves things into it |
|---|---|---|
| Working directory | Files as they exist on disk right now | (editing them directly) |
| Staging area (index) | Exactly what the next commit will contain | git add |
| Local repository | Permanent, immutable commit history | git commit |
| Remote | The shared copy other people push to and pull from | git push / git pull |
The commands below are grouped by what you're trying to do, not an exhaustive reference (see the official Git reference docs for every flag), but the set that covers nearly all day-to-day Git work.
| Command | Description | Copy |
|---|---|---|
git init | Initialize a new, empty repository in the current directory. | |
git clone <url> | Clone a remote repository into a new local directory. | |
git status | Show staged, unstaged, and untracked changes in the working directory. | |
git add <file> | Stage a file's changes for the next commit. | |
git add . | Stage every changed and new file in the current directory. | |
git commit -m "message" | Commit staged changes to history. | |
git commit -am "message" | Stage all changes to already-tracked files and commit, in one step. |
| Command | Description | Copy |
|---|---|---|
git branch | List local branches. | |
git branch -a | List local and remote-tracking branches. | |
git switch -c <branch> | Create a new branch and switch to it. | |
git switch <branch> | Switch to an existing branch. | |
git merge <branch> | Merge the named branch into the current branch, creating a merge commit if history has diverged. | |
git rebase <branch> | Reapply the current branch's commits on top of another branch's tip. | |
git branch -d <branch> | Delete a branch, only if it has already been merged. |
| Command | Description | Copy |
|---|---|---|
git remote -v | List configured remotes and their URLs. | |
git fetch | Download objects and refs from a remote, without merging anything into the current branch. | |
git pull | Fetch, then merge (or rebase, with --rebase) the remote-tracking branch into the current one. | |
git push | Push local commits on the current branch to its upstream. | |
git push -u origin <branch> | Push a new branch and set it to track origin/<branch> for future plain pushes and pulls. |
| Command | Description | Copy |
|---|---|---|
git restore <file> | Discard uncommitted working-directory changes to a file. | |
git restore --staged <file> | Unstage a file, keeping its working-directory changes intact. | |
git reset --soft HEAD~1 | Undo the last commit, keeping its changes staged. | |
git reset --hard HEAD~1 | Undo the last commit and discard its changes entirely - irreversible for anything not saved elsewhere. | |
git revert <commit> | Create a new commit that reverses a previous one - safe for commits already pushed or shared. |
| Command | Description | Copy |
|---|---|---|
git stash | Save uncommitted changes aside and return the working directory to HEAD. | |
git stash list | List saved stashes. | |
git stash pop | Reapply the most recent stash and remove it from the stash list. | |
git stash drop | Delete a stash without reapplying it. |
| Command | Description | Copy |
|---|---|---|
git tag | List tags. | |
git tag -a v1.0.0 -m "message" | Create an annotated tag (with a message and metadata) at the current commit. | |
git push origin v1.0.0 | Push a single tag to the remote. | |
git push --tags | Push every local tag to the remote. |
Syntax
git commit -m "<type>: <short summary>"
# e.g.
git commit -m "fix: handle empty response body in retry logic"git switch -c feature/checkout-retry
# ... make changes, commit ...
git push -u origin feature/checkout-retryExamples
git switch -c feature/rate-limit
git add lib/rate-limit.ts
git commit -m "feat: add token-bucket rate limiter"
git push -u origin feature/rate-limitgit log --oneline --graph --allA compact, visual view of every branch's commit history and where they diverged - the fastest way to actually see what a merge or rebase is about to do.
git log --oneline --graph --all
Visual Diagram
Common Mistakes
- Running
git add .without checkinggit statusfirst, accidentally staging generated files, secrets, or unrelated work-in-progress. - Rebasing a branch that other people have already pulled, which rewrites commit hashes and forces everyone else's local copy to diverge.
- Using
git reset --hardto "clean up" without realizing any uncommitted or unstashed work is discarded permanently, not moved anywhere recoverable. - Committing directly to a shared default branch (
main) instead of a feature branch, making it impossible to review or roll back a change independently of everything else. - Force-pushing (
git push --force) to a shared branch to "fix" a mistake, silently overwriting commits a teammate may have already based work on;--force-with-leaseat least fails safely if the remote has moved since you last fetched.
Performance
git statusandgit addon a very large repository can be slow if the working directory contains many untracked files Git has to scan; a well-maintained.gitignorekeeps that scan fast.- Shallow clones (
git clone --depth 1) fetch only the most recent history, dramatically speeding up CI checkouts of large repositories where full history isn't needed. - Repository size grows with every large binary ever committed, even after the file is deleted, because history is immutable; binaries belong in Git LFS or object storage, not the repository itself.
Best Practices
- Commit small, focused changes with descriptive messages; a commit should represent one logical change, not "end of day checkpoint."
- Use feature branches for anything beyond a trivial fix, and merge or rebase into the default branch through a reviewed pull/merge request.
- Prefer
git revertovergit reset --hardfor undoing anything that has already been pushed and could be shared by others. - Keep a
.gitignorecurrent for build artifacts, dependencies, and local environment files, sogit status/git add .only ever surfaces real changes. - Write commit messages that explain why, not just what - the diff already shows what changed; the message is where the reasoning that won't survive in the code belongs.