如何在Git中只保存阶段性更改?

有没有办法让我把阶段性的变化藏起来?我遇到问题的情况是,我在给定的时间内处理了几个bug,并且有几个未分阶段的更改。我希望能够单独运行这些文件,创建我的.patch文件,并将它们保存起来,直到代码被批准。这样,当它被批准时,我可以隐藏我的整个(当前)会话,弹出错误并推送代码。

我做错了吗?我是否误解了git如何以其他方式简化我的过程?

199013 次浏览

为什么不提交对某个错误的更改,并根据该提交及其前身创建一个补丁呢?

# 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.patch0002-fix-bug-321.patch

或者您可以为每个错误创建单独的分支,这样您就可以单独合并或重新建立错误修复,甚至在它们不起作用时删除它们。

是否绝对有必要同时处理多个bug ?这里的“立刻”指的是“同时对文件进行多个错误的编辑”。因为除非您绝对需要,否则我在您的环境中一次只处理一个错误。这样你就可以使用本地分支机构&rebase,我发现这比管理一个复杂的隐藏/阶段容易得多。

假设master在提交b,现在处理bug #1。

git checkout -b bug1

现在您在分支bug1上。做一些修改,提交,等待代码审查。这是本地的,所以你不会影响到其他人,而且从git差异中制作补丁应该很容易。

A-B < master
\
C < bug1

现在您正在处理bug2。从回来git checkout master。创建一个新分支git checkout -b bug2。进行更改,提交,等待代码审查。

    D < bug2
/
A-B < master
\
C < bug1

让我们假设另一个人犯了E &当你在等待审查的时候。

    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分支,然后你就可以走了。在您的工作空间中,一次只处理一个错误,但是通过使用本地分支,您的存储库可以处理多个错误。这样就避免了复杂的舞台舞蹈。

请在评论中回答ctote的问题:

那么,您可以回到为每个bug存储,并且一次只处理一个bug。至少这省去了分期问题。但尝试过之后,我个人觉得很麻烦。在git日志图中,存储有点混乱。更重要的是,如果你把事情搞砸了,你就无法挽回了。如果你有一个脏的工作目录,你弹出一个隐藏,你不能“撤消”这个弹出。搞砸已经存在的提交要困难得多。

所以# EYZ0。

当您将一个分支重新基于另一个分支时,您可以交互地进行(-i标志)。当您这样做时,您可以选择对每次提交做什么。Pro Git是一本很棒的书,它也是HTML格式的在线,并且有一个关于rebase &挤进:

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

所以,是的,压扁是有点痛苦,但我仍然建议它比大量使用存储。

根据你对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工作流程。

是的,使用双藏是可能的

  1. 准备好你需要藏匿的所有文件。
  2. # EYZ0运行。这个命令将创建一个包含所有的更改(有舞台和无舞台)的存储,但是将阶段性更改保留在工作目录中(仍然处于阶段性状态)。
  3. 运行# EYZ0
  4. 现在你的"good stash"有了仅分阶段文件

现在如果你需要隐藏之前的非暂存文件,只需应用第一个隐藏(就是用--keep-index创建的那个),现在你可以删除你隐藏到"good stash"的文件。

享受

我制作了一个脚本,只存储当前正在上演的内容,并保留其他所有内容。当我开始做太多不相关的改变时,这是非常棒的。简单地暂存与所需的提交和存储无关的内容。

(感谢bartjoomiej提供的起点)

#!/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}

git stash push --staged #从2.35开始

只保存当前暂存的更改。这类似于基本的git commit,只是状态被提交给了stash而不是当前分支。

对于最新的git,您可以使用--patch选项

git stash push --patch   # since 2.14.6


git stash save --patch   # for older git versions
git会询问你文件中的每一个更改是否添加到stash中。
你只需回答yn

< p > # EYZ0
双藏的别名:

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"'

在这个场景中,我更喜欢为每个问题创建新的分支。我使用了一个前缀temp/,所以我知道我可以稍后删除这些分支。

git checkout -b temp/bug1

准备修复bug1的文件并提交它们。

git checkout -b temp/bug2

然后,您可以根据需要从相应的分支中选择提交,并提交一个拉取请求。

为了达到同样的目的…

  1. 只准备您想要处理的文件。
  2. # EYZ0
  3. # EYZ0
  4. # EYZ0
  5. # EYZ0

繁荣。你不想要的文件都被藏起来了。您要的文件都准备好了。

git stash --keep-index是一个很好的解决方案…除了在已删除的路径上无法正常工作,这已在Git 2.23(2019年第三季度)中得到修复

参见托马斯·古默勒(tgummerer) 提交b932f6a(2019年7月16日) (由滨野朱尼奥——gitster——提交f8aee85中合并,2019年7月25日)

stash:修复了使用--keep-index删除文件的问题

git stash push --keep-index应该保留所有更改 被添加到索引中,在索引中和在磁盘上

当前,当一个文件从索引中删除时,这是不正确的行为 **——keep-index当前恢复文件,而不是将其删除在磁盘上

修复行为使用'git checkout'在无覆盖模式 可以忠实地恢复索引和工作树。
这也简化了代码

请注意,如果未跟踪的文件与已在索引中删除的文件具有相同的名称,则这将覆盖未跟踪的文件。

要修剪意外的更改,特别是删除多个文件,请执行以下操作:

git add <stuff to keep> && git stash --keep-index && git stash drop

换句话说,把垃圾藏起来,把它们一起扔掉。

在git版本2.17.1中测试

更新2022-04-21

只需使用@VonC的答案,这是对原始问题https://stackoverflow.com/a/70231955/2959469的新惯用方法


只需为git# EYZ1参数添加-- $(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.1v2.21.0.windows.1

< >强限制:< / >强

  • 请注意,这将隐藏每一个单一的东西,如果你没有文件上演。
  • 同样,如果你有一个文件,这只是部分上演(即只有一些改变的行,被上演,而其他一些改变的行没有),那么 整个文件将被存储(包括未暂存的行)

更新 2022年1月:Git 2.35已经发布,stash现在支持--staged参数。因此,这个答案在Git 2.35+中已经过时了。参见vonc的回答:https://stackoverflow.com/a/70231955/430128

旧的回答:

在Git中仅存储索引(分阶段更改)比想象中要困难得多。我发现@Joe的回答工作得很好,并把它的一个小变化变成了这个别名:

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"

它:

  1. 验证是否存在实际的阶段变化(如果存在,git diff --cached --exit-code将返回非零状态)。HT: @nandilugio

  2. 它将这两个阶段性和非阶段性更改推到临时存储中,只保留阶段性更改。

  3. 然后将阶段性更改推入我们想要保留的stash。传递给别名的参数,例如--message "whatever"将被添加到这个stash命令中。

  4. 它弹出临时存储以恢复原始状态并删除临时存储,然后

  5. < p >终于“removes"通过反向补丁应用程序从工作目录中存储的更改。

对于相反的问题,只存储非阶段更改(别名stash-working),请参阅这个答案

# EYZ1;# EYZ0

创建别名后:

git config --global alias.stash-staged '!bash -c "git stash -- \$(git diff --staged --name-only)"'

这里git diff返回--staged文件列表 然后我们将这个列表作为pathspec传递给git stash命令

从# EYZ0:

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.


我还没有见过不需要使用git stash的解决方案:

您甚至根本不需要使用git stash。您可以使用专用的分支如这里所述来解决这个问题(分支很便宜)。

实际上,你可以用几个连续的命令分别隔离非变化和阶段性变化,你可以把它们捆绑在一个git别名中:

创建并切换到一个新的分支,在那里您将分别提交阶段性和非阶段性更改:在这里看到的

在任何时候,您都可以从创建的分支中git cherry-pick -e一个提交,将其应用到您想要的地方(-e更改其提交消息)。

当你不再需要它时,你可以删除这个“隐藏分支”。你可能不得不使用-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
< p > # EYZ0
# EYZ0 < / p >

当然,您也可以使用连续两次存储实现相同的结果

正如在其他回答中所述,您可以使用git stash (-k|--keep-index)与其他命令结合使用,使用一些方法来仅隐藏非阶段性或仅阶段性的更改。

我个人觉得-k选项非常令人困惑,因为它存储了所有东西,但保持阶段性变化处于阶段性状态(这解释了为什么&;--keep-index")。而储存东西通常会把它移动到一个储存入口。使用-k,非阶段性的更改通常会被存储,但阶段性的更改只是复制到相同的存储条目。


第0步:你的git状态中有两个东西:一个包含阶段性变化的文件,另一个包含非阶段性变化的文件。

第一步:保存非阶段性+阶段性变化,但将阶段性变化保留在索引中:

git stash -k -m "all changes"

-m "..."部分是可选的,git stash -k实际上是git stash push -k的别名(它不会推送任何远程btw不用担心),它接受-m选项来标记你的隐藏条目以明确(像提交消息或标签,但对于隐藏条目)。它是已弃用的git stash save的新版本。


步骤1bis(可选):

git stash

保存阶段性变化(仍然在索引中)。 这一步对于下面的内容不是必需的,但是它表明,如果您愿意,您可以只将阶段性的更改放在存储条目中。 如果你使用这一行,你必须在继续第二步之前git stash (pop|apply) && git add -u


第二步:

git commit -m "staged changes"

提交只包含从步骤0开始的阶段性更改,它包含与步骤1bis开始的隐藏条目相同的内容。


第三步:

git stash (pop|apply)

恢复第1步的存储。 注意,这个隐藏条目包含了所有内容,但是由于您已经提交了阶段性更改,所以这个隐藏只会添加从第0步开始的非阶段性更改

注:“restore"这里并不是指“git restore”,这是一个不同的命令。


第四步:

git add -u

将弹出的存储的内容添加到索引中


第五步:

git commit -m "unstaged changes"

“Unstaged"在这里,“staged"在步骤2和步骤3的注释中,指步骤0。你实际上是在进行“阶段性变化”。从第0步开始。


< p >完成了! 您现在有两个独立的提交,其中包含从第0步开始的(un)阶段性更改。 您可能希望修改/重新设置它们以进行其他更改或重命名/删除/压缩它们。 取决于你对你的隐藏堆栈做了什么(popapply),你可能还想要git stash (drop|clear)它。您可以看到您使用git stash (list|show)

保存条目

另一种方法是用你不想被存储的文件创建一个临时提交,然后存储剩余的文件,轻轻地删除上次提交,保持文件完整:

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"在这里还用于保存新文件(这可能是您真正想要的)。

在Git 2.35 (Q1 2022)中,"git stash"(man)学习了--staged选项来隐藏添加到索引中的内容(而不是其他内容)。

所以现在这是官方支持的(8年后)。

参见提交a8a6e06(2021年10月28日)和提交41 a28eb(2021年10月18日)以及谢尔盖·奥加纳夫(sorganov)
(由滨野朱尼奥——gitster——提交44 ac8fd中合并,2021年11月29日)

stash:为“推送”和“保存”执行“——staging”选项

署名:Sergey Organov

只保存已上演的更改。

这种模式允许轻松地隐藏一些与当前正在进行的工作无关的更改,以便以后重用。

与“stash push --patch”不同,--staged支持使用任何工具来选择要隐藏的更改,包括但不限于“git add --interactive(man)

git stash现在包含在它的手册页中:

# EYZ0

git stash现在包含在它的手册页中:

# EYZ0

git stash现在包含在它的手册页中:

# EYZ0

# EYZ0

该选项仅对pushsave命令有效。

仅保存当前暂存的更改。这类似于 基本的git commit,除了状态被提交到stash

--patch选项的优先级高于这个选项。

git stash现在包含在它的手册页中:

# EYZ0

当你在巨大的变化中,你发现一些 不相关的问题,你不想忘记修复,你可以做 更改(s),暂存它们,并使用git stash push --staged保存它们 留着以后用。
这类似于提交分阶段的更改,

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

如果你没有一个更新的有--staged选项的git,下面是如何直接做到这一点。

git stash命令只是一个复杂的shell脚本,用于操作树对象和提交等等。我们可以手动完成它所做的事情。

概述

隐藏堆栈记录特殊的提交。我们将从阶段性更改中创建一个提交,然后手动将其转移到存储中。然后,取消提交。

设置

我有一个项目,其中有两个变化Makefile。一个是有舞台的,一个是没有舞台的:

$ git diff --cached
diff --git a/Makefile b/Makefile
index 4ca6058f..c8c7480a 100644
--- a/Makefile
+++ b/Makefile
@@ -605,7 +605,7 @@ conftest2: conftest1.c conftest2.c
$(V)if echo | $(CC) -dM -E - | grep -s __ANDROID__ >  /dev/null 2>&1 ; then \
echo yes ; \
fi
-
+# FOO
.PHONY: conftest.clean
conftest.clean:
$(V)rm -f conftest$(EXE) conftest.[co] \


$ git diff
diff --git a/Makefile b/Makefile
index c8c7480a..270c313d 100644
--- a/Makefile
+++ b/Makefile
@@ -611,3 +611,4 @@ conftest2: conftest1.c conftest2.c
$(V)rm -f conftest$(EXE) conftest.[co] \
conftest2$(EXE) conftest[12].[oc] \
conftest.err
+# BAR

添加# FOO行是分阶段的;# BAR的添加是无阶段的。

步骤1:创建树对象。

首先,我们从当前索引(包含分段项)创建一个树对象。

$ git write-tree
0d9651ad74328e747a053a9434d9867c8cd79d41 <-- output

步骤2:创建两次提交。

首先,从树中创建一个提交,它有一个父分支,当前分支HEAD:

$ git commit-tree -p HEAD -m 'add # FOO' 0d9651ad74328e747a053a9434d9867c8cd79d41
baa34222e781078d82cefed519ff105715c7f665 <-- output

然后,从树中创建另一个提交,它有两个父:HEAD和我们刚刚创建的baa34222...提交:

$ git commit-tree -p HEAD -p baa34222e781078d82cefed519ff105715c7f665 -m 'add # FOO' 0d9651ad74328e747a053a9434d9867c8cd79d41
2c96b028e475a05d84f472da7f2a70ac53d0ac90 <-- output

这个双亲的2c96b02...将是我们安装到存储中的提交。

注意,git commit-tree不是git commit。这是一个低级别的命令。这些提交不会对当前分支做任何事情;我们只是在Git的存储中分配对象,并没有对我们所在的分支做任何事情,也没有改变索引或工作树。

步骤3。

接下来,我们将这个提交写进.git/refs/stash。你可能需要备份这个文件。

$ echo 2c96b028e475a05d84f472da7f2a70ac53d0ac90 > .git/refs/stash

步骤4。

我们将同样的提交钩子到.git/logs/refs/stash文件中。在编辑之前,文件中的最后一行看起来是这样的:

b1819d98ab24720796315b9497236172d1fb1f5f 3b2ecc6604d77c9df4fe72efd1fbd384b2c43f76 Au Thor <author@example.com> 1654892876 -0700 On master: elim-aliases

我们手动添加这条伪行:

3b2ecc6604d77c9df4fe72efd1fbd384b2c43f76 2c96b028e475a05d84f472da7f2a70ac53d0ac90 Au Thor <author@example.com> 1654892876 -0700 On master: add # FOO

您可能还想备份此文件。然而,如果出了问题,事情很容易恢复。

注意,这一新行中的左哈希3b2ecc...与前一行中的右哈希相同。这是之前的存储提交,与我们在这里所做的无关,必须重复将这一行链接到存储堆栈中。右边是哈希值2c96b028e4...。然后剩下的人都假装离开了。在时区-0700后面有一个硬标签,而不是空格。我只是复制粘贴了一下。

第5步。

我们验证我们已经将commit添加到存储堆栈:

$ git stash list | head -3
stash@{0}: On master: add # FOO
stash@{1}: On master: elim-aliases
stash@{2}: On master: compiler-safe-eval

和:

$ git stash show -p
diff --git a/Makefile b/Makefile
index 4ca6058f..c8c7480a 100644
--- a/Makefile
+++ b/Makefile
@@ -605,7 +605,7 @@ conftest2: conftest1.c conftest2.c
$(V)if echo | $(CC) -dM -E - | grep -s __ANDROID__ >  /dev/null 2>&1 ; then \
echo yes ; \
fi
-
+# FOO
.PHONY: conftest.clean
conftest.clean:
$(V)rm -f conftest$(EXE) conftest.[co] \

它在那里;git stash认为我们的提交是“类似于存储”的;承诺,并接受它。

总结

  1. 我们手动获取一个分阶段变化的索引,并生成一个树对象。

  2. 然后我们将树对象转换为常规提交对象,然后再进行一个双父提交。双父对象作为类存储提交是可以接受的。

  3. 最后,我们通过编辑一对文件手动将这个提交修补到隐藏堆栈中。

附录

我们没有执行任何操作索引或工作树的不安全命令。但是,我们已经不安全地操作了git存储堆栈。如果出现问题,以下是如何修复它(而不是从备份文件恢复):

  1. 删除我们添加到.git/logs/refs/stash的假行,这样这仍然是最后一行:

    b1819d98ab24720796315b9497236172d1fb1f5f 3b2ecc6604d77c9df4fe72efd1fbd384b2c43f76 Au Thor <author@example.com> 1654892876 -0700 On master: elim-aliases
    
  2. 取右侧散列3b2ecc6604d77c9df4fe72efd1fbd384b2c43f76并将其植入.git/refs/stash文件:

    $ echo 3b2ecc6604d77c9df4fe72efd1fbd384b2c43f76  > .git/refs/stash`
    

以前的收藏现在恢复了。

您可以使用--staged仅保存阶段性更改。

git stash --staged

文档:# EYZ0