I have been working with git for just over a month. Indeed I have used reset for the first time only yesterday, but the soft reset still doesn\'t make much sense to me.
You can use git reset --soft
to change the version you want to have as parent for the changes you have in your index and working tree. The cases where this is useful are rare. Sometimes you might decide that the changes you have in your working tree should belong onto a different branch. Or you can use this as a simple way to collapse several commits into one (similar to squash/fold).
See this answer by VonC for a practical example: Squash the first two commits in Git?
Another potential use is as an alternative to stashing (which some people don't like, see e.g. https://codingkilledthecat.wordpress.com/2012/04/27/git-stash-pop-considered-harmful/).
For example, if I'm working on a branch and need to fix something urgently on master, I can just do:
git commit -am "In progress."
then checkout master and do the fix. When I'm done, I return to my branch and do
git reset --soft HEAD~1
to continue working where I left off.
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:
commit --amend
is only about not moving HEAD, while allowing to redo the current commit)Just found this example of combining:
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 ofprojectA
.
(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 ofprojectA
.When I decide to update both projects, I don't simply pull from
projectA
andprojectB
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 combinesprojectA
,projectB
and my local commits.
The tricky part here is that this is an octopus merge (three heads), butprojectB
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:
git reset --soft
is the answer.
One possible usage would be when you want to continue your work on a different machine. It would work like this:
Checkout a new branch with a stash-like name,
git checkout -b <branchname>_stash
Push your stash branch up,
git push -u origin <branchname>_stash
Switch to your other machine.
Pull down both your stash and existing branches,
git checkout <branchname>_stash; git checkout <branchname>
You should be on your existing branch now. Merge in the changes from the stash branch,
git merge <branchname>_stash
Soft reset your existing branch to 1 before your merge,
git reset --soft HEAD^
Remove your stash branch,
git branch -d <branchname>_stash
Also remove your stash branch from origin,
git push origin :<branchname>_stash
Continue working with your changes as if you had stashed them normally.
I think, in the future, GitHub and co. should offer this "remote stash" functionality in fewer steps.
SourceTree is a git GUI which has a pretty convenient interface for staging just the bits you want. It does not have anything remotely similar for amending a proper revision.
So git reset --soft HEAD~1
is much more useful than commit --amend
in this scenario. I can undo the commit, get all the changes back into the staging area, and resume tweaking the staged bits using SourceTree.
Really, it seems to me that commit --amend
is the more redundant command of the two, but git is git and does not shy away from similar commands that do slightly different things.
A great reason to use 'git reset --soft <sha1>
' is to move HEAD
in a bare repo.
If you try to use the --mixed
or --hard
option, you'll get an error since you're trying to modify and working tree and/or index that does not exist.
Note: You will need to do this directly from the bare repo.
Note Again: You will need to make sure the branch you want to reset in the bare repo is the active branch. If not, follow VonC's answer on how to update the active branch in a bare repo when you have direct access to the repo.