Git path · Combining work · Topic 4 of 13

Merge, rebase and fast-forward: what each one rewrites

Not started

When two branches have both moved on from a common commit, you can bring them together in two ways. A merge creates one new commit with two parents and changes nothing that already exists. A rebase replays your commits one by one on top of the other branch, creating new commits with new hashes, and moves your branch to the copies. And if only one side has moved, there is nothing to join: Git can fast-forward by moving the pointer.

Why it matters: Choosing between merge and rebase is mostly about one question: are you willing to replace commits that other people may already have?

  1. 1Fast-forward just moves the pointer

    If the target is an ancestor of the other branch, merging only moves the branch forward. No new commit.

  2. 2A merge adds a commit with two parents

    Nothing existing changes. History shows that two lines of work were joined.

  3. 3Rebase makes new commits

    Each of your commits is replayed onto the new base as a new commit with a new hash; the branch moves to the copies.

  4. 4Don't rebase what others have

    Rebasing shared commits forces everyone who has the old ones to sort out the difference.

1

How it works

The explanation, with examples you can run.

Fast-forward

Each commit below adds one file named after it. c is a small helper so the examples stay short.

git init -q -b main demo && cd democ() { echo "$1" > "$1.txt"; git add "$1.txt"; git commit -q -m "$1"; }c A; c Bgit switch -q -c feature; c Cgit switch -q maingit merge feature
Updating 6a57305..0605270Fast-forward C.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 C.txt

main was an ancestor of feature, so Git moved main to C. Many teams merge pull requests with --no-ff so each feature leaves a merge commit, or set git merge --ff-only to refuse anything but a fast-forward.

A true merge

Now let both branches move on:

git switch -q feature; c Dgit switch -q main; c Egit merge -q --no-edit featuregit log --oneline --graphgit cat-file -p HEAD | head -3
*   d01cd76 Merge branch 'feature'|\  | * ae76f44 D* | 142af93 E|/  * 0605270 C* 6a57305 B* 74bbda3 Atree 1bfd30eae5765ed9ea8d9cf2b38535fc1cdf1bb5parent 142af93c96d4bec1b8f29853a9b3dc30d99a1a67parent ae76f440ec825afc3e8b597e5ba34ea9a776ea9c

The merge commit has two parent lines. D and E keep their hashes; history shows exactly what happened, including that work went on in parallel.

Rebase instead

Undo that merge and rebase feature onto main instead:

git reset -q --hard HEAD~1git switch -q featuregit rev-parse --short HEADgit rebase maingit log --oneline --graph
ae76f44Successfully rebased and updated refs/heads/feature.* c1e1e0c D* 142af93 E* 0605270 C* 6a57305 B* 74bbda3 A

D was ae76f44; now it is a different commit with E as its parent. The history is a straight line, and main can fast-forward to it. The original D still exists, reachable through the reflog, until garbage collection.

Choosing

MergeRebase
Existing commitsUntouchedReplaced by copies
HistoryShows parallel work and when it was joinedLinear, as if done in sequence
ConflictsResolved once, in the merge commitPossibly once per replayed commit
Safe on shared branchesYesNo, unless everyone agrees

A common rule: rebase your own unpublished work to keep it tidy (git pull --rebase, git rebase -i to squash fixups), and merge when combining branches other people use. git rebase --update-refs also moves any branches stacked on top of the one you rebase.

2

Check yourself

3 questions. Get them all right to finish this topic.

  1. 1.main has not moved since you branched feature off it. What does git merge feature do on main?
  2. 2.After git rebase main on your feature branch, your three commits have new hashes. Why?
  3. 3.Which is safe to do to a branch that teammates have already pulled?

0 of 3 right so far. Get all three to finish this topic.