3
 /-----------------------O
R---A------------M---D
 \------B---C---/

As far as I understand I can run git rebase either with --preserve-merges or without.

In the tree shown above, rebasing A..D onto O with the option, I believe will result in:

R---O---A------------M---D
     \------B---C---/

Doing the same without the option will give:

R---O---A---B---C---D

Is there a way to achieve

R---O---A---S---D

with S being a squash of B and C?

When I do an interactive rebase I think the commits are sorted A B C D, so the information that B and C are on a branch is not shown. How can I tell Git to automatically (mark to) squash exactly those commits that were on a branch that was merged?

AndreKR
  • 32,613
  • 18
  • 106
  • 168
  • possible duplicate of [Combine the first two commits of a Git repository?](http://stackoverflow.com/questions/435646/combine-the-first-two-commits-of-a-git-repository) – Greg Burghardt Sep 15 '15 at 15:37
  • My reading is that it's not a dupe: a non-interactive solution appears to be wished here. @AndreKR could you confirm? – JB. Sep 15 '15 at 15:45
  • Exactly. That's what I wanted to say with the last paragraph. I do not know which commits should be squashed, I want this information **from Git**. The solution can be partially interactive, but Git needs to give me the list of commits to squash. I'll rephrase the question. – AndreKR Sep 16 '15 at 01:58

1 Answers1

-1

Edit: it turns out the question was not "how do I do this particular operation" but, apparently, a completely different question. Here's my rephrase of the different question.

Given one starting point commit identifier R, one tip-of-branch commit identifier T, and one onto-commit identifier O, how would one come up with a commit sequence that identifies some commits that can be squashed?

Unfortunately, I can't tell from this revised question (which I'm not sure is properly revised in the first place) what qualifies some commits as squash-able and others as non-squashable. If we allow arbitrary amounts of graph to occur between R and T, we might have:

R -- A - B ------ C - D -- E - T
          \     /         /
           F - G -- H -- I

for instance, and it's not clear to me whether A-B should be squashed, or C-D-E, or D-E; or H-I, and so on. It seems likely that commits that are themselves merges and/or serve as branch-points should remain, but they themselves could be squashed-into.

Edit some more: I see the question has also been updated: the commits to squash are those that were themselves on a branch—but given my example above, all of F-G-H-I were probably "on a branch" that was merged back in more than once, and perhaps not all of those should be squashed. So I'm still at a loss for choosing "squash candidates". If we constrain the problem further, they might be "any commit that's on the R..T chain that is not on the --first-parent segment of the R..T chain, perhaps.

[original answer follows]


Here's how I would do this semi-manually (note that if git rebase -i could do it, this is pretty much how it would do it: interactive rebase is basically a series of cherry-picks, with some special casing). Below, O stands for the SHA-1 of commit O, or a branch or tag name that resolves to it.

$ git checkout -b rewrite O     # get a new "rewrite" branch
$ git cherry-pick A             # copy commit A
$ git cherry-pick --no-commit B # get B, no commit yet
$ git cherry-pick --no-commit C # add C: this is your S
$ git diff                      # check on it, do any fixing up needed
$ git commit                    # commit S, write new commit msg
                                # (might grab messages from B&C)
$ git cherry-pick D             # add commit D

You can omit the --no-commit on the pick of C if its commit message is what you want and you're sure there's nothing to fix up. Or, use git cherry-pick --edit to start with C's commit message and edit it (or, as usual, git commit --amend or whatever).

This does depend on the existing merge M being trivial or mostly-trivial. If it's an "evil merge" with a lot of changes hidden in it, one or two more git cherry-picks of M, with -m 1 and/or -m 2, might be required before picking D.

The "rewrite" branch is then your desired result. If you start it with a better name than "rewrite" you won't have to rename it either. :-)

torek
  • 448,244
  • 59
  • 642
  • 775
  • I don't understand. Instead of this I could do a simple interactive rebase, as I described in my question. You suggest that I manually enter `B` and `C`. The whole point of the question is how to get the information about **which** commits should be squashed **from Git**. – AndreKR Sep 16 '15 at 01:57
  • Ah, I'm afraid the idea behind your question was not at all clear. – torek Sep 16 '15 at 02:00
  • If my guess (you're looking for the IDs of commits in `R..T`, in `git rev-list` output, that are not in `git rev-list --first-parent R..T`) is correct, you can use git rev-list twice, I think, to generate the IDs. (Hm, or maybe you need something fancier like using `comm`: I don't see a way to apply `--not` to the stdin list.) – torek Sep 16 '15 at 02:20
  • You have a point in that I didn't think about multiple merges. However, the way I wrote my question (every merge commit should become a squashed commit), it can only be `R - A - B - (F+G) - C - D - (H+I) - T`. – AndreKR Sep 16 '15 at 02:27
  • OK, in that case I think you want: `git rev-list R..T` (to get the master list), then some set of operations to separate the "non first parent" commits from the "first parent" commits, find the merges and non-merges (rev-list has both `--merges` and `--no-merges` or you can simply inspect the number of parents associated with each commit), and generate your final sequencing based on where the merges are. Probably that's easier to do in a script than directly with `git rev-list` though. Once you have the list, it's just a matter of cherry-picking with `--no-commit` as needed, mod conflicts. – torek Sep 16 '15 at 02:42
  • A few last notes occur to me to add here: you'll definitely want `--topo-order` when you generate the initial list of all commits in `R..T`, e.g., `git rev-list --topo-order --parents R..T` would give you a good master list along with parent information for merge-detecting. Then `git rev-list --first-parent R..T` gets you commits on the "main line" that you should *not* squash (in some unspecified order, add `--topo-order` if desired). And, git definitely does not know how to do this kind of thing "out of the box", so you'll have to write something, but `git rev-list` is the key here. – torek Sep 16 '15 at 02:54