git reset --soft , which will keep your files, and stage all changes back automatically. git reset --hard , which will completely destroy any changes and remove them from the local directory. Only use this if you know what you're doing.
reset --soft : History changed, HEAD changed, Working directory is not changed. reset --mixed : History changed, HEAD changed, Working directory changed with unstaged data. reset --hard : History changed, HEAD changed, Working directory is changed with lost data. It is always safe to go with Git --soft.
So, in short, we can say that “git reset” is a command, whereas “git reset –hard” is its variation that is used when you want to wipe out all the traces of your last commit.
With reset --soft , it will simply stop there. Now take a second to look at that diagram and realize what happened: it essentially undid the last git commit command. When you run git commit , Git creates a new commit and moves the branch that HEAD points to up to it.
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.
"Oops. Those three commits could be just one."
So, undo the last 3 (or whatever) commits (without affecting the index nor working directory). Then commit all the changes as one.
> git add -A; git commit -m "Start here."
> git add -A; git commit -m "One"
> git add -A; git commit -m "Two"
> git add -A' git commit -m "Three"
> git log --oneline --graph -4 --decorate
> * da883dc (HEAD, master) Three
> * 92d3eb7 Two
> * c6e82d3 One
> * e1e8042 Start here.
> git reset --soft HEAD~3
> git log --oneline --graph -1 --decorate
> * e1e8042 Start here.
Now all your changes are preserved and ready to be committed as one.
Are these two commands really the same (reset --soft
vs commit --amend
)?
Any reason to use one or the other in practical terms?
commit --amend
to add/rm files from the very last commit or to change its message. reset --soft <commit>
to combine several sequential commits into a new one.And more importantly, are there any other uses for reset --soft
apart from amending a commit?
I use it to amend more than just the last commit.
Let's say I made a mistake in commit A and then made commit B. Now I can only amend B.
So I do git reset --soft HEAD^^
, I correct and re-commit A and then re-commit B.
Of course, it's not very convenient for large commits… but you typically shouldn't do large commits anyway.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With