Git 痛苦第 2 部分:当 Git 不会提交、拉取或推送文件时,这意味着什么?



基本的 git 本体仍然是虚幻的,令人深感沮丧。

我们在 github.com 有一个主存储库。假设我们在该存储库中有 1 个文件,即一个 R 文件。

  1. 我克隆。

  2. 我的协作者克隆。

  3. 她编辑。她承诺。她推。

  4. 编辑,我承诺。我推。我不能,因为她已经推动了。

  5. 所以我拉。我合并代码。我让它工作正常。我又推了推。我不能。

![已拒绝] HEAD -> 主控(非快进) 错误:无法将某些引用推送到"https://github.com/PeterLeopold/myRepo" 提示:更新被拒绝,因为当前分支的提示落后 提示:它的远程对应物。集成远程更改(例如 提示:"git pull ...")然后再推。 提示:有关详细信息,请参阅"git push --help"中的"关于快进的说明"。

  1. 我又拉了。我不能。

错误:您尚未完成合并(MERGE_HEAD存在)。 提示:请在合并前提交更改。 致命:由于未完成的合并而退出。

  1. 我再次承诺。我不能。

错误:无法提交,因为您有未合并的文件。 提示:在工作树中修复它们,然后使用"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 addgit 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来记住这一点,像这样用全大写写写,1HEAD附加到的名称当前分支该名称指向的提交当前提交。 因此,无论我们在此处附加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是如何做到这一点的:我们选择一个提交,如CD,使用分支名称,如masterdev。 然后,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

合并基础最好的共享提交:两个分支上的提交,距离每个分支提示不太远。 分支提示是JL提交,这里很明显,最好的共享提交因此是提交H3

所以 Git 现在所做的是,对于快照中的每个文件,H,将该文件复制到索引中的插槽 #1。 如果文件被命名为README.mdbar.all.png,则索引现在具有 #1README.md和 #1bar.all.png。 对于快照中的每个文件J,将该文件复制到索引中的插槽 #2。 如果J具有相同的两个文件,则这些文件将进入插槽 2 和插槽 2README.mdbar.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向后工作并达到MJI


3在更纠结的图形中,并不总是清楚合并基础在哪里。 在某些情况下,有多个"最佳"提交,事情变得有点复杂。 我们不会担心这里的那些。

4了解索引的原因有很多,但在合并时必须操纵它这一事实可能是最重要的一个。 但是,如果不了解索引,也无法正确解释未跟踪的文件


git fetchgit push

Git 是分布式的。 这意味着有一个充满提交的存储库,但其他人也是如此。 在常见的设置中,还要做不止一个"其他人":可能有多个贡献者,无论如何,你和他们很可能都同意有一个中央存储库是存储库:所有其他克隆都只是这个集中存储的影子。

这不是 Git 内部的工作方式。 在 Git 中,每个存储库都是一个对等方:没有更精通或更不熟练的 Git 存储库。 但是很容易假GitHub上的Git存储库是"真正的"。 这样工作并没有错。 请记住,GitHub 存储库只是另一个克隆,在获取、推送和分支名称等方面的工作方式与任何 Git 克隆相同。

现在,每个 Git 存储库都有自己的分支名称。 哪些 Gits 共享是提交。 提交具有唯一的哈希 ID,因此每个 Git 都可以判断它是否拥有另一个提交的所有提交。

当您和其他人克隆中央存储库时,你们都从同一组提交开始。 您的 Gits 还采用中央存储库的分支名称,并将它们复制到您自己的远程跟踪名称,如origin/masterorigin/develop。 这些名称对于您自己的存储库也是私有的:只是每次您将 Git 连接到中央存储库时,您的 Git 都可以读出它们的分支名称,并更新您的远程跟踪名称。

创建远程跟踪名称后,您的 Git 存储库现在为您创建一个分支名称,通常为master. 此名称指向与您的origin/master相同的提交,您的 Git 将其设置为通过哈希 ID 指向与其 Git 相同的提交:

...--G--H   <-- master (HEAD), origin/master

让我们看看接下来会发生什么。

  1. 我克隆[从中央Git]。
  2. 我的协作者克隆 [来自中央 Git]。
  3. 她编辑。她承诺。她推。

当她运行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,他们根本没有签出任何东西。5git 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 实际上无法提交冲突的合并。 它可能应该能够 - 应该有一种方法可以暂停和恢复合并 - 但它不能。

最新更新