-1

Given this git branch,

-- c1 ---- c2 ------ HEAD

I want to combine c1 and c2 and everything in between into one commit.

Requirements:

  • I know c1 and c2 hashes, but not their relative positions to HEAD.
  • I want to do this programmatically (call it in a script), so I cannot use interactive mode.

I know this question has been asked several times, but I cannot find any existing answer that satisfies those requirements.

Felix Fourcolor
  • 320
  • 2
  • 8
  • can you explain in more details your constraint "I want to do this programmatically" ? – LeGEC Jun 06 '23 at 06:52
  • e.g: is there some strong constraint to have this action run in an unsupervised context (a CI runner ...) ? is there some reason why you would have to apply the same rewriting action several times ? – LeGEC Jun 06 '23 at 06:53

2 Answers2

2

This would do it (call the branch you are on originalBranch):

git switch --det c2
git switch -c temp
git reset --soft c1
git commit -msquash  
git rebase --onto temp c2 originalBranch
git branch -D temp

The idea is this:

  • We get ourselves back at c2 and plant a branch name temp there, so that we can find it again.
  • We then squash from c2 all the way back to c1 inclusive — that's the soft reset followed by a commit; this is a type 1 regret. The name temp now points to this new single squash commit.
  • Finally, we return to whatever was the original HEAD (which I have named originalBranch) and move all the commits starting at (but not including) the old c2 onto the new single squash commit, which (as I have already said) is pointed to by the name temp.
  • Our work is now done, but we no longer need the branch name temp for anything, so we delete it.

So we have now gone from this:

A -- B -- C1 -- ? -- ? -- C2 -- D -- E -- F <- originalBranch

To this:

A -- B -- C1 -- squash -- D' -- E' -- F' <- originalBranch
            \
             -- ? -- ? -- C2 -- D -- E -- F

The old commits, as you can see from my diagram, still exist, but they will all be mopped up during the next garbage collection because no name points to them any longer.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • 1
    Please don't provide magic scripts that leave beginners scratching their heads and without clue what is going on. Could you please explain the idea behind the script? – j6t Jun 02 '23 at 06:26
  • Why do I get merge conflict error? – Felix Fourcolor Jun 02 '23 at 06:48
  • @FelixFourcolor : matt's script should work *if there is no merge commit between `c2` and `HEAD`*. Do you have merge commits ? check `git log --oneline --graph HEAD`, or `git log --merges --oneline c2..HEAD` (<- will only display merge commits, if no output then no merge commits) – LeGEC Jun 02 '23 at 07:06
  • @LeGEC There are. What do I do then? – Felix Fourcolor Jun 02 '23 at 09:05
  • Resolve the conflicts and resume the rebase. But unfortunately you can't do that in a script. You could try adding `--rebase-merges` to the rebase command... – matt Jun 02 '23 at 11:45
  • "I know `c1` and `c2` hashes, but not their relative positions to `HEAD`." If `c2` precedes `c1`, then this script will do some time travel. – Raymond Chen Jun 02 '23 at 13:25
  • This answer doesn't work for me because of merge conflicts, and I trust @LeGEC that it's impossible to resolve them in a script. But since this is the best we have, I'll accept this answer to close the question. – Felix Fourcolor Jun 11 '23 at 03:57
0

I don't think there is any tool that does this out of the box, but it can be scripted.

Pseudo code:

  • checkout c1^
  • do a squash-merge of c2 and commit the results
  • cherry pick c2..HEAD onto the results

Potential problem:

  • if there were any merge commits between c1^ and HEAD, cherry picking c2..HEAD will create a mess.
joanis
  • 10,635
  • 14
  • 30
  • 40