Combining Multiple Commits Into One Prior To Push
GitCommitGit CommitGit Problem Overview
This question pertains not only to how to accomplish this task, but to whether doing so is good or bad practice with Git.
Consider that locally I do most work on the master branch, but I have created a topical branch I will call "topical_xFeature". In the process of working on "topical_xFeature" and switching back and forth to do other work on the master branch, it turns out that I have made more than one commit on the "topical_xFeature" branch, but between each commit, I have done no push.
First, would you consider this bad practice? Would it not be wiser to stick with one commit per branch per push? In what cases would it be good to have multiple commits on a branch before a push is made?
Second, how shall I best accomplish bringing the multiple commits on the topical_xFeature branch into the master branch for a push? Is it a nuisance to not worry about it and just do the push where multiple commits get pushed, or is it less annoying to somehow merge the commits into one and then push? Again, how to do this?
Git Solutions
Solution 1 - Git
For your first question, no, there's nothing wrong with pushing multiple commits at once. Many times, you may want to break your work down into a few small, logical commits, but only push them up once you feel like the whole series is ready. Or you might be making several commits locally while disconnected, and you push them all once you're connected again. There's no reason to limit yourself to one commit per push.
I generally find that it's a good idea to keep each commit a single, logical, coherent change, that includes everything it needs to work (so, it does not leave your code in a broken state). If you have a two commits, but they would cause the code to be broken if you only applied the first one, it might be a good idea to squash the second commit into the first. But if you have two commits where each one makes a reasonable change, pushing them as separate commits is fine.
If you do want to squash several commits together, you can use git rebase -i
. If you're on branch topical_xFeature
, you would run git rebase -i master
. This will open an editor window, with a bunch of commits listed prefixed by pick
. You can change all but the first to squash
, which will tell Git to keep all of those changes, but squash them into the first commit. After you've done that, check out master
and merge in your feature branch:
git checkout topical_xFeature
git rebase -i master
git checkout master
git merge topical_xFeature
Alternatively, if you just want to squash everything in topical_xFeature
into master
, you could just do the following:
git checkout master
git merge --squash topical_xFeature
git commit
Which one you choose is up to you. Generally, I wouldn't worry about having multiple smaller commits, but sometimes you don't want to bother with extra minor commits, so you just squash them into one.
Solution 2 - Git
This is the way I generally follow to combine multiple Commits into a single commit before I push the code.
To achieve this, I suggest you use 'squash' concept provided by GIT.
Follow the below steps.
1) git rebase -i master (instead of master you can also use a specific commit)
open the rebase interactive editor, where it will show all your commits. Basically where you need to identify the commits which you want to merge into a single commit.
Imagine these are your commits and shown something like this in the editor.
pick f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file
It's important to note that these commits are listed in the opposite order than you normally see them using the log command. Means, the older commit will be shown first.
-
Change 'pick' to 'squash' for last committed changes. something like shown below. Doing that so, your last 2 commits will be merged with the first one.
pick f7f3f6d changed my name a bit
squash 310154e updated README formatting and added blame
squash a5f4a0d added cat-file
You can also use short form if you have lot of commits to combine:
p f7f3f6d changed my name a bit
s 310154e updated README formatting and added blame
s a5f4a0d added cat-file
for editing use 'i', it will enable the editor for insertion. Keep in mind top most(oldest) commit cannot be squashed as there is no previous commit to combine with. So it has to be picked or 'p'. Use 'Esc' to exit insert mode.
- Now, save the editor with the following command. :wq
When you save that, you have a single commit that introduces the changes of all three previous commits.
Hope this will help you.
Solution 3 - Git
First: nothing tells you to only have one commit per branch per push: a push is a publication mechanism allowing you to publish a local history (i.e. a collection of commits) on a remote repo.
Second: a git merge --no-ff topical_xFeature
would record on master as a single commit your topic work, before pushing master
.
(That way, you keep topical_xFeature
around for further evolutions, that you can record on master
as a single new commit on the next merge --no-ff.
If getting rid of topical_xFeature
is the goal, then git merge --squash
is the right option, as detailed in Brian Campbell's answer.)
Solution 4 - Git
Switch to the master branch and make sure you are up to date.
git checkout master
git fetch
this may be necessary (depending on your git config) to receive updates on origin/master
git pull
Merge the feature branch into the master branch.
git merge feature_branch
Reset the master branch to origin's state.
git reset origin/master
Git now considers all changes as unstaged changes. We can add these changes as one commit. Adding . will also add untracked files.
git add --all
git commit
Ref: https://makandracards.com/makandra/527-squash-several-git-commits-into-a-single-commit
Solution 5 - Git
-
First choose which commit you want everything to come after.
git reflog 5976f2b HEAD@{0}: commit: Fix conflicts 80e85a1 HEAD@{1}: commit: Add feature b860ddb HEAD@{2}: commit: Add something
-
Reset to your selected head (I have chosen
HEAD@{2}
)git reset b860ddb --soft
-
git status
(just to be sure) -
Add your new commit
git commit -m "Add new commit"
Note: HEAD@{0}
& HEAD@{1}
Are now merged into 1 commit, this can be done for multiple commits also.
git reflog
again should display:
git reflog
5976f2b HEAD@{0}: commit: Add new commit
b860ddb HEAD@{1}: commit: Add something
Solution 6 - Git
A Tool To Automate Multiple Commits Into One
as Kondal Kolipaka says. Using "git rebase -i"
The logic of "git rebase"
When using "git rebase -i", git generates the git-rebase-todo file in the current .git/rebase-merge directory, and then invokes the git editor to let users edit the git-rebase-todo file for processing. So the tool needs to meet:
- Modify the git editor to the tool which we provided;
- The tool processes the git-rebase-todo file.
Modify the default git editor
git config core.editor #show current default git editor
git config --local --replace-all core.editor NEW_EDITOR # set the local branch using NEW_EDITOR as git editor
So, the tool need change the git editor and process the git-rebase-todo file. The tool using python below:
#!/usr/bin/env python3
#encoding: UTF-8
import os
import sys
def change_editor(current_file):
os.system("git config --local --replace-all core.editor " + current_file) # Set current_file as git editor
os.system("git rebase -i") # execute the "git rebase -i" and will invoke the python file later with git-rebase-todo file as argument
os.system("git config --local --replace-all core.editor vim") # after work reset the git editor to default
def rebase_commits(todo_file):
with open(todo_file, "r+") as f:
contents = f.read() # read git-rebase-todo's content
contents = contents.split("\n")
first_commit = True
f.truncate()
f.seek(0)
for content in contents:
if content.startswith("pick"):
if first_commit:
first_commit = False
else:
content = content.replace("pick", "squash") # replace the pick to squash except for the first pick
f.write(content + "\n")
def main(args):
if len(args) == 2:
rebase_commits(args[1]) # process the git-rebase-todo
else:
change_editor(os.path.abspath(args[0])) # set git editor
if __name__ == "__main__":
main(sys.argv)
Ref: https://liwugang.github.io/2019/12/30/git_commits_en.html