如何在Git中仅隐藏未暂存的更改?

380

我想使用以下工作流程:

  1. 暂存一些更改。
  2. 将未暂存的更改保存到堆栈中。
  3. 对阶段中的内容进行某些操作(构建、测试等)。
  4. 提交。
  5. 恢复未暂存的更改。

有没有办法执行第2步?

示例:

git init
echo one >file
git add file
git commit
echo two >>file
git add file
echo three >>file
git stash push
test
git commit
git stash pop

4
如果我没记错的话,“--keepindex”正是这样做的。 - sehe
5
如果构建失败,我不想提交这个版本。我知道我可以删除提交记录,但如果可能的话,我希望不用提交就能解决这个问题。 - Unapiedra
Sehe,谢谢。我可以确认这个有效。天哪,我看了过时的http://linux.die.net/man/1/git-stash手册。`man git stash`更好。 - Unapiedra
这帮助我学习了'index' == 暂存区。 - JonnyRaa
git push [-k|--keep-index]时,没有必要限制stash数据。相反,您可以在应用时决定并使用git cherry-pick -m2 -n stash仅选择未暂存的更改。请参见详细答案。 - kxr
显示剩余5条评论
16个回答

446

git stash push 命令有一个选项--keep-index,它可以完全满足您的需求,所以运行以下命令:

git stash push --keep-index

12
我一直使用 git stashsave 命令。也许是程序员的习惯使然,我总是坚持使用 apply/pop 命令来保持对称性。 - vhallac
184
注意:这仍将隐藏您所有的更改;与常规的 git stash save 唯一的区别在于它同时保留了已经暂存的更改。在上面的工作流程中,这应该很好地工作,因为您只需要将隐藏保存在本地副本之上,而本地副本已经有了一半的隐藏更改(Git 足够聪明,可以忽略掉它们)。但如果您在重新应用隐藏之前编辑了代码,则在应用时可能会出现合并冲突。FYI。 - peterflynn
4
@ytpete 这个问题困扰了我很多次。我真的希望 git 有一种方法可以只存储你不想要的东西... 我经常提交一些东西,然后做一个完整的 git stash,知道如果我提交的有问题,我可以使用 git commit --ammend 进行修正。 - rjmunro
35
这个解决方案对我来说不起作用,因为它存在peterflynn所描述的问题。这并不是一个好答案,因为它仍然隐藏了暂存的更改。有人有更好的解决方案吗? - user643011
5
文档似乎表明stash save现在已经被弃用了:“该选项已被弃用,建议使用git stash push。它与“stash push”的不同之处在于它不能接受路径参数,并且任何非选项参数都将形成消息。” - jocull
显示剩余10条评论

90

这可以分为3个步骤完成:保存已暂存的更改,隐藏其他所有更改,使用已暂存的更改还原索引。这基本上是:

git commit -m 'Save index'
git stash push -u -m 'Unstaged changes and untracked files'
git reset --soft HEAD^

这将完全按照你的要求执行。


4
注意:-u 参数也会将未被跟踪的文件存储起来。 - ma11hew28
这种方法本质上是在更多的工作中复制了 git stash save --keep-index 所做的事情。我看不出有任何优势。 - Inigo
19
不,这种方法并不会复制那种方法。请看peterflynn对被采纳答案的评论。 - Alexander Klauer
非常好,但是暂存未提交的更改的目的是在提交之前测试已暂存的更改。因此,执行第三个命令 git reset --soft HEAD^ 没有任何意义。 - Géry Ogam
专业提示:通常可以将单个字符标志组合在一起:-u -m 可以变成 -um,例如 git stash push -um "Greatest stashing ever" - Adi H
是的,这正是 OP 所要求的,也是我所寻找的。2018 年的编辑有所帮助。 - WeakPointer

46
从Git 2.35+(2022年第一季度)开始,您现在可以在git stash push命令中使用--staged标志(man),仅将更改暂存到索引中
由于您的问题要求完全相反的操作,我们有两个选择:
1. 反转操作,如下所示:
git stash push --staged            # Stash staged changes
git stash                          # Stash everything else
git stash pop stash@{1}            # Restore staged changes stash

将您想要暂存的更改进行分阶段,而不是您想要保留的更改。现在您只需运行以下命令:
git stash push --staged

我从另一个S/O帖子上的这个回答中得到了这个信息。

1
好的,我已经更新了我的答案,以引用我的另一个答案 - VonC
1
太棒了!这比我的git diff解决方案更优雅。 - Géry Ogam
这是一个很好的解决方案!我唯一的改变是我还需要储藏新文件,所以我运行了git stash --include-untracked,而不仅仅是git stash作为步骤2。 - ADataGMan
使用低于2.35版本的git,你可以先将想要存储的内容存储起来(使用git stashgit stash -p {你的文件名}),然后进行提交并弹出你的存储内容。 - undefined

37

不带暂存更改的Stash

--keep-index / -k的问题

在Git中,仅储存工作树(未暂存更改)比应该更困难。接受的答案以及其他答案会储存未暂存更改,并通过--keep-index保留阶段。

然而,不明显的是,--keep-index也会储存已暂存更改。已暂存更改最终同时出现在阶段和stash中。这很少是人们想要的,因为对stash进行任何中间更改都可能在稍后弹出stash时导致冲突。

别名解决方案

此别名很好地储存了只有工作副本更改:

stash-working = "!f() { \
  git commit --quiet --no-verify -m \"temp for stash-working\" && \
  git stash push \"$@\" && \
  git reset --quiet --soft HEAD~1; }; f"

该命令将临时提交已暂存的更改,从剩余的更改中创建一个存储(并允许传递其他参数,例如 --include-untracked--message 作为别名参数),然后重置临时提交以恢复已暂存的更改。

它类似于 @Simon Knapp 的答案,但有一些小差异--它在临时操作上使用了 --quiet ,并接受任意数量的参数用于存储 push ,而不是硬编码 -m,并且添加了 --soft 到最终的重置,使索引保持原样。它还在提交时使用 --no-verify 来避免来自 pre-commit 钩子的工作副本更改(HT:@Granfalloner)。

对于相反的问题,即只存储已暂存的更改(别名 stash-index),请参见此答案


5
作为这段代码的进一步改进,值得在 git commit 中添加 --no-verify 选项,否则由于预提交钩子而造成的隐式临时提交可能会严重破坏工作目录。 - Granfalloner
1
我在将其作为函数使用时更加顺利,但不确定为什么zsh无法处理上述语法:stash-working() { git commit --quiet --no-verify -m "temp for stash-working" && git stash push "$@" && git reset --quiet --soft HEAD~1; } - Lotus
2
@Lotus 尝试将其完全复制到您的 .gitconfig[alias] 部分中。这样,zsh 就完全不涉及了。 - Raman

37
git stash save --keep-index

另外,关于:

为什么不在将更改暂存后提交它们? - Shin

A:因为您应该始终提交测试过的代码 :) 这意味着您需要仅运行与要提交的更改相关的测试代码。

当然,除了这一点之外,作为经验丰富的程序员,您还具有测试和审查只有部分更改的内在冲动,但这只是某种程度上的玩笑


1
实际上,该命令也会隐藏已经暂存的更改。=( - Eugen Konkov

27

简而言之; 自从 git 2.35 实现了标志: git stash [push [-S|--staged]],你可以这样做:

git stashu

添加此别名后:

git config --global alias.stashu '!git stash push -S; git stash; git stash pop --index stash@{1}'"

解释:

git stash push -S                  # Stash staged changes
git stash                          # Stash everything else
git stash pop --index stash@{1}    # Restore staged changes into index

$ git config --global alias.stashu '!git stash push -S; git stash; git stash pop --index stash@{1}'

$ git diff 
```diff
diff --git a/src/js/modal.js b/src/js/modal.js
index d07c085..766e39a 100644
--- a/src/js/modal.js
+++ b/src/js/modal.js
@@ -6,10 +6,12 @@
 import "jquery-validation/dist/jquery.validate";
 import "bootstrap/dist/js/bootstrap.bundle";
 
+staged
 const FormDataJson = require('form-data-json-convert');
 FormDataJson.defaultOptionsToJson.uncheckedValue =  false;
 FormDataJson.defaultOptionsToJson.skipEmpty      =  true;
 
+unstaged
 import { TabulatorFull as Tabulator } from 'tabulator-tables/dist/js/tabulator';
 
 import { ajax_query } from "./common/ajax";
``` 

$ git add -p
```diff
@@ -6,7 +6,8 @@
 import "jquery-validation/dist/jquery.validate";
 import "bootstrap/dist/js/bootstrap.bundle";
 
+staged
 const FormDataJson = require('form-data-json-convert');
 FormDataJson.defaultOptionsToJson.uncheckedValue =  false;
 FormDataJson.defaultOptionsToJson.skipEmpty      =  true;
``` 

(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]? y
```diff
@@ -9,7 +10,8 @@
 const FormDataJson = require('form-data-json-convert');
 FormDataJson.defaultOptionsToJson.uncheckedValue =  false;
 FormDataJson.defaultOptionsToJson.skipEmpty      =  true;
 
+unstaged
 import { TabulatorFull as Tabulator } from 'tabulator-tables/dist/js/tabulator';
 
 import { ajax_query } from "./common/ajax";
```
(2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,?]? n

$ git status
On branch dev-Modal
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
modified:   modal.js

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
modified:   modal.js

$ git diff
```diff
diff --git a/src/js/modal.js b/src/js/modal.js
index ac48408..766e39a 100644
--- a/src/js/modal.js
+++ b/src/js/modal.js
@@ -11,6 +11,7 @@ const FormDataJson = require('form-data-json-convert');
 FormDataJson.defaultOptionsToJson.uncheckedValue =  false;
 FormDataJson.defaultOptionsToJson.skipEmpty      =  true;
 
+unstaged
 import { TabulatorFull as Tabulator } from 'tabulator-tables/dist/js/tabulator';
 
 import { ajax_query } from "./common/ajax";
``` 

$ git stashu 
Saved working directory and index state WIP on dev-Modal: 9f6e760 Imported Tabulator module
Saved working directory and index state WIP on dev-Modal: 9f6e760 Imported Tabulator module
On branch dev-Modal
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
modified:   src/js/modal.js

Dropped stash@{1} (509be8cf1afa340faf77aa6a2b8d008aa82a980a)

$ git stash show -p
```diff
diff --git a/src/js/modal.js b/src/js/modal.js
index d07c085..ed509ed 100644
--- a/src/js/modal.js
+++ b/src/js/modal.js
@@ -10,6 +10,7 @@ const FormDataJson = require('form-data-json-convert');
 FormDataJson.defaultOptionsToJson.uncheckedValue =  false;
 FormDataJson.defaultOptionsToJson.skipEmpty      =  true;
 
+unstaged
 import { TabulatorFull as Tabulator } from 'tabulator-tables/dist/js/tabulator';
 
 import { ajax_query } from "./common/ajax";
```

**BONUS for those who read till here. The alias to stash staged changes:
```
$ git config --global alias.stashs 'stash push -S'
```
And now you can `stash staged`:
```
git stashs
```


使用 git version 2.7.4,您可以使用 --patch 选项:

git stash save --patch

然后 git 会询问您是否将更改添加到存储区。
然后您只需回答 yn

您可以像往常一样恢复工作目录:

git stash pop

或者,如果您想在贮藏中保留已保存的更改:

git stash apply

这太棒了。虽然有点费力,但至少你可以跳过和添加整个文件。 - Dustin Oprea

24

要将未标记(未添加到提交)的文件添加到暂存区,运行以下命令:

git stash -k
如果您想将新添加的文件(未暂存 - 不是绿色的)也包含在隐藏区(stash)中,请按照以下步骤操作:
git stash -k -u

然后你可以提交已暂存的文件。之后,你可以使用以下命令获取上一次被stash的文件:

git stash pop

git stash pop

1
我认为这应该被接受为答案,因为已经被接受的那个如果你对同一文件进行了一些暂存和未暂存的编辑,你会得到 patch does not apply 的错误。 - iRestMyCaseYourHonor
如果您在暂存文件后修改了它,然后执行 git stash -k -ugit stash pop,您将会失去这些更改。请参见 https://dev59.com/YHcPtIcB2Jgan1znPtjE。 - Tobias Bergkvist
这个方法不起作用,因为它会将所有的文件(包括已暂存和未暂存的文件)都存入stash中。-k或者--keep-index并不能阻止已暂存的文件被添加到stash中,它只是为了方便操作后保留stash的完整性(而不是从stash中移除)。 - Adi H

7

Git没有一个仅存储未暂存更改的命令。

但是,Git允许您指定要存储哪些文件。

git stash push --message 'Unstaged changes' -- app/controllers/products_controller.rb test/controllers/products_controller_test.rb

如果您只想在这些文件中存储特定的更改,请添加--patch选项。

git stash push --patch --message 'Unstaged changes' -- app/controllers/products_controller.rb test/controllers/products_controller_test.rb
< p > --include-untracked 选项允许您隐藏未跟踪的文件。

git stash push --include-untracked --message 'Untracked files' -- app/controllers/widgets_controller.rb test/controllers/widgets_controller_test.rb

运行git help stash(或man git-stash)以获取更多信息。

注意:如果您的未暂存更改比较混乱,则@alesguzik的答案可能更容易。


5
扩展之前的答案,有时我会有一组复杂的更改已经暂存,但是我希望先提交一个单独的更改。例如,我可能发现了一个错误或其他不正确的代码,我想先修复它再进行暂存的更改。一个可能的解决方法是这样的:
首先将所有更改都存储起来,但保留已暂存的更改 $ git stash save --keep-index [--include-untracked]
现在将已暂存的更改单独存储 $ git stash save 进行修复并测试,然后提交更改: $ git add [--interactive] [--patch] $ git commit -m“fix...”
现在恢复之前暂存的更改: $ git stash pop 解决任何冲突,并注意,如果有冲突,git 将应用但不会删除顶部的stash条目。
(... 然后提交已暂存的更改,恢复所有其他更改的stash,并继续...)

3

我使用一个别名(alias),它接受一个字符串作为要存储条目的消息。

mystash = "!f() { git commit -m hold && git stash push -m \"$1\" && git reset HEAD^; }; f"

操作:

  • 提交索引中的所有内容,
  • 将工作树中发生的更改储藏起来(当然也可以添加-u-a),
  • 将最后一次提交重置回工作区(可能需要使用--soft以保留它在索引中的位置)。

谢谢,我用--soft标志有了一些成功,但我添加了两个小改进:如果没有文件被暂存,则使用git commit --allow-empty,并设置储藏信息的默认值为 $1。同时,我必须在最后添加 # 以避免信息被重复执行。这是我的代码:git config --global alias.save '!git commit --no-verify --allow-empty -m hold && git stash push -um "${1-unstaged}" && git reset --soft HEAD^ #' - Simon Baslé

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接