Combining Git commits with squash
Imagine you have done lots of commits, i.e. several commits for fixing a bug, but you don’t need all of them, and somehow you want to meld them and squash them into a single commit. You can use rebase. Git always squashes a newer commit into an older commit or “upward”. Let’s create some files:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
mkdir remote repos1 repos2 cd remote/ git init --bare cd ../repos1/ git clone ../remote/ . touch a git add . git commit -m "a added" touch b-1 git add . git commit -m "b-1 added" touch b-2 git add . git commit -m "b-2 added" touch b-3 git add . git commit -m "b-3 added" touch c git add . git commit -m "c added" touch d git add . git commit -m "d added" |
Your tree would be something like this:
But now you would like to squash b-1, b2 and b-3 into one commit. So copy the hash ID of b-3 and execute rebase:
1 2 |
git rebase -i <b-3 HASH ID>~3 git rebase -i 02f4da994c622f7db23a051b3cad3d92385b21c2~3 |
This will open your editor and the first few lines are:
1 2 3 4 5 |
pick 5833ab5 b-1 added pick b7d1e42 b-2 added pick 8d7dae1 b-3 added pick b2fa00f c added pick f1d7439 d added |
Git will melt new commits into older ones, and we want to squash b-3 and b-2 into b-1 so change it into:
1 2 3 4 5 |
pick 5833ab5 b-1 added squash b7d1e42 b-2 added squash 8d7dae1 b-3 added pick b2fa00f c added pick f1d7439 d added |
Close all instances of your editor and save them. This will open another editor to write a new commit message.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
# This is a combination of 3 commits. # This is the 1st commit message: b-1 added # This is the commit message #2: b-2 added # This is the commit message #3: b-3 added # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # # Date: Thu Nov 26 11:04:26 2020 +0100 # # interactive rebase in progress; onto ef211ef # Last commands done (3 commands done): # squash b54c2ca b-2 added # squash 30e735c b-3 added # Next commands to do (2 remaining commands): # pick 591529e c added # pick 7960530 d added # You are currently rebasing branch 'master' on 'ef211ef'. # # Changes to be committed: # new file: b-1 # new file: b-2 # new file: b-3 # |
Write your new commit message and save it. Your new tree should look like this:
Squashing commits in git after push
Squash the commits locally (for the last N commits) :
1 |
git rebase -i origin/<branch-name>~<number-of-commits-from-last> <branch-name> |
force push them with +
:
1 |
git push origin +master |
Refs: 1
Difference between --force
and +
:
Note that --force
applies to all the refs that are pushed, hence using it with push.default
set to matching
or with multiple push destinations configured with remote.*.push
may overwrite refs other than the current branch (including local refs that are strictly behind their remote counterparts). To force a push to only one branch, use a +
in front of the refspec to push (e.g git push origin +master
to force a push to the master
branch).
Refs: 1
Rebasing branches onto the base
When you create a feature branch and start working on that, your master might grow and also your branch. Let’s create a repository and add “a” and b:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
mkdir remote repos1 repos2 cd remote/ git init --bare cd ../repos1/ git clone ../remote/ . touch a git add . git commit -m "a added" touch b git add . git commit -m "b added" |
Then let’s create a feature branch for “c”
1 2 3 4 5 6 7 8 9 10 |
git checkout -b feature-c touch c-1 git add . git commit -m "c-1 added" touch c-2 git add . git commit -m "c-2 added" touch c-3 git add . git commit -m "c-3 added" |
Now let’s get back to master and “d” and “e”:
1 2 3 4 5 6 7 |
git checkout master touch d git add . git commit -m "d added" touch e git add . git commit -m "e added" |
At a certain point, you will decide to merge with the master branch, and your tree looks like this:
1 |
git merge feature-c -m "feature-c merged into master" |
By doing this you will the following tree, which in a big project might become very complicated and difficult to track.
As you can see, the branch feature-c is 2 commits behind of the master branch. You can rebase your feature branch onto the master branch, so you can make it as your feature branch has just branched from your master.
1 2 |
git checkout feature-c git rebase master |
1 2 |
git checkout master git merge feature-c |
By doing so, you finally end up with a “linear history”. You can also give tags to your commits so you make your tree more readable:
1 2 |
git tag <tage name> SHA1 ID git tag feature-c 392111d90a31a6bbbd0dc1c8ea15bd8dec7dd0d3 |
If you want git to do a rebase instead of a merge when pulling:
1 |
git pull --rebase |
or you can set it as the default action in your gitconfig file:
1 |
git config --global pull.rebase true |
Rebase with a merge conflict
Let’s create a repository:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
mkdir remote repos1 cd remote/ git init --bare cd ../repos1/ git clone ../remote/ . echo "from master branch " $RANDOM >>a { echo "from master branch " ; date ; } >> a echo "from master branch " $RANDOM >>a git add . git commit -m "a added" echo "from master branch " $RANDOM >>b { echo "from master branch " ; date ; } >> b git add . git commit -m "b added" #git push --set-upstream origin master |
And create a feature branch and modify some files and add some files:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
git checkout -b feature-c touch c-1 git add . git commit -m "c-1 added" touch c-2 git add . git commit -m "c-2 added" touch c-3 git add . git commit -m "c-3 added" echo "from feature branch " $RANDOM >>a sed -i "1imore changes from feature branch $(echo $RANDOM)" a sed -i "6imore changes from feature branch $(echo $RANDOM)" a date >> a git add . git commit -m "a modified in feature branch" #git push --set-upstream origin feature-c sed -i "2imore changes from feature branch $(echo $RANDOM)" a sed -i "3imore changes from feature branch $(echo $RANDOM)" a git add . git commit -m "a modified again in feature branch" #git push echo "from master branch " $RANDOM >>d git add . git commit -m "d added" #git push sed -i "4imore changes from feature branch $(echo $RANDOM)" a ls >> a git add . git commit -m "a modified one more time again in feature branch" #git push |
Now let’s switch back to master and update some files that have been updated in the feature branch:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
git checkout master sed -i "4ichanges from master branch $(echo $RANDOM)" a date >> a git add . git commit -m "a modified in main" sed -i "41changes from master branch $(echo $RANDOM)" b git add . git commit -m "b modified" sed -i "3ichanges from master branch $(echo $RANDOM)" a sed -i "6ichanges from master branch $(echo $RANDOM)" a git add . git commit -m "a modified again in main" touch e git add . git commit -m "e added" sed -i "3imore changes from master branch $(echo $RANDOM)" a git add . git commit -m "a modified one more time in main" #git push |
Now let’s switch back to the feature branch and rebase it on master:
1 2 |
git checkout feature-c git rebase master |
Since you have modified a and b you will get a merge conflict:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
First, rewinding head to replay your work on top of it... Applying: c-1 added Applying: c-2 added Applying: c-3 added Applying: a modified in feature branch Using index info to reconstruct a base tree... M a Falling back to patching base and 3-way merge... Auto-merging a CONFLICT (content): Merge conflict in a error: Failed to merge in the changes. Patch failed at 0004 a modified in feature branch hint: Use 'git am --show-current-patch' to see the failed patch Resolve all conflicts manually, mark them as resolved with "git add/rm ", then run "git rebase --continue". You can instead skip this commit: run "git rebase --skip". To abort and get back to the state before "git rebase", run "git rebase --abort". |
Now we have to resolve the merge conflict:
1 2 |
git checkout feature-c git rebase master |
Since you have modified a and b you will get a merge conflict:
1 |
git mergetool |
Now since you are rebasing on master, the changes from the feature branch are remote and master is local:
since we had a merge conflict on the file “a” git will create a file named “a.orig”, after resolving the conflict, we delete this file and continue by:
1 |
git rebase --continue |
Rebase remote branch onto master
What if we have had pushed our changes into remote? On that occasion, you should “Rebase remote branch onto master”
This will remove your local feature-c repository, update references, and change to feature-c branch, but because you deleted it, this command will also download the origin/feature-c
1 2 3 4 5 |
git branch -D feature-c git pull --all git checkout feature-c git rebase -p origin/master git push -f origin feature-c |
Refs: [1]