How can I stash only staged changes in Git?
Asked Answered
T

18

767

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?

Tabatha answered 7/2, 2013 at 19:50 Comment(4)
Yes, you probably are doing things wrong to get into this situation. Still a useful question. You really ought to stash or branch before starting on the next fix. The, tangential, answer stackoverflow.com/a/50692885 is probably a better way to handle this in git. Playing around with the stash often does weird stuff to my work-area if I have pulled commits from upstream.Siward
With Git 2.35 (Q1 2022), this is officially supported with git stash push --staged. See my answer belowEleonoreeleoptene
@Tabatha at this point you should really consider switching the accepted answer to another one mentioning the push --staged switch.Fortyfive
Does this answer your question? How do you tell git to stash the index only?Heda
G
785

Yes, It's possible with DOUBLE STASH

  1. Stage all your files that you need to stash.
  2. Run 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).
  3. Run git stash push -m "good stash" (add the -u flag to include new files in the stash)
  4. Now your "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

Goodnatured answered 5/10, 2015 at 14:42 Comment(19)
It stashes changes in submodules even though they are not staged. Is there a way around this?God
this somehow left all new files (even staged) out.Committeeman
@Aurimas, to stash new files, you need to use the -u switch.Latchkey
when you reapply the first stash and get all changed back while you might be only interested in your unstages changes use git stash apply --index option. This will try to keep your un(staged) state. Its easier to remove the unwanted changes from the working tree now.Gabel
While I did not need to do exactly what this answer said, knowing about the --keep-index flag was very helpful.Castle
i thought this answer was very confusing, will go with git stash push --patch insteadIman
@Bartłomiej Semańczyk, your answer was very helpful to me, just had a case where I had to un-do an accidental file deletion caused by some buggy code that I've wrote, messed up more than my git working tree, used your answer to fix my problem, kudos. Posted it further down below if somebody runs into the same problem as I just did.Green
Version 2.17.1 of git stash -m "good stash" gives error: pathspec '[...]/good stash' did not match any file(s) known to git. Did you forget to 'git add'?. I'll update the answer with the new syntax (replace the -m with save).Shrew
Updated again with push -m. I just learned that save is deprecated now.Shrew
I run git stash drop stash@{n} after this.Philoprogenitive
You can include untracked files in step 2 running git stash --keep-index --include-untracked.Demagogy
This worked well however it didn't un-stash the unstaged items. I ran git stash pop --index 1 to restore unstaged changes and delete the unstaged stash. Only "good stash" is in the stash list afterwards.Belldame
Staged changes go into the named stash. Unstaged changes go into the anonymous stash created by step 2. It might be slightly clearer if that stash also had a message so you could identify it more easily with git stash list.Sibilant
git stash apply stash@{1} is the command to apply the first stash that you stashed.Jettiejettison
Please stop upvoting this outdated and hacky answer. Just use git stash push --staged. Look at all the other answers.Jori
Please continue upvoting this very current and working answer. Solution offered by @Jori has typical limitations that does not exist in that answer.Electrostatics
Then you should elaborate on what limitation yours overcomes right in the answer, because as far as anyone searching for this question is concerned, this is a workaround solution that predates git having a built-in option to accomplish thisJori
No, it is not. Sorry;)Electrostatics
Git's user friendliness in a nutshell... :DEstrellaestrellita
S
350

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"'
Sizar answered 29/6, 2017 at 11:55 Comment(12)
Technically does not answer the question - but a really nice technique which achieves selective stashing.Netti
Agree, this is okay, but the idea here with the question is I've ALREADY done all of this work of staging the changes I want to do something with (ostensibly originally to commit, but now want to stash), not looking to just do it all over again.Modulator
does not work for newly created files (works on the modified files only)Steddman
@DerekLiang: Newly created files are not tracked at all. You probably should check -u|--include-untracked option of git-stashSizar
From the docs: "save: This option is deprecated in favour of git stash push. It differs from 'stash push' in that it cannot take pathspecs, and any non-option arguments form the message."Carnify
I'm using git version 2.7.1.windows.2 and it doesn't have git stash push but it has git stash save --patchShardashare
@DerekLiang: Newly created file is one patch. You can not split it. But you can press e command and then remove + lines which you do not want to stashSizar
Beware: creating and running the 2ns alias "git move-staged" without the 1st one messed up my repo.Radiotelegram
WARNING: The git move-staged command dropped my latest commit when I had a file partially staged. Had to recover from the origin.Radiotelegram
agree it technically doesn't answer the question but it's the technique i use instead of staging and wanting to stash what i staged.Robinrobina
git stash push --staged works really nicely with git version 2.38.1.windows.1.Cowfish
More attention needed at VonC's answer. Now you can just git stash -S, see https://mcmap.net/q/13286/-how-can-i-stash-only-staged-changes-in-gitScreenplay
S
206

UPDATE 2022-04-21

Just use @VonC's answer which is the new idiomatic approach to the original question https://mcmap.net/q/13286/-how-can-i-stash-only-staged-changes-in-git


TL;DR FOR OLD APPROACH Just add -- $(git diff --staged --name-only) for your git <pathspec> parameter

Here is a simple one-liner:

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).
Samal answered 23/1, 2020 at 9:14 Comment(11)
I think this is the best option on the situation described: easy to understand and no black magic involved!Novick
@KalpeshPanchal can you share your alias? I'm not sure how to escape it, so it's not interpreting it correctly.Drews
@IgorNadj Sure! Here it is: github.com/panchalkalpesh/git-aliases/commit/…Seldun
please, note that (at least on my machine, 2.27.0.windows.1) this works only if you are in the top-level dir of your repository.Beadle
For people that often have staged+unstaged changes for a single file, don't miss out on that answer - it seems much better.Zetana
@marco6, Use the --relative flag to fix thatMraz
This doesn't seem to work if a deleted file is staged.Bilestone
Yeah it doesn't work for deleted and added files unfortunately.Massimo
what if you have staged and unstaged changed in the same file?Mincing
@Mincing I tried it, if one part of the change of the file is in staged status, and the other part of the change is in unstaged status, the whole file will be put into the stash status.Reims
doesn't work if a file contains both stashed AND unstashed changes.Jon
E
189

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

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 and save 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
----------------------------------------------------------------
Eleonoreeleoptene answered 5/12, 2021 at 6:23 Comment(2)
git stash --staged fails if the staged and unstaged hunks touch the same lines of a file.Wheelsman
Nice that you don't need use use -u with this, anything staged whether tracked or not will be stashed.Anthotaxy
D
58

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}
Durer answered 22/9, 2016 at 16:55 Comment(3)
I'd add that you can turn the script into a git command by following thediscoblog.com/blog/2014/03/29/custom-git-commands-in-3-stepsThereby
This is great! I've tweaked it to prompt the user for a stash description if they don't enter one on the command line: gist.github.com/brookinc/e2589a8c5ca33f804e4868f6bfc18282Chantell
Thanks, I upvoted and turned it into an alias here: https://mcmap.net/q/13286/-how-can-i-stash-only-staged-changes-in-git.Reginaldreginauld
P
43

To accomplish the same thing...

  1. Stage just the files you want to work on.
  2. git commit -m 'temp'
  3. git add .
  4. git stash
  5. git reset HEAD~1

Boom. The files you don't want are stashed. The files you want are all ready for you.

Palladino answered 5/10, 2018 at 19:55 Comment(3)
This is easily the best answer and the easiest to rememberLupien
Upvoted because useful, but this doesn't answer the question -- what ends up in the stash is the unstaged changes at step #1, whereas the question is asking about stashing just the staged changes. You're answering the opposite of this question: https://mcmap.net/q/13560/-how-to-stash-only-unstaged-changes-in-git/430128. I built an alias that essentially does this, with some enhancements here: https://mcmap.net/q/13560/-how-to-stash-only-unstaged-changes-in-git.Reginaldreginauld
@Reginaldreginauld Just add another git stash and the latest stash will contain the staged changes.Landre
R
25

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://mcmap.net/q/13286/-how-can-i-stash-only-staged-changes-in-git.

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:

  1. Validates that there are actually staged changes (git diff --cached --exit-code returns a non-zero status if there are). HT: @nandilugio

  2. It pushes both the staged and unstaged changes into a temporary stash, leaving the staged changes alone.

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

  4. It pops the temporary stash to restore the original state and remove the temporary stash, and then

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

Reginaldreginauld answered 26/3, 2020 at 19:49 Comment(4)
How would you do the opposite of this? I need to stash just the unstaged changesGruel
@Gruel See the link in the last sentence.Reginaldreginauld
Great answer! I've updated it a bit not to do wrong things when there's nothing staged by adding ! git diff --cached --exit-code --quiet && \ as first line, so we abort (non-zero exit first in the chain of &&s) in that case. Also note that this doesn't work when setting diff.noprefix = true (git version 2.29.2), but it was no problem for me since I have also aliases for diff, so I've just added --no-prefix to those.Champ
git stash --staged fails if the staged and unstaged hunks touch the same lines of a file.Wheelsman
P
23

You can use --staged to stash staged changes only.

git stash --staged

documentation: https://git-scm.com/docs/git-stash#Documentation/git-stash.txt-push-p--patch-S--staged-k--no-keep-index-u--include-untracked-a--all-q--quiet-m--messageltmessagegt--pathspec-from-fileltfilegt--pathspec-file-nul--ltpathspecgt82308203

Piliferous answered 11/8, 2022 at 7:53 Comment(1)
you can also use -SBuenabuenaventura
V
15

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.

Valma answered 5/6, 2018 at 5:52 Comment(4)
While fancy stashing sounds is nice to know about, in practice this seems like an approach I'm less likely to bork.Posticous
Use "git cherry-pick tmpCommit" to get the temporary commit back w.o. a merge-commit or "git merge tmpCommit" + " git reset HEAD^ " to get the changes without the commit.Siward
As this answer shows sometimes it is better to ask directly what you want to achieve instead of how to achieve it with given technique. Temporary branches and cherry-pick are handy in complicated situations.Dugout
if you staged a file partially, you will need to stash your changes before moving back to the original branch and popping them againStint
A
7

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.

Abrego answered 7/2, 2013 at 19:58 Comment(0)
E
4

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.

Eleonoreeleoptene answered 27/7, 2019 at 20:36 Comment(0)
L
2

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

Lashelllasher answered 29/11, 2020 at 18:24 Comment(0)
H
2

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.

Overview

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.

The setup

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.

Step 1: create tree object.

First, we create a tree object from the current index (which holds the staged items).

$ git write-tree
0d9651ad74328e747a053a9434d9867c8cd79d41 <-- output

Step 2: create two commits.

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.

Step 3.

Next, we write this commit into .git/refs/stash. You may want to back up this file.

$ echo 2c96b028e475a05d84f472da7f2a70ac53d0ac90 > .git/refs/stash

Step 4.

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.

Step 5.

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.

Summary

  1. We manually took the index with a staged change, and produced a tree object.

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

  3. Lastly, we patched this commit into the stash stack manually by editing a pair of files.

Appendix

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):

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

Holding answered 11/6, 2022 at 6:44 Comment(0)
E
0

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.

Erhard answered 7/2, 2013 at 20:26 Comment(15)
Thanks for the detailed post! This solves a lot of my issues for sure - the only problem I see is our current team has requested we keep all deliveries to a single commit. :(Tabatha
If they don't need or want your working history in the production repo that's fine: make your tracking master history-less by applying diffs rather than merging branches. You can work out how to keep a decorated-master branch that has the actual merged history and do your real work from that, that way it'll be easy to automate generating the correct diffs.Caligula
Note that git checkout master; git checkout -b bug2 can be shortened to git checkout -b bug2 master. The same applies to git checkout bug1; git rebase master; git checkout master; git merge bug1, which is identical to git rebase master bug1; git push . bug1:master (granted, the push trick is not obvious)Abrego
Thanks for the tipps, knittlErhard
ctote: What do you mean by a single commit? bug1 has to be delivered in a single commit? Or they want bug1, bug2, and bug3 all in a single commit? Either way, that's probably just a question of rebasing (and possibly squashing with rebase -i). If you give a detailed example of your problem, I can again provide a detailed answer.Erhard
Sorry, I should have been more clear. Yeah, so they want to keep our code history "clean" by only having a single commit per bug. This way, once an architect starts reviewing my code they can view a single consolidated commit. So, I assumed, if I wanted to branch to bug1, then start working on bug2, I'd have to commit b1 before transitioning to b2. I keep hearing horror stories about "trust me, you don't want to squash commits - it's so nasty" I haven't tried it; what's the big deal?Tabatha
I gave a walkthrough for stashing above in the main answer so I could use fancy formattingErhard
Thanks for all of the detail! The current process that most people have adopted (due to the limitation on allowed commits) has been to clone the same stream twice, once for development and the other for delivering / build testing. Essentially, they will #hackhack on "dev", create patch files and apply them to "baseline" to build test, back out all of their patch files and apply each patch individually to deliver. [continued...]Tabatha
[...] Can you see an advantage of one over the other? The primary advantage that is argued to me is, it's easier to do build tests on several bugs through applying several patch files to their "baseline" stream, rather than trying to keep all of their bugs logically separate in git.Tabatha
Yep, I can see their point. It is easier to apply stashed changes to a master branch for testing. But it's also much easier to screw up your changes pulling them in and out of stash. You're just going to have to find a workflow that works for you.Erhard
If you go with local branches & commits for bugs, you may want to check out: stackoverflow.com/questions/7516632/…. It would allow you to apply a branch to master without losing your branch pointer. If you decide to go with stashes, might I recommend working on only one bug at a time, stashing it, and then you can use git stash apply when you want to test multiple at once (but preserving their separate patches in the stash).Erhard
I've downvoted because this doesn't answer the original question. I'm in a branch working on something, and I just made a change that I think should be committed to the integration branch separately. All I want to do is stage that change and stash it so I can switch to another branch and commit separately, instead of my current "work in progress" branch. (Warning, git ranting ahead.) It is absurd that this is so difficult to do; I have to imagine that this is a common occurrence. (Working in one branch and spotting a quick change that needs to be made and forgetting to switch first.)Roehm
May have helped the OP but did not answer the question for future reference. I really wish people who answer would not assume they know all use-cases and assert their opinions. If you are going to give opinions only, at least answer the OP's original question first.Predial
There's nothing incorrect here, and it's a valuable perspective, but it also pretty objectively does not answer the question at all, so downvoted.Exhibition
in some cases (on very tiny projects, or projects not yet shared with others) it's more handy to work on a single branch and add clean commits for steps that you've done imho. But your answer most likely the best way to go in other cases. In a current case of mine, I just had to fix a tiny problem, wouldn't dare to make a branch for just one two-liner commit, as it can stand clean and tidy on it's own.Green
S
0

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.

Swanger answered 26/8, 2015 at 7:40 Comment(1)
I cannot see how this answer could help. It is not related to original question.Unsettle
G
0

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

Green answered 9/8, 2019 at 10:16 Comment(1)
a downvote without a comment is neither helpful to me nor the next reader... zaenks grumpy anon. Though I can imagine one problem with this one-liner: One should be very careful not to forget to add all wanted changes to the index, otherwise those important changes will also be deleted. But then again, un-careful use of any cli tool can be very dangerous to one's precious time and job in the worst case.Green
E
0

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)

Elagabalus answered 18/10, 2020 at 14:59 Comment(0)
S
-1

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.

Sizar answered 30/5, 2020 at 12:39 Comment(4)
Does this work properly if for some specific file I have both staged, and unstaged changes? At first glance, --name-only, it doesn't look like it handles that case. that answer seems much better.Zetana
Also, that's basically a copy of this answerZetana
@quetzalcoatl: Yes, it similar to that answer. But this one provide an alias which saves your typing for next commandsSizar
@quetzalcoatl: You are right. --name-only will not handle staged/unstaged changes at once. But why you mark first and then stash? It would be better to stash required changes right now. I recommend you to try git stash push --patch described hereSizar

© 2022 - 2024 — McMap. All rights reserved.