If you use git to secure the outcome of your work frequently, the code responsible for one functionality may be scattered across many commits within a feature branch. You can still commit a unit of work as a whole even if you pushed it to the origin in several stages.
Requirements
- The familiarity with Gitflow Workflow – we are working on a local
feature_branch
, all work is done only within this one branch and there are no commits or partial merges on the develop.
Squash commits during a pull request
When using a tool like BitBucket, commits will be automatically squashed during a pull request with a message that lists all integrated commits.
It may be just enough if you didn’t have to push the work half done at the end of your work day and complete it in another commit. We want to be able to keep the app working properly even if there is a need to revert some commits. We should never encounter the moment in the git history when the app is broken.
Do not rewrite the git history if there are other programmers working on the branches affected. |
Squash latest commits
If you want to compress 3 latest commits on your local feature_branch
, reset the HEAD
:
1 |
$ git reset --soft HEAD~3 |
Now all the changes from those 3 commits awaits to be aggregated into one:
1 |
$ git commit -m "The united message for all three commits." |
To fix the inconsistency between local
and origin
push the repo with the --force-with-lease
option to be sure that you will not interfere with somebody else’s work:
1 |
$ git push --force-with-lease origin feature_branch |
The feature_branch
has squashed commits and the git history is the same in both local and remote repositories. You can continue your work on the branch and merge it with develop when you are done.
Squash only selected commits
You are on the local feature_branch
, it’s late Friday afternoon and there is no way you will finish the task. You want to save what you did so far, even though the app crashes, so you commit and push on the remote:
1 2 3 |
$ git add -A $ git commit -m "Work - part 1." $ git push origin feature_branch |
On Monday you add the missing code and the app is again working properly, so you make another commit:
1 |
$ git commit -m "Work - part 2." |
And then you add something more to the branch and commit it as well:
1 |
$ git commit -m "Distinct changes that you want to keep in a separate commit." |
The history of the branch looks like this:
1 2 3 4 5 |
$ git log commit Distinct changes that you want to keep in a separate commit. commit Work - part 2. commit Work - part 1. commit Initial commit. |
If necessary, you need to be able to revert commits safely – it happens only when every one of them keeps the app in a working condition. Therefore you need to squash Work - part 1
and Work - part 2
into one commit and keep the rest as it is.
We want to keep all commits before Work-part 1
intact, so we will rewrite the history only within the three latest commits:
1 |
$ git rebase -i HEAD~3 |
The rebase editor displays specified commits ordered from the oldest to the newest:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
pick d5f8d35 Work - part 1. pick 78488a2 Work - part 2. pick dfaae62 Distinct changes that you want to keep in a separate commit. # Rebase f3f29ed..dfaae62 onto f3f29ed (3 commands) # # Commands: # p, pick = use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message # x, exec = run command (the rest of the line) using shell # d, drop = remove commit # # These lines can be re-ordered; they are executed from top to bottom. # # If you remove a line here THAT COMMIT WILL BE LOST. |
Rebase will squash a newer commit with the older one. Just write squash
instead of pick
in the lines of commits you want to integrate with the preceding one. In our example we want to keep together Work - part 1
and Work - part 2
so we will put squash only in the line with Work - part 2
:
1 2 3 |
pick d5f8d35 Work - part 1. squash 78488a2 Work - part 2. pick dfaae62 Distinct changes that you want to keep in a separate commit. |
Save changes and exit the rebase editor. After that you will be able to write a new commit message for both squashed commits in a commit editor:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# This is a combination of 2 commits. # This is the 1st commit message: Work - part 1. # This is the commit message #2: Work - part 2. # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # # Date: Thu Aug 23 14:33:41 2018 +0200 # # interactive rebase in progress; onto f3f29ed … |
Specify the new message and comment the one that is no longer needed:
1 2 3 4 5 6 7 8 9 |
# This is a combination of 2 commits. # This is the 1st commit message: Work - part 1 and 2 combined. # This is the commit message #2: #Work - part 2. … |
Save the changes and exit. Now the git history looks like this:
1 2 3 4 |
$ git log commit Distinct changes that you want to keep in a separate commit. commit Work - part 1 and 2 combined. commit Initial commit. |
If you want to keep the original commit message “Work – part 2.” use fixup
instead of squash
:
1 2 3 |
pick d5f8d35 Work - part 1. fixup 78488a2 Work - part 2. pick dfaae62 Distinct changes that you want to keep in a separate commit. |
and the git history will look like this:
1 2 3 4 |
$ git log commit Distinct changes that you want to keep in a separate commit. commit Work - part 2. commit Initial commit. |
Too keep the local feature_branch
compatible with the origin, push the branch with the --force-with-lease
option to be sure that you will not interfere with somebody else’s work:
1 |
$ git push --force-with-lease origin feature_branch |
You can find useful info in this git ready post.
Forgot to add something to the latest commit?
Instead of committing the forgotten changes and squashing the commits, you can change the latest commit with --amend
. Just add the remaining files with:
- $ git add -A (all remaining files)
- or $ git add file_name (a specified file)
and run:
1 |
$ git commit --amend --no-edit |
The latest commit contains the previously added files and the new ones like they were committed together in one action. The --no-edit
option leaves the commit message unmodified.
You should always have a very good reason to rewrite the git history and be sure that nobody from the team will be affected by conflicts when you decide to do this. |
Merge feature branch to the develop in one commit
If you are going to merge the feature_branch
to the develop
branch, you can make it in one commit – use the --squash
option:
1 2 3 |
$ git checkout develop $ git merge --squash feature_branch $ git commit -m "The united message for all commits from the feature_branch." |
The feature_branch
still has multiple commits, but, if you delete branches after they are merged – there is no need to fix the inconsistency.
Photo by Jason Blackeye on StockSnap