'git stash apply' with Interactive Mode
Asked Answered
G

4

69

I have a serie of files into a stash (stash{0}) and I would like to git apply just some parts/hunks of these files (usually known as Interactive mode).

Is it possible?

I've seen that is possible to perform a

git stash save -p 'Stash name'

but it seems not possible to do

git stash apply -p 'Stash name'

Do you know a way to achieve it?

Gambrell answered 28/1, 2015 at 15:16 Comment(3)
Relevant: https://mcmap.net/q/40880/-how-would-i-extract-a-single-file-or-changes-to-a-file-from-a-git-stashInchon
Relevant: https://mcmap.net/q/40880/-how-would-i-extract-a-single-file-or-changes-to-a-file-from-a-git-stashInchon
Context for the mystery links given by @0_ in the above comments: those are answers to the question How would I extract a single file (or changes to a file) from a git stash?. Although that question is very similar in its goal, this question and its responses are focused on interactively patching from the stash.Pibgorn
O
114

Is it possible?

Yes it is!

git checkout -p stash@{0}

Where you can replace the 0 in stash@{0} with the index of the stash you want to apply.

Use git stash list and git show -p stash@{n} if unsure which n is the stash you want to apply.

Don't forget to git stash drop stash@{n} when you know you won't need that stash anymore, since git checkout obviously will not drop the stash for you.

Why does it work?

The key is to realize that stashes are, in essence, references to commits just like tags and branches.

Indeed, they're stored in .git/refs/stash, one line per stash hash.

Caveats

As @mgadda mentioned in the comments below, git checkout -p tries to apply the whole difference between a commit and the current workspace.

In the case of a git stash, if the stash you're trying to apply was done against a different commit, then git checkout -p stash@{n} will try to apply interactively all the differences between the commit stash@{n} and the commit of the current workspace, including all their parent commits that are different.

For example, if you're trying to apply a stash that was saved "many commits ago" into the current workspace, git checkout -p stash@{n} will try to apply not only the changes in the stash proper, but will also try to revert all changes that happened between the commit on which the stash is based and the current commit.

Conversely, if you're trying to apply a stash "from the future", i.e. into a branch that is a number of commits from before the commit on which the stash is based, then git checkout -p stash@{n} will try to also apply all the other changes that happened between the current commit and the commit from the future, besides the changes from the stash itself.

(In case you're wondering, git checkout -p stash@{n} a stash from a parallel branch will try to revert all changes between the current commit and the original branching point and also apply all changes between the branching point and the other branch, besides the change in the stash).

Workarounds

There are a few workarounds, none of them are perfect for every situation:

    1. Be really careful with the patches you accept when you do git checkout -p stash@{n}
    1. Do a git stash pop, then git stash again before doing git checkout -p .... But if you wanted to do a partial apply of your stash to avoid conflicts, this won't really help. In that case, see solution 4 below.
    1. If you have a graphical diff tool supported by git (like meld), you can use git difftool and "apply left" only the changes you're interested in:
    • git difftool -d stash@{n} to compare a whole stash and all its files

    • git difftool stash@{n} -- path/to/file to compare a single file

    1. (Based on @andrew's answer) On a detached head, go back to the "parent" commit of the stash you're interested in, apply the stash, re-stash interactively only the parts you're interested in, go back and reapply the smaller stash.

Step by step:

git checkout stash@{n}^  # notice the "^". 

# Now you're in a detached head in the parent commit of the stash.
# It can be applied cleanly:
git stash apply stash@{n}

# Now save only the diffs you're interested in:
git stash -p

# remove the rest of the old stash
git checkout -- .  # be careful or you could remove unrelated changes

# go back to the branch where you want to apply the smaller stash
git checkout <my previous branch>

# apply the smaller stash
git stash pop
Orleans answered 28/4, 2017 at 19:47 Comment(1)
There's a caveat here that needs mentioning: because stashes are just commits, that means they also have parent commits which are not guaranteed to be the same parent commits as the one you want to interactively apply changes to. Rule of thumb: if you stashed from some other commit than the currently checked out commit, this technique will not do what you expect. Workaround: apply the entire set of changes from your stash (with git stash pop), then stash again (git stash). Now you can git checkout -p as desired.Tyburn
H
13

What I often do (in git bash) is

git stash show -p 'stash@{0}' >tmp.patch

Then I edit the file and remove the parts I don't want. Finally I say

<tmp.patch git apply

or

<tmp.patch patch -p1

It doesn't work for binary files, though, but neither does the accepted answer (using checkout -p) for them.

Harwood answered 18/12, 2019 at 8:18 Comment(3)
Nice alternative, thanks! – I think that anyway, as in the accepted answer, it will be necessary to checkout the parent commit that the stash refers to, prior to creating the patch; otherwise it will end up also including a lot of changes from intermediate commits (in between the "currently checked out commit" and the "stash's parent commit"), as mentioned in mgadda's comment.Gambrell
no, the content of tmp.patch does not depend on what you've checked out while creating it. Only git apply will be affected and it will report merge conflicts if the affected lines of code have changed in the meantimeHarwood
Sorry, I missed the "I edit the file and remove the parts I don't want" part. That is also, IMHO, the annoying part since editing manually is error prone.Gambrell
S
4

One possible way is to reset the index and then use interactive add

# 0. ensure there are no uncommitted changes
git status

# 1. apply a changeset as is
git stash apply stash@{n}
# ... fix or discard conflicts if any

# 2. reset the index 
git reset

# 3. interactively add the required chunks (except new files)
git add -p

# 4. stash all other changes
git stash save --keep-index "comment"
# 4. or just discards all other changes in the working tree
git checkout-index -f -a

# 5. commit
git commit -m "comment"

Another way is to use interactive reset in place of interactive add.

# 0. ensure the working tree does not have unstaged changes
git status

# 1. apply a changeset as is
git stash apply stash@{n}
# ... fix or discard conflicts if any

# 2. interactively exclude the unneeded chunks from the index 
git reset -p

# 3. stash all other changes
git stash save --keep-index "comment"
# 3. or just discards all other changes in the working tree
git checkout-index -f -a

# 4. commit
git commit -m "comment"
Sundae answered 5/9, 2018 at 13:16 Comment(0)
N
3

I don't think there's a way to apply changes by hunks (or even by file). You will have to apply the stash, then stash the changes you don't want interactively (with git stash save -p). If you're worried about conflicts, you can stash any uncommitted changes first, apply your stash, stash any conflicting hunks and then apply the other stash.

Nuisance answered 28/1, 2015 at 15:27 Comment(2)
Yeah, I'm mainly asking cause I want to avoid conflicts. The purpose is to get some changes from (let's say) branch_A and be able to put them on branch_B, being able to directly avoid the conflicts that these two branches could have. Your solution works, but is exactly the "complicated" way that I wanted to avoid ;-PGambrell
The reverse approach, to just git stash save interactively the hunks I do want and then restore it on the desired branch, instead of git stash apply interactively the hunks I do want, seems the best approach.Gambrell

© 2022 - 2024 — McMap. All rights reserved.