616

In Git, is there a way to merge all changes from one branch into another, but squash to a single commit at the same time?

I often work on a new feature in a separate branch and will regularly commit/push - mainly for backup or to transfer what I'm working on to another machine. Mostly those commits say "Feature xxx WIP" or something redundant.

Once that work is finished and I want to merge WIP branch back into master, I'd like to discard all those intermediate commits, and just a have a single clean commit.

Is there an easy way to do this?

Alternatively, how about a command that squashes all commits on a branch since the point where it was branched?

ux.engineer
  • 10,082
  • 13
  • 67
  • 112
Brad Robinson
  • 44,114
  • 19
  • 59
  • 88

9 Answers9

761

Another option is git merge --squash <feature branch> then finally do a git commit.

From Git merge

--squash

--no-squash

Produce the working tree and index state as if a real merge happened (except for the merge information), but do not actually make a commit or move the HEAD, nor record $GIT_DIR/MERGE_HEAD to cause the next git commit command to create a merge commit. This allows you to create a single commit on top of the current branch whose effect is the same as merging another branch (or more in case of an octopus).

0 _
  • 10,524
  • 11
  • 77
  • 109
fseto
  • 9,898
  • 1
  • 19
  • 14
  • 1
    Cool feature! I love git. While I'll definitely be using this in the future now, I'd still recommend getting to know your way around rebase -i. It's a good skill to have, just in case you really did want to make them more than just one commit. – Will Buck Feb 10 '12 at 17:21
  • 6
    A word of caution: this works, but the default commit message includes the log from the branch being merged. The problem is it looks similar to the format you normally see where the entire text shown does not actually become part of the commit message, but in this case it does. So if you don't want all that, you need manually remove all of it from your commit message. I should have tested this before using it... – still_dreaming_1 Aug 13 '14 at 01:30
  • 39
    That, and, be warned that the branch will not be seen as merged. http://stackoverflow.com/questions/19308790/git-branch-merged-no-merged-and-squash-option – rxgx Oct 03 '14 at 23:07
  • 8
    IMHO this should have been called `rebase --squash` – Andy Feb 02 '17 at 11:24
  • so (since it does not really merge the feature branch) this would be appropriate if you were going to delete the feature branch after commit. Is that correct? (I am not a git expert) – Andrew Spencer Apr 11 '17 at 08:53
  • Ok this only works on branch basis as requested in the question (=whole branch). I needed this for a sum of commits only and so I couldn't use this. – testing Jul 03 '20 at 15:48
  • Worked very well, creating a single commit from the feature branch (with multiple commits and merges before) to the main branch. Clean approach and gives you flexibility to revert everything in one go if anything goes wrong! – coretechie Jan 11 '23 at 05:11
270

Found it! Merge command has a --squash option

git checkout master
git merge --squash WIP

at this point everything is merged, possibly conflicted, but not committed. So I can now:

git add .
# git add -u  # might be preferable, see below
git commit -m "Merged WIP"
MikeW
  • 5,504
  • 1
  • 34
  • 29
Brad Robinson
  • 44,114
  • 19
  • 59
  • 88
  • 3
    what does the `git add .` do? – Be Kind To New Users Dec 23 '15 at 22:01
  • 1
    @MichaelPotter It adds all the files and changes – Daksh Shah Jan 05 '16 at 18:34
  • 3
    `git add .` adds all non-ignored files in the current directory, I would be wary of picking up unintended files this way. – Jake Cobb Jan 07 '16 at 19:02
  • 12
    As an alternate to `git add .` you can use `git add -u` to only add files which have been already added to the tree. – Brandon Ogle Apr 29 '16 at 15:24
  • 33
    Suggesting that a "git add ." be also done is confusing. When I do the "git merge --squash WIP", it already has the squashed changes in the index. All that is needed is to commit them. Doing a "git add ." will add changes that happen to be in the working directory, but which were not part of the feature branch. The question was how to commit the changes in the feature branch as one commit. – John Pankowicz Dec 30 '16 at 17:24
  • Thanks. This was exactly what I needed. Except, I first created a new feature branch off of master, so that I could create a PR off of this feature branch. Then I merged the old feature branch into this (as specified). And as someone else said, everything was already staged. I just need to commit. – yngwietiger May 03 '23 at 16:34
38

Try git rebase -i master on your feature branch. You can then change all but one 'pick' to 'squash' to combine the commits. See squashing commits with rebase

Finally, you can then do the merge from master branch.

fseto
  • 9,898
  • 1
  • 19
  • 14
  • 13
    Yes that works, but I don't want the hassle of interactive rebase. I just want everything since the branch flattened. – Brad Robinson Sep 13 '10 at 00:18
  • 3
    +1 This makes for a clean history. It's a lot easier to identify and manage commits as individual patches, cards, stories, etc. – rxgx Oct 03 '14 at 22:59
25

Using git merge --squash <feature branch> as the accepted answer suggests does the trick but it will not show the merged branch as actually merged.

Therefore an even better solution is to:

  • Create a new branch from the latest master, commit in the master branch where the feature branch initiated.
  • Merge <feature branch> into the above using git merge --squash
  • Merge the newly created branch into master. This way, the feature branch will contain only one commit and the merge will be represented in a short and tidy illustration.

This wiki explains the procedure in detail.

In the following example, the left hand screenshot is the result of qgit and the right hand screenshot is the result of:

git log --graph --decorate --pretty=oneline --abbrev-commit

Both screenshots show the same range of commits in the same repository. Nonetheless, the right one is more compact thanks to --squash.

  • Over time, the master branch deviated from db.
  • When the db feature was ready, a new branch called tag was created in the same commit of master that db has its root.
  • From tag a git merge --squash db was performed and then all changes were staged and committed in a single commit.
  • From master, tag got merged: git merge tag.
  • The branch search is irrelevant and not merged in any way.

enter image description here

raratiru
  • 8,748
  • 4
  • 73
  • 113
  • 2
    This, for me, is actually much nicer way than doing `git rebase -i master` and playing with the interactive mode. Easy to remember and easy to work with. – Niko Föhr Sep 28 '20 at 08:26
9

2020 updated

With the --squash flag it looks like two parallel branches with no relation:

enter image description here

Sorting commits related to date looks like:

enter image description here

Personally, i don't like --squash option, try this trick, maybe it fits your needs, i use it for small projects:

  1. git init
  2. git checkout -b dev
  3. Several commits in dev
  4. After you did some great commits in dev(but not merged to master yet), if you do not want all commits copied to master branch, then make intentionally change something in master and (add some empty lines in README file and commit in master),
  5. git merge dev It results merge conflict(empty lines in README), resolve it, commit with new message you want, and you are DONE. Here is the visual representation of it.

null commit for intentionally merge conflict, name it anything you prefer

null commit for intentionally merge conflict

Akbar Pulatov
  • 2,955
  • 2
  • 16
  • 33
  • 2
    The idea, is to keep the [history linear](https://stackoverflow.com/a/56179936/2996101). However, If a merge should be visible in the graph, it can be presented with [one commit only](https://stackoverflow.com/a/60714645/2996101) to avoid the clutter of all micro-commits produced during development. However, if all commits are desired in the graph, `merge` does the trick natively. – raratiru Dec 14 '20 at 00:52
  • It seems wrong to intentionally create merge conflicts because your graphical representation is not nice. Just use a different representation instead of merge conflicts if you think it's necessary to have those small connection lines in your graphic. –  Nov 12 '22 at 11:04
5

I have created my own git alias to do exactly this. I'm calling it git freebase! It will take your existing messy, unrebasable feature branch and recreate it so that it becomes a new branch with the same name with its commits squashed into one commit and rebased onto the branch you specify (master by default). At the very end, it will allow you to use whatever commit message you like for your newly "freebased" branch.

Install it by placing the following alias in your .gitconfig:

[alias]
  freebase = "!f() { \
    TOPIC="$(git branch | grep '\\*' | cut -d ' ' -f2)"; \
    NEWBASE="${1:-master}"; \
    PREVSHA1="$(git rev-parse HEAD)"; \
    echo "Freebaseing $TOPIC onto $NEWBASE, previous sha1 was $PREVSHA1"; \
    echo "---"; \
    git reset --hard "$NEWBASE"; \
    git merge --squash "$PREVSHA1"; \
    git commit; \
  }; f"

Use it from your feature branch by running: git freebase <new-base>

I've only tested this a few times, so read it first and make sure you want to run it. As a little safety measure it does print the starting sha1 so you should be able to restore your old branch if anything goes wrong.

I'll be maintaining it in my dotfiles repo on github: https://github.com/stevecrozz/dotfiles/blob/master/.gitconfig

Stephen Crosby
  • 1,157
  • 7
  • 19
  • worked like a bliss! you might as well take a look at https://www.evernote.com/shard/s52/sh/7f8f4ff1-9a68-413f-9225-c49e3ee2fafd/8efbe48e76710ac51cf0fe9cdd10dbcd – Ilya Sheershoff Aug 13 '19 at 13:20
  • 1
    This requires `grep` and therefore would need some additional tweaking in Windows. – Niko Föhr Sep 28 '20 at 08:03
  • +1 this is very elegant. For a similar implementation of this idea which adds some more features, bells & whistles, see https://github.com/arielf/clean-push – arielf Jan 24 '21 at 04:19
3

You have a master branch and feature branch. You have lots of commits on a feature branch. You don't want all commits of the feature branch to appear on the master's commit history. Follow these steps

  1. Create a new branch from the latest master code and you are in that branch
git checkout -b latest_MCode
  1. Now merge your feature branch into latest_Mcode branch
git merge --squash feature
  1. Do commit without -m param

git commit # without -m

An editor should be a popup with all the commit logs, and files changed from the feature branch. You can see all feature branch commits here. If you want you can delete everything, and write only one line of the commit message you want to show after merging into master. Press i then write your message then press Esc->:wq->Enter to Save and quit the editor. 4. Merge your newly created branch into master

git checkout master
git merge latest_Mcode
git push

You are done! The original answer can be found at GithubLink

Gurjinder Singh
  • 9,221
  • 1
  • 66
  • 58
-1

git merge --squash <feature branch> is a good option .The "git commit" tells you all feature branch commit message with your choice to keep it .

For less commit merge .

git merge do x times --git reset HEAD^ --soft then git commit .

Risk - deleted files may come back .

-7

You can do this with the "rebase" command. Let's call the branches "main" and "feature":

git checkout feature
git rebase main

The rebase command will replay all of the commits on "feature" as one commit with a parent equal to "main".

You might want to run git merge main before git rebase main if "main" has changed since "feature" was created (or since the most recent merge). That way, you still have your full history in case you had a merge conflict.

After the rebase, you can merge your branch to main, which should result in a fast-forward merge:

git checkout main
git merge feature

See the rebase page of Understanding Git Conceptually for a good overview

NamshubWriter
  • 23,549
  • 2
  • 41
  • 59
  • This didn't work for me. I just created a simple test repo, with a WIP branch and tried the above and got merge conflicts (even though I hadn't made any changes on master). – Brad Robinson Sep 13 '10 at 00:14
  • If feature was created from main (git checkout -b feature main) and you had a recent merge from main, you shouldn't get conflicts from the rebase – NamshubWriter Sep 13 '10 at 00:17
  • OK, tried it again. Didn't get conflicts this time, but the history wasn't squashed. – Brad Robinson Sep 13 '10 at 00:23
  • Taking another look at the git-merge documentation, you are right, some of the commits will stay. If you have done previous merges from "main" to "feature" then the rebase will remove some of them, but not all. – NamshubWriter Sep 13 '10 at 00:28
  • Just remember that rebasing may be quite dangerous if the feature branch has been previously published. More info on [this SO question](http://stackoverflow.com/questions/2715085/rebasing-and-what-does-one-mean-by-rebasing-pushed-commits). – Robert Rossmann Jan 11 '14 at 23:16
  • Mate, you don't need to merge if you are rebasing. Probably you got confused by git lens. – Davide Arcinotti Jun 14 '22 at 15:50