How to ignore added hunks in `git stash -p`

白昼怎懂夜的黑 提交于 2021-02-06 10:08:40

问题


Imagine this scenario:

# edit two files
git add -p // add hunks from one file

Now when you run git stash -p, it will again ask you whether you want to stash the hunks that you just selected via git add -p. Is there some way to configure git to ignore these already-added hunks by default? Most of the time, I don't want to stash stuff that I added already.


回答1:


Okay, from comments, what's wanted is a stash of all changes that haven't already been added (whether excluded during a git add -p, or just not added yet).

The reason for this is to apply some tests/potential tweaks to the staged state, before committing it.

That's a straight git stash -k, stash everything as usual but keep the indexed state and the corresponding worktree around, i.e. clean from the worktree everything I'm not about to commit.

So:

git stash -k
git clang-format
git commit

and the repository now has four interesting snapshots: the original content aka stash base, the snapshotted index, the snapshotted worktree, and the current index(, commit, and worktree) which is the index snapshot at stash^2 with the cleanups applied. Note that all three of the new snapshots aka commits here have the stash base as parent.

Now you want your worktree changes back, but clearly the changes from the base to the stashed index and worktree do not match the ones in the current index and worktree (and new commit, those all match), so when git goes to pop the stash it will find conflicts: changes from the stashed base to the stashed index do not match the changes from the stash base to the current index.

If Git offered what you want directly, a "stash all the worktree changes except the ones in the index", you could have used that and then the stash pop wouldn't have any trouble, a straight git stash pop would do it. Fortunately, if Git's good at anything, it's merging, combining, splitting, and general all-around munging of differences.

git cherry-pick -nm2 stash
# cherry-pick updated the index, too. maybe git reset here to unstage that stuff.
git stash drop

Stash pop is a merge of the changes from the stash base to the stashed state with the changes from the stash base (which is usually remarkably similar to the current base) to the current state. You want the stashed worktree changes back in your worktree, but only the ones you hadn't already added, since the ones you had added are all still here, they're just a little different now.

So the cherry-pick is -n, no commit, -m2, mainline for changes is its second parent, i.e. all the differences you had made but not added when stashing.

An example might help,

 cd `mktemp -d`
 git init
 printf >file %s\\n 1 2 3 4 5
 git add .;git commit -m1
 printf >file %s\\n 1 2a 3 4 5
 git add .
 printf >file %s\\n 1 2a 3 4a 5

now you've effectively git add -p'd the 2a change, and the 4a change is only in your worktree.

 $ git stash -k
 $ cat file
 1
 2a
 3
 4
 5
 $ sed -i '2s,^,_,' file   # indent the 2a line
 $ git commit -am2

Now, the initial commit, :/1, is 1 2 3 4 5, your current commit, index and worktree are all 1 _2a 3 4 5, your stashed index is 1 2a 3 4 5 and your stashed worktree is 1 2a 3 4a 5.

The changes you want back are the difference between your stashed index and your stashed worktree, that's the stash commit's differences from its second parent. Hence, that cherry-pick.


Alternate ways of spelling the cherry-pick include

git cherry-pick -nm1 -Xours stash

which applies all the stashed worktree changes but takes the local version in case of conflict (basically it's finding and throwing away the conflicting differences instead of just avoiding them as the -m2 does) and

git diff stash^2..stash|git apply -3

Making all this more easy on yourself is scripting territory, the easiest way to talk about setting it up is as a git alias,

git config --global alias.poptree '!git cherry-pick -nm2 stash; git reset; git stash pop'

and you now have a git poptree command to do what you want.


edit: as a further fillip, suppose you had gone ahead and done some more work before remembering your stashed worktree changes, the cherry-pick will correctly update the worktree and index but the reset will back out any changes you had already added to the new index. Now you're in core-command territory,

( index=`git write-tree` &&
  git cherry-pick -nm2 stash &&
  git read-tree $index &&
  git stash drop
)

is how I'd implement this for real.




回答2:


There is a similar example in the manpage:

man git stash:

"Testing partial commits
You can use git stash save --keep-index when you want 
to make two or more commits out of the changes in the 
work tree, and you want to test each change before 
committing:

# ... hack hack hack ...
$ git add --patch foo            # add just first part to the index
$ git stash save --keep-index    # save all other changes to the stash"*

I can confirm:

If you use git stash -p (which implies --keep-index), you still get asked if the changes that are already in the index, should be stashed (as you have described).

So, it seems the manpage is confusing, which is also mentioned elsewhere: https://github.com/progit/progit2/issues/822

To sum it up:

--keep-index (or -p which implies --keep-index) just leaves the index intact. The changes already staged still get inserted into the stash. And AFAIK, there is no way to do what you described.

Or, more precicely (again from the manpage):

With --patch, you can interactively select hunks from 
the diff between HEAD and the working tree to be stashed. 

The stash entry is constructed such that its index state 
is the same as the index state of your repository, and its 
worktree contains only the changes you selected interactively.

Alternatives:

There are at least 3 ways you could achieve what you want (more or less):

  • Don't use -p with git stash. Stash everything (with --keep-index and possibly --all, to make sure you've stowed away everything safely).
  • Commit your staged changes before stashing. That way you won't have a diff between HEAD and working tree for these changes you want to omit from stash. But, what if you're not sure you want to commit this yet? You can always make changes later and use --amend to change existing commit.
  • Unstage your changes (remove from index) and then stash.



回答3:


When it comes to git stash push (since calling git stash without any arguments is equivalent to git stash push), consider adding the --keep-index option.

It means all changes already added to the index are left intact.

So the -p (patch) option should not query for those (already cached) hunks.

Note: The --patch option implies --keep-index, so (for testing) make sure you are using the latest Git version available (2.17) and try git stash push -p.

If the issue persists, then, as commented, doing a commit first will allow for a stash -p to operate with a clean index.
The git reset --soft @~ will restore the committed index.



来源:https://stackoverflow.com/questions/50242489/how-to-ignore-added-hunks-in-git-stash-p

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!