GitLearn — Command Comparisons
GitLearn
⟳ Flow
15px

🔀 Command Comparisons

The commands that look similar but do very different things — explained side by side.

clone vs fetch vs pull Getting code

All three bring code from a remote server to your machine — but at completely different stages of the journey.

git clone
First time only
Downloads the entire repo — all history, all branches — and creates a new folder on your machine. Sets up the remote (origin) automatically.
Use whenYou don't have the repo yet. You only ever clone once per machine.
git fetch
Download, don't apply
Downloads new commits and branches from the remote into a hidden area (origin/main). Your working files are not touched at all.
Use whenYou want to see what's new on the remote before deciding to apply it.
git pull
Download + apply
git fetch followed immediately by git merge (or rebase). Downloads new commits and updates your current branch.
Use whenYou want to get the latest changes and apply them to your branch right now.
GitLab (remote) origin/main Your local branch git clone ─────────────────────────────────────────► creates the whole repo git fetch ──────────────► updates origin/main (your files unchanged) git pull ──────────────► updates origin/main ──► merges into your branch
💡
Prefer git fetch + review over git pull when you're on a feature branch. git pull auto-merges — which can create an unexpected merge commit. Fetch first, then consciously choose to merge or rebase.

In practice

Day 1 — you just joined the team: Use git clone. It downloads everything and sets up the folder. You never clone again on this machine.

Day 15 — you're mid-feature and a teammate tells you they pushed a fix you need: Use git fetch origin then git rebase origin/main. Fetch checks what arrived; rebase pulls it under your work without creating a stray merge commit.

Monday morning — you just want the latest before starting the day: On main, git pull is fine. It's your own branch, fast-forward only, no surprise merge commits.

# Day 1 — clone once
git clone https://gitlab.company.com/team/MyApp.git

# Mid-feature — check what's new without touching your files
git fetch origin
git log origin/main --oneline -5    # see the incoming commits
git rebase origin/main              # apply when ready

# Morning catch-up on main
git switch main
git pull

merge vs rebase Combining branches

Both bring commits from one branch into another. The difference is how they do it — and what the history looks like afterwards.

git merge
Joins two histories, preserves them
Creates a new "merge commit" that has two parents — one from each branch. Both branches' histories are preserved exactly as they happened.
Use whenMerging a feature branch into main via an MR. The merge commit is a clear record of when the feature landed.
git rebase
Replays your commits on top
Takes your commits and re-applies them on top of another branch, one by one. No merge commit. History looks like you always worked from the latest code.
Use whenKeeping your feature branch in sync with main while you work. Keeps history clean and linear.
Before: main A ── B ── C \ feature D ── E After git merge main (on feature): main A ── B ── C \ \ feature D ── E ── M ← merge commit with two parents After git rebase main (on feature): main A ── B ── C \ feature D' ── E' ← commits replayed, no merge commit
mergerebase
History shapeNon-linear, shows branchesLinear, as if no branching happened
Merge commitYesNo
Safe on shared branches?YesNo — rewrites commit hashes
Conflict handlingOne conflict resolution sessionMay pause once per replayed commit
Common useMR / PR final merge into mainKeeping feature branch in sync
⚠️
Never rebase a branch that other people are working on. Rebase rewrites commit hashes — anyone who pulled the old version will have a broken history. Only rebase your own local feature branches.

In practice

You're building a login feature. While you were working, Alice pushed 3 commits to main that include a shared config change you need.

Use rebase to pull Alice's changes under yours: git fetch origin then git rebase origin/main. Your feature commits land on top of Alice's — clean, linear history. When your MR is reviewed, the diff is only your changes.

Your feature MR is approved and ready to land in main.

GitLab does a merge — it creates a merge commit that says "feature/login merged into main at 14:32". The branch history is preserved, and it's clear exactly when and what landed.

The rule of thumb: rebase to sync, merge to land.


reset vs revert vs restore Undoing things

Three commands with "undo" in their DNA — but they operate at completely different levels.

git reset
Move HEAD backwards
Moves the branch pointer back in history, effectively removing commits. Rewrites history. What happens to the files depends on the flag used.
Use whenUndoing commits that haven't been pushed to a shared branch.
git revert
Create a new "undo" commit
Creates a brand new commit that does the opposite of a previous commit. Does not rewrite history. The original commit stays in the log.
Use whenUndoing a commit that has already been pushed to a shared branch.
git restore
Discard file changes
Discards changes in your working files or staging area. Doesn't touch commits at all — it just resets a file back to a known state.
Use whenThrowing away edits to a file before you've committed them.
History: A ── B ── C ── D (D is the latest commit, HEAD) git reset HEAD~1 A ── B ── C (D is gone from history) git revert D A ── B ── C ── D ── D' (D' undoes what D did) git restore file.js → no commits changed — just the file on disk is reset

git reset has three modes — all of them move HEAD backwards, but differ in what happens to the files:

FlagCommitsStaging areaWorking filesUse for
--softRemovedChanges kept stagedUnchangedRe-commit with a better message / split into multiple commits
--mixed (default)RemovedChanges unstagedUnchangedRe-stage selectively before re-committing
--hardRemovedClearedDeletedCompletely throw away work — destructive, no undo
🛑
git reset --hard permanently deletes your uncommitted changes. There is no undo for this. For commits already pushed to a shared branch, always use git revert instead.

In practice

You typed a commit message with a typo and immediately regret it — not pushed yet:

git commit --amend -m "Fix: correct message" — rewrites just the last commit message. Or git reset --soft HEAD~1 to fully undo the commit and re-stage everything.

You accidentally committed your .env file — not pushed yet:

git reset HEAD~1 (mixed, the default) — puts the commit back as staged changes. Remove the file, update .gitignore, then re-commit.

You pushed a broken commit to main an hour ago and other people have already pulled it:

git revert a1b2c3d then git push — creates a new commit that undoes the damage. Nobody's history breaks. This is the only safe option once others have the commit.

You edited a file and want to throw away all your changes before committing:

git restore src/config.js — resets the file to the last committed state. No commits involved at all.


switch vs checkout Branch switching

git checkout used to do everything — switch branches, restore files, detach HEAD. It was confusing. In 2019, Git split it into two focused commands.

git checkout
The old way — does too many things
Originally used for switching branches, restoring files, and checking out specific commits. Still works, but the overloaded behaviour makes it easy to run the wrong variant by accident.
StatusStill valid. You'll see it everywhere in older guides and Stack Overflow answers.
git switch
The new way — branches only
Does one thing: switches branches (and optionally creates them). Clearer intent, harder to misuse.
StatusPreferred in modern Git (2.23+). Use this going forward.
TaskOld way (checkout)New way (switch / restore)
Switch to a branchgit checkout maingit switch main
Create and switch to a new branchgit checkout -b feature/xgit switch -c feature/x
Discard changes to a filegit checkout -- file.jsgit restore file.js
Check out a specific commit (detached HEAD)git checkout a1b2c3dgit switch --detach a1b2c3d
💡
Use git switch and git restore for new work. When reading old docs or Stack Overflow answers, you'll see git checkout — it still works, just mentally map it to the newer commands.

In practice

You find a Stack Overflow answer from 2018 that says git checkout -b feature/x — does it still work?

Yes. The old command still works. The modern equivalent is git switch -c feature/x. Both do exactly the same thing. Use switch going forward so your brain isn't confused between "switch branch" and "restore file" — two very different operations that checkout used to handle with almost identical syntax.

The most common mistake with old checkout: people would run git checkout filename.js expecting to switch to a branch, but instead silently discarded all their unsaved changes in that file. git switch refuses to accept a filename — it only takes branch names — which makes this mistake impossible.


push vs push --force vs push --force-with-lease Pushing

Push sends your commits to GitLab. Force push is needed after rewriting history — but the type of force push matters a lot.

git push
Normal push — always safe
Sends your new commits to the remote. Only works if the remote branch doesn't have commits your local branch doesn't know about (a "fast-forward" push).
Use whenAlways, by default.
git push --force
Overwrites remote — dangerous
Forcibly replaces the remote branch with your local branch, no questions asked. If a teammate pushed something since you last fetched, their commits are destroyed.
Use whenAlmost never. Use --force-with-lease instead.
git push --force-with-lease
Force push — with a safety check
Same as --force, but first checks that nobody else has pushed to the branch since you last fetched. If they have, Git stops and warns you instead of overwriting their work.
Use whenAfter a rebase or interactive rebase on your own feature branch.
Situation: you rebased your feature branch, now need to force push Remote: A ── B ── C ── D (D was pushed by a teammate while you were rebasing) Local: A ── B ── C'── D' (your rebased commits) git push --force → overwrites D silently — teammate's work is gone ❌ git push --force-with-lease → detects D, stops and warns you ✓
🛑
Never force-push main, develop, or any branch others are working on. Force push is only appropriate on your own feature branch, after a rebase, before others have pulled it.

In practice

You ran git rebase origin/main on your feature branch and now git push is rejected:

Rebase rewrites commit hashes, so your local history no longer matches what's on GitLab. GitLab rejects the push to protect you. This is the one situation where force-push is correct — run git push --force-with-lease origin feature/my-branch. The --force-with-lease flag checks nobody else pushed to your branch while you were rebasing.

Why not just use --force?

Imagine you rebased, then your teammate (not knowing you were rebasing) pushed one commit to your branch. git push --force silently deletes their commit — they lose their work with no warning. git push --force-with-lease detects this and stops, letting you fetch and reconcile first.

# Normal daily push
git push

# First push of a new branch (sets up tracking)
git push -u origin feature/my-branch

# After a rebase — use force-with-lease, never --force
git push --force-with-lease origin feature/my-branch