639

I am trying to understand the differences between git merge --squash <branch> and git rebase [--interactive] <base>.

Depending on the command, I am not sure what new commits appear on the current branch, if the original commits in <branch> are rewritten or left intact, and whether Git records a merge relationship.

1
  • 3
    For the record, no, rebase and squash are completely different operations. Also, the title of the question and its body ask different things. I can't imagine how this question is still as broken after 15 years. Commented Jun 13, 2024 at 7:47

6 Answers 6

632

Merge commits: retains all of the commits in your branch and interleaves them with commits on the base branchenter image description here

Merge Squash: retains the changes but omits the individual commits from history enter image description here

Rebase: This moves the entire feature branch to begin on the tip of the master branch, effectively incorporating all of the new commits in master

enter image description here

More on here


The first two diagrams come from About pull request merges on the GitHub Docs

Sign up to request clarification or add additional context in comments.

6 Comments

That first diagram looks completely wrong to me. Somehow, commit D has ended up with no parent.
I don't see the difference between "Merge commit" and "Squash and merge". It seems the master branch looks identical in the final state in both cases.
@IMSoP I see what you mean - based on the text below the diagram it looks like the arrow (->) should be a plus (+). Hope this makes sense :-D
@dan Even so, the link from C to D doesn't just disappear; the whole point of a merge commit is that both sides of the merge retain a full chain of history, so A, B and C are reachable from both parents of F. I think Github are doing their users a real disservice by using a diagram that differs so much from what git is actually modelling.
@dgg The link isn't "deleted", it's just not shown on that very misleading diagram. Commits in git are immutable, so if commit D is really the same commit, it has the same parent, forever. I've raised an issue against the GH docs here: github.com/github/docs/issues/21507
|
478

Both git merge --squash and git rebase --interactive can produce a "squashed" commit. But they serve different purposes.

will produce a squashed commit on the destination branch, without marking any merge relationship. (Note: it does not produce a commit right away: you need an additional git commit -m "squash branch")

This is useful if you want to throw away the source branch completely, going from (schema taken from SO question):

git checkout stable X stable / a---b---c---d---e---f---g tmp 

to:

git merge --squash tmp git commit -m "squash tmp" # In the following graph, G is d--e--f--g squashed together X-------------G stable / a---b---c---d---e---f---g tmp 

and then deleting tmp branch.


Note: git merge has a --commit option, but it cannot be used with --squash. It was never possible to use --commit and --squash together. Since Git 2.22.1 (Q3 2019), this incompatibility is made explicit:

See commit 1d14d0c (24 May 2019) by Vishal Verma (reloadbrain). (Merged by Junio C Hamano -- gitster -- in commit 33f2790, 25 Jul 2019)

merge: refuse --commit with --squash

Previously, when --squash was supplied, 'option_commit' was silently dropped. This could have been surprising to a user who tried to override the no-commit behavior of squash using --commit explicitly.

git/git builtin/merge.c#cmd_merge() now includes:

if (option_commit > 0) die(_("You cannot combine --squash with --commit.")); 

replays some or all of your commits on a new base, allowing you to squash (or more recently "fix up", see this SO question), going directly to:

git checkout tmp git rebase -i stable stable X----------------G tmp / a---b 

If you choose to squash all commits of tmp (but, contrary to merge --squash, you can choose to replay some, and squashing others).

So the differences are:

  • squash does not touch your source branch (tmp here) and creates a single commit where you want.
  • rebase allows you to go on on the same source branch (still tmp) with:
    • a new base
    • a cleaner history

10 Comments

G is c--d--e--f--g squashed together?
@Wayne: yes, G in those examples represent the tmp commits squashed together.
@Th4wn: Since Git reasons with snapshots of a all project, G won't represent the same content than g, because of changes introduced by X.
@VonC: not sure about that last comment. If you have a git merge --no-ff temp instead of git merge --squash temp, then you get a messier history, but you can also do things like git revert e, much more easily. It's a messy, but honest and pragmatic history, and the main branch still remains fairly clean.
@naught101 I agree. As explained in stackoverflow.com/a/7425751/6309 though, it is also about not breaking git bisect or git blame when used too often (as in git pull --no-ff: stackoverflow.com/questions/12798767/…). There isn't one approach anyway, which is why this article described three (stackoverflow.com/questions/9107861/…)
|
211

Let's start by the following example:

enter image description here

Now we have 3 options to merge changes of feature branch into master branch:

  1. Merge commits
    Will keep all commits history of the feature branch and move them into the master branch
    Will add extra dummy commit.

  2. Rebase and merge
    Will append all commits history of the feature branch in the front of the master branch
    Will NOT add extra dummy commit.

  3. Squash and merge
    Will group all feature branch commits into one commit then append it in the front of the master branch
    Will add extra dummy commit.

You can find below how the master branch will look after each one of them.

enter image description here

In all cases:
We can safely DELETE the feature branch.

8 Comments

can you explain what is dummy commit in 2nd picture ?? I am a beginner in git.
@Yusuf, it's just an extra commit which contains both branches updates, it's default commit message = "Megre branch XYZ into master"
For "Squash and merge": there is a commit with all grouped commits plus an "extra dummy commit"?
@leticia the commit with all grouped commits = the "extra dummy commit" itself, as the above graph
Then I would argue that 'squash and merge' will not add an extra dummy commit, but rather will just 'rebase'/append the commit in front of the master branch. Dummy commit in the context you are describing it above is not the same in 1. and 3. as dummy commit in 1 is 'Merge branch XYZ into master which is producing this commit' and dummy commit in 3 is 'Squashed commits into this one commit which is not additional commit produced by merge'
|
110

Merge squash merges a tree (a sequence of commits) into a single commit. That is, it squashes all changes made in n commits into a single commit.

Rebasing is re-basing, that is, choosing a new base (parent commit) for a tree. Maybe the mercurial term for this is more clear: they call it transplant because it's just that: picking a new ground (parent commit, root) for a tree.

When doing an interactive rebase, you're given the option to either squash, pick, edit or skip the commits you are going to rebase.

Hope that was clear!

3 Comments

When should I rebase and when should I squash?
It does not matter which you use but I recommend rebase. Rebase changes the parent node of the feature branch but merge does not and I recommend it because it keeps the commit structure simpler but as a git user, it makes not different. stackoverflow.com/questions/2427238/….
12

The way I have learned to understand the value and difference between squash, merge, etc. is to put together this short tutorial for myself.


If you follow this it will explain everything you want to know about squash, merge as well as fast forward, and rebase.


Feature Branches

Say you have Main branch at the Origin, with a history of Commits, A.

enter image description here

You pull from Main, and create a new branch, FeatureA, such that you have commit history:

  • A, F1, F2, F3 (three commits on FeatureA branch)

enter image description here

You want to push you changes into Origin and merge into Main. Since Main on Origin hasn’t changed you are also able to Fast Forward.

Merge with Fast Forward

Apply all the commit history of FeatureA branch on top of Main’s commit history.

enter image description here

Merge with Squash

An option available whenever your branch has multiple commits is to squash. Squashing combines the history of FeatureA branch into a single commit when merging with Main.

enter image description here

Fast forward isn’t possible if there have been other changes on Main.

If you made changes to the FeatureA branch, meanwhile others had pushed into Main, such that the Origin history of Commits is now: A, B, C

enter image description here

You want to push you changes into Main branch on Origin, and you have a few different options:

  • Merge (without fast forward)
  • Rebase,
  • Stash and Pull.

Merge (aka. Merge without fast-forward)

If you were to fetch a local copy of Origin’s Main branch and merge it into your local FeatureA branch, you would create a commit for that merge, M. You local history on FeatureA would be: A, F1, F2, F3, M, where M is the merge commit.

enter image description here

You could then merge to Main (squashing if you wanted). Note that if you don’t squash, you will introduce a Merge commit into the Main commit history:

  • A, B, C, F1, F2, M.

You could avoid this by squashing your branch when merging to Main, as described above in the Squash section, resulting in: A B C F, like so:

enter image description here

Rebase

Rebase is another option to avoid introducing a merge commit into the history. It essentially takes your changes, and makes it as if you had begun on the latest Main, rather than on an outdated version of Main. For example, after rebasing your Feature branch would branch off from A, B, C, (instead of A, as it was originally). The result:

  • A, B, C, F1, F2, F3

enter image description here

  • A, B, C, F (with squash)

enter image description here

Note, there is no Merge commit, M

The way Git achieves this is to systematically--commit-by-commit--apply the changes from origin’s Main to your branch. The end result is as if you had not started your FeatureA branch in the past based off commit A, but as if you started all your changes on top of the latest Main.

The problem is, in the way this is done. For each commit that has to be applied into your FeatureA branch, git checks for merge conflicts, and you have to resolve them. If there has been more than one change to Main on Origin since your FeatureA branch, you can be resolving merge conflicts over and over again.

Perhaps a stash and pull is easier?

Stash and Pull

Stash your FeatureA, Fetch/Pull an up to date local copy of Main from Origin, and either create a new branch and apply your stash. (Or merge your new Main branch into your branch, and apply your stash. If doing the latter, squash to avoid a merge in the commit history.) Then you can push to Origin / create pull request.

Comments

0

Merge and rebase both retain commit history vs squash doesn't. Retaining commit history has a huge impact and I learned it the hard way in the following scenario:

We have master, develop and feature branches. Feature is created off of develop and merged to master upon release. Hotfix branch was created off of master and merged (so develop doesn't know about it). Master was not merged back to develop after release so they got out of sync where merge from master back into develop shows changes and forward merge from develop to master also shows changes (even when nothing in develop has changed).

The cause of this was squashing commits when master is merged back to develop. Doing a regular merge which keeps the commit history, fixed the problem. Merge from develop to master doesn't show any changes now as expected.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.