Practical uses of git reset –soft?

git reset is all about moving HEAD, and generally the branch ref.
Question: what about the working tree and index?
When employed with --soft, moves HEAD, most often updating the branch ref, and only the HEAD.
This differ from commit --amend as:

  • it doesn’t create a new commit.
  • it can actually move HEAD to any commit (as commit --amend is only about not moving HEAD, while allowing to redo the current commit)

Just found this example of combining:

  • a classic merge
  • a subtree merge

all into one (octopus, since there is more than two branches merged) commit merge.

Tomas “wereHamster” Carnecky explains in his “Subtree Octopus merge” article:

  • The subtree merge strategy can be used if you want to merge one project into a subdirectory of another project, and the subsequently keep the subproject up to date. It is an alternative to git submodules.
  • The octopus merge strategy can be used to merge three or more branches. The normal strategy can merge only two branches and if you try to merge more than that, git automatically falls back to the octopus strategy.

The problem is that you can choose only one strategy. But I wanted to combine the two in order to get a clean history in which the whole repository is atomically updated to a new version.

I have a superproject, let’s call it projectA, and a subproject, projectB, that I merged into a subdirectory of projectA.

(that’s the subtree merge part)

I’m also maintaining a few local commits.
ProjectA is regularly updated, projectB has a new version every couple days or weeks and usually depends on a particular version of projectA.

When I decide to update both projects, I don’t simply pull from projectA and projectB as that would create two commits for what should be an atomic update of the whole project.
Instead, I create a single merge commit which combines projectA, projectB and my local commits.
The tricky part here is that this is an octopus merge (three heads), but projectB needs to be merged with the subtree strategy. So this is what I do:

# Merge projectA with the default strategy:
git merge projectA/master

# Merge projectB with the subtree strategy:
git merge -s subtree projectB/master

Here the author used a reset --hard, and then read-tree to restore what the first two merges had done to the working tree and index, but that is where reset --soft can help:
How to I redo those two merges, which have worked, i.e. my working tree and index are fine, but without having to record those two commits?

# Move the HEAD, and just the HEAD, two commits back!
git reset --soft HEAD@{2}

Now, we can resume Tomas’s solution:

# Pretend that we just did an octopus merge with three heads:
echo $(git rev-parse projectA/master) > .git/MERGE_HEAD
echo $(git rev-parse projectB/master) >> .git/MERGE_HEAD

# And finally do the commit:
git commit

So, each time:

  • you are satisfied with what you end up with (in term of working tree and index)
  • you are not satisfied with all the commits that took you to get there:

git reset --soft is the answer.

Leave a Comment