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 differs from commit --amend as:
- it doesn't create a new commit.
- it can actually move HEAD to any commit (as
commit --amendis 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 are 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.
ProjectAis regularly updated,projectBhas 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
projectAandprojectBas 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,projectBand my local commits.
The tricky part here is that this is an octopus merge (three heads), butprojectBneeds 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 do 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 terms 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.
Note that --no-soft does not make sense, and Git 2.42 (Q3 2023) tells you so now.
See commit 3821eb6 (19 Jul 2023) by Junio C Hamano (gitster).
(Merged by Junio C Hamano -- gitster -- in commit e672bc4, 27 Jul 2023)
e672bc4f76:Merge branch 'jc/parse-options-reset'
Command line parser fix. * jc/parse-options-reset: reset: reject
--no-(mixed|soft|hard|merge|keep)option
You would get an "unknown option" error.
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 differs from commit --amend as:
- it doesn't create a new commit.
- it can actually move HEAD to any commit (as
commit --amendis 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 are 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.
ProjectAis regularly updated,projectBhas 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
projectAandprojectBas 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,projectBand my local commits.
The tricky part here is that this is an octopus merge (three heads), butprojectBneeds 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 do 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 terms 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.
Note that --no-soft does not make sense, and Git 2.42 (Q3 2023) tells you so now.
See commit 3821eb6 (19 Jul 2023) by Junio C Hamano (gitster).
(Merged by Junio C Hamano -- gitster -- in commit e672bc4, 27 Jul 2023)
e672bc4f76:Merge branch 'jc/parse-options-reset'
Command line parser fix. * jc/parse-options-reset: reset: reject
--no-(mixed|soft|hard|merge|keep)option
You would get an "unknown option" error.
Use Case - Combine a series of local commits
"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.
E.g.
> 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.
Short answers to your questions
Are these two commands really the same (reset --soft vs commit --amend)?
- No.
Any reason to use one or the other in practical terms?
commit --amendto 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?
- See other answers :)
git reset --soft HEAD~1
`git reset --soft` instead of `git rebase -i HEAD~N`
Is my understanding of Git Reset Correct?
How to Undo "git reset -HEAD~"?
git reset --soft -> resets the head, has committed changes, has changes in directory
git reset --mixed -> resets the head, does not contain latest commits, has changes in directory
git reset --hard -> resets the head, does not contain latest commits, does not contain changes in directory
The project I currently work at wants PRs to get squashed before the commit, according to those guidelines:
https://www.kubernetes.dev/docs/guide/github-workflow/#squash-commits
This suggests to use git rebase -i HEAD~N.
The question is how to get to the number N.
It is quite easy if you don't have merges in your branch.
If you merged the upstream branch into your branch while developing, it is not that easy to get to the number N. At least I have found no easy way.
I discovered:
git reset --soft $(git merge-base main HEAD)
This works fine, except that I need to create the commit message again, but that's no big issue.
Question:
Does above reset --soft way of squashing all commits of a branch has any draw-backs?
Up to now it worked fine, but maybe there are cases where this method could fail?