仅在 git 中暂存分阶段的更改 - 有可能吗?
Posted
技术标签:
【中文标题】仅在 git 中暂存分阶段的更改 - 有可能吗?【英文标题】:Stashing only staged changes in git - is it possible? 【发布时间】:2013-01-23 11:05:23 【问题描述】:有没有一种方法可以只存储我的分阶段更改?我遇到问题的场景是我在给定时间处理了几个错误,并且有几个未分阶段的更改。我希望能够单独暂存这些文件,创建我的 .patch 文件,然后将它们隐藏起来,直到代码获得批准。这样,当它被批准后,我可以隐藏我的整个(当前)会话,弹出该错误并推送代码。
我是不是走错了路?我是否误解了 git 如何以其他方式工作以简化我的流程?
【问题讨论】:
是的,您可能做错了事才陷入这种情况。仍然是一个有用的问题。在开始下一个修复之前,您真的应该隐藏或分支。切线的答案***.com/a/50692885 可能是在 git 中处理此问题的更好方法。如果我从上游拉取提交,玩弄存储经常会对我的工作区产生奇怪的影响。 使用 Git 2.35(2022 年第一季度),git stash push --staged
正式支持此功能。见my answer below
【参考方案1】:
是的,DOUBLE STASH
是可能的-
暂存所有需要暂存的文件。
运行
git stash --keep-index
。此命令将使用您的 ALL 更改(已暂存和未暂存)创建一个暂存区,但会将暂存的更改保留在您的工作目录中(仍处于暂存状态)。李>
运行git stash push -m "good stash"
现在您的"good stash"
有只有暂存文件。
现在,如果您在存储之前需要未暂存的文件,只需应用第一个存储(使用 --keep-index
创建的文件),现在您可以删除存储到 "good stash"
的文件。
享受
【讨论】:
这不知何故遗漏了所有新文件(甚至暂存)。 @Aurimas,要存储新文件,您需要使用-u
开关。
当您重新应用第一个 stash 并将所有更改恢复时,您可能只对 unstages 更改感兴趣,请使用 git stash apply --index
选项。这将尝试保持您的未(暂存)状态。现在更容易从工作树中删除不需要的更改。
我认为这个答案很混乱,将使用git stash push --patch
代替
您可以在运行git stash --keep-index --include-untracked
的步骤2中包含未跟踪的文件。【参考方案2】:
使用最新的 git,您可以使用 --patch
选项
git stash push --patch # since 2.14.6
git stash save --patch # for older git versions
并且 git 会询问您文件中的每个更改是否添加到存储中。
你只需回答y
或n
UPDDOUBLE 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"'
现在您可以暂存文件,然后运行 git stash-staged
。
结果您的暂存文件将保存到存储中。
如果您不想保留暂存文件并将它们移动到存储中。然后你可以添加另一个别名并运行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"'
【讨论】:
技术上并没有回答这个问题——但是一个非常好的技术可以实现选择性存储。 同意,这没关系,但这里的问题是我已经完成了所有这些我想要做的更改的分期工作(表面上最初是提交,但现在想要藏起来),而不是想从头再来。 不适用于新创建的文件(仅适用于修改后的文件) @DerekLiang:根本不跟踪新创建的文件。您可能应该检查git-stash
的-u|--include-untracked
选项
来自docs: "save: 此选项已弃用,取而代之的是 git stash push。它与 'stash push' 不同它不能采用路径规范,并且任何非选项参数都会形成消息。”【参考方案3】:
TL;DR 只需为您的 git
<pathspec>
参数添加-- $(git diff --staged --name-only)
这是一个简单的单行:
git stash -- $(git diff --staged --name-only)
然后简单地添加一条消息:
git stash push -m "My work in progress" -- $(git diff --staged --name-only)
在 v2.17.1 和 v2.21.0.windows.1
上测试限制:
请注意,如果您没有暂存文件,这将隐藏所有内容。 此外,如果您有一个仅部分暂存的文件(即只有一些更改的行被暂存,而其他一些更改的行没有),那么 整个文件将被隐藏(包括未暂存的行)。
【讨论】:
我认为这是描述情况的最佳选择:易于理解且不涉及任何黑魔法! 这很整洁。我最终用它创建了一个别名! @IgorNadj 当然!这是:github.com/panchalkalpesh/git-aliases/commit/… 请注意(至少在我的机器上,2.27.0.windows.1)这仅在您位于存储库的***目录中时才有效。 @marco6,使用--relative
标志来解决这个问题【参考方案4】:
我制作了一个脚本,它只存储当前上演的内容并保留其他所有内容。当我开始进行太多不相关的更改时,这真是太棒了。只需暂存与所需提交无关的内容并将其隐藏即可。
(感谢 Bartłomiej 的起点)
#!/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
【讨论】:
我要补充一点,您可以按照 thediscoblog.com/blog/2014/03/29/custom-git-commands-in-3-steps 将脚本转换为 git 命令 这太棒了!如果用户不在命令行上输入存储描述,我对其进行了调整以提示用户提供存储描述:gist.github.com/***inc/e2589a8c5ca33f804e4868f6bfc18282 谢谢,我在这里投票并把它变成了别名:***.com/a/60875067/430128。 酷!这完成了工作。其他解决方案不是我想要的。谢谢【参考方案5】:为了完成同样的事情......
-
仅暂存您要处理的文件。
git commit -m 'temp'
git add .
git stash
git reset HEAD~1
轰隆隆。您不想要的文件被隐藏起来。你想要的文件都准备好了。
【讨论】:
这是最好的答案,也是最容易记住的 赞成是因为有用,但这并不能回答问题——最终在存储中的是步骤 #1 中的未分级更改,而问题是询问是否仅存储分级更改。你正在回答这个问题的反面:***.com/q/7650797/430128。我构建了一个基本上可以做到这一点的别名,并在此处进行了一些增强:***.com/a/60875082/430128。 @Raman 只需添加另一个 git stash,最新的 stash 将包含分阶段的更改。【参考方案6】:在 Git 中仅存储索引(分阶段更改)比应有的困难。我发现@Joe 的answer 运行良好,并将它的一个小变体变成了这个别名:
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"
它:
验证是否存在实际分阶段的更改(git diff --cached --exit-code
如果有,则返回非零状态)。 HT:@nandilugio
它将分阶段和非分阶段的更改推送到一个临时存储区,只留下分阶段的更改。
然后它将分阶段的更改推送到存储区,这是我们想要保留的存储区。传递给别名的参数,例如 --message "whatever"
将被添加到此存储命令中。
它会弹出临时存储以恢复原始状态并删除临时存储,然后
最后通过反向补丁应用程序从工作目录中“删除”隐藏的更改。
有关仅存储未暂存更改(别名stash-working
)的相反问题,请参阅this answer。
【讨论】:
你会如何做相反的事情?我只需要存储未暂存的更改 @scaly 查看最后一句中的链接。 很好的答案!我已经对其进行了一些更新,以免在通过添加! git diff --cached --exit-code --quiet && \
作为第一行的情况下不做任何错误的事情,因此在这种情况下我们中止(&&
s 链中的非零退出首先)。另请注意,这在设置 diff.noprefix = true
(git 版本 2.29.2)时不起作用,但这对我来说没问题,因为我也有 diff 的别名,所以我刚刚添加了 --no-prefix
到那些。跨度>
【参考方案7】:
在这种情况下,我更喜欢为每个问题创建新的分支。我使用前缀 temp/,所以我知道以后可以删除这些分支。
git checkout -b temp/bug1
暂存修复 bug1 的文件并提交。
git checkout -b temp/bug2
然后,您可以根据需要从各个分支中挑选提交并提交拉取请求。
【讨论】:
虽然花哨的隐藏声音很高兴知道,但实际上这似乎是一种我不太可能失败的方法。 使用“git cherry-pick tmpCommit”来获取临时提交 w.o.合并提交或“git merge tmpCommit”+“git reset HEAD^”以获取没有提交的更改。 正如这个答案所示,有时最好直接询问您想要实现的目标,而不是如何使用给定的技术实现它。在复杂的情况下,临时分支和樱桃采摘很方便。 如果您部分暂存文件,则需要先存储更改,然后再返回原始分支并再次弹出它们【参考方案8】:在 Git 2.35(2022 年第一季度)中,“git stash
”(man) 学习了 --staged
选项来隐藏已添加到索引中的内容(仅此而已)。
所以现在正式支持(8 年后)。
参见commit a8a6e06(2021 年 10 月 28 日)和 commit 41a28eb(2021 年 10 月 18 日)Sergey Organov (sorganov
)。(由 Junio C Hamano -- gitster
-- 合并于 commit 44ac8fd,2021 年 11 月 29 日)支持>
stash
: 为 'push' 和 'save' 实现 '--staged' 选项签字人:Sergey Organov
仅存储暂存的更改。
此模式允许轻松隐藏以供以后重用一些与当前正在进行的工作无关的更改。
与'
stash push --patch
'不同,--staged
支持使用任何工具来选择要隐藏的更改,包括但不限于'git add --interactive
'(man) .
git stash
现在包含在其man page 中:
'git stash' [push [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]
git stash
现在包含在其man page 中:
save [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]
git stash
现在包含在其man page 中:
-S
--staged
此选项仅对
push
和save
命令有效。仅存储当前暂存的更改。这类似于 基本
git commit
,除了状态被提交到存储区 当前分支的。
--patch
选项优先于这个选项。
git stash
现在包含在其man page 中:
Saving unrelated changes for future use
当你正处于巨大的变化之中,你会发现一些 您不想忘记修复的无关问题,您可以执行 更改,暂存它们,并使用
git stash push --staged
存储它们 供将来使用。 这类似于提交分阶段的更改, 只有提交最终会在 stash 中,而不是在当前分支中。---------------------------------------------------------------- # ... 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 ----------------------------------------------------------------
【讨论】:
【参考方案9】:您为什么不提交某个错误的更改并从该提交及其前身创建一个补丁?
# 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
然后,要创建适当的补丁,请使用git format-patch
:
git format-patch HEAD^^
这将创建两个文件:0001-fix-bug-123.patch
和 0002-fix-bug-321.patch
或者您可以为每个错误创建单独的分支,这样您就可以单独合并或重新定位错误修复,如果它们不起作用,甚至删除它们。
【讨论】:
【参考方案10】:git stash --keep-index
是一个很好的解决方案...除了它在已删除的路径上无法正常工作,这已在 Git 2.23(2019 年第三季度)中修复
参见Thomas Gummerer (tgummerer
)commit b932f6a(2019 年 7 月 16 日)。(由 Junio C Hamano -- gitster
-- 合并于 commit f8aee85,2019 年 7 月 25 日)
stash
:修复使用--keep-index
处理删除的文件
git stash push --keep-index
应该保留所有更改 已添加到索引中,无论是在索引中还是在磁盘上。目前,当从索引中删除文件时,这不会正确运行。不是将其保留在磁盘上删除,而是 **--keep-index 当前恢复文件。* *
通过在无覆盖模式下使用“
git checkout
”来修复该行为 可以忠实还原索引和工作树。 这也简化了代码。请注意,如果未跟踪的文件与已在索引中删除的文件同名,这将覆盖未跟踪的文件。
【讨论】:
【参考方案11】:另一种方法是使用您不想隐藏的文件创建一个临时提交,然后隐藏剩余的文件并轻轻删除最后一次提交,保持文件完整:
git add *files that you don't want to be stashed*
git commit -m "temp"
git stash --include-untracked
git reset --soft HEAD~1
这样你只触摸你想被触摸的文件。
注意,这里使用“--include-untracked”来存储新文件(这可能是您真正想要的)。
【讨论】:
【参考方案12】:是否绝对有必要同时处理多个错误? “一次”是指“同时为多个错误编辑文件”。因为除非您绝对需要,否则我一次只会在您的环境中处理一个错误。这样你就可以使用本地分支和变基,我发现这比管理复杂的存储/阶段要容易得多。
假设 master 在提交 B。现在处理错误 #1。
git checkout -b bug1
现在您在分支 bug1 上。进行一些更改,提交,等待代码审查。这是本地的,因此您不会影响其他任何人,并且从 git diffs 制作补丁应该很容易。
A-B < master
\
C < bug1
现在您正在处理 bug2。使用git checkout master
返回返回以掌握。创建一个新分支,git checkout -b bug2
。进行更改、提交、等待代码审查。
D < bug2
/
A-B < master
\
C < bug1
让我们假设其他人在您等待审核时在 master 上提交了 E & F。
D < bug2
/
A-B-E-F < master
\
C < bug1
当您的代码获得批准后,您可以通过以下步骤将其变基为 master:
git checkout bug1
git rebase master
git checkout master
git merge bug1
这将导致以下结果:
D < bug2
/
A-B-E-F-C' < master, bug1
然后你可以推送、删除你的本地 bug1 分支,然后你就可以走了。在您的工作空间中一次出现一个错误,但通过使用本地分支,您的存储库可以处理多个错误。这避免了复杂的舞台/隐藏舞蹈。
在cmets中回答ctote的问题:
好吧,您可以为每个错误返回隐藏,一次只处理一个错误。至少可以为您节省分期问题。但是试过这个,我个人觉得很麻烦。在 git 日志图中,Stash 有点混乱。更重要的是,如果你把事情搞砸了,你就无法恢复。如果你有一个脏的工作目录并且你弹出一个存储,你不能“撤消”那个弹出。搞砸已经存在的提交要困难得多。
所以git rebase -i
。
当您将一个分支变基到另一个分支时,您可以交互地进行(-i 标志)。执行此操作时,您可以选择要对每个提交执行的操作。 Pro Git 是一本很棒的书,它也是 html 格式的在线书籍,并且有一个很好的关于 rebase 和 squashing 的部分:
http://git-scm.com/book/ch6-4.html
为方便起见,我将逐字窃取他们的示例。假设您有以下提交历史记录,并且您想将 bug1 变基并压缩到 master 上:
F < bug2
/
A-B-G-H < master
\
C-D-E < bug1
这是您在输入 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.
#
要将分支的所有提交压缩为单个提交,请将第一个提交保留为“pick”,并将所有后续“pick”条目替换为“squash”或简单的“s”。您也将有机会更改提交消息。
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
所以,是的,squashing 有点痛苦,但我仍然会推荐它而不是大量使用 stash。
【讨论】:
感谢您的详细帖子!这肯定解决了我的很多问题——我看到的唯一问题是我们当前的团队要求我们将所有交付保持在一个提交中。 :( 如果他们不需要或不希望您在生产仓库中的工作历史,那很好:通过应用差异而不是合并分支来减少跟踪主历史。您可以研究如何保留具有实际合并历史记录的装饰主分支并从中进行实际工作,这样就可以轻松自动生成正确的差异。 请注意,git checkout master; git checkout -b bug2
可以缩短为 git checkout -b bug2 master
。这同样适用于git checkout bug1; git rebase master; git checkout master; git merge bug1
,它与git rebase master bug1; git push . bug1:master
相同(当然,push
的技巧并不明显)
我在主要答案中提供了一个演练,以便我可以使用花哨的格式
我投了反对票,因为这不能回答最初的问题。我在一个分支工作,我刚刚做了一个我认为应该单独提交到集成分支的更改。我要做的就是暂存更改并将其存储,以便我可以切换到另一个分支并单独提交,而不是我当前的“正在进行的工作”分支。 (警告,git 在前面咆哮。)这很难做到,这太荒谬了。我不得不想象这是一个常见的事件。 (在一个分支中工作,发现需要进行的快速更改却忘记了先切换。)【参考方案13】:
在您对 Mike Monkiewicz 的回答中,我建议使用更简单的模型:使用常规开发分支,但使用合并的 squash 选项在您的主分支中获得单个提交:
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
这个过程的好处是可以使用正常的git工作流程。
【讨论】:
我看不出这个答案有什么帮助。它与原始问题无关。【参考方案14】:要删除意外更改,尤其是删除多个文件,请执行以下操作:
git add <stuff to keep> && git stash --keep-index && git stash drop
换句话说,把垃圾藏起来,把它和藏起来的东西一起扔掉。
在 git 版本 2.17.1 中测试
【讨论】:
没有评论的反对票对我和下一位读者都没有帮助...... zaenks 脾气暴躁的匿名。虽然我可以想象这个单行的一个问题:一个人应该非常小心,不要忘记将所有想要的更改添加到索引中,否则那些重要的更改也会被删除。但话又说回来,在最坏的情况下,不小心使用任何 cli 工具都可能对一个人的宝贵时间和工作造成极大的危险。【参考方案15】:我还没有看到这个不需要使用git stash
的解决方案:
您甚至根本不需要使用git stash
。您可以使用专门的分支 as covered here 来解决这个问题(分支很便宜)。
确实,您可以使用一些可以捆绑到一个 git 别名中的连续命令来单独隔离非和暂存更改:
创建并切换到一个新分支,您将在其中分别提交暂存和未暂存的更改:see here
在任何时候,您都可以git cherry-pick -e
从创建的分支提交一次,以将其应用到您想要的位置(-e
更改其提交消息)。
当你不再需要它时,你可以删除这个“stash 分支”。您可能必须使用-D
选项来强制删除(而不是-d
普通选项),因为所述分支未合并,git 可能会认为如果您删除它可能会丢失数据。如果您没有在删除之前对其进行精心挑选的提交,那是正确的:
git branch -D separated-stashes
您还可以为您的 ~/.gitconfig
添加别名以自动执行此行为:
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"
当然,你也可以使用two consecutive stashes达到同样的效果
正如其他答案中所述,您有一些方法可以使用 git stash (-k|--keep-index)
与其他命令相结合,仅存储未暂存或仅暂存的更改。
我个人觉得-k
选项非常令人困惑,因为它隐藏了所有内容,但将分段更改保持在分段状态(这解释了为什么“--keep-index
”)。而存储某些东西通常会将其移动到存储条目。使用 -k
时,未暂存的更改会正常隐藏,但暂存的更改只会复制到同一个隐藏条目。
第 0 步:您的 git status 中有两件事:一个包含暂存更改的文件,另一个包含未暂存更改的文件。
第 1 步:存储未分级的 + 分级更改,但将分级更改保留在索引中:
git stash -k -m "all changes"
-m "..."
部分是可选的,git stash -k
实际上是git stash push -k
的别名(它不会远程推送任何内容,顺便说一句,别担心)它接受-m
选项来标记您的存储条目以清楚起见(像提交消息或标签,但用于存储条目)。它是已弃用的git stash save
的更新版本。
第 1 步之二(可选):
git stash
存储分阶段的更改(仍在索引中)。
此步骤对于以下内容不是必需的,但表明您可以根据需要仅将暂存的更改放入存储条目中。
如果您使用此行,则必须先git stash (pop|apply) && git add -u
,然后才能继续第 2 步。
第 2 步:
git commit -m "staged changes"
提交仅包含步骤 0 中的暂存更改,它包含与步骤 1bis 中的隐藏条目相同的内容。
第 3 步:
git stash (pop|apply)
从第 1 步恢复存储。 请注意,此存储条目包含所有内容,但由于您已提交分阶段更改,此存储将仅添加步骤 0 中的未分阶段更改。
nb:这里的“restore”并不意味着“git restore”,这是一个不同的命令。
第四步:
git add -u
将弹出的存储内容添加到索引中
第 5 步:
git commit -m "unstaged changes"
这里的“Unstaged”,与第2步和第3步的cmets中的“staged”一样,指的是第0步。您实际上是在暂存并提交从第0步开始的“暂存更改”。
完成!
您现在有两个单独的提交,其中包含来自步骤 0 的(未)暂存更改。
您可能想要修改/重新设置它们以进行其他更改或重命名/删除/压缩它们。
根据您对 stash 堆栈所做的操作(pop
或 apply
),您可能还需要 git stash (drop|clear)
它。您可以看到您使用git stash (list|show)
隐藏条目
【讨论】:
【参考方案16】:TL;DR;
git stash-staged
创建别名后:
git config --global alias.stash-staged '!bash -c "git stash -- \$(git diff --staged --name-only)"'
这里git diff
返回--staged
文件列表--name-only
然后我们将这个列表作为pathspec
传递给git stash
commad。
来自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.
【讨论】:
如果对于某些特定文件我同时进行了暂存和未暂存的更改,这是否可以正常工作?乍一看,--name-only
,它似乎无法处理这种情况。 that answer 似乎好多了。
另外,这基本上是this answer的副本
@quetzalcoatl:是的,它类似于那个答案。但是这个提供了一个别名,可以保存您为下一个命令输入的内容
@quetzalcoatl:你是对的。 --name-only
不会立即处理暂存/未暂存的更改。但是为什么你先标记然后隐藏?最好立即存储所需的更改。我建议你试试git stash push --patch
描述的here以上是关于仅在 git 中暂存分阶段的更改 - 有可能吗?的主要内容,如果未能解决你的问题,请参考以下文章