Fetch, Pull, and Push
These three commands are the only ones that touch the network, and keeping them straight removes most remote-related surprises. fetch downloads new objects and moves your remote-tracking refs without disturbing your branches; pull does that fetch and then immediately integrates the result into your current branch; push sends your commits the other way.
The part that separates a careful engineer from a dangerous one is the force flags. --force and --force-with-lease look interchangeable until the day one of them silently deletes a teammate's work, so it is worth knowing exactly what each checks before it overwrites.
Fetch
git fetch contacts the remote, downloads any new commits, and updates the refs under refs/remotes/* — and nothing else. Your local branches stay where they are and your working tree is untouched. That is precisely what makes it the safe move: you can fetch at any moment to see what changed upstream, then decide how to integrate it.
After a fetch, git log main..origin/main shows exactly the commits the remote has that you do not, with no risk to your work.
Pull
git pull is fetch followed by an integration step. By default that step is a merge, which can create a merge commit; with --rebase it instead replays your local commits on top of the updated upstream, keeping history linear. The defaults are governed by config: pull.rebase true makes rebase the default, and pull.ff only refuses to integrate unless the result is a clean fast-forward.
On a shared branch the merge default is what produces a trail of "Merge branch 'main' of origin" commits that clutter history for no real benefit.
Fast-Forward vs Non-Fast-Forward
An update fast-forwards when one tip is a direct descendant of the other: integrating then just slides the pointer forward with no new commit. A push fast-forwards when your branch tip is a descendant of the remote tip. When it is not — because someone else pushed in the meantime — the push is rejected, and a pull in the same situation produces a merge (or, with --rebase, replays your work).
A rejected push is Git protecting the remote from losing commits, not a failure to fix with --force reflexively.
Force Pushing Safely
git push --force overwrites the remote branch unconditionally, discarding whatever was there. git push --force-with-lease overwrites only if the remote tip still matches the value you last fetched; if a teammate pushed since then, the lease check fails and the push aborts, leaving their commits intact.
The catch is timing: if you fetch the very branch you are about to force-overwrite right before pushing, the lease is now satisfied against the teammate's new commit, and --force-with-lease offers no protection at all.
--force clobbers the remote branch no matter what is there, silently destroying any commits pushed since your last fetch. It asks no questions and gives no warning.
--force-with-lease checks that the remote tip still matches your last-known value and aborts otherwise, so a teammate's intervening push stops the overwrite. Never use bare --force on a shared branch.
- Running bare
git push --forceon a shared branch and erasing a colleague's commits that landed after your last fetch. - Using
git pullwith no rebase config on a shared branch, littering history with "Merge branch 'main' of origin" commits. - Fetching the branch you are about to overwrite immediately before
--force-with-lease— the lease is now satisfied and protects nothing. - Expecting
git fetchto change your working tree, then thinking the update was "lost" when files do not move. - Setting
pull.ff only, getting blocked on a divergent history, and not realizing the block is deliberate protection against an unintended merge.
- Replace
--forcewithgit push --force-with-leaseon any branch others might touch. - Default to rebase pulls with
git config --global pull.rebase trueto keep history linear. - Inspect upstream changes with
git fetchfirst, then integrate deliberately rather than blindly pulling. - Set
git config pull.ff onlyso a divergent history forces an explicit merge-or-rebase choice. - Review what you are about to send with
git log @{u}..before every push.
hg pull fetch-only plus hg update, rejects non-fast-forward by defaultSubversion svn update/commit hit the central server directlyPerforce p4 sync/p4 submit round-trip to the serverFossil fossil sync is bidirectionalKnowledge Check
Why is --force-with-lease safer than --force, and when is it still not?
- It aborts if the remote tip moved since your last fetch — but fetching that branch first satisfies the lease and removes the protection
- It is always safe because it never overwrites the remote branch under any conditions, treating the remote tip as strictly read-only and appending your commits underneath it instead of replacing whatever the server already holds
- It backs up the remote branch to a ref under
refs/lease-backup/*before pushing, so a clobbered push is never lost and can always be restored - It only works on your own private feature branches that no one else has cloned, refusing to run against any branch a teammate might also be pushing to
What is the difference between fetch and pull for your working tree?
fetchupdates only remote-tracking refs and leaves your tree alone;pullalso integrates into your branch and changes files- Both rewrite your working tree identically and update the same branch, replaying remote commits onto your checkout and overwriting tracked files in exactly the same way every time
fetchintegrates and changes the tree;pullonly downloads objects- Neither one touches the working tree or your tracked files, since both commands stop at downloading objects into the object store and never advance any branch you have checked out
Why does a push get rejected without a fast-forward?
- Your tip is not a descendant of the remote tip, so accepting it would drop the remote's commits
- The remote server ran out of disk storage mid-push and aborted the transfer once its object database could no longer grow to hold your new commits
- Your latest commit message was malformed or empty
- A fast-forward is required only for the very first push that creates a branch on the remote, and every later push skips the check entirely
On a shared branch, what is the tradeoff of rebase pulls versus merge pulls?
- Rebase keeps history linear and avoids noise merges; merge preserves the exact integration point but clutters shared history
- Rebase pulls are always wrong on any branch you share, because replaying your local commits on top of fetched work corrupts the shared branch and must never be used in a team setting
- Merge pulls are faster and rebase is slower, with no difference in history
- There is no difference at all in the resulting history shape, because both end up writing an identical sequence of commits with the same parent links
What does git fetch update, and what does it leave alone?
- It updates
refs/remotes/*and leaves your local branches and working tree untouched - It updates your current branch and merges the remote changes automatically, advancing your checked-out tip in a single step
- It rewrites your working tree files but leaves your branches alone
- It changes nothing locally; all the new data stays on the server until a later pull pulls it down into your repository
You got correct