Practical uses of git reset --soft?
Asked Answered
M

13

209

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.

I understand I can use the soft reset to edit a commit without altering the index or the working directory, as I would with git commit --amend.

Are these two commands really the same (reset --soft vs commit --amend)? Any reason to use one or the other in practical terms? And more importantly, are there any other uses for reset --soft apart from amending a commit?

Marvelous answered 5/3, 2011 at 11:31 Comment(0)
C
145

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 --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 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 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 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.

Carrol answered 5/3, 2011 at 12:36 Comment(2)
Note to self: simple example of squashing with git reset --soft: stackoverflow.com/questions/6869705/…Carrol
its also useful if you committed to the wrong branch. all changes go back to staging area and move with you while you checkout the right branch.Giovannigip
S
80

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 --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?

  • See other answers :)
Sacco answered 3/10, 2014 at 1:22 Comment(3)
See other answers for "are there any other uses for reset --soft apart from amending a commit - No"Horripilation
In response to the last comment -- agreed in part, but I'd say that amending a single commit is too specific. For instance, you might want to, once a feature is written, squash everything and carve out new commits that are more useful for reviewers. I'd say that soft resets (including plain git reset) are good when you (a) want to rewrite history, (b) don't care about the old commits (so you can avoid the fussiness of interactive rebase), and (c) have more than one commit to change (otherwise, commit --amend is simpler).Poco
Oh, wow. That's really handy, and simultaneously intuitive. Nice!Ingrown
G
26

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.

Gurgitation answered 6/3, 2011 at 2:29 Comment(4)
git commit --fixup HEAD^^ git rebase --autosquash HEAD~X works well, too.Obedient
git rebase --interactive HEAD^^ where you choose to edit both commits A and B. This way keeps the commit messages of A and B, if needed you still can modify those too.Alisander
How do I re-commit B after I have reset to A?Donella
Another way that's probably less work: check your git log, get the commit hash of B, then git reset A, make and add changes, git commit --amend, git cherry-pick <B-commit-hash>.Execrative
K
25

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.

Koloski answered 24/9, 2015 at 6:25 Comment(1)
Update (now that I understand git better): --soft is actually unnecessary here, unless you really care about having the changes be immediately staged. I now just use git reset HEAD~ when doing this. If I have some changes staged when I need to switch branches, and want to keep it that way, then I do git commit -m "staged changes", then git commit -am "unstaged changes", then later git reset HEAD~ followed by git reset --soft HEAD~ to completely restore the working state. Although, to be honest I do both of these things a lot less now that I know about git-worktree :)Koloski
M
13

One practical use is if you have committed to your local repo already (ie. git commit -m ) then you can reverse that last commit by doing git reset --soft HEAD~1

Also for your knowledge, if you have staged your changes already (ie with git add .) then you can reverse the staging by doing git reset --mixed HEAD or i've commonly also just used git reset

lastly, git reset --hard wipes everything out including your local changes. The ~ after head tells you how many commits to go to from the top.

Maisey answered 21/2, 2016 at 16:39 Comment(1)
git reset --soft HEAD ~1 gives me fatal: Cannot do soft reset with paths. I think we need to remove the space after HEAD so it would be git reset --soft HEAD~1Judenberg
V
8

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.

Varied answered 17/5, 2011 at 18:24 Comment(2)
As mentioned in your previous answer (stackoverflow.com/questions/4624881/…), this is true when you have a direct access to the bare repo (which you mention here). +1 though.Carrol
@Carrol Yes, absolutely correct and thanks for adding the note! I keep forgetting to add that since I'm assuming resets are done directly from the repo. Also, I'm assuming that the branch the person wants to reset in the bare repo is their active branch. If the branch is not their active branch, then the need to update per your answer (stackoverflow.com/questions/3301956/…) on how to update the active branch for bare repos. I'll update the answer with the active branch info as well. Thanks again!!!Varied
G
7

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?

Gaming answered 5/3, 2011 at 11:40 Comment(2)
That is a good question that I hadn't found before. But I am not sure I can see how to put changes onto another branch using reset soft. So I have checkout Branch, then I use git reset --soft anotherBranch and then commit there? But you don't really change the ckeckout branch, so will you commit to Branch or to anotherBranch?Marvelous
When doing this it is important you don't use git checkout because that will alter your tree. Think of git reset --soft as a way of just manipulating the revision HEAD points to.Gaming
S
7

One possible usage would be when you want to continue your work on a different machine. It would work like this:

  1. Checkout a new branch with a stash-like name,

    git checkout -b <branchname>_stash
    
  2. Push your stash branch up,

    git push -u origin <branchname>_stash
    
  3. Switch to your other machine.

  4. Pull down both your stash and existing branches,

    git checkout <branchname>_stash; git checkout <branchname>
    
  5. You should be on your existing branch now. Merge in the changes from the stash branch,

    git merge <branchname>_stash
    
  6. Soft reset your existing branch to 1 before your merge,

    git reset --soft HEAD^
    
  7. Remove your stash branch,

    git branch -d <branchname>_stash
    
  8. Also remove your stash branch from origin,

    git push origin :<branchname>_stash
    
  9. 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.

Shive answered 1/7, 2014 at 17:46 Comment(1)
I want to point out that the first stash and pop on your first machine are completely unnecessary, you can just create a new branch directly from a dirty working copy, commit, and then push the changes up to the remote.Arella
U
4

While I really like the answers in this thread, I use git reset --soft for a slightly different, but a very practical scenario nevertheless.

I use an IDE for development which has a good diff tool for showing changes (staged and unstaged) after my last commit. Now, most of my tasks involve multiple commits. For example, let's say I make 5 commits to complete a particular task. I use the diff tool in the IDE during every incremental commit from 1-5 to look at my changes from the last commit. I find it a very helpful way to review my changes before committing.

But at the end of my task, when I want to see all my changes together (from before 1st commit), to do a self code-review before making a pull request, I would only see the changes from my previous commit (after commit 4) and not changes from all the commits of my current task.

So I use git reset --soft HEAD~4 to go back 4 commits. This lets me see all the changes together. When I am confident of my changes, I can then do git reset HEAD@{1} and push it to remote confidently.

Unprecedented answered 4/7, 2018 at 1:52 Comment(4)
... then do git add --patch and git commit repeatedly to build the commit series you'd have constructed if you knew what you were doing all along. Your first commits are like the notes on your desk or the first draft of a memo, they're there to organize your thinking, not for publication.Shatterproof
Hey, I didn't quite get what you are suggesting.Unprecedented
Just that instead of doing git reset @{1} to restore your first-draft series, you can instead build a for-publication series from git add -p and git commit.Shatterproof
Yes, correct. One other way is (the one I usually follow) to rebase on upstream/master and squash the commits into a single one.Unprecedented
B
3

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.

Berkow answered 15/7, 2014 at 0:33 Comment(0)
P
2

I usually use both git reset --soft and git commit --amend in order to combine commits. For example if I have a commit history like:

master: A--B--C

At this point I just want to keep commit A and delete commit B and C but still keep the changes. First:

git reset --soft <commit A's hash>

because the option --soft just bring you back at a specific commit and still keep the changes. Now you can apply those change to your commit A:

git commit --amend

Or you can rename that commit as well:

git commit --amend -m 'your new commit message'

After that, if you want to push the to the remote repository, you will have to use:

git push -f origin master

Please note that it seems like you 'went back' to your old commit but in fact, the amended commit is a new commit and has replaced that old commit A (you can check it's hash id by using git log), but in the end, you still have a nice commit history like you intended.

Prairie answered 9/2, 2022 at 16:34 Comment(0)
P
1

Another use case is when you want to replace the other branch with yours in a pull request, for example, lets say that you have a software with features A, B, C in develop.

You are developing with the next version and you:

  • Removed feature B

  • Added feature D

In the process, develop just added hotfixes for feature B.

You can merge develop into next, but that can be messy sometimes, but you can also use git reset --soft origin/develop and create a commit with your changes and the branch is mergeable without conflicts and keep your changes.

It turns out that git reset --soft is a handy command. I personally use it a lot to squash commits that dont have "completed work" like "WIP" so when I open the pull request, all my commits are understandable.

Porte answered 23/1, 2019 at 12:54 Comment(0)
B
1

I use git reset --soft HEAD~1 to add to a hastily made incomplete commit, but not just to its message (like git --amend would) but also to make code changes to what was previously committed. A typical code addition would be a bump in our internal ML library version.

Bloater answered 10/8, 2023 at 16:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.