【问题标题】:Git stash apply did not return working directory?Git stash apply 没有返回工作目录?
【发布时间】:2017-06-22 23:06:55
【问题描述】:

我提交了一些文件并将其推送到远程。然后发现我有问题,并想恢复推送以进行一些编辑。我隐藏、还原并想重新应用存储,但应用后,我的工作目录仍然缺少文件。请帮忙。这是历史。

$ git commit -m "Model package"
[dev ec5e61d] Model package
 40 files changed, 1306 insertions(+), 110 deletions(-)

$ git push

$ git log
commit ec5e61d2064a351f59f99480f1bf95927abcd419
Author: Me
Date:   Mon Feb 6 

    Model package


$ git revert ec5e61d2064a351f59f99480f1bf95927abcd419
error: Your local changes to the following files would be overwritten by merge:
        model/R/train.R
Please, commit your changes or stash them before you can merge.
Aborting



$ git stash
Saved working directory and index state WIP on dev: ec5e61d Model package
HEAD is now at ec5e61d Model package


$ git revert ec5e61d2064a351f59f99480f1bf95927abcd419
[dev 062f107] Revert "Model package"
 40 files changed, 135 insertions(+), 1331 deletions(-)


$ git stash apply
CONFLICT (modify/delete): model/R/train.R deleted in Updated upstream and modified in Stashed changes. Version Stashed changes of model/R/train.R left in tree.


$ git stash apply
model/R/train.R: needs merge
unable to refresh index

$ git commit model/R/train.R
[dev ed41d20] Resolve merge conflict
 1 file changed, 138 insertions(+)
 create mode 100644 model/R/train.R


 $ git stash apply
On branch dev
Your branch is ahead of 'origin/dev' by 2 commits.
  (use "git push" to publish your local commits)
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   scripts/gm.r

但我的文件没有从存储区返回!

$ git stash list
stash@{0}: WIP on dev: ec5e61d Model package

$ git stash show
 model/R/train.R | 79 ++++++++++++++++++++++----------------------
 scripts/gm.r     | 87 ++++++++++++++++++++++++++++++++-----------------
 2 files changed, 97 insertions(+), 69 deletions(-)

【问题讨论】:

  • 为什么要继续运行git stash apply?你没有读过第一个说train.R 文件有冲突的吗?如果您因藏匿而发生冲突,没什么大不了的,只需处理它即可。看起来有人在远程删除了该文件,但 Git 告诉您它正在将其留在本地。在这一点上,这是一团糟,我没有答案。
  • 我犯了冲突
  • 那你为什么一直申请隐藏?
  • 我以为我必须提交冲突然后再次申请?原来我想错了。
  • 存储是一次性的,假设你只存储了一次,这就是你所做的。如果文件在上游被删除时发生冲突,那么只需执行git add model/R/train.R 并提交。然后你应该完成了。

标签: git


【解决方案1】:

你在这里混合了很多东西。特别是,您使用了git stash git revert,同时还进行了各种提交。我们需要把它们分开。

git stash 做了什么

虽然git stash 可能会变得非常复杂,1 但它的正常使用相当简单。您创建一个存储,它是各种提交。这具有“清理”工作树和索引的副作用,例如git reset --hard HEAD(实际上在git stash 中有一个文字git reset --hard HEAD 命令)。然后你切换到其他提交,git stash apply stash,如果一切顺利,git stash drop stash。

apply 步骤将您所做的特殊存储提交转换为补丁,然后像git apply -3 一样应用它,必要时进行三向合并。因此,如果你想用普通的 git commit 模拟简化的过程,你可以这样做——你不需要 git reset --hard 部分,因为你为新的提交创建了一个真正的分支:

git checkout -b tempbranch
git add -A
git commit -m 'stash my work-in-progress'
git checkout <something else>
git show tempbranch | git apply -3

要删除提交(如删除存储),您只需删除临时分支:

git branch -D tempbranch

尽管如此,无论您如何保存和应用存储,提取存储可能会导致执行三向合并。因此,它可能会发生合并冲突。这就是你遇到的。


1主要的复杂之处是:git stash 分别保存 当前索引 当前工作树,如两次提交(没有分支)。因此,这很像运行git commit 两次。但是,当您使用git stash apply 时,默认情况下,它这两个提交合并到工作树中的一个更改中。

如果您在进行存储之前没有进行任何准备,则此复杂性将变得无关紧要。或者,如果您对所有内容进行了上演,然后没有更改工作树,那么 会使这种复杂性无关紧要。在任何情况下,如果您在没有--index 的情况下应用存储,则应用步骤将在需要时优先考虑工作树更改而不是索引更改。

另外一个复杂的问题是,如果您将-u-a 选项添加到您的git stash save 步骤,Git 会保存一个第三次 提交。这些 3 次提交的存储更难应用,应谨慎使用。


提交和补丁

在继续讨论合并冲突之前,让我们先简要了解一下 Git 提交中的内容,以及补丁(或差异)是什么。

提交是一个快照。这是您告诉 Git 跟踪的所有文件,以您运行 git commit 时的形式。更具体地说,它是当时 index 中的内容。索引,也称为暂存区,是您告诉 Git 在下一次提交中保存什么的地方,使用 git add 更新索引中的项目。

提交不是补丁。这不是更改:它是快照。但是,每个2 提交都有一个父级(或“上一个”)提交,Git 可以轻松地比较与其父级的提交。这会将提交变成一个补丁:

  • 上次保存了 A、B 和 C。
  • 这次节省了 A、B 和 D。
  • 因此,您删除了 C,并添加了 D。

因此一个提交可以变成一个补丁。补丁只是指令:添加这些东西,删除这些其他东西。您(或 Git)可以将补丁应用于其他提交,然后从结果中进行新的提交。如果应用程序删除 C 并添加 D,并且您进行了新提交,则 new 提交的补丁是“删除 C 并添加 D”——这也是 old 提交的补丁补丁。

将提交变成补丁,然后在其他地方再次播放,重复更改


2至少有一个提交——你做过的第一个——是特别的,因为它之前没有个提交,即没有父提交。此外,合并提交很特别,因为它们有 两个(甚至更多)父级。但我们最感兴趣的是普通的单亲提交。

因为git stash 进行提交,所以存储提交也有父级。事实上,这就是 Git 将 stash 变成补丁的方式,就像 Git 将普通提交变成补丁一样。


使用git cherry-pickgit revert

(注意:您只直接使用了git revert,但我将两者都包括在内,因为它们非常相似。)

当我们将提交变成补丁时,如上所述,然后将其应用到另一个分支3这是一个git cherry-pick 操作。我们说的是“我们上次在那边做的改变是个好主意:让我们在这里再做一次同样的改变。”我们可以对 相同 文件进行相同的更改 - 这总是有效的 - 或对仅 足够相似 的文件进行相同的更改,以便我们实际上可以进行相同的更改。 p>

“apply a patch”和“make a cherry-pick”之间的本质区别在于“make a cherry-pick”需要一个源提交,而不仅仅是一个补丁。默认情况下,git cherry-pick 也会自动从中进行新的提交——因此这也可以重用原始提交的提交消息。

一旦您了解了补丁以及 git cherry-pick 的工作原理,git revert 的作用就变得非常明显:它只是反向应用补丁。你将 Git 指向某个特定的提交,并告诉它撤消该提交所做的任何事情。 Git 把那个提交变成了一个补丁;补丁说“删除C并添加D”;然后 Git 删除 D 并添加 C。与 git cherry-pick 一样,git revert 默认从结果中进行新的提交。


3从技术上讲,我们只需要将它应用到另一个提交。但这涉及到我们所说的“分支”是什么意思的问题:见What exactly do we mean by "branch"?


合并冲突

合并背后的想法很简单。我们——无论“我们”是谁——做了一些改变,他们——无论“他们”是谁——做了一些改变——但我们都开始相同的快照。我们要求 Git 结合我们的 更改和他们的 更改。例如,假设你们都从 F1F2F3 开始,更改了文件 F1 而他们没有,他们 更改了文件F2 而你没有。这真的很容易组合:将您修改后的F1 和他们修改后的F2。在F3 中,您在文件顶部附近更改了一行,而他们在底部附近更改了一行。这有点难以组合,但仍然不是问题:Git 只接受这两项更改。

不过,有些更改根本无法合并。这些更改是合并冲突

合并冲突有多种形式。最常见的情况发生在“你”(在您的提交中)和“他们”(在他们的提交中)修改同一文件中的同一行时。当我们接受我们的提交并将它/它们变成一个补丁时,就会发生这种情况——“删除 C 并添加 D”——但他们的更改是 keep C 并添加 E。 Git 不知道要使用哪个更改:我们应该保留 C 并添加 D 和 E,还是删除 C 并仅保留 E,还是什么?

但是,这不是您得到的:您遇到了修改/删除冲突。当您说在文件 train.R 中我们应该删除 C 并添加 D 时,就会发生修改/删除冲突——他们说“扔掉整个 train.R 文件”。

所有合并冲突的情况下,Git 会举起它的比喻手并暂时放弃进行合并。它将冲突文件写入您的工作树,声明合并冲突,然后停止。现在你的工作是完成合并——通过提出正确快照——然后git addgit commit得到结果。

合并冲突当然会在您执行git merge 时发生(此时非常清楚哪个是“我们的”,哪个是“他们的”)。4 但是它们也可以在git apply -3-3 表示“使用三路合并”)、git cherry-pickgit revert 期间发生。如果补丁没有干净地应用,Git 可以确定要使用哪个公共基础,然后找出更改的两个部分:您正在应用的补丁中的内容,以及从公共基础到您正确的提交的更改现在,您正在尝试使用 git cherry-pickgit revert 进行修补。


4“我们的”版本在所有情况下都是HEAD 版本。在 rebase 期间也是如此,但是 rebase 是通过重复一系列的樱桃挑选来工作的,此时,HEAD 是您正在构建的新替换分支。参见 CommaToast 的this comment:“既然头脑是思想的所在地,它是身份的源泉,也是自我的源泉,因此将 HEAD 所指的任何东西都视为‘我的’更有意义...”。


关于git commit的最后一点说明

我在上面提到过,您通常将git add 文件从工作树复制到索引/暂存区。在合并冲突期间,这也将文件标记为已解决,这就是为什么您的工作是编辑文件首先,然后在其上使用git add。一旦您修复并git add-ed 所有文​​件,您可以git commit 生成的合并。这样就完成了合并,或者樱桃挑选、恢复或变基步骤,或者你正在做的任何有冲突的事情。

当您像这样运行 git commit 时,没有特定的命名文件,Git 会提交索引(暂存区域)内容。结果,新的HEAD 提交与索引匹配。我们稍后会用到这个事实。

但是,如果您运行 git commit &lt;path&gt;,这自动 暂存命名文件(就像您运行 git add file),然后提交——嗯,某事。这部分有点棘手。默认行为就像您运行 git commit --only &lt;path&gt;

使用--only,Git 不会获取任何其他已暂存的文件。也就是说,如果你有git added 五个文件,没有一个名为README.txt,然后你运行git commit README.txt,Git 使用README.txt 的工作树版本进行新的提交,但HEAD 版本的其他五个文件。换句话说,它只更改命名文件,保留所有其他文件的先前版本。

使用--include,Git 也会暂存命名的&lt;file&gt;,并提交整个结果。也就是说,这就像在运行git commit 之前执行git add &lt;file&gt;。这比--only 的行为更容易解释,但是当然如果你想要这个,你可以git add &lt;file&gt;

不过,在任何一种情况下,一旦做出新的提交,&lt;file&gt; 就会被暂存,就好像你有 git add-ed 它一样。所以在我们的--only 示例中,有五个文件add-ed,索引现在已经更新了所有六个文件。当然,有一个新的HEAD 提交,README.txt 现在匹配新的HEAD 提交,所以只有五个差异暂存。

那么,现在让我们看看你做了什么

你显示的第一个命令是这样的:

$ git commit -m "Model package"
[dev ec5e61d] Model package
 40 files changed, 1306 insertions(+), 110 deletions(-)

我们没有看到您的任何git adds,但您必须添加 40 个文件,以便 Git 说“40 个文件已更改”。但你也必须拥有git add-ed 的东西,我们稍后会看到。

接下来,你展示:

$ git push

没有输出。目前尚不清楚推送的是什么(如果有的话)(这取决于您的push.default 配置,并且可能当前分支是否具有上游设置,等等)。但是,无论你可能推送了什么,你自己的本地仓库内容都没有改变,后来的证据表明推送成功,将提交ec5e61d推送到origin

现在您显示两个带有一些输出的命令:

$ git log
commit ec5e61d2064a351f59f99480f1bf95927abcd419
Author: Me
Date:   Mon Feb 6 

    Model package

这是您所做的提交:ec5e61d,在分支 dev 上,如 git commit 输出所示。

$ git revert ec5e61d2064a351f59f99480f1bf95927abcd419
error: Your local changes to the following files would be overwritten by merge:
        model/R/train.R
Please, commit your changes or stash them before you can merge.
Aborting

这很有趣,因为它表明您在工作树中对model/R/train.R 进行了更改,即使您最近运行了git commit,提交了ec5e61d

这意味着model/R/train.R索引 中的任何内容都与model/R/train.R 的工作树中现在的内容不匹配。从这里我们无法准确判断索引中的内容,只是它与工作树不匹配。

git revertAborting 时,这意味着它什么也没做:它发现你的工作树不是“干净的”,即与HEAD 提交不匹配ec5e61d。默认情况下,git revert 要求工作树与 HEAD 提交匹配。

接下来,你跑了:

$ git stash
Saved working directory and index state WIP on dev: ec5e61d Model package
HEAD is now at ec5e61d Model package

git stash 命令进行了提交——实际上,两次提交,一次用于索引,一次用于工作树;但索引与HEAD 提交相同,所以我们真正需要关心的是工作树提交——然后“清理”工作树以匹配HEAD 提交。

换句话说,stash 提交——或者更确切地说,我们关心的两个提交之一——具有model/R/train.R 的工作树版本。它还具有任何其他已修改(和跟踪)但尚未提交的文件的工作树版本,以及仍然与HEADec5e61d 提交匹配的所有剩余文件的工作树版本。 (与往常一样,每个提交都有 every 文件。这就是“成为快照”的意思。稍后我们将能够看到更多关于隐藏工作之间的不同之处 -树提交和ec5e61d 提交。)

一旦git stash 隐藏了所有这些,它就会使用git reset --hard HEAD 丢弃索引和工作树更改。 (它们被安全地保存在存储库中,因此在索引和工作树中不再需要。)所以现在您的工作树中的 model/R/train.R 文件与 HEAD 中的文件匹配。

现在你重新运行你的git revert:

$ git revert ec5e61d2064a351f59f99480f1bf95927abcd419
[dev 062f107] Revert "Model package"
 40 files changed, 135 insertions(+), 1331 deletions(-)

而这一次,由于您正在恢复刚刚提交的更改,“反向修补”很容易成功(撤消您刚刚所做的事情是微不足道的)。 Git 在分支 dev 上进行新的提交 062f107,因此最后两个提交是:

... <- ec5e61d <- 062f107   <-- dev

存储本身附加到提交 ec5e61d(每个存储都直接附加到您进行存储时为 HEAD 的提交)。

现在您尝试应用存储,但由于合并冲突而失败:

$ git stash apply
CONFLICT (modify/delete): model/R/train.R deleted in Updated upstream
and modified in Stashed changes. Version Stashed changes of
model/R/train.R left in tree.

这特别有趣,因为这意味着model/R/train.R 必须已被还原提交062f107删除,这意味着它必须已在提交中添加 ec5e61d。同时,存储提交在转换为补丁时会显示“将此更改为 model/R/train.R”。

因为 Git 不知道如何将“完全删除此文件”与“进行此更改”结合起来,所以它将整个隐藏的工作树版本 model/R/train.R 留在了工作树中。现在你的工作是找出应该在工作树中的内容,以及git add

同时,任何 other 更改 Git 应该作为补丁应用,Git did 作为补丁应用到所有其他文件。这些更改已为提交暂存,即,这些文件的索引已更新。

接下来,你跑了:

$ git stash apply
model/R/train.R: needs merge
unable to refresh index

这会尝试再次应用存储,但不能,所以(幸运的是)它什么也不做。这使得未完成的合并未完成。

然后你跑了:

$ git commit model/R/train.R
[dev ed41d20] Resolve merge conflict
 1 file changed, 138 insertions(+)
 create mode 100644 model/R/train.R

请注意,这具有git commit &lt;file&gt; 形式。这个git adds model/R/train.R 来自工作树,然后跳过其他添加的文件,并从结果中创建一个新的HEAD 提交ed41d20。所以这个新的提交与旧的HEAD 062f107 具有所有相同的文件除了 model/R/train.R:

... <- ec5e61d <- 062f107 <- ed41d20   <-- dev

现在你又跑了git stash apply

$ git stash apply
On branch dev
Your branch is ahead of 'origin/dev' by 2 commits.
  (use "git push" to publish your local commits)
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   scripts/gm.r

就像上次一样,这会将隐藏提交转换为补丁并尝试应用补丁。补丁没有正确应用,所以 Git 尝试了三路合并:找到基本版本,找到你的 HEAD (ed41d20) 版本(“我们的”)中的内容,区分这些,并将该差异与补丁结合起来。事实证明,该差异与补丁完全相同,即补丁已经应用。所以你在最后得到了git status 的输出,它显示了一个修改过的文件和两个提交——当然,这些必须是062f107ed41d20——你有origin 还没有.

最后,你展示这些:

$ git stash list
stash@{0}: WIP on dev: ec5e61d Model package

$ git stash show
 model/R/train.R | 79 ++++++++++++++++++++++----------------------
 scripts/gm.r     | 87 ++++++++++++++++++++++++++++++++-----------------
 2 files changed, 97 insertions(+), 69 deletions(-)

这完成了我们对隐藏内容的了解:就像提交 ec5e61d 一样,除了两个文件 model/R/train.Rscripts/gm.r。从提交 ec5e61d 转换为 stash5 中的工作树提交的 git diff 说明说要在这两个文件中删除 69 条原始行,并插入 97 条替换行。

这就是git stash apply 试图做的,只是它无法从完全删除的model/R/train.R 中删除 任何 行,所以它只是把整个保存的工作树 stash@{0}工作树中文件 model/R/train.R 的版本。


5同样,前面我们证明了存储中的索引提交与ec5e61d 提交完全匹配。因此,我们可以忽略索引提交,只关注工作树提交。


下一步做什么

此时您应该做什么取决于您想要什么结果。我不能给你正确的答案,但我可以给你重要的问题:

  • 您希望拥有哪些提交?您希望在每次提交中生成哪些源代码树快照?
  • 其他人是否已经拥有提交ec5e61d 的副本? (即,其他人是否有权访问您将提交 ec5e61d 推送到的上游存储库?)

提交ec5e61d 存在于您的 存储库和您在git push 命令处将其推回的存储库中。在另一个存储库中,名称dev 可能指向ec5e61d。与其父提交相比,它具有文件 model/R/train.R 作为新文件,并且还对其他 39 个文件进行了其他更改或新创建。

Commit 062f107 仅存在于您的存储库中。这是对提交ec5e61d 的精确回退,因此,它可能没有用,除非您需要确保从任何其他存储库中完全回退ec5e61d

提交ed41d20 也仅存在于您的存储库中。它是ec5e61d父项 中所有内容的副本,除了添加了model/R/train.R 的隐藏版本。

您当前的工作树主要匹配ed41d20,除了它修改了scripts/gm.r,在ec5e61d 和存储提交之间的补丁中所做的任何更改,也在工作树中以相同的方式更改。而且,由于ed41d20 主要匹配ec5e61d 的父级,ec5e61d 的父级和工作树之间的差异相当于您在隐藏中的scripts/gm.r 中更改的任何内容,加上model/R/train.R 的隐藏版本.

你还有藏匿处,也是。如果这有用,请保留它,直到它不再有用为止。一旦您确定它不再有用,请使用 git stash drop 将其删除(此步骤非常难以撤消,因此请确保您已完成)。

您可能希望完全放弃最近的两次提交(还原和仅提交一个添加的文件版本model/R/train.R)。在这种情况下,您可以 git reset --hard origin/dev 放弃这两个提交,并将您的工作树恢复到您提交 ec5e61d 时的状态。

如果您是上游存储库的唯一用户(这是一个非常大的“如果”),并且提交 ec5e61d 本身并没有真正有用,您可能想要丢弃 that 提交也是如此——但要小心,不仅因为“如果”部分非常大,还因为该特定提交变得更难恢复——尽管不像存储那么难。只要您还有存储,恢复和git stash apply 结果很容易重复:您只需运行相同的git 命令。

【讨论】:

  • 那是非常全面的,现在我可能不会再犯同样的错误了。谢谢你。我的可取之处在于,我可以确定没有人阻止我的推动,因为这一切都发生在几分钟之内。所以我制作了冲突文件的副本,将 HEAD 重置回 ec5e61,然后从那里开始工作。我丢失了历史记录,但我所有的文件都恢复了原位。
猜你喜欢
  • 2013-02-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-27
  • 2015-03-11
  • 1970-01-01
  • 2019-03-05
相关资源
最近更新 更多