Git Command Reference
Every Git tutorial teaches you commands one at a time, but once you’re working day to day you need a map of the whole toolbox: which command does what, when to reach for it, and how the dozens of subcommands relate to one another. This lesson is that map – a single, organized reference to the Git commands you will use constantly, grouped by purpose, with worked examples and the internals behind them. Keep it bookmarked; you will come back to it long after you finish the rest of this course.
Overview: How Git’s Command Surface Is Organized
Git’s command-line interface has more than 150 subcommands, but almost everything you do falls into a handful of categories: configuring Git itself, creating or cloning a repository, recording snapshots, branching and merging, synchronizing with a remote, inspecting history, and undoing changes. Git itself distinguishes porcelain commands – the ones ordinary users touch, like commit, merge, and log – from plumbing commands, the lower-level building blocks porcelain is made of, like hash-object, cat-file, and update-ref. This lesson focuses on porcelain, but understanding the plumbing model underneath makes every porcelain command make sense.
Underneath every porcelain command, Git is manipulating three things: the working tree (the files you see and edit on disk), the index (also called the staging area – a file at .git/index listing exactly what will go into the next commit), and the object database (the content-addressed store under .git/objects). Every version of every file’s content is stored as a blob, named by the SHA-1 hash of its content. A tree object is a snapshot of a directory: it lists blobs and other trees by name and file mode. A commit object points to exactly one tree (the full snapshot at that point), to one or more parent commits, and carries metadata – author, committer, timestamp, and message. A branch is nothing more than a small file containing a commit hash, living under .git/refs/heads/; moving a branch just means writing a new hash into that file. HEAD points to the branch you currently have checked out – or, in detached HEAD state, directly to a commit – and it is what Git updates when you commit and what it reads to know what your working tree should be compared against.
The Core Command Reference
The table below groups the commands you will reach for constantly. When you are not sure which command applies to a task, find the row that matches your intent.
| Category | Command | What it does |
|---|---|---|
| Setup & Config | git init |
Creates a new, empty repository in the current directory (adds a .git folder). |
| Setup & Config | git clone <url> |
Downloads an existing repository and its full history, and checks out its default branch. |
| Setup & Config | git config |
Reads or sets configuration (user name/email, aliases, defaults) at --local, --global, or --system scope. |
| Snapshotting | git status |
Shows the working tree and index state: staged, unstaged, and untracked changes. |
| Snapshotting | git add |
Copies file contents from the working tree into the index, staging them for the next commit. |
| Snapshotting | git commit |
Records a permanent snapshot of the index as a new commit object. |
| Snapshotting | git restore |
Discards working-tree changes, or unstages files from the index with --staged. |
| Snapshotting | git rm / git mv |
Removes or renames a tracked file and stages that change in one step. |
| Branching & Merging | git branch |
Lists, creates, or deletes branches. A branch is just a pointer to a commit. |
| Branching & Merging | git switch |
Changes which branch HEAD points to, or creates and switches to a new one with -c. |
| Branching & Merging | git checkout |
The older, multi-purpose command for switching branches or restoring files; still very common. |
| Branching & Merging | git merge |
Combines another branch’s history into the current one, creating a merge commit when needed. |
| Branching & Merging | git rebase |
Replays the current branch’s commits on top of another branch’s tip, producing new commits and linear history. |
| Sharing & Updating | git remote |
Manages the named URLs (typically origin) your repository talks to. |
| Sharing & Updating | git fetch |
Downloads commits, branches, and tags from a remote without touching your working tree. |
| Sharing & Updating | git pull |
Runs git fetch, then merges (or rebases, with --rebase) the remote branch into yours. |
| Sharing & Updating | git push |
Uploads local commits to a remote branch, updating its pointer there. |
| Inspection | git log |
Shows commit history, newest first, with author, date, and message. |
| Inspection | git diff |
Shows line-by-line differences between the working tree, the index, and commits. |
| Inspection | git show |
Displays a single commit’s metadata and the diff it introduced. |
| Inspection | git blame |
Shows which commit and author last touched each line of a file. |
| Undoing | git reset |
Moves the current branch pointer, optionally updating the index and working tree; rewrites history. |
| Undoing | git revert |
Creates a new commit that undoes an earlier commit’s changes, without rewriting history. |
| Undoing | git stash |
Temporarily shelves uncommitted changes so you can switch context, then reapplies them later. |
| Undoing | git cherry-pick |
Applies the changes from a specific commit elsewhere onto the current branch. |
GitHub (gh CLI) |
gh pr create, gh pr merge, gh issue list |
Command-line equivalents of the GitHub web UI for pull requests and issues. |
Examples
Example 1: Starting a project
git init my-project
cd my-project
git add README.md
git commit -m "chore: initial commit"
Output:
Initialized empty Git repository in /home/user/my-project/.git/
[main (root-commit) a1b2c3d] chore: initial commit
1 file changed, 1 insertion(+)
create mode 100644 README.md
git init creates the .git directory – the object database and refs live there from this point on. git add hashes README.md‘s content into a blob and records it in the index. git commit then writes a tree object from the index and a commit object pointing at that tree, and moves main to point at the new commit. The (root-commit) label means this commit has no parent – it is the first one in the repository.
Example 2: A feature branch workflow
git switch -c feature/login-page
git add src/login.js
git commit -m "feat: add login form validation"
git push -u origin feature/login-page
Output:
Switched to a new branch 'feature/login-page'
[feature/login-page 9f8e7d6] feat: add login form validation
1 file changed, 42 insertions(+)
Enumerating objects: 5, done.
Writing objects: 100% (3/3), 512 bytes | 512 KiB/s, done.
To github.com:yourname/yourrepo.git
* [new branch] feature/login-page -> feature/login-page
Branch 'feature/login-page' set up to track remote branch 'feature/login-page' from 'origin'.
git switch -c creates a new branch reference pointing at the current commit and moves HEAD to point at that new branch – no files change yet. After committing, feature/login-page points one commit ahead of main. The -u (--set-upstream) flag on the first push tells Git to remember that this local branch tracks the newly created remote branch, so future plain git push and git pull commands on this branch know where to go without naming the remote again.
Example 3: Undoing before you commit
git status
git restore --staged config.yml
git restore config.yml
Output:
On branch feature/login-page
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: config.yml
After git restore --staged config.yml:
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
modified: config.yml
After git restore config.yml:
nothing to commit, working tree clean
The first git restore --staged call copies the file’s version from the last commit back into the index, unstaging it – the working tree copy is untouched. The second call, without --staged, copies the file from the index back into the working tree, discarding your edits there. This pair replaces the old, more overloaded git checkout -- config.yml and git reset HEAD config.yml commands with clearer, single-purpose ones.
Example 4: Inspecting history
git log --oneline -n 3
git diff HEAD~1 HEAD
Output:
a1b2c3d (HEAD -> main) feat: add index page
9f8e7d6 feat: add login form validation
c4d5e6f chore: initial commit
diff --git a/src/index.js b/src/index.js
index e69de29..8f94a3c 100644
--- a/src/index.js
+++ b/src/index.js
@@ -0,0 +1,3 @@
+function renderHome() {
+ return "Welcome";
+}
--oneline compresses each commit to its abbreviated hash and subject line, and -n 3 limits it to the three most recent commits. git diff HEAD~1 HEAD compares the tree of the commit one before HEAD against the tree of HEAD itself, showing exactly which lines changed between those two snapshots.
How It Works Step By Step
Take git add src/login.js followed by git commit -m "..." and git switch -c feature/x. Internally:
git addreads the file from the working tree, computes a SHA-1 hash of its content, writes a new blob object to.git/objectsif that exact content doesn’t already exist there, and updates the index to point the pathsrc/login.jsat that blob.git commitwalks the index, builds one or more tree objects representing the full directory structure being committed, writes a commit object that references the root tree plus the current branch tip as its parent, and then updates the ref file for the current branch (for example.git/refs/heads/main) to contain the new commit’s hash.HEADdoesn’t change here because it already points at the branch name, not at a specific commit.git switch -c feature/xcreates a new file under.git/refs/heads/namedfeature/xcontaining the current commit’s hash, then rewrites.git/HEADso it containsref: refs/heads/feature/xinstead ofref: refs/heads/main. No blobs, trees, or commits are created – branching is cheap precisely because it is just writing one small pointer file.git merge other-branchcompares the tip commits of the two branches, finds their common ancestor, and if the change sets don’t overlap, either fast-forwards the current branch’s pointer (if no divergent commits exist on the current branch) or creates a new merge commit with two parents, whose tree reflects both branches’ changes combined.
Common Mistakes
Mistake 1: Staging everything blindly
git add .
git commit -m "wip"
This stages every modified and untracked file in the directory, including build artifacts, editor files, or secrets you never meant to commit – and a vague message like “wip” gives future readers (including you) no way to know what changed. Check git status first, list what’s actually staged, and keep an accurate .gitignore so generated files never show up as untracked in the first place.
git status
git add src/index.js
git commit -m "feat: add index page"
Mistake 2: Bare force-pushing a shared branch
git push --force origin main
A bare --force overwrites whatever is on the remote unconditionally, even if a teammate pushed commits you haven’t seen – those commits are simply discarded from the branch’s history on the remote. Use --force-with-lease instead: it fails safely if the remote has moved since you last fetched, so you can pull and reconcile first rather than silently erasing someone’s work.
git push --force-with-lease origin main
Mistake 3: Rebasing a branch other people already pulled
git switch main
git rebase feature/login-page
git push --force origin main
Rebase’s golden rule: never rebase commits that others have already fetched or based work on. Rebasing rewrites commit hashes, so everyone else’s local main diverges from the rewritten one, and force-pushing over a shared branch multiplies the damage. If main is shared, use git merge to bring branches together instead – it never rewrites existing commits.
git switch main
git merge feature/login-page
git push origin main
Best Practices
- Run
git statusbefore everygit addand everygit commitso you know exactly what you’re about to stage or record. - Write commit messages in a consistent style, such as Conventional Commits (
feat:,fix:,chore:,docs:), so history is easy to scan and can drive automated changelogs. - Prefer
git switchandgit restorefor everyday branch and file operations; reservegit checkoutfor cases where you specifically need its dual behavior or are following older documentation. - Default to
git push --force-with-lease, never bare--force, when you must overwrite a remote branch you own (like your own feature branch after an interactive rebase). - Use
git log --oneline --graphregularly to build an intuition for how your branches and merges actually relate to one another. - Keep a personal
~/.gitconfigwith useful aliases (for examplegit config --global alias.st status) so the commands you run most end up short and memorable.
Practice Exercises
- You’ve edited three files but only want to commit two of them together with a clear message, leaving the third for a separate commit. Using the reference table, work out which staging and commit commands accomplish this without touching the third file.
- You committed a change directly to
mainthat should have gone on a feature branch, and it hasn’t been pushed yet. Figure out how to move that commit onto a new branch and resetmainback to where it was, using commands from the Branching and Undoing rows of the table. - You have five old feature branches locally that were already merged into
mainweeks ago. Work out which single-purpose command (from the Branching & Merging row) lists merged branches, and how you would delete the stale ones safely.
Summary
- Git commands fall into a handful of categories: setup, snapshotting, branching, sharing with remotes, inspection, and undoing – use the reference table to find the right one fast.
- Every snapshot is built from blobs (file content) and trees (directory listings); a commit points to one tree and its parent commit(s); a branch is just a movable pointer to a commit.
git switchandgit restorereplace the overloaded parts ofgit checkoutwith clearer, single-purpose commands, butcheckoutstill works and appears constantly in the wild.git fetchonly downloads;git pulldownloads and then merges or rebases – know which one you’re running.- Never rebase or bare force-push a branch other people already have local copies of; use
--force-with-leaseand prefer merge for shared history.
