May 9th, 2019

Mundane git commit-tree tricks, Part 4: Changing a squash to a merge

Suppose you performed a git merge --squash, and then later realized that you meant it to be a true merge rather than a squash merge.

In diagrams, suppose you have this:

 
A M1 ← ← ← M2   master
  ↖︎      
    F1 F2       feature

From a starting commit A, the master branch continues with a commit M1. From that same starting commit A, a feature branch adds commits F1 and F2. Now you decide to take the feature branch into the master branch, and you resolve the merge as a squash. This means that the resulting commit M2 technically has only a single parent M1. The other alleged parent F2 is just a dotted line, indicating that it is just a figment of your imagination.

You then realize that you should have accepted the feature branch as a true merge rather than a squash. What can you do?

Naturally, you could hard reset the master branch back to M1 and redo the merge. But that means you have to redo all the merge conflicts, and that may have been quite an ordeal. And if that was a large merge, then even in the absence of conflicts, you still have a lot of files being churned in your working directory.

Much faster is simply to create the commit you want and reset to it.

git commit-tree HEAD^{tree} -p HEAD~ -p F2 -m comment

Note: If using the Windows CMD command prompt, you need to type

git commit-tree HEAD^^{tree} -p HEAD~ -p F2 -m comment

for reasons discussed earlier.

This creates a tree identical to what is currently checked in (which is the merge result), whose parents are M1 and F2. In other words, it colors in the dotted line.

A M1 ← ← ← *   master
  ↖︎       ↙︎
    F1 F2       feature

The result of the git commit-tree command is a hash printed to stdout. You can git reset to that hash to make that the branch head.

Topics
Other

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

5 comments

Discussion is closed. Login to edit/delete existing comments.

  • Alex Thiessen

    I’d do like (untested):
    checkout master
    reset –hard master^
    merge feature
    checkout master@{1} . # resolves all conflicts
    add . # mark resolved
    merge –continue

    This probably leads to more build activity and SSD wear than your version, yet many devs don’t care.

  • Simon Clarkstone

    This week has made me more aware of ways I could be using Git that I'm not. I learnt early on in my usage of Git that once you start thinking about a Git repo as a directed graph it becomes obvious how you need to re-arrange the graph and you only need to learn which commands exist to do each step (like the from this week).Tangent: It took me a while, but I finally...

    Read more
    • Raymond ChenMicrosoft employee Author

      I use these tricks in the UWP samples repo far more than I do in the Windows OS repo. Churning the working directory forces everything to rebuild, which makes it very disruptive to work in progress.

      • Simon Clarkstone

        Ah yes, I forgot you mentioned in posts 1 and 2 that this was for the UWP samples repo.I just looked at the number of projects in there and I didn’t realise it was quite that huge, so I see why you wouldn’t want all that lot to rebuild.

  • Mantas M.

    This situation came up in #git yesterday, and I would have used a different approach:
    git replace –graft HEAD HEAD^ F2 && git commit –amend -C HEAD
    (The second command was originally “git filter-branch HEAD^!”, which is less ugly, but if you want the commit to be signed, you’d have to follow that up with –amend anyway…)