Reputation: 18342
Is there a way I can stash just my staged changes? The scenario I'm having issues with is when I've worked on several bugs at a given time, and have several unstaged changes. I'd like to be able to stage these files individually, create my .patch files, and stash them away until the code is approved. This way, when it's approved I can stash my entire (current) session, pop that bug and push the code.
Am I going about this the wrong way? Am I misunderstanding how git can work in other ways to simplify my process?
Upvotes: 827
Views: 317500
Reputation: 1329692
TLRD; With Git 2.35 (Q1 2022), "git stash
"(man) learned the --staged
option to stash away what has been added to the index (and nothing else).
So this is now officially supported (8 years later).
Usage:
git add <file1> <file2>
# Stash only the staged changes:
git stash push --staged -m "Stash only staged changes"
git stash list
git stash pop stash@{0}
See commit a8a6e06 (28 Oct 2021), and commit 41a28eb (18 Oct 2021) by Sergey Organov (sorganov
).
(Merged by Junio C Hamano -- gitster
-- in commit 44ac8fd, 29 Nov 2021)
stash
: implement '--staged' option for 'push
' and 'save
'Signed-off-by: Sergey Organov
Stash only the changes that are staged.
This mode allows to easily stash-out for later reuse some changes unrelated to the current work in progress.
Unlike '
stash push --patch
',--staged
supports use of any tool to select the changes to stash-out, including, but not limited to 'git add --interactive
'(man).
git stash
now includes in its man page:
'git stash' [push [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]
git stash
now includes in its man page:
save [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]
git stash
now includes in its man page:
-S
--staged
This option is only valid for
push
andsave
commands.Stash only the changes that are currently staged. This is similar to basic
git commit
except the state is committed to the stash instead of current branch.The
--patch
option has priority over this one.
git stash
now includes in its man page:
Saving unrelated changes for future use
When you are in the middle of massive changes and you find some unrelated issue that you don't want to forget to fix, you can do the change(s), stage them, and use
git stash push --staged
to stash them out for future use.
This is similar to committing the staged changes, only the commit ends-up being in the stash and not on the current branch.---------------------------------------------------------------- # ... hack hack hack ... $ git add --patch foo # add unrelated changes to the index $ git stash push --staged # save these changes to the stash # ... hack hack hack, finish curent changes ... $ git commit -m 'Massive' # commit fully tested changes $ git switch fixup-branch # switch to another branch $ git stash pop # to finish work on the saved changes ----------------------------------------------------------------
Note: "git stash -S
"(man) did not handle binary files correctly, which has been corrected with Git 2.46 (Q3 2024), batch 1.
See commit 5fb7686 (22 Apr 2024) by Adam Johnson (adamchainz
).
(Merged by Junio C Hamano -- gitster
-- in commit 90f6b5a, 30 Apr 2024)
stash
: fix "--staged
" with binary filesHelped-By: Jeff King
Helped-By: Randall S. Becker
Signed-off-by: Adam Johnson
"
git stash --staged
"(man) errors out when given binary files, after saving the stash.This behaviour dates back to the addition of the feature in 41a28eb ("
stash
: implement '--staged
' option for 'push
' and 'save
'", 2021-10-18, Git v2.35.0-rc0 -- merge listed in batch #1).
Adding the "--binary
" option of "diff-tree
" fixes this.
The "diff-tree
" call instash_patch()
also omits "--binary
", but that is fine since binary files cannot be selected interactively.
Upvotes: 231
Reputation: 2891
In VSCode, you can use Stash
> Stash Staged
. In git bash, you can use git stash --staged
Documentation
Upvotes: 3
Reputation: 25282
git stash push -S
Create the alias for this command: git config --global alias.stashs 'stash push -S'
.
Here you can find how to stash only unstaged
changes.
OLD
git stash push --staged
# since 2.35
Stash only the changes that are currently staged. This is similar to basic git commit
except the state is committed to the stash instead of current branch.
OLD
With latest git you may use --patch
option
git stash push --patch # since 2.14.6
git stash save --patch # for older git versions
And git will ask you for each change in your files to add or not into stash.
You just answer y
or n
OLD UPD
Alias for DOUBLE STASH:
git config --global alias.stash-staged '!bash -c "git stash --keep-index; git stash push -m "staged" --keep-index; git stash pop stash@{1}"'
Now you can stage your files and then run git stash-staged
.
As result your staged files will be saved into stash.
If you do not want to keep staged files and want move them into stash. Then you can add another alias and run git move-staged
:
git config --global alias.move-staged '!bash -c "git stash-staged;git commit -m "temp"; git stash; git reset --hard HEAD^; git stash pop"'
Upvotes: 419
Reputation: 61880
Yes, It's possible with DOUBLE STASH
git stash --keep-index
. This command will create a stash with ALL of your changes (staged and unstaged), but will leave the staged changes in your working directory (still in state staged).git stash push -m "good stash"
(add the -u
flag to include new files in the stash)"good stash"
has ONLY staged files.Now if you need unstaged files before stash, simply apply first stash (the one created with --keep-index
) and now you can remove files you stashed to "good stash"
.
Enjoy
Upvotes: 809
Reputation: 1081
You can use --staged
to stash staged changes only.
git stash --staged
Upvotes: 29
Reputation: 58667
If you don't have a newer git which has the --staged
option, here is how to do it directly.
The git stash
command is just a complicated shell script, which manipulates tree objects and commits and such. We can do the things that it does, manually.
The stash stack records special commits. We are going to create a commit out of the staged changes, and then manually transfer it into the stash. Then, get rid of the commit.
I have a project in which there are two changes to the Makefile
. One is staged and one is unstaged:
$ git diff --cached
diff --git a/Makefile b/Makefile
index 4ca6058f..c8c7480a 100644
--- a/Makefile
+++ b/Makefile
@@ -605,7 +605,7 @@ conftest2: conftest1.c conftest2.c
$(V)if echo | $(CC) -dM -E - | grep -s __ANDROID__ > /dev/null 2>&1 ; then \
echo yes ; \
fi
-
+# FOO
.PHONY: conftest.clean
conftest.clean:
$(V)rm -f conftest$(EXE) conftest.[co] \
$ git diff
diff --git a/Makefile b/Makefile
index c8c7480a..270c313d 100644
--- a/Makefile
+++ b/Makefile
@@ -611,3 +611,4 @@ conftest2: conftest1.c conftest2.c
$(V)rm -f conftest$(EXE) conftest.[co] \
conftest2$(EXE) conftest[12].[oc] \
conftest.err
+# BAR
The addition of the # FOO
line is staged; the addition of # BAR
is unstaged.
First, we create a tree object from the current index (which holds the staged items).
$ git write-tree
0d9651ad74328e747a053a9434d9867c8cd79d41 <-- output
First, create a commit from the tree, which has one parent, the current branch HEAD
:
$ git commit-tree -p HEAD -m 'add # FOO' 0d9651ad74328e747a053a9434d9867c8cd79d41
baa34222e781078d82cefed519ff105715c7f665 <-- output
Then, create another commit from the tree, which has two parents: HEAD
and the baa34222...
commit we just made:
$ git commit-tree -p HEAD -p baa34222e781078d82cefed519ff105715c7f665 -m 'add # FOO' 0d9651ad74328e747a053a9434d9867c8cd79d41
2c96b028e475a05d84f472da7f2a70ac53d0ac90 <-- output
This two-parent 2c96b02...
will be the commit we install into the stash.
Note that git commit-tree
is not git commit
. It is a lower-level command. These commits are not doing anything to your current branch; we are just allocating objects in Git's storage, and not doing anything with the branch that we are on or altering the index or working tree.
Next, we write this commit into .git/refs/stash
. You may want to back up this file.
$ echo 2c96b028e475a05d84f472da7f2a70ac53d0ac90 > .git/refs/stash
We hook the same commit into the .git/logs/refs/stash
file. Before the edit, the last line in the file looks like this:
b1819d98ab24720796315b9497236172d1fb1f5f 3b2ecc6604d77c9df4fe72efd1fbd384b2c43f76 Au Thor <[email protected]> 1654892876 -0700 On master: elim-aliases
We manually add this fake line:
3b2ecc6604d77c9df4fe72efd1fbd384b2c43f76 2c96b028e475a05d84f472da7f2a70ac53d0ac90 Au Thor <[email protected]> 1654892876 -0700 On master: add # FOO
You may also want to back up this file. However, if something goes wrong, things are easy to restore.
Note that the left hash 3b2ecc...
in this new line is the same as the right hash in the previous line. That's the previous stash commit, unrelated to what we are doing here, which has to be repeated to link this line into the stash stack. To the right of it, we have our hash 2c96b028e4...
. Then the rest of the line faked out. There is a hard tab after the timezone -0700
, not spaces. I just copy pasted that.
We verify that we have added the commit to the stash stack:
$ git stash list | head -3
stash@{0}: On master: add # FOO
stash@{1}: On master: elim-aliases
stash@{2}: On master: compiler-safe-eval
and:
$ git stash show -p
diff --git a/Makefile b/Makefile
index 4ca6058f..c8c7480a 100644
--- a/Makefile
+++ b/Makefile
@@ -605,7 +605,7 @@ conftest2: conftest1.c conftest2.c
$(V)if echo | $(CC) -dM -E - | grep -s __ANDROID__ > /dev/null 2>&1 ; then \
echo yes ; \
fi
-
+# FOO
.PHONY: conftest.clean
conftest.clean:
$(V)rm -f conftest$(EXE) conftest.[co] \
There it is; git stash
thinks our commit is a "stash-like" commit, and accepts it.
We manually took the index with a staged change, and produced a tree object.
We then turned the tree object into a regular commit object and then one more two-parent commit. The two-parent object is acceptable as a stash-like commit.
Lastly, we patched this commit into the stash stack manually by editing a pair of files.
We have not executed any unsafe commands that manipulate our index or working tree. However, we have unsafely manipulated the git stash stack. If something goes wrong, here is how to fix it (other than restoring from backup files):
Delete the fake line we added to .git/logs/refs/stash
, so that once again this is the last line:
b1819d98ab24720796315b9497236172d1fb1f5f 3b2ecc6604d77c9df4fe72efd1fbd384b2c43f76 Au Thor <[email protected]> 1654892876 -0700 On master: elim-aliases
Take the right side hash 3b2ecc6604d77c9df4fe72efd1fbd384b2c43f76
and plant it into the .git/refs/stash
file:
$ echo 3b2ecc6604d77c9df4fe72efd1fbd384b2c43f76 > .git/refs/stash`
The previous stash is now restored.
Upvotes: 2
Reputation: 4185
UPDATE 2022-04-21
Just use @VonC's answer which is the new idiomatic approach to the original question https://stackoverflow.com/a/70231955/2959469
TL;DR FOR OLD APPROACH Just add
-- $(git diff --staged --name-only)
for your git<pathspec>
parameter
git stash -- $(git diff --staged --name-only)
And to add a message simply:
git stash push -m "My work in progress" -- $(git diff --staged --name-only)
Tested on v2.17.1 and v2.21.0.windows.1
Limitations:
- Please be aware that this will stash every single thing, if you have no files staged.
- Also if you have a file that is only partially staged ( i.e. only some changed lines, are staged while some other changed lines are not), then the whole file will get stashed (including unstaged lines).
Upvotes: 209
Reputation: 19685
UPDATE January 2022: Git 2.35 has been released, and stash
now supports a --staged
parameter. This answer is therefore obsolete for Git 2.35+. See answer by vonc: https://stackoverflow.com/a/70231955/430128.
Old Answer:
Stashing just the index (staged changes) in Git is more difficult than it should be. I've found @Joe's answer to work well, and turned a minor variation of it into this alias:
stash-index = "!f() { \
! git diff --cached --exit-code --quiet && \
git stash push --quiet --keep-index -m \"temp for stash-index\" && \
git stash push \"$@\" && \
git stash pop --quiet stash@{1} && \
git stash show -p | git apply -R; }; f"
It:
Validates that there are actually staged changes (git diff --cached --exit-code
returns a non-zero status if there are). HT: @nandilugio
It pushes both the staged and unstaged changes into a temporary stash, leaving the staged changes alone.
It then pushes the staged changes into the stash, which is the stash we want to keep. Arguments passed to the alias, such as --message "whatever"
will be added to this stash command.
It pops the temporary stash to restore the original state and remove the temporary stash, and then
Finally "removes" the stashed changes from the working directory via a reverse patch application.
For the opposite problem of stashing just the unstaged changes (alias stash-working
) see this answer.
Upvotes: 26
Reputation: 1172
Another approach to this is to create a temporary commit with files you don't want to be stashed, then stash remaining files and gently remove last commit, keeping the files intact:
git add *files that you don't want to be stashed*
git commit -m "temp"
git stash --include-untracked
git reset --soft HEAD~1
That way you only touch files that you want to be touched.
Note, "--include-untracked" is used here to also stash new files (which is probably what you really want).
Upvotes: 2
Reputation: 21
I haven't seen this solution that requires no use of git stash
:
You don't even need to use git stash
at all. You can work this out using a dedicated branch as covered here (branches are cheap).
Indeed, you can isolate separately un- and staged changes with a few consecutive commands that you could bundle together into a git alias :
Create and switch to an new branch where you'll commit separately staged and unstaged changes : see here
At any moment you can git cherry-pick -e
one commit from the created branch to apply it where you want (-e
to change its commit message).
When you don't need it anymore, you can delete this "stash branch". You may have to use the -D
option to force deletion (instead of the -d
normal option) because said branch is not merged and git might consider that you risk losing data if you delete it. That is true if you haven't cherry-picked commits that were on it before deletion :
git branch -D separated-stashes
You can also add an alias to your ~/.gitconfig
in order to automate this behavior :
git config --global alias.bratisla '!git switch -c separated-stashes; git commit -m "staged changes"; git add -u; git commit -m "unstaged changes"; git switch -' # why this name ? : youtu.be/LpE1bJp8-4w
before "stashing"
after "stashing"
Of course, you can also achieve the same result using two consecutive stashes
As stated in other answers, you have some ways to stash only unstaged or only staged changes using git stash (-k|--keep-index)
in combination with other commands.
I personally find the -k
option very confusing, as it stashes everything but keeps staged changes in staged state (that explains why "--keep-index
"). Whereas stashing something usually moves it to a stash entry. With -k
the unstaged changes are stashed normally, but staged ones are just copied to the same stash entry.
Step 0 : you have two things in your git status : a file containing staged changes, and another one containing unstaged changes.
Step 1 : stash unstaged + staged changes but keep the staged ones in the index :
git stash -k -m "all changes"
The -m "..."
part is optional, git stash -k
is actually an alias for git stash push -k
(that does not push anything remotely btw don't worry) which accepts a -m
option to label you stash entries for clarity (like a commit message or a tag but for a stash entry). It is the newer version of the deprecated git stash save
.
Step 1bis (optional) :
git stash
Stash staged changes (that are still in the index).
This step is not necessary for the following, but shows that you can put only staged changes in a stash entry if you want to.
If you use this line you have to git stash (pop|apply) && git add -u
before continuing on step 2.
Step 2 :
git commit -m "staged changes"
Makes a commit containing only staged changes from step 0, it contains the same thing as the stash entry from step 1bis.
Step 3 :
git stash (pop|apply)
Restores the stash from step 1. Note that this stash entry contained everything, but since you already committed staged changes, this stash will only add unstaged changes from step 0.
nb: "restore" here does NOT mean "git restore", which is a different command.
Step 4 :
git add -u
Adds the popped stash's content to the index
Step 5 :
git commit -m "unstaged changes"
"Unstaged" here, as "staged" in steps 2 and 3's comments, refers to step 0. You are actually staging and committing the "staged changes" from step 0.
Done !
You now have two separated commits containing (un)staged changes from step 0.
You may want to amend/rebase them for either additional changes or to rename/drop/squash them.
Depending on what you did with your stash's stack (pop
or apply
), you might also want to git stash (drop|clear)
it. You can see you stash entries with git stash (list|show)
Upvotes: 0
Reputation: 25282
TL;DR;
git stash-staged
After creating an alias:
git config --global alias.stash-staged '!bash -c "git stash -- \$(git diff --staged --name-only)"'
Here git diff
returns list of --staged
files --name-only
And then we pass this list as pathspec
to git stash
commad.
From man git stash
:
git stash [--] [<pathspec>...]
<pathspec>...
The new stash entry records the modified states only for the files
that match the pathspec. The index entries and working tree
files are then rolled back to the state in HEAD only for these
files, too, leaving files that do not match the pathspec intact.
Upvotes: -1
Reputation: 1064
To prune an accidental change, especially the deletion of multiple files, do the following:
git add <stuff to keep> && git stash --keep-index && git stash drop
in other words, stash the crap and throw it away with the stash altogether.
Tested in git version 2.17.1
Upvotes: 0
Reputation: 1329692
git stash --keep-index
is a good solution... except it did not work correctly on paths that have been removed, which has been fixed in Git 2.23 (Q3 2019)
See commit b932f6a (16 Jul 2019) by Thomas Gummerer (tgummerer
).
(Merged by Junio C Hamano -- gitster
-- in commit f8aee85, 25 Jul 2019)
stash
: fix handling removed files with--keep-index
git stash push --keep-index
is supposed to keep all changes that have been added to the index, both in the index and on disk.Currently this doesn't behave correctly when a file is removed from the index.
Instead of keeping it deleted on disk, **--keep-index currently restores the file.**Fix that behaviour by using '
git checkout
' in no-overlay mode which can faithfully restore the index and working tree.
This also simplifies the code.Note that this will overwrite untracked files if the untracked file has the same name as a file that has been deleted in the index.
Upvotes: 4
Reputation: 1183
To accomplish the same thing...
git commit -m 'temp'
git add .
git stash
git reset HEAD~1
Boom. The files you don't want are stashed. The files you want are all ready for you.
Upvotes: 43
Reputation: 508
In this scenario, I prefer to create new branches for each issue. I use a prefix temp/ so I know that I can delete these branches later.
git checkout -b temp/bug1
Stage the files that fix bug1 and commit them.
git checkout -b temp/bug2
You can then cherry pick the commits from the respective branches as require and submit a pull request.
Upvotes: 16
Reputation: 663
I made a script that stashes only what is currently staged and leaves everything else. This is awesome when I start making too many unrelated changes. Simply stage what isn't related to the desired commit and stash just that.
(Thanks to Bartłomiej for the starting point)
#!/bin/bash
#Stash everything temporarily. Keep staged files, discard everything else after stashing.
git stash --keep-index
#Stash everything that remains (only the staged files should remain) This is the stash we want to keep, so give it a name.
git stash save "$1"
#Apply the original stash to get us back to where we started.
git stash apply stash@{1}
#Create a temporary patch to reverse the originally staged changes and apply it
git stash show -p | git apply -R
#Delete the temporary stash
git stash drop stash@{1}
Upvotes: 58
Reputation: 19950
Out of your comments to Mike Monkiewicz answer I suggest to use a simpler model: Use regular development branches, but use the squash option of the merge to get a single commit in your master branch:
git checkout -b bug1 # create the development branch
* hack hack hack * # do some work
git commit
* hack hack hack *
git commit
* hack hack hack *
git commit
* hack hack hack *
git commit
git checkout master # go back to the master branch
git merge --squash bug1 # merge the work back
git commit # commit the merge (don't forget
# to change the default commit message)
git branch -D bug1 # remove the development branch
The advantage of this procedure is that you can use the normal git work flow.
Upvotes: 0
Reputation: 4729
Is it absolutely necessary to work on several bugs at once? And by "at once," I mean "having files edited for multiple bugs at the same time." Because unless you absolutely need that, I'd only work on one bug at a time in your environment. That way you can use local branches & rebase, which I find far easier than managing a complex stash/stage.
Let's say master is at commit B. Now work on bug #1.
git checkout -b bug1
Now you're on branch bug1. Make some changes, commit, wait for code review. This is local, so you're not affecting anyone else, and it should be easy enough to make a patch from git diffs.
A-B < master
\
C < bug1
Now you're working on bug2. Go back to master with git checkout master
. Make a new branch, git checkout -b bug2
. Make changes, commit, wait for code review.
D < bug2
/
A-B < master
\
C < bug1
Let's pretend that someone else commits E & F on master while you're waiting on review.
D < bug2
/
A-B-E-F < master
\
C < bug1
When your code has been approved, you can rebase it on to master with the following steps:
git checkout bug1
git rebase master
git checkout master
git merge bug1
This will result in the following:
D < bug2
/
A-B-E-F-C' < master, bug1
Then you can push, delete your local bug1 branch, and off you go. One bug at a time in your workspace, but with using local branches your repository can handle multiple bugs. And this avoids a complicated stage/stash dance.
Answer to ctote's question in the comments:
Well, you can go back to stashing for each bug, and only work with one bug at a time. Atleast that saves you the staging issue. But having tried this, I personally find it troublesome. Stashes are a bit messy in a git log graph. And more importantly, if you screw something up you can't revert. If you have a dirty working directory and you pop a stash, you can't "undo" that pop. It's much harder to screw up already existing commits.
So git rebase -i
.
When you rebase one branch onto another, you can do it interactively (the -i flag). When you do this, you have the option to pick what you want to do with each commit. Pro Git is an awesome book which is also online in HTML format, and has a nice section on rebasing & squashing:
http://git-scm.com/book/ch6-4.html
I'll steal their example verbatim for convenience. Pretend you have the following commit history, and you want to rebase & squash bug1 onto master:
F < bug2
/
A-B-G-H < master
\
C-D-E < bug1
Here's what you will see when you type git rebase -i master bug1
pick f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file
#
# Commands:
# p, pick = use commit
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#
To squash all commits of a branch down into a single commit, keep the first commit as "pick" and replace all subsequent "pick" entries with "squash" or simply "s". You will get the opportunity to change the commit message, too.
pick f7f3f6d changed my name a bit
s 310154e updated README formatting and added blame
s a5f4a0d added cat-file
#
# Commands:
# p, pick = use commit
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
So yeah, squashing is a bit of a pain, but I would still recommend it over heavy use of stashes.
Upvotes: 0
Reputation: 265928
Why don't you commit the change for a certain bug and create a patch from that commit and its predecessor?
# hackhackhack, fix two unrelated bugs
git add -p # add hunks of first bug
git commit -m 'fix bug #123' # create commit #1
git add -p # add hunks of second bug
git commit -m 'fix bug #321' # create commit #2
Then, to create the appropriate patches, use git format-patch
:
git format-patch HEAD^^
This will create two files: 0001-fix-bug-123.patch
and 0002-fix-bug-321.patch
Or you can create separate branches for each bug, so you can merge or rebase bug fixes individually, or even delete them, if they don't work out.
Upvotes: 7