开发者

How do you tell git to stash the index only?

I have just used "git add -p" to add a bunch of changes to the index, and I just realised that I missed a 开发者_运维技巧change that should've gone into the previous commit.

I can't commit --amend now because I've added all these new changes to the index, and I don't want to use 'git reset' to remove them all from the index as it will take ages to add them all back in again.

What I need is something like 'git stash' that will only stash the index - it should leave the working files alone. Then I can stash the index, add the missing change, commit it, then pop the stash and have my index back the way it was.

It doesn't look like 'git stash' is able to do this, but am I missing something? Thanks!


The closest thing I've found is git stash --patch. It walks you through each of the changes to working tree and index letting you choose what to stash.

http://www.kernel.org/pub/software/scm/git/docs/git-stash.html


Why not cheat?

git stash --keep-index

to get everything out of there that's not in the index currently. Then,

git stash

to get a stash with just the stuff that's staged.

git stash pop

the first stash, add your changes. Then,

git commit --amend ...
git reset --hard

to clean up the working tree and then

git stash pop --index

to get your index changes back.


Simplest way is to leave off that change right now, make your new commit, then create a second commit with just that change you want to use to amend and then use git rebase -i to squash it with the original HEAD.

An alternative would be to make your commit, tag it, roll back with git reset HEAD^, add that one change and amend HEAD, then cherry-pick your tagged commit.


As of version 2.35, git offers the --staged option in the git stash push subcommand:

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


Commit your index, create a fixup commit, and rebase using autosquash:

git commit
git add -p                         # add the change forgotten from HEAD^
git commit --fixup HEAD^           # commits with "fixup! <commit message of HEAD^>"
git rebase --autosquash -i HEAD~3


git stash does actually create a commit with the index content, and then adds a commit with the content of all tracked files on top of it

To view this : create a stash, then run

git log --oneline --graph stash@{0}

So technically, when you stash, you can get back your index through stash@{0}^2 :

$ git show --name-only stash@{0}^2
$ git checkout stash@{0}^2 -- .

You can also stash, and then get the content of the tracked-but-not-added files :

# get the diff between what was indexed and the full stashed content :
$ git diff -p stash@{0}^2 stash@{0}  >  diff.patch
# apply this diff :
$ git apply diff.patch


magit for emacs lets you do this with the command magit-stash-index


This seems to work. I haven't tested it under all situations to make sure it's robust:

git commit -m _stash && git stash && git reset HEAD^ && git stash save  && git stash pop stash@{1}

But, it effectively commits the index temporarily, stashes the working directory, reverts the commit to get the index back, saves it as another stash, then restores the original working directory again from the stash.

This is simplified by adding this as a git alias:

[alias]
    istash = "!f() { git commit -m _stash && git stash && git reset HEAD^ && git stash save $1 && git stash pop stash@{1}; }; f"

This allows me to use it like:

git istash [optional stash name]


Most answers here are quite complicated, dangerous, or don't work in all situations. With a lot of changes "in-flight" (both in the index and the working tree), I don't want to even think about hard resetting, stashing, committing every change prematurely only to bring it back to life, or anything else that should work. The only thing I want to do is change the index itself.

Fortunately, there's an easy way to do that!

git diff --cached > 1.diff
git reset
# add and commit changes that should be separate
git apply --cached 1.diff

And there you have it! Save the current index as a diff, reset it, add and commit your separate changes, then reapply the changes to the index you saved.

In the case where the changes you wanted to separate were already applied to the index (and you don't want to bother removing them), you have to use one additional git reset HEAD^ before git apply in order to reset the index to the state it was when you took the diff. Otherwise, the patch won't apply again (as it has already been applied with your commit), and git will absolutely refuse to partially apply a patch.


git stash -k
git stash
git stash apply stash@{1}
git stash drop stash@{1}


Here's a little script I've come up with in the past to do exactly this:

(Note: I originally posted this at https://stackoverflow.com/a/17137669/531021, but it seems to apply here as well. These aren't exactly duplicate questions, so I think it serves as a possible answer in both cases)

#!/bin/sh

# first, go to the root of the git repo
cd `git rev-parse --show-toplevel`

# create a commit with only the stuff in staging
INDEXTREE=`git write-tree`
INDEXCOMMIT=`echo "" | git commit-tree $INDEXTREE -p HEAD`

# create a child commit with the changes in the working tree
git add -A
WORKINGTREE=`git write-tree`
WORKINGCOMMIT=`echo "" | git commit-tree $WORKINGTREE -p $INDEXCOMMIT`

# get back to a clean state with no changes, staged or otherwise
git reset -q --hard

# Cherry-pick the index changes back to the index, and stash.
# This cherry-pick is guaranteed to suceed
git cherry-pick -n $INDEXCOMMIT
git stash

# Now cherry-pick the working tree changes. This cherry-pick may fail
# due to conflicts
git cherry-pick -n $WORKINGCOMMIT

CONFLICTS=`git ls-files -u`
if test -z "$CONFLICTS"; then
    # If there are no conflicts, it's safe to reset, so that
    # any previously unstaged changes remain unstaged
    #
    # However, if there are conflicts, then we don't want to reset the files
    # and lose the merge/conflict info.
    git reset -q
fi

You can save the above script as git-stash-index somewhere on your path, and can then invoke it as git stash-index

# <hack hack hack>
git add <files that you want to stash>
git stash-index

Now the stash contains a new entry that only contains the changes you had staged, and your working tree still contains any unstaged changes.

The main gotcha is that you may not be able to cleanly remove the indexed changes without causing conflicts, e.g. if the working tree contains changes that depend on the indexed changes.

In this case, any such conflicts will be left in the usual unmerged conflict state, similarly to after a cherry-pick/merge.

e.g.

git init
echo blah >> "blah"
git add -A
git commit -m "blah"

echo "another blah" >> blah
git add -A
echo "yet another blah" >> blah

# now HEAD contains "blah", the index contains "blah\nanother blah"
# and the working tree contains "blah\nanother blah\nyetanother blah"

git stash-index

# A new stash is created containing "blah\nanother blah", and we are
# left with a merge conflict, which can be resolved to produce
# "blah\nyet another blah"


I had to do this, and ended up using some of the git stash options intended for scripting. Git stash create let's you create (but not apply) a stash object (which includes the index). Git stash store adds it to the git stash stack without doing the git reset --hard that git stash save does implicitly. This, effectively, let's you stash an index by adding a stash object to the stack and resetting the index manually:

# This will add an entry to your git stash stack, but *not* modify anything
git stash store -m "Stashed index" $(git stash create)

# This will reset the index, without touching the workspace.
git reset --mixed 

You've now, effectively, stashed your index and can do a git stash pop --index when you're done.

One caveat is that the stash object created includes both the files changed in the workspace and the index, even though you're only concerned about the index, so there's a possibility of conflicts when you try and pop from the stack even though you used the --index flag (which means "also apply the index from the stash", not "only apply the index from the stash"). In that case, you can do a "git reset --hard" before popping (because the changes that you're resetting happen to also be the same as the changes that you're popping or applying immediately after, it's not as dangerous as it seems.)


If you add files ready to commit to the index, but want to stage only them, you can use the function below.

>> momomo.com.git.stash.added

or

>> momomo.com.git.stash.added "name of the  stash"

Bash function:

momomo.com.git.stash.added() {
    local name="$1";

    if [[ "${name}" == "" ]]; then
        name="$(date)"
    fi

    # This will stash everything, but let the added ones remain
    # stash@{1} 
    git stash --keep-index

    # This will stash only the remaining ones
    # @stash@{0}
    git stash save "${name}"

    # Restore everything by applying the first push stash which is now at index 1
    git stash apply stash@{1}

    # Then drop it
    git stash drop stash@{1}

    # At the top of the stash should now be only the originally indexed files
}

Note that you will need to re add things to the index now.


Let's assume your working copy contains some dirty changes and changes in the index.

To move changes from index to stash we use option -p that allows to ignore changes in the index. So we need extra steps to "invert" operation: first take away dirty changes, then stash index, than return dirty changes:

$ git stash push -m dirty -p
$ git stash push -m index

$ git stash list
stash@{0}: On master: index
stash@{1}: On master: dirty

$ git stash pop stash@{1}

As it might be boring to accept every change in an interactive hunk selection mode there is an interactive option:

a - stash this hunk and all later hunks in the file


There is no need for such complicated answers for most situations. Assuming I'm not mistaken and understood the situation well.

As it is said, Magit has a perfect functionality for this specific situation. You can do what Magit does to some extent (with similar workflows) only using git commands like below:

git add -u  # Add all the changed files to stage
git restore --staged -p .  # Add what you don't want to the unstaged section where you can stash
# Then do the classic `-k`
git stash -k  # Stash all the unstaged hunks
git restore --staged .  # unstage everything and continue with your development

The idea is to do the reverse of what you want and then swap the unstage with your stage for there is no reverse to -k (as far as I know) that stashes "just the index".


Here's a (kind of) simple recipe (at least to follow):

git commit -m "Will come back and this will be removed, no worries"
git stash save "will drop it, no worries"
git reset --soft HEAD~1 # getting rid of previous revision
git stash save "this is what I really wanted to stash, set the right message"
git stash pop stash@{1} # get files that I didn't want to stash on the working tree
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜