-1

I followed the post on how to do squash commits from here so I did :

git reset --soft HEAD~20
git commit
git push -f

I have a lot of commits and between them, I have few merges I did from the develop branch to my feature branch to update my branch with changes for example :

commit (merge): Merge remote-tracking branch 'origin/develop' into feature/my_coo_branch

after doing the git reset --soft HEAD~20 command I found out that all the merges disappeared from my branch.
why? and how can I avoid it?

knittl
  • 246,190
  • 53
  • 318
  • 364
user63898
  • 29,839
  • 85
  • 272
  • 514
  • 4
    This kind of `git reset` says to strip commits from the tip of a branch. Merge commits are commits. You said to strip them; Git did. – torek Feb 10 '21 at 14:33
  • sorry didn't understand what you said – user63898 Feb 10 '21 at 15:25
  • 1
    I'm afraid I don't know any simpler way to say it. Commits are real things, like beads on a string. If you have a string of ten beads, and take off the last four, you'll have six left. The commits are ordered, like the beads on a string: you can't take one out of the middle, you can only take some number off the end. – torek Feb 10 '21 at 15:30
  • in this command i dont want to omit commit or delete or remove in any form i just want to squash the commits to 1 commit . the problem is that the merges just disappeared this is what i try to understand why ...? – user63898 Feb 10 '21 at 15:35
  • 3
    The notion of "squashing" commits is an illusion. To achieve it, you *take away* the old commits (say, 2, or 4, or 50 old commits) and *make one new commit*. Since 1 is less than 2, or 4, or 50, and since Git shows you commits one at a time, it now "looks as though" you turned the many commits into a single one. But what you really did was *take away* the old commits, then *make one new commit*. – torek Feb 10 '21 at 15:38
  • ok i know thsi , but still, i don't understand what happened to the merges? the commits i did that are not merges was " squashed" just fine – user63898 Feb 10 '21 at 15:47
  • 2
    The merges are commits too. If you made commit C1, then merge M1, then commit C2, you can remove C2 off the end and you're left with C1-M1 at the end. To remove C1 as well, you must next remove M1, then you can remove C1. That's where the merges went: in the trash bin, along with the other commits you removed. – torek Feb 10 '21 at 17:46
  • i didnt remove them they merged into 1 commit this is what i try to tell you all the code is present beside the merges – user63898 Feb 11 '21 at 09:40
  • 1
    Yes, the *code* is there; that's what the `--soft` does. No, you *did* remove the merges; that's what the `reset` does. Use `git log --graph`, optionally with `--oneline`, to view the *commit graph* before and after the reset. This is what I've been trying to tell you: *merge commits* are about recording *how* we got the code. Commits—of any kind—are about recording the code. A regular (non-merge) commit records one link backwards in history, while a merge commit records two, but both record the code. – torek Feb 11 '21 at 14:20
  • Thanks! and now the question is how do i make squash commits while i have merge commits in them ? – user63898 Feb 11 '21 at 14:52
  • 1
    So, that's the thing about squashing: it destroys history (by removing commits, which *are* the history). You have your choice: keep the history, or toss it. That's all there is, that one choice. In general, a good way to deal with this is to not squash anything with merge commits in it, and not to put merge commits into anything you intend to squash. – torek Feb 11 '21 at 15:03

1 Answers1

0

When you run git reset --soft HEAD~20, the last 20 commits are removed. If you had some merge commits in those 20 commits, they are removed too. That's what you told Git to do and Git did as instructed.

I'm not sure what you mean exactly by "keeping the merge commits" when you state that you want to squash the last 20 commits (i.e. remove many commits and replace them with a single new commit).

A merge commit is just like any other commit, with the only difference being that it has two (or more) parent: lines pointing to its ancestor commits. A regular commit only has a single parent: line. And just like any other commit references a tree object, a merge commit too references a tree object. The tree captures a full snapshot of your code at any given time.

With that information, there is one way to interpret your question: "How to pretend that a squashed commit actually merges history?" or in other words: "Can a squashed commit have multiple parents?".

The answer is yes, with a little trickery and usage of low-level plumbing commands.

A commit in Git always points to a single tree (tree: line) and references 0 or more parent commits (parent: line(s)). A merge commit references 2 or more parent commits. You now want to create a commit with the tree of your current HEAD and many parents, each parent pointing to the tip of your merged branches (this is called an octopus merge).

To do that, git commit-tree can be used:

git commit-tree HEAD^{tree} \
  -p HEAD~20 \
  -p tip-of-merged-branch-1 \
  -p tip-of-merged-branch-2 \
  -p … \
  <<MSG
Your commit message comes here

With commit body, etc.
MSG

You need to manually find the commit hashes of the merged branch tips. Some clever scripting could help you. Get a list of commits, grep for commit message "Merge", get the second parent (or everything but the first parent in case of octopus merges). Be aware that this creates an evil merge.

Note that this is a low-level command and all warranty is void. Know what you are doing.

knittl
  • 246,190
  • 53
  • 318
  • 364