Work is done within a feature branch created from master. You've incrementally gotten the feature branch working as expected; however, you'd prefer to re-organize your commits so that you can better reason about the changes.
% mkdir repo
% cd repo
% git init
% touch {a,b,c}.txt
% git add --all
% git commit -m 'added a, b, and c'
% git checkout -b feature/a-b-c
#=> Switched to a new branch 'feature/a-b-c'
# apathetic, yet deceptively entertaining commit message for e.txt.
% touch e.txt
% git add e.txt
% git commit -m 'added some file -- I guess it was e.txt; whatever!'
% touch d.txt
% git add d.txt
% git commit -m 'added d.txt'
# oh nooooh; we've done this out of order...oh well, let's continue but fix it later.
% touch f.txt
% git add f.txt
% git commit -m 'added f.txt'
% touch n.txt
% git add n.txt
% git commit -m 'added n.txt -- looks like we have gotten ahead of ourselves'
- The commits are out of order (though, this normally doesn't matter as long as the code works as expected)
n.txt
shouldn't be here; however, we should preserve it as it can be used with another feature branch.- Also, we've incorrectly named our branch
feature/a-b-c
; it should befeature/d-e-f
% git show --format=oneline --abbrev-commit
% git checkout -b temp-stash master
% git cherry-pick --no-commit 6773a35
% git status
% git stash save 'added n.txt'
% git stash list
#=> stash@{0}: On temp-stash: added n.txt
% git checkout feature/a-b-c
#=> Switched to branch 'feature/a-b-c'
% git branch -D temp-stash
- undo commit
6773a35
(we won't lose anything since the content is stashed). - rename
feature/a-b-c
tofeature/d-e-f
. - squash all of the commits into a single commit allowing a neat merge.
-
git revert
: while this would work, on it's own, it further extends the many commit problem by adding yet another commit. -
git rebase --interactive
: this would work just fine in most cases; however, the syntax is a bit daunting if you haven't spent a lot of time using it. Also, for some use-cases, there are other valid methods. That being said, for completness, the correctgit rebase --interactive
command sequence would be:% git rebase --interactive HEAD~5 #=> since there are 5 commits that we want to squash together: #=> the initial commits of {d,e,f,n}.txt (4) and the revert of n.txt (1) #=> 4 + 1 = 5 (that was hard)
NOTE: if you were following along with the
git rebase --interactive
, do not go forward with it, abort!
% git revert 6773a35
% ls -lah
#=> look mah, no n.txt
% git checkout -b feature/d-e-f master
#=> correcting our branch name
% git branch
% git merge --squash feature/a-b-c
% git status
% git commit -m 'added d, e, and f'
% git log
% git branch -D feature/a-b-c
% git branch
If you have an existing pull request out on the original topic branch, after squashing, you might think you need to open a new pull request. That is not the case. You can simply force push (use --force
or +HEAD
) over the old un-squashed branch and your pull request comparison view will reflect a single commit while the files updated count will be as it was pre-squash.
In other words, you should not have to open a new pull request losing valuable code review comments.
-
Try not to do that if you can :)- it is an open invitation for pain and makes
git rebase --interactive
even less attractive. That's a shame because sometimes, it's the right tool. -
If you must do the "shared" branch dance, make sure to use
git merge --squash
orgit rebase --interactive
ONLY once the feature is complete, just before issuing a pull request. Those sharing the branch should be well-informed that they MUST stop integrating into the branch immediately. Renaming the branch can certainly help in this regard.