您在这里混合了很多东西。特别是,在使用
git stash
和
git 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结合我们的更改和他们的更改。例如,假设您都使用F1
,F2
和F3
开始,并且您更改了文件F1
而他们没有,他们更改了文件F2
而您没有。这很容易组合:采用您修改的F1
和他们修改的F2
。在F3
中,您更改了文件顶部附近的一行,而他们更改了文件底部附近的一行。这有点难以组合,但仍然不是问题:Git只需要采用两个更改。
但有些更改根本无法组合。这些更改是合并冲突。
合并冲突有多种形式。最常见的情况是当“您”(在您的提交中)和“他们”(在他们的提交中)同时修改相同行的相同文件时。当我们将我们的提交转换为补丁 - “删除C并添加D” - 但他们的更改表示要保留C并添加E。Git不知道使用哪个更改:我们应该保留C并添加D和E,还是删除C并仅保留E,或者怎么做呢?
但这不是您遇到的情况:您遇到了修改/删除冲突。当您说在文件train.R
中,我们应该删除C并添加D,而他们则说“扔掉整个train.R
文件”时,就会发生修改/删除冲突。
在所有合并冲突的情况下,Git都会放弃合并,并暂时在您的工作树中写入有冲突的文件,声明合并冲突,然后停止。现在轮到您来完成合并 - 通过提供正确的快照,然后git add
和git 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
git stash apply
?你没有看到第一个警告,说train.R
文件有冲突吗?如果从存储区恢复文件时出现冲突,没关系,你只需要处理它即可。看起来像是有人从远程删除了该文件,但 Git 告诉你它在本地保留了该文件。此时,情况很混乱,我也无法给出答案。 - Tim Biegeleisengit add model/R/train.R
并提交即可。然后你就完成了。 - Tim Biegeleisen