2

I have an existing repository that has root R, then a few dozens of commits including multiple merges, up to X, and then linear history up to Y. I'd like to squash everything from R to X into a single commit and force push it. How can I do it without a lot of effort involving re-resolving merges?

Alternatively, this problem could be phrased as changing the root commit from R to X and cutting off the graph before X.

Here is an illustration simplifying the commit graph:

R ---- I want to squash from here... | A |\ B C | | D E | |\ F G H | |/ I J |\ \ K L M |/ | N / |/ O | X ---- to here. | P | Q | Y 

Squashing everything with regular rebase would require re-resolving multiple merge commits. I am aware of git rerere, but I do not know how to use it in this situtation. It was not enabled when commiting all that.

3 Answers 3

2

This can be done with rebasing but it's much easier to do by hand

git checkout --orphan temp X # now you are on a brand new branch with no history, and your working tree is just like X git commit -m "Single shot" # now let's carry over X up to Y git cherry-pick X..Y 

If you like the result, set your branch over here and live happily ever after.

Update: If the history after D is complex, it might be better to run a rebase for the last step:

git rebase --rebase-merges X Y --onto temp 

And if that history is complex and it includes merges with conflicts, I can offer this script to take care of that last step to avoid having to redo the merges with conflicts:

https://github.com/eantoranz/git-replay

Sign up to request clarification or add additional context in comments.

11 Comments

Why not just a reset --soft X and recommit? I can't believe I'm asking that to "Mr Reset Soft" himself :-D (and it's a genuine question, I wonder if there's a difference I'm missing)
@RomainValeri Woudn't that squash the wrong half of the commits? Notice that the presented graph is "upside down" (compared to what git log --graph would show).
If I didn't do it, it's for a reason @RomainValeri :-) The chart is upside down. The root is R and the tip is Y. How is it going?
@eftshift0 That's what I was missing. D'oh. The direction of the chart. Sorry for the noise.... ^^
Still does not work because --orphan requires a value - it should probably go right after checkout. EDIT: git checkout --orphan X temp works!
|
1

Preface: I am not a fan of squashing and would generally advice against it. There can be situations where you want to do this for good reasons (like undoing awful parallel mirror branch merges that some graphical tools/frontends (like Visual Studio) produces), but please make really sure you are not squashing commits as a deodorant to cover up for poor version control hygiene instead of using and filtering out temporary commits properly.


That said, here is how to do what's asked, completely conflict free:

git status # Make sure you start with no untracked files. git branch new_branch1 commit_R git branch new_branch2 commit_Y git switch new_branch1 git diff HEAD commit_X | git apply - # This makes the worktree identical to X, in # effect "fast-forward merge" of R..X git add . git commit -m "Some message describing the R..X changes" git rebase --onto new_branch1 commit_X new_branch2 git branch --delete new_branch1 # Now you're done, new_branch2 is now R'--X'--P'--Q'--Y' # If you want to (forcibly) change the original branch that pointed to Y do the # following: git switch the_original_branch git reset --hard new_branch2 git branch --delete new_branch2 

Since new_branch1 and commit_X have the exact same content (the very goal of the diff...apply commands) when running the rebase command, there will be zero conflicts because applying P..Y on top of either is exactly the same.

3 Comments

Thank you for the help and new insights into the problem! I understand the general idea behind these commands, particularly the diff | apply trick, but this solution does not work because the repo has some non-text assets: error: cannot apply binary patch to '(...).png' without full index line and error: (...).png: patch does not apply. Maybe some more tweaking is needed to make it work. The other answer circumvents that by creating an orphaned branch, though I don't know if your answer with it in the place of the diff | apply trick would work.
Ah, binary files. Yes, those will not be created with apply. To have such files included, cherry pick the commit(s) that introduces those files (or the last commit to modify the files) right before doing diff | apply. Maybe the cherry-picks will include text files as well, but that does not significant (if this creates conflicts you can choose the easy way out and just delete them). Then proceed with diff | apply as described. You will then end up with intermediate "add image" commits R'--I1--I2--I3--X'--P'--Q'--Y' which you then can reduce to R'--X''--P'... with interactive rebase.
@Xilexio Actually git apply - can handle binary files if you feed it with git diff --binary ....
1

The simplest way is to replace the parent pointer and using git-filter-repo(1) [1] to propagate the changes upwards and make them permanent.

If you want F to point to C:

git replace F 

Then paste in the hash of C.

parent C 

Propagate and make the changes permanent:

git filter-repo 

This will error out and require --force. Read the warning and perhaps proceed.

Why not git replace [-f] --graft <commit> [<parent>...]?

Grafts seem to be slated for eventual deprecation.

Notes

  1. Third-party tool

1 Comment

I think it's the grafts file in .git/info they're going to abandon, not the --graft option on the more general command that replaced it.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.