I'm getting ready to initiate a pull request on a branch I'm working on. On github, I see that I'm 5 commits ahead of master, so I'd like to squash my commits into one. I run a git log to see what the previous commits are:
git log --oneline
4363273 Updated Order_Entry with bulk UPDATE command
e7e0c64 Updated Order Entry module and Orders Schema
2cff23e Merge branch 'order_schema'
104b2ce Orders Schema
f7d57cf Order Entry updated to handle and log responses from LC
afa1b7b Merge pull request #18 from project/bugfix/mockvenue
4b2c8d8 Return correct string in mock venue API
Now I think I want to squash those top 5 commits listed above (4363273-f7d57cf). So I then run:
git rebase -i HEAD~5
4363273 Updated Order_Entry with bulk UPDATE command
pick 44768b2 Add script to run simulation on a strategy
pick f82ec8d Implement mock venue
pick f7d57cf Order Entry updated to handle and log responses from LC
pick 4b2c8d8 Return correct string in mock venue API
pick 104b2ce Orders Schema
pick 4363273 Updated Order_Entry with bulk UPDATE command
How come the list of commits shown after running git rebase doesn't match the first 5 commits when I run git log. Particularly, why do e7e0c64 and 2cff23e appear in git log but not git rebase?
* 4363273 Updated Order_Entry with bulk UPDATE command
* e7e0c64 Updated Order Entry module and Orders Schema
|\
| * 2cff23e Merge branch 'order_schema'
| |\
| | * 104b2ce Orders Schema
| * | afa1b7b Merge pull request #18 from project/bugfix/mockvenue
| |\ \
| | |/
| |/|
| | * 4b2c8d8 Return correct string in mock venue API
| |/
* | f7d57cf Order Entry updated to handle and log responses from LC
|/
* 8ed2260 Merge pull request #17 from project/mockvenue
FYI, rebase doesn't normally preserve merge commits, you need to pass the -p or --preserve-merges flag for that...but there's probably an easier way to do what you want to do.
So the differences are: squash does not touch your source branch ( tmp here) and creates a single commit where you want. rebase allows you to go on on the same source branch (still tmp ) with: a new base.
Although git rebases may help create a linear repo, it can lead to more conflicts when many developers keep appending commits to the head of the main branch. The best solution for either problem is to use git squash commits. The commits entering the main branch become comprehensive and manageable.
Which commits are missing, what is your current branch, and how does the current branch differ from branch development? git rebase -i development should attempt to rebase every commit in your current branch that is not already part of branch development. so I'm checked out to 'mybranch'. I did a couple git . and git commits.
Doing git squash commits organizes your commit history. The commands to use during interactive rebase or git merge are: to group the target branch's commits before merging the feature branch with the main one. On the flip side, you should not use git squash git commits if you plan to recover the mini-commits.
You can either do git rebase -i <commit-hash> OR git rebase -i HEAD~4. Git will now put you into an editor with the above text in it, and a little explanation of what can be done. You have plenty of options available to you from this screen, but right now we’re just going to squash everything into one commit.
Your graph shows numerous merges. This is the source of the issue. It's worth noting here that HEAD~5
means "five steps backwards following --first-parent
".
Let's cover a bit of background first. Generally speaking, you can't rebase a merge, and rebase usually doesn't try (it usually just discards them). Using git rebase -p
will try to preserve merges, and will often succeed, but it's very difficult to use interactively (because the edit script does not have a representation for merges).
We can see more once we understand how rebase works. Suppose that we have a commit graph like this:
B - C - D <-- other-branch
/
... - A
\
E - F - G <-- your-branch
Rebase takes a series of commits, and turns them into changesets / patches. That is, if you have commits E
, F
, and G
that follow commit A
, git must produce a diff from A
to E
, then from E
to F
, and finally from F
to G
. These represent "how to convert from the base commit" (which is currently A
) "to the tip, as a sequence of modifications."
Then, rebase turns to the new base commit, in this case commit D
, and applies these patches in sequence. The change from A
to E
, as applied to D
, makes a new commit E'
. The change from E
to F
, applied to E'
, makes a new commit F'
. The final change becomes G'
.
Changing a commit to a patch (by comparing it with its parent) and then applying the patch is literally a git cherry-pick
. In other words, rebase
is just a series of cherry-pick operations, picking commits E
, F
, and G
onto a new branch extending from D
. The new commit graph would look like this:
B - C - D <-- other-branch
/ \
... - A E' - F' - G' <-- your-branch
\
E - F - G [reflog only]
If we try to do this same thing with a merge commit, we run into a problem. A merge has two (or more) parents, and cannot be cherry-picked without human assistance: you must tell git cherry-pick
which parent is the one to diff against.
What git rebase -p
does is redo the merge, rather than attempt a cherry-pick. The rebase documentation has this example, in which you might rebase A
onto Q
:
X
\
A---M---B
/
---o---O---P---Q
They do not show the result but it should look like this (ideally, with the original A--M--B
sequence greyed-out):
X--------
\ \
A---M---B |
/ |
---o---O---P---Q |
\ |
A'--M'--B'
Note that new commit M'
has to be a merge between A'
and (unchanged) commit X
. This works if the merge is a normal (non-"evil") merge but is obviously a bit tricky, at the least.
Let's get back to your particular situation, where git log --graph
has given the text below (which I've modified just a bit). It's a bit hard to turn sideways (the other graphs above have predecessor commits on the left, and successors on the right, while the git log --graph
output has predecessors below and successors above), but I'll take a quick stab at it, by adding single-letter codes for each commit:
H * 4363273 Updated Order_Entry with bulk UPDATE command
G * e7e0c64 Updated Order Entry module and Orders Schema
|\
F | * 2cff23e Merge branch 'order_schema'
| |\
E | | * 104b2ce Orders Schema
D | * | afa1b7b Merge pull request #18 from project/bugfix/mockvenue
| |\ \
| | |/
| |/|
C | | * 4b2c8d8 Return correct string in mock venue API
| |/
B * | f7d57cf Order Entry updated to handle and log responses from LC
|/
A * 8ed2260 Merge pull request #17 from project/mockvenue
Now with A
as the leftmost commit and H
as the rightmost:
C---D
/___/ \
//__--E-F
/// \
A-----B-----G--H
The direct (first-parent) line of ancestry goes from H
to G
, then to B
, then to A
. This means that HEAD~5
is a commit we can't even see (two to the left of A
), and git rebase -i HEAD~5
should list all of these commits except for merges (D
, F
and G
). That would be five commits: A
, B
, C
, E
, and H
. But based on its log message, A
is also a merge. We're missing information here and can't draw the complete graph (which is just as well since the compact form has a lot of lines in it).
In any case, this is in fact what's happening. The rebase
command finds commits to cherry-pick by listing every commit reachable from the tip commit (HEAD
, which is commit H
) that is not reachable from the first excluded commit (HEAD~5
, a commit two steps to the left of A
, so we can't see it). It then throws out merge commits and will cherry-pick each remaining commit to build a new, linear branch.
Whether this makes sense, and which commits you should cherry-pick, are not something anyone else can answer for you.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With