Git暂存应用未返回工作目录?

3
我把一些文件提交并推送到远程,但后来发现有问题,想撤销推送并进行编辑。我使用了stash、回滚操作,并希望重新应用stash,但是应用之后,我的工作目录仍然缺少这些文件。请帮忙看一下历史记录。
$ 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 告诉你它在本地保留了该文件。此时,情况很混乱,我也无法给出答案。 - Tim Biegeleisen
我提交了冲突。 - Jean
那你为什么还要继续应用存储? - Tim Biegeleisen
我原以为我需要提交冲突,然后再重新应用?结果发现我想错了。 - Jean
Stash 是一次性的东西,这是假设你只 stash 了一次,而这就是你所做的。如果文件因为被上游删除而冲突,则只需执行 git add model/R/train.R 并提交即可。然后你就完成了。 - Tim Biegeleisen
谢谢,我会记住下次的。但是我现在有这个问题。 - Jean
1个回答

7
您在这里混合了很多东西。特别是,在使用git stashgit revert的同时,还进行了各种提交。我们需要分开来看。

git stash的作用

虽然git stash可能变得非常复杂,但它的正常使用相当简单。您创建一个stash,它是一种提交方式。这会产生“清理”工作树和索引的副作用,就像git reset --hard HEAD一样(事实上,在git stash内部有一个字面的git reset --hard HEAD命令)。然后,切换到另一个提交,应用stash:git stash apply,如果一切顺利,则丢弃stash:git stash drop

应用步骤将您制作的特殊stash提交转换为补丁,然后像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 git branch -D ```
git branch -D tempbranch

尽管如何保存和应用存储,提取存储可能导致执行三方合并。因此,可能会出现合并冲突。这就是你遇到的情况。
主要的复杂性在于:git stash分别保存了当前索引和当前工作树,作为两个提交(不属于任何分支)。因此,这很像运行了两次git commit。但是,当你使用git stash apply时,默认情况下它会将这两个提交合并为一个工作树中的更改。
如果在制作存储之前没有暂存任何内容,则该复杂性变得无关紧要。或者,如果你暂存了所有内容,然后没有更改工作树,那么也会使这种复杂性变得无关紧要。在任何情况下,如果你应用存储而没有使用--index选项,则应用步骤将优先考虑工作树更改而不是索引更改(如果需要)。
另一个复杂性在于,如果你在git stash save步骤中添加了-u或-a选项,则Git会保存第三个提交。这些包含三个提交的存储难以应用,应谨慎使用。
在我们继续讨论合并冲突之前,让我们简要了解一下Git提交中有什么,以及补丁(或差异)是什么。
提交是一个快照。它是你告诉Git要跟踪的所有文件,以及当你运行git commit时它们的形式。更具体地说,它是那个时间点索引中的内容。提交不是补丁。它不是更改:它是一个快照。然而,每个提交都有一个父(或“前一个”)提交,Git可以轻松地将提交与其父进行比较。这将提交转换为补丁。
因此,一个提交可以变成一个补丁。一个补丁只是指令:添加这些内容,删除那些内容。你(或Git)可以将补丁应用到其他提交中,然后从结果中创建一个新的提交。如果应用程序删除了C并添加了D,你再进行一次新的提交,那么新提交的补丁就是“删除C并添加D”,这也是旧提交的补丁。
将提交转化为补丁,并在其他地方再次运行它,就会重复这个更改。
至少有一个提交 - 你所做的第一个提交 - 是特殊的,因为它没有父提交。此外,合并提交也是特殊的,因为它们有两个(甚至更多)父提交。但我们在这里主要关心普通的单父提交。
因为`git stash`会产生提交,所以stash提交也有父提交。事实上,这正是Git将stash转换为补丁的方式,就像Git将普通提交转换为补丁一样。
使用`git cherry-pick`和`git revert`
(注意:你直接使用了`git revert`,但我在这里包含了两者,因为它们非常相似。)
当我们像上面那样将提交转换为补丁,然后将其应用到另一个分支时,这就是一个`git cherry-pick`操作。我们说“我们上次在那里做的更改是一个好主意:让我们在这里再做同样的更改”。我们可以对相同的文件进行相同的更改 - 这总是有效的 - 或者对足够相似的文件进行相同的更改,以便我们实际上可以进行相同的更改。
“应用补丁”和“进行cherry-pick”的基本区别在于,“进行cherry-pick”需要源提交,而不仅仅是补丁。默认情况下,`git cherry-pick`还会自动从中创建一个新的提交-因此,这也可以重用原始提交的提交消息。
一旦你理解了补丁以及`git cherry-pick`的工作方式,`git revert`的操作就变得非常简单明了:它只是反向应用补丁。你将Git指向某个特定的提交,并告诉它撤销该提交所做的任何操作。Git将该提交转换为补丁;补丁说“删除C并添加D”;然后Git删除D并添加C。与`git cherry-pick`一样,默认情况下,`git revert`会从结果中创建一个新的提交。

3从技术上讲,我们只需要将其应用于另一个提交。但这引出了一个问题,即我们所说的“分支”是什么意思:请参见我们所说的“分支”到底是什么?


合并冲突

合并背后的想法很简单。我们 - 不管“我们”是谁 - 做了一些更改,而他们 - 不管“他们”是谁 - 也做了一些更改,但是我们都从相同的快照开始。我们请求Git结合我们的更改和他们的更改。例如,假设您都使用F1F2F3开始,并且更改了文件F1而他们没有,他们更改了文件F2而您没有。这很容易组合:采用您修改的F1和他们修改的F2。在F3中,您更改了文件顶部附近的一行,而他们更改了文件底部附近的一行。这有点难以组合,但仍然不是问题:Git只需要采用两个更改。

但有些更改根本无法组合。这些更改是合并冲突

合并冲突有多种形式。最常见的情况是当“您”(在您的提交中)和“他们”(在他们的提交中)同时修改相同行的相同文件时。当我们将我们的提交转换为补丁 - “删除C并添加D” - 但他们的更改表示要保留C并添加E。Git不知道使用哪个更改:我们应该保留C并添加D和E,还是删除C并仅保留E,或者怎么做呢?

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

在所有合并冲突的情况下,Git都会放弃合并,并暂时在您的工作树中写入有冲突的文件,声明合并冲突,然后停止。现在轮到您来完成合并 - 通过提供正确的快照,然后git addgit commit结果。

合并冲突当然可以在进行git merge时发生(此时“我们”的版本和“他们”的版本非常清晰)。但是它们也可能发生在git apply -3(-3表示“使用三向合并”),git cherry-pick和git revert期间。如果补丁无法干净地应用,Git可以找出要使用的公共基础,然后找出两个部分的更改:正在应用的补丁中的内容以及从公共基础到您现在所在的提交之间发生了什么更改,您正在尝试使用git cherry-pick或git revert来修补。
关于“ours”的版本,在所有情况下都是HEAD版本。这在rebase期间也是如此,但是rebase通过执行一系列重复的cherry-picks工作,并且此时HEAD是您正在构建的新替换分支。请参见CommaToast的这条评论:“由于头部是思维所在的位置,这是身份的来源,这是自我的来源,因此认为指向HEAD的任何东西都是‘我的’,这样就更有意义了...”。
最后关于git commit的一个注意事项。如上所述,通常会使用git add将文件从工作树复制到索引/暂存区。这也标记了文件在合并冲突期间已解决,这就是为什么您需要编辑文件,然后再使用git add的原因。一旦您修复并git add了所有文件,您就可以提交结果合并的git commit。这完成了合并、cherry-pick、revert或rebase步骤或任何具有冲突的操作。
当您像这样运行git commit时,没有指定命名文件,Git会提交索引(暂存区)内容。因此,新的HEAD提交与索引匹配。我们稍后将使用这个事实。
如果您运行git commit

那真是非常全面,现在我可能不会再犯同样的错误了。谢谢。我的救命稻草是我可以确定没有人拉了我的推送,因为这一切都发生在几分钟内。所以我复制了冲突的文件,将HEAD重置回ec5e61,并从那里开始工作。我失去了历史记录,但所有的文件都恢复到了原位。 - Jean

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