Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why git rebase doesn't show commits I want to squash?

Tags:

git

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

enter image description here

like image 650
Brosef Avatar asked Feb 26 '16 18:02

Brosef


People also ask

Why merge commit is not showing in rebase?

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.

Is squash same as rebase?

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.

Should I use Git squash or Git REBASE?

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.

What does Git REBASE -I development do?

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.

What is Git squash 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.

How do I REBASE a commit in Git?

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.


1 Answers

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
  1. 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."

  2. 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.

like image 146
torek Avatar answered Oct 03 '22 04:10

torek