Lesson 5 of 5 in Rewriting History

Rewriting shared branches

Story: Some timelines are already in the archive. You can still straighten them, as long as you never write over someone else's events.

So far you've rewritten commits that were only in your repository. This Lesson covers two situations where other people are involved: catching up with a branch your teammates push to, and replacing a branch you've already pushed.

Pulling with a rebase

You've committed on main but haven't pushed yet, and a teammate has pushed to main on origin. A plain git pull merges their commit into yours with a merge commit, as you learned in Remotes & Collaboration. git pull --rebase fetches their commit too, but then rebases instead of merging: your unpushed commits are replayed on top of theirs.

$ git pull --rebase
From https://git.example/team/project
   4607830..76fd3eb  main       -> origin/main
Successfully rebased and updated refs/heads/main.

History stays one straight line, with your commits last, and git push goes through as usual. Only your unpushed commits are rewritten, so nobody else is affected. If a replayed commit conflicts with theirs, the rebase stops, and you resolve it as in Rebase conflicts: fix the file, git add it and git rebase --continue, or back out with git rebase --abort.

If you'd rather git pull always worked this way, set it once: git config pull.rebase true. You met that setting in Fetching and pulling, set to false.

To bring a branch of your own up to date with main on origin, name it: on your branch, git pull --rebase origin main fetches main from origin and rebases your branch onto it, all in one go.

Replacing a branch you've pushed

Suppose you pushed menu yesterday, and today you rebased it onto the newest main. The rebase made copies, so your menu no longer contains the commits that origin has. A plain push is refused:

$ git push
To https://git.example/team/project.git
 ! [rejected]        menu -> menu (non-fast-forward)
error: failed to push some refs to 'https://git.example/team/project.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. If you want to integrate the remote changes,
hint: use 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Don't follow that hint here. git pull would merge your old commits back in, next to their copies. You don't want to integrate the commits on origin. You want to replace them.

That's what git push --force-with-lease does:

$ git push --force-with-lease
To https://git.example/team/project.git
 + 9bb03b7...f555dfb menu -> menu (forced update)

origin's menu now matches yours, and the old commits fade.

The lease

The lease is your origin/menu: where your repository last saw menu on origin. --force-with-lease replaces menu on origin only if it's still there. If a teammate has pushed to menu since your last fetch, the push is refused, and nothing changes:

$ git push --force-with-lease
To https://git.example/team/project.git
 ! [rejected]        menu -> menu (stale info)
error: failed to push some refs to 'https://git.example/team/project.git'

Then:

  1. git fetch to bring in what they pushed.
  2. Look at it: git log --oneline origin/menu lists menu on origin, newest first. Your teammate's commits are on top, then your own old commits, the ones your rebase copied.
  3. Copy their commits onto your menu, for example with git cherry-pick origin/menu.
  4. git push --force-with-lease again.

Be careful with step 1. A fetch moves origin/menu, which renews the lease, so the next --force-with-lease would replace their work without a word. Always look at what you fetched before you force.

Never a plain --force

git push --force replaces the branch on origin whatever is there, so it can silently throw away a teammate's work. Lessons and Checkpoints don't run it. Use --force-with-lease whenever you mean to replace a branch.

Whose branch is it?

Rewrite a pushed branch only when it's yours, and tell anyone who has it, because their copies still have the old commits. Never rewrite main or another branch the whole team builds on. For those, merge, or use git revert from Undoing Things.

New commands

  • git pull --rebase
  • git push --force-with-lease

Explainer: Pull with a rebase, push with a lease

project: your repo, you are hereorigin: remote host (bare), https://git.example/team/project.git99860cd Add a README99860cdd1c0363 Add the home paged1c03634607830 Style the heading4607830cc39727 Add a menu page (unreachable)cc397272f04f67 Add an about page (unreachable)2f04f673c4f2f4 Remove the page margin3c4f2f49b36823 Add an about page9b36823dc825d2 Add a menu pagedc825d2mainorigin/HEADorigin/mainHEAD → menuorigin/menu99860cd Add a README99860cdd1c0363 Add the home paged1c03634607830 Style the heading4607830cc39727 Add a menu page (unreachable)cc397273c4f2f4 Remove the page margin3c4f2f49b36823 Add an about page9b36823dc825d2 Add a menu pagedc825d2mainmenu
The repository at the end of the Explainer.
Graph as text

project: your repo, you are here

  • HEAD → menu at dc825d2 "Add a menu page"

Branches and tags

  • main at 9b36823 "Add an about page", tracks origin/main
  • menu at dc825d2 "Add a menu page", tracks origin/menu
  • origin/HEAD at 9b36823 "Add an about page"
  • origin/main at 9b36823 "Add an about page"
  • origin/menu at dc825d2 "Add a menu page"

Commits, newest first

  1. dc825d2 "Add a menu page"; parent 9b36823; refs: HEAD → menu, origin/menu
  2. 9b36823 "Add an about page"; parent 3c4f2f4; refs: main, origin/HEAD, origin/main
  3. 3c4f2f4 "Remove the page margin"; parent 4607830
  4. 4607830 "Style the heading"; parent d1c0363
  5. d1c0363 "Add the home page"; parent 99860cd
  6. 99860cd "Add a README"; no parent

Unreachable

  • 2f04f67 "Add an about page"; parent 4607830
  • cc39727 "Add a menu page"; parent 4607830

origin: remote host (bare), https://git.example/team/project.git

Branches and tags

  • main at 9b36823 "Add an about page"
  • menu at dc825d2 "Add a menu page"

Commits, newest first

  1. dc825d2 "Add a menu page"; parent 9b36823; refs: menu
  2. 9b36823 "Add an about page"; parent 3c4f2f4; refs: main
  3. 3c4f2f4 "Remove the page margin"; parent 4607830
  4. 4607830 "Style the heading"; parent d1c0363
  5. d1c0363 "Add the home page"; parent 99860cd
  6. 99860cd "Add a README"; no parent

Unreachable

  • cc39727 "Add a menu page"; parent 4607830

Not shown: teammate.

Challenge 1 of 3

Story: The Partner filed an event while you were writing yours. File yours after theirs, in one straight line.

On main you've committed Add an about page, but you haven't pushed it. A teammate has just pushed a commit to main on origin.

Bring their commit into your main with your commit replayed on top of it, so there's no merge commit. Then push main to origin.

Unlocks after Remotes & Collaboration and Undoing Things.

Terminal

Unlocks after Remotes & Collaboration and Undoing Things.

This Lesson can be played once Remotes & Collaboration and Undoing Things are complete. Its text and Explainer are open to everyone.