基本的 git 本体仍然是虚幻的,令人深感沮丧。
我们在 github.com 有一个主存储库。假设我们在该存储库中有 1 个文件,即一个 R 文件。
-
我克隆。
-
我的协作者克隆。
-
她编辑。她承诺。她推。
我 编辑,我承诺。我推。我不能,因为她已经推动了。
所以我拉。我合并代码。我让它工作正常。我又推了推。我不能。
![已拒绝] HEAD -> 主控(非快进) 错误:无法将某些引用推送到"https://github.com/PeterLeopold/myRepo" 提示:更新被拒绝,因为当前分支的提示落后 提示:它的远程对应物。集成远程更改(例如 提示:"git pull ...")然后再推。 提示:有关详细信息,请参阅"git push --help"中的"关于快进的说明"。
- 我又拉了。我不能。
错误:您尚未完成合并(MERGE_HEAD存在)。 提示:请在合并前提交更改。 致命:由于未完成的合并而退出。
- 我再次承诺。我不能。
错误:无法提交,因为您有未合并的文件。 提示:在工作树中修复它们,然后使用"git add/rm" 提示:根据需要标记分辨率并进行提交。 致命:由于未解决的冲突而退出。
U ksUpgrade.R
[冲突得到了很好的解决!
所以。现在我有完美的合并代码,无法将其发送回存储库。所以我通过电子邮件将其发送给我的合作者。 。
。我错过了什么?莱姆猜。git 还在 alpha 中?
附言在上一个问题中,我了解到 100% 的附属工作产品(文件、图形、表格)不能由 github 进行版本控制/共享,因为这些文件是在每次执行代码时生成的,git 必须始终尝试合并字节,并且不能简单地对它们进行分层(除非它们被标记为二进制,但它们不是,所以为了最大限度地提高生产力,必须欺骗 git。是的,欺骗 git 是一个专业提示,它是有据可查的!
我和我的合作者还没有使用分支或叉子。这些故障模式仍然指日可待。
(这种体验隐约让人想起 Java 中根本无法解决的类路径不兼容问题。
告诉我它会变得更好 . . . :<</p>
这是"git 状态"输出。您会注意到我一直在忙于尝试删除每个不是 R 或 Rmd 文件的文件。我在很大程度上但并不完全成功。
未暂存的更改:
(使用 "git add/rm ..."更新将提交的内容)
(使用"git 还原..."放弃工作目录中的更改)
deleted: README.html deleted: README.md deleted: SourceRawData.csv modified: age_dcu.png modified: bar.af.png modified: bar.all.png modified: bar.ca.png deleted: ksPrepForSSSSS.csv deleted: prepForTTTTT.csv deleted: senxxx.csv deleted: timaaaa.csv
未跟踪的文件:
(使用"git add ..."包含在将要提交的内容中)
tables for manuscript_2.docx tables for manuscript_3.docx ~$bles for manuscript_2.docx ~$bles for manuscript_3.docx
您可以尝试以下步骤:
- 请注意您在第 4 点中使用
git log
进行的提交的<commit-id>
。 - 将存储库的状态重置为该提交
git reset --hard <commit-id>
(您将丢失所有未提交的更改) - 执行提取操作,以防有任何新更改
- 执行合并操作(可能使用
--no-commit --no-ff
) - 提交更改
- 推送到远程
分布式协作工作很难。
Git 试图让它变得简单。 Git 失败。 不要责怪 Git...好吧,一定要责怪它,因为它试图让它变得容易有一堆缺陷。:-) 但事情出了问题的地方:
所以我拉。我合并代码。我让它工作正常。我又推了推。我不能。
这是因为您尚未提交合并。
你说你已经解决了它们(我会相信你),但现在你必须告诉Git你已经解决了它们。 这是一个单独的步骤。 你必须使用git add
或git rm
来告诉Git。
我在下面有一个很长的答案;从中阅读任何你喜欢的东西。
关于提交需要了解的内容
你需要一个好的心智模型,这样你就不会迷路。 从这个开始:Git 实际上与文件或分支无关。Git 实际上是关于提交。要使用 Git 完成工作,您需要提交。 存储库主要是一大堆提交。 所以你需要知道:提交到底是什么?
提交分为两部分:
它保存所有文件的快照。 这不是对文件的更改,而是实际文件本身的更改。 快照是提交的主要数据。
而且,它包含一些元数据或有关提交的信息:例如,谁在何时进行。
这些部分都是完全的,完全只读的,永远冻结的。 同时,要找到一个提交,你(或者至少是 Git)需要知道它的名字。
提交的名称是一些丑陋的大哈希ID,例如9fadedd637b312089337d73c3ed8447e9f0aa775
。 这些东西对人类消费毫无用处;您最多可以剪切并粘贴一个,或者在必要时使用缩写。 但重要的是要记住,这些是 Git 每次提交的真实名称。 每个提交都有一个唯一的提交:一旦你进行了一个新的提交,从此以后,任何地方、任何时间的其他提交都不能有这个名字。 从某种意义上说,该哈希名称甚至在您提交之前就已保留给该提交 - 但哈希取决于您进行该提交的确切秒数,因此基本上不可能预测未来的 ID。
每个提交中的一个关键元数据是其上一次提交的原始哈希 ID。 这使得每次提交有点像链上的珍珠,只是每颗珍珠只向后连接。 Git 无法记录未来提交的哈希 ID,因为它无法预测,并且一旦提交,任何提交都无法更改,因此这些链接只能向后。
当我们有带有提交哈希 ID 的东西时,我们说这个东西指向给定的提交。 因此,每个提交都指向其前身:
... <-F <-G <-H
我使用大写字母来代替真正的哈希 ID。 链中的最后一个是H
,所以H
指向G
,指向F
,依此类推。 如果我们跟踪链的时间足够长,我们最终会找到有史以来的第一个提交,这是第一个,毕竟不会指向后方。
关于分支名称的须知事项
实际上有很多需要知道的,但让我们从简单的部分开始。 每个 Git 存储库(每个克隆)都有自己的分支名称。 每个分支名称仅保存一个提交的哈希 ID。
让我们绘制一个只有三个提交的简单链:
A--B--C <-- master
名称master
指向C
;C
指向B
;B
指向A
,这是第一次提交,所以我们停止了。 与此同时,我懒得(嗯......)在提交之间绘制箭头。 我们知道他们只走一条路——倒退——这就足够了;一旦它们被制造出来,它们就不能改变,所以我们可以在方便的时候忽略这里的方向。 但是,分支名称中的箭头可以更改。
现在让我们添加第二个分支名称,dev
用于开发。 我们也要指出C
,就像这样:
A--B--C <-- master, dev
现在我们需要知道:我们使用哪个分支名称?Git 通过在其中一个分支名称附加一个特殊名称HEAD
来记住这一点,像这样用全大写写写,1。HEAD
附加到的名称是当前分支,该名称指向的提交是当前提交。 因此,无论我们在此处附加HEAD
名称,当前的提交都将C
。 让我们将HEAD
附加到dev
:
A--B--C <-- master, dev (HEAD)
现在,让我们以通常的不进行任何合并的方式(您已经熟悉)进行新的提交。 这个新提交将获得一些丑陋的哈希ID,但我们将其称为D
:
A--B--C <-- master
D <-- dev (HEAD)
名称HEAD
仍然附加到名称dev
,但现在名称dev
指向新的提交D
。 新的提交指向旧的提交C
作为其父级,因为我们在D
时使用的是提交C
。
当我们这样做时,我们使分支名称指向最后一个或最近的提交。 这一切都是自动的。当前分支名称在新提交上前进。 旧的提交仍然可以通过从末尾开始并向后工作来找到。 这就是 Git 一直在做的事情:它向后工作。
1在 Windows 和 MacOS 上,您通常可以不以小写形式键入head
。 这是一个坏习惯,因为(a)它通常不适用于Linux,Git只是不理解它,(b)当你开始使用git worktree add
时它停止工作,其方式甚至比在Linux上不起作用更糟糕:它开始选择错误的提交。
索引和您的工作树
是时候谈谈索引和你的工作树了。 我们之前对此进行了掩饰,因为您已经知道如何像D
这样的新提交,但现在了解所有细节很重要,因为我们需要它们进行合并。
提交中的文件将永远冻结。 它们实际上无法改变。 它们还以特殊的、压缩的、仅限 Git 的格式存储。 这意味着它们可以存档,但对于完成任何实际工作毫无用处。 要对提交中的文件进行一些工作,我们必须让 Git 将这些文件复制出来,并将它们重新转换为可用的普通文件。
可用的文件位于您的工作树中,您可以在其中查看它们并处理它们。 这让我们想到了关于文件与提交的第一点:提交中的文件不是您可以查看和处理的文件。您必须先从提交中删除这些文件。
git checkout
命令执行此操作。 有一种简单但错误的方式来描述git checkout
是如何做到这一点的:我们选择一个提交,如C
或D
,使用分支名称,如master
或dev
。 然后,Git 从该提交中提取文件。
从理论上讲,这就足够了:我们在提交中有一个所有已提交文件的副本,在你的工作树中每个文件的另一个可用副本。 您可以处理它们,然后再次提交并从工作树中的文件进行新的提交。 这很容易,这就是其他一些版本控制系统所做的。 唉,这不是 Git 所做的。
相反,Git 保留每个文件的第三个副本,介于提交的副本和可用副本之间。阿拉伯数字中间副本位于 Git 的索引中。 Git 会在您第一次执行git checkout
时从您选择的提交中填充它。 因此,每个文件始终有三个副本:提交中的冻结副本、索引的副本以及您可以查看和使用的工作树副本。
这个名字——"索引"——很糟糕。 因此,Git 现在经常称它为暂存区,它描述了如何使用它。 文件的索引副本采用冻结格式,准备进入下一次提交。 如果更改工作树副本,则可能需要将工作树副本复制回索引,替换准备冻结的副本。 这就是git add
的作用:它从你的工作树复制到 Git 的索引中,使文件暂存,从而更新提交。
文件以前在那里! 嗯,它是,也就是说,除非它不是 - 如果它是一个全新的文件,它现在不在HEAD
中:它现在只在索引和工作树中。
因此,如果索引副本与HEAD
副本不匹配,则会暂存文件以进行提交。 如果文件是全新的,当然不匹配;否则,它以明显的方式匹配或不匹配。
由于工作树是计算机上的一组常规文件和目录/文件夹,因此您还可以创建不复制到索引中的工作树文件。 位于工作树中但不在索引中的文件是未跟踪的文件。 Git 经常会抱怨未跟踪的文件:git status
会不断通知您它们的存在。 (你可以让它闭嘴,但我们稍后会留给后面。
这让我们进入了关于文件和提交的第二点:将进入下一次提交的文件是 Git索引中的文件,而不是您可以在工作树中看到的文件。当你运行git commit
时,Git 只是打包索引中此时的任何内容。这就是下一个快照。
关于索引和工作树有很多微妙之处,我们在这里根本不会介绍。 Git 的索引可能是它最棘手的部分,特别是因为你无法真正看到它。 你也不能真正看到提交那么好,但至少git log
告诉你它们。 没有面向用户的命令可以告诉您有关索引的很多信息,除了git status
...git status
只能通过比较事物来告诉您(请参阅下面有关status
的更多信息)。
2从技术上讲,索引中的内容是模式、文件名和对内部 Git blob 对象的引用。 但是,除非你开始使用 Git 的一些内部命令,否则你可以将索引视为保存一个单独的副本。
分支最终分化...或不
当我们有示例三提交存储库时,我们在dev
上进行了第四次提交并得到:
A--B--C <-- master
D <-- dev (HEAD)
我们现在可以git checkout master
:
A--B--C <-- master (HEAD)
D <-- dev
这会从索引和工作树中删除提交D
,并将提交C
放入其中。 好吧,只要我们没有未承诺的工作,它就可以了。 如果我们这样做,事情就会变得复杂。 无论如何,假设它确实如此。
现在我们可以进行一些更改,git add
它们(您现在知道这些更改会将更新的工作树文件复制回索引,使它们准备好提交)并git commit
进行新的提交E
:
E <-- master (HEAD)
/
A--B--C
D <-- dev
当这种情况持续一段时间时,你会得到如下所示的内容:
o--o--o <-- branch1
/
...--o--o
o--o <-- branch2
等等。 但有时你只有:
A--B--C <-- master (HEAD)
D <-- dev
在这种情况下,如果你有一个分支名称指向一个提交,该提交也是早期分支的一部分——提交C
在两个分支上,这是另一个 Git 的特点——并且没有分歧,git merge dev
会发现这种情况,实际上不会进行任何合并。 它只是将名称向前移动master
,并检查出另一个提交:
git merge dev
结果在:
A--B--C
D <-- master (HEAD), dev
(之后我们可以再次将它们全部绘制在一条线上)。
Git 将这种非合并称为快进合并。 有时,这就是您想要的。 有时不是。 有时无论如何都是不可能的。
关于何时需要快进合并以及何时不需要的问题没有正确答案。 但是如果你不想要一个,并且 Git 认为它应该这样做,你可以告诉 Git不要做快进,做一个真正的合并。 无论如何,当分支出现分歧时,例如,当您有:
I--J <-- branch1
/
...--G--H
K--L <-- branch2
您需要进行真正的合并。
真正的合并
当 Git 进行真正的合并时,它需要覆盖它的索引和你的工作树。 这意味着当您开始时,它们必须是"干净的"(匹配当前提交),至少在一般情况下是这样。 如果它们不干净,您将收到一个错误,合并甚至不会开始,除了一些特殊情况。
您现在已经知道索引包含下一个建议的提交。 但是当你开始合并时,Git会扩展索引。 Git 不是只保存一组文件,而是让索引保存三组文件。 这三组文件位于索引中的编号暂存槽中。
合并称为三向合并,可能是因为它有三个输入。 三个输入是:
- 一个合并基提交,Git 自己找到;
- 您当前的提交,通过
HEAD
和当前分支名称找到;以及 - 另一个提交,您可以通过
git merge
命令选择。
在本例中,假设您执行git checkout branch1; git merge branch2
,以便具有:
I--J <-- branch1 (HEAD)
/
...--G--H
K--L <-- branch2
合并基础是最好的共享提交:在两个分支上的提交,距离每个分支提示不太远。 分支提示是J
和L
提交,这里很明显,最好的共享提交因此是提交H
。3
所以 Git 现在所做的是,对于快照中的每个文件,H
,将该文件复制到索引中的插槽 #1。 如果文件被命名为README.md
和bar.all.png
,则索引现在具有 #1README.md
和 #1bar.all.png
。 对于快照中的每个文件J
,将该文件复制到索引中的插槽 #2。 如果J
具有相同的两个文件,则这些文件将进入插槽 2 和插槽 2README.md
bar.all.png
。 然后 Git 将文件从L
读取到索引中,到插槽 3。
现在,每个索引槽中的每个提交都有每个文件的副本,Git 开始解析这些文件。 解析的副本进入"插槽零",这是正常的非合并索引状态。
如果所有三个副本都匹配,则任何副本都可以。 从三个编号的插槽中撕下所有三个插槽,并将一个放入插槽零。 (工作树副本很好,不要管它。
如果您的副本不同,但基础和他们的副本相同,请拿走您的副本。 取下他们的,把你的放到插槽零。 (工作树副本仍然很好,因为我们使用了你的。
如果他们的副本不同,但基础和你的相同,请拿走他们的副本。 (将工作树副本替换为他们的副本。
如果这三者都不同,Git 实际上必须努力工作。
当三个暂存槽中的三个副本都不同时,Git 实际上会尝试合并文件。 Git 是否可以以及何时可以做到这一点有点复杂。 如果 Git认为它自己正确地合并了这些副本,Git 会将生成的文件写入你的工作树和它自己的零插槽,并删除其他三个副本。 如果 Git 认为它没有正确合并这些内容,Git 会将其混乱的合并尝试留在您的工作树中——除非它认为该文件是二进制的,在这种情况下,它根本不接触该文件——并将所有三个副本保留在三个编号的插槽中。
这些编号的插槽(如果有仍在使用中)会将合并标记为冲突。 你的工作是以某种方式(随心所欲)想出正确的合并文件并将其复制到索引槽零中,清除槽 1、2 和 3。 一种简单的方法是(至少通常)编辑工作树中的混乱合并,然后运行git add
,它复制到索引并执行所有插槽重置。
还有其他一些方法可以解决合并冲突,我们只会轻描淡写。 假设您删除了一个文件,并且他们对其进行了修改,因此基本副本及其副本是不同的,但您根本没有副本。 这也会导致合并冲突,但这一次,插槽 #2(--ours
副本)只是空的。 您仍然有未合并的文件,但现在您必须以其他方式合并它。 您可能仍然使用git add
,它仍然以相同的方式工作,或者可能git rm
从文件所在的任何插槽中删除文件。
无论如何,如果您遇到合并失败,这就是您的命运。 您处于这种未合并状态。您必须完成合并,否则将中止合并。这些是你唯一的选择。必须解析所有未解析的索引条目。 这是您需要了解索引的原因之一。四只有在解决所有冲突后才能提交。
一旦你有一个合并提交,你就有了这个:
I--J
/
...--G--H M <-- branch1 (HEAD)
/
K--L <-- branch2
新的合并提交像往常一样具有快照(基于您在索引中放入的任何内容,位于正常的插槽零位置),并且有两个父项。它指向现有的提交J
就像普通提交一样,但它也指向提交L
,即您选择合并的提交。
您可以继续添加普通(非合并)提交:
I--J
/
...--G--H M--N--O--P <-- branch1 (HEAD)
/
K--L <-- branch2
并且图形只是正常增长,分支名称继续指向分支中的最后一个提交。 请注意,过去只在branch2
上提交的提交K-L
现在都在两个分支上,但提交I-J-M-N-O-P
都只在branch1
上:你可以从P
开始,然后向后工作到所有提交,遵循M
的两个链接,但你不能从L
向后工作并达到M
或J
或I
。
3在更纠结的图形中,并不总是清楚合并基础在哪里。 在某些情况下,有多个"最佳"提交,事情变得有点复杂。 我们不会担心这里的那些。
4了解索引的原因有很多,但在合并时必须操纵它这一事实可能是最重要的一个。 但是,如果不了解索引,也无法正确解释未跟踪的文件。
git fetch
和git push
Git 是分布式的。 这意味着您有一个充满提交的存储库,但其他人也是如此。 在常见的设置中,还要做不止一个"其他人":可能有多个贡献者,无论如何,你和他们很可能都同意有一个中央存储库是存储库:所有其他克隆都只是这个集中存储的影子。
这不是 Git 内部的工作方式。 在 Git 中,每个存储库都是一个对等方:没有更精通或更不熟练的 Git 存储库。 但是很容易假装GitHub上的Git存储库是"真正的"。 这样工作并没有错。 请记住,GitHub 存储库只是另一个克隆,在获取、推送和分支名称等方面的工作方式与任何 Git 克隆相同。
现在,每个 Git 存储库都有自己的分支名称。 哪些 Gits 共享是提交。 提交具有唯一的哈希 ID,因此每个 Git 都可以判断它是否拥有另一个提交的所有提交。
当您和其他人克隆中央存储库时,你们都从同一组提交开始。 您的 Gits 还采用中央存储库的分支名称,并将它们复制到您自己的远程跟踪名称,如origin/master
和origin/develop
。 这些名称对于您自己的存储库也是私有的:只是每次您将 Git 连接到中央存储库时,您的 Git 都可以读出它们的分支名称,并更新您的远程跟踪名称。
创建远程跟踪名称后,您的 Git 存储库现在为您创建一个分支名称,通常为master
. 此名称指向与您的origin/master
相同的提交,您的 Git 将其设置为通过哈希 ID 指向与其 Git 相同的提交:
...--G--H <-- master (HEAD), origin/master
让我们看看接下来会发生什么。
- 我克隆[从中央Git]。
- 我的协作者克隆 [来自中央 Git]。
- 她编辑。她承诺。她推。
当她运行git commit
时,她得到了一个新的,唯一的哈希ID,与其他所有哈希ID不同。 我们称之为I
. 她的 Git 更新了她的master
:
...--G--H <-- origin/master
I <-- master (HEAD)
然后她跑git push
.
她的 Git 现在调用了中央 Git。 她的 Git 对那个中心 Git 说:嘿,我被告知要给你提供哈希I
提交。 它的父级是H
. 你想要I
吗?他们说:我没见过I
,哎呀! 不过,我已经有了H
。 只要给我承诺I
.
他们将提交I
暂时粘贴到他们的存储库中,没有名称:
...--G--H <-- master
I [no name]
请记住,这是中央存储库中的视图。 我省略了HEAD
因为我们不在乎他们签出了哪个分支,如果有的话,如果是 GitHub,他们根本没有签出任何东西。5她git push
的最后一部分是她的 Git 询问他们的 Git:如果可以,请将您的master
设置为现在指向I
。
他们没有理由反对,她有权提出请求,所以他们这样做了。 他们的 Git 现在有:
...--G--H--I <-- master
她的 Git 更新了她的origin/master
,以便她:
...--G--H--I <-- master (HEAD), origin/master
另一方面,您仍然拥有以下内容:
...--G--H <-- master (HEAD), origin/master
您的 Git 与中央 Git 没有联系,因此您的 Git 不知道有一个新的提交I
。
编辑,我承诺。我推。
您可以编辑并提交,并使用新的唯一哈希 ID 进行新提交。 让我们画出来:
...--G--H <-- origin/master
J <-- master (HEAD)
你运行git push
,所以你的 Git 调用中央 Git 并提供提交J
(使用父H
)。 他们拿J
并暂时隔离:
...--G--H--I <-- master
J [no name]
现在你的 Git 问他们的 Git:如果可以,请将你的master
设置为指向提交J
。但这一次他们说不!他们说:如果我这样做,我将失去找到提交I
的能力。Gits 向后工作。 从I
,它找到H
,然后G
等等。 从J
,它可以找到H
,但H
只是指向后方。 他们的 Git 可以说,如果他们移动master
指向J
,他们将失去提交I
。 所以他们只是说不:不是快进。
这个快进术语与我们之前看到的git merge
操作实际上并未合并的术语相匹配。 Git 需要更改分支名称的结果,正如git push
所建议的那样,成为这些快进操作之一。
这就是您的推送被拒绝的原因。现在您需要采取纠正措施。
5服务器通常具有裸存储库,这些存储库没有工作树。 这是必要的,因为在有人处理分支时更改分支名称会弄乱所有内容。 尽管如此,服务器裸存储库仍然有一个HEAD
,它控制着很少有人关心的功能,我们不会在这里讨论。
关于git pull
要解决问题,您首先需要获得协作者的新提交。 这是一个git fetch
操作。 此时可以运行git fetch
- 它始终是安全的 -您的Git 存储库将像这样调整:
...--G--H--I <-- origin/master
J <-- master (HEAD)
你的 Git 已将共享提交I
添加到你的集合中,并更新了你的origin/*
名称以匹配他们的名称。您的分支名称均保持不变。
但是,现在,您需要以某种方式将您在提交J
中所做的与她在提交I
中所做的结合起来。 这个组合步骤是——或者可以,无论如何——一个普通的普通git merge
。
您可以运行:
git merge origin/master
它告诉你的 Git:找到我的提交J
(由我的HEAD
找到)和提交I
(由origin/master
找到)之间的公共合并基础。 然后,将H
中的文件放入插槽 1,将J
中的文件放入插槽 2,将I
中的文件放入插槽 3。 然后尝试合并。
git pull
命令是一个方便的命令,它只是将运行git fetch
与第二个 Git 命令(通常git merge
)组合在一起。 如果 Git 能够单独组合你的工作和她的工作,Git将提交结果。
(注意:合并不是您唯一的选择。 您也可以使用git rebase
. 不过,变基其实比合并更复杂,这个答案已经太长了......
Git 究竟什么时候可以组合工作?
要回答这个问题,请回到提交是快照的事实。 我们有:
J <-- master (HEAD)
/
...--H
I <-- origin/master
Git 必须将合并基础H
中的快照与J
中的快照进行比较,您的提交。 无论您更改了什么,这就是 Git 必须对提交H
中的文件执行的操作。 但是 Git 还必须将H
中的快照与I
中的快照进行比较:她的提交。 无论她做了什么,这就是 Git 必须对提交H
中的文件做的事情。
当你在提交中进行了大量更改(例如"删除文件")并且她进行了零碎的更改(例如"修改同一文件")时,Git 根本无法组合这些更改。 Git可以组合的唯一更改是在行上进行的更改(当然,或者在完全独立的文件中)。 Git 非常面向行。 Git 将运行以下内部等效项:
git diff --find-renames <hash-of-H> <hash-of-J> # what you changed
git diff --find-renames <hash-of-H> <hash-of-I> # what she changed
如果这些差异显示她更改了bar.all.png
的第 47 行,您最好不要更改第 47 行,否则 Git 将不知道如何组合这些。
当然,*.png
文件没有行。 所以 Git永远无法将这些结合起来! Git 在这里是错误的工具。 如果您有图像合并工具,请考虑从三个索引槽中提取所有三个 PNG 文件,并对这三个文件运行图像合并工具。
观察提交图
在 Git 中完成任何事情的一个关键是观察提交图,以便您可以看到您的提交与其他人的提交不同的地方,以及它们在哪里连接。 要查看图表,请考虑花哨的图形查看器(可能会绘制漂亮的图形查看器),或使用git log --decorate --oneline --graph
。 D、O 和 G 选项提供如下所示的输出:
* 9fadedd637 (HEAD -> master, origin/master, origin/HEAD) Merge branch 'ds/default-pack-use-sparse-to-true'
|
| * 2d657ab95f pack-objects: flip the use of GIT_TEST_PACK_SPARSE
| * de3a864114 config: set pack.useSparse=true by default
* | 3bab5d5625 The second batch post 2.26 cycle
[snip]
当前提交,来自HEAD
,位于顶部。 这是一个合并提交,因此它有两个指向先前提交的链接。 Git 将每个提交单独放在一行上——由于--oneline
选项,一行——图形绘制非常粗糙,但可用。
关于git status
git status
命令有两种不同的模式,它将根据您是否有未合并的文件自动选择。
如果没有未合并的文件(如果索引中的所有内容都处于阶段零),则git status
的输出将仅列出暂存文件和未暂存文件。 但是,如果您确实有未合并的文件,Git 将显示未合并的文件,而不是任何未暂存的文件。
当一切都处于零阶段时,git status
内部运行两个快速git diff
:
- 第一个比较
HEAD
与索引。 无论这里有什么不同,Git 都说为提交而分阶段。 对于匹配的文件,Git 什么也没说。 - 第二个比较索引与工作树。 无论这里有什么不同,Git 都说不是为提交而暂存的。 对于匹配的文件,Git 什么也没说。
因此,这可以让您查看索引中的内容,有点像:您看到它如何比较,而不是其中的实际内容。
关于git stash
另一个答案建议git stash
. 我通常避免git stash
. 它所做的是提交,然后使用git reset --hard
擦除您在索引和工作树中所做的任何操作(尽管您所做的都保存在它所做的提交中)。 它所做的提交不在任何分支上,即使按照 Git 标准也很难看到和使用。 所以我认为最好只做普通的提交。
请注意,由于 Git 从索引进行提交,因此git stash
在面对冲突合并时变得无能为力。 Git 实际上无法提交冲突的合并。 它可能应该能够 - 应该有一种方法可以暂停和恢复合并 - 但它不能。