合并主机将所有更改添加到我的分支

suu*_*iam 4 git github

我正在一个分支上工作,我更改了 5 个左右的文件。在这样做的同时,其他人已经将超过 100 个文件中的更改推送到 master。现在,在我的分支机构工作时,我想merge master时不时地去我当地的分支机构。我会这样做:

git checkout master
git pull
git checkout my-branch
git merge master
git push

但现在,由于某种原因,master 上其他人更改的所有文件都添加到了我的更改中。所以如果我真的push在之后merge master,它会显示我更改了 >100 个文件,而不是仅仅 5 个。我做错了什么?谢谢。

tor*_*rek 7

这里实际上没有任何问题:你只是误解了 Git 所说的内容。(我想,Git 可能被误解这一事实可能被认为是一个问题,但在实践中,无论是 Git 还是任何其他版本控制系统,这东西都很困难,需要学习和经验。)

\n\n

关于 Git、文件和提交,需要了解一些关键事项:

\n\n
    \n
  • Git 在您与之交互的层面上存储的是提交。像这样的分支名称master很有用,但它们实际上只是帮助 Git\xe2\x80\x94 和你\xe2\x80\x94查找提交。我们稍后会看到它是如何工作的。

  • \n
  • 提交确实会存储文件,但您通常一次会处理整个提交。你告诉 Git:让我提交 X,对于某个标识提交的X ,你将获得该提交的所有文件。您要么拥有提交\xe2\x80\x94,因此拥有所有文件\xe2\x80\x94,要么根本没有提交,因此您没有任何文件。

  • \n
  • 每个提交都有一个唯一的 ID。这个 ID 是它的哈希 ID,它是一个由看起来随机的字母和数字组成的丑陋的大字符串,例如9fadedd637b312089337d73c3ed8447e9f0aa775. 该哈希 ID 一旦存在,就意味着提交,而不是任何其他提交。

  • \n
  • 任何一项提交的内容都是完全、完全、100% 只读的。存储在提交中的文件以及任何提交的元数据都无法更改。(这样做的原因是哈希 ID 是提交内容的加密校验和。如果您取出提交,修改其任何位,然后将其放回去,您将获得一个新的、不同的提交,其中一个新的、不同的哈希 ID。旧的提交仍然在那里:您刚刚又添加了一个提交。)

  • \n
  • 每个提交的所有文件快照就是这样:一个快照。也就是说,提交根本存储更改。

  • \n
  • 但是当您查看提交时,Git 通常会向您显示更改。这是一个诡计!但这也是一件好事,因为无论如何这通常更有趣。

  • \n
  • Git可以将提交显示为更改的原因是因为大多数提交都存储单个先前提交或提交的原始哈希 ID。因此,给定任何一个提交X,Git 都可以后退一步来查找X之前的 提交。该提交也有一个快照。

  • \n
\n\n

Git 可以\xe2\x80\x94 并且\xe2\x80\x94 只是提取两个快照(父快照子快照),然后比较它们。对于每个相同的文件 Git 根本不说任何内容。对于每个不同的文件,Git 都会向您显示一个秘诀:从文件的父级副本开始。在这里添加这一行。删除那里的那个。根据需要重复,当您完成添加和删除后,您将获得子提交中的文件版本。

\n\n

当您有一行简单的提交(全部连续)时,您可以绘制它们或思考它们,如下所示:

\n\n
... <-F <-G <-H ...\n
Run Code Online (Sandbox Code Playgroud)\n\n

其中H代表找到提交的某个哈希 ID。提交H本身包含其父级的哈希 ID,我们将其称为G。这让 Git 可以找到G. 包含其父G的哈希 ID ,这让 Git 可以查找,等等。FF

\n\n

像这样的分支名称只是保存链中最后一次master提交的哈希 ID 。最后一次提交向后指向其父级,父级再次向后指向,依此类推。所以我们可以将其绘制为:

\n\n
...--F--G--H   <-- master\n
Run Code Online (Sandbox Code Playgroud)\n\n

我们实际上不需要将从一个提交到下一个提交的连接箭头绘制为箭头,因为它们无法更改。任何提交的任何部分都不能更改。所以他们总是指向后面。然而,从分支名称出来的箭头确实发生了变化。我们可以从以下开始:

\n\n
...--G--H   <-- master\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后添加一个新的分支名称,以便我们可以在不触及我们的master情况下进行新的提交:

\n\n
...--G--H   <-- master, dev\n
Run Code Online (Sandbox Code Playgroud)\n\n

但最终我们将向我们的分支添加一个新的提交。让我们添加特殊名称HEADdev记住这是我们正在使用的名称\xe2\x80\x94我们在运行git checkout dev\xe2\x80\x94时使用的名称并将其绘制如下:

\n\n
...--G--H   <-- master, dev (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在我们将进行新的提交。它将获得一些大而丑陋的随机哈希 ID,但我们只需调用它I,并像这样绘制它:

\n\n
          I\n         /\n...--G--H   <-- master, dev (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n\n

I指向H,因为H是我们 make 时的当前提交I

\n\n

现在来了一个聪明的技巧:Git 将I\ 的哈希 ID 写入分支名称。更改的分支名称是HEAD附加到: 的分支名称dev。所以现在dev指向I而不是H

\n\n
          I   <-- dev (HEAD)\n         /\n...--G--H   <-- master\n
Run Code Online (Sandbox Code Playgroud)\n\n

现有的提交没有改变。(毕竟没有人可以。)但是我们的提交I现在存在,并指向现有提交H,现在我们的名称dev指向 commit I,它现在是当前提交。

\n\n

当我们进行新的提交时J,Git 会做同样的事情,给我们:

\n\n
          I--J   <-- dev (HEAD)\n         /\n...--G--H   <-- master\n
Run Code Online (Sandbox Code Playgroud)\n\n

不过,此时我们可能会运行git checkout masterand git pull(或git fetch && git merge)并获取其他人所做的一些新提交。为了对称起见,我将绘制其他人所做的两个提交。这也使我们master超越了他们的两个新提交:

\n\n
          I--J   <-- dev\n         /\n...--G--H\n         \\\n          K--L   <-- master (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n\n

当前分支是now master当前提交是 now L。您可能想知道为什么我将它们画在单独的行上:主要是为了强调提交到两个分支H上。这一奇怪的事实\xe2\x80\x94 提交一次可以在多个分支上\xe2\x80\x94 对于 Git 来说有些特殊。

\n\n

我们现在可以运行git checkout dev准备合并masterdev. 第一步只是HEAD移至dev

\n\n
          I--J   <-- dev (HEAD)\n         /\n...--G--H\n         \\\n          K--L   <-- master\n
Run Code Online (Sandbox Code Playgroud)\n\n

我们现在可以合并两个分支。我们实际上是在合并提交,因为 Git 就是关于提交的,但让我们看看这是如何工作的。

\n\n

我们的提交中I-J,我们对一些文件进行了一些更改。在他们的提交中K-L,他们\xe2\x80\x94无论他们是谁\xe2\x80\x94对某些文件进行了一些更改。我们即将进行一个新的合并提交,这个合并提交将保存一个快照,就像每个提交一样。这张快照中应该包含什么内容?

\n\n

答案是:我们希望这张快照能够将我们的工作与他们的工作结合起来。也就是说,我们希望从共享的公共提交中的每个文件开始。从图中可以清楚地看出,最好的共享共同起点是 commit H。该提交位于两个分支上。也是如此G,但H更好因为它最接近JL

\n\n

因此,Git 将从H. 它将与 进行比较 ,看看我们改变了什么。我们更改的每个文件都有一个配方:添加一些行,删除一些行。然后,Git 将再次启动 中的内容,并与进行比较,看看它们发生了什么变化。他们更改的每个文件都有一个配方:添加一些行,删除一些行。HJH HL

\n\n

Git 现在结合了这些更改方法。无论我们在哪里更改了文件而他们没有更改,结果都是我们的文件。无论他们更改了文件而我们没有更改,结果都是他们的文件。如果我们更改了一个特定文件,Git会合并我们的更改。这是合并的困难部分:合并更改。

\n\n

如果我们更改的与他们更改的行不同(并且配方也没有相邻或邻接的行),Git 将能够自行组合这些更改。或者,如果我们和他们对某些行进行完全相同的更改\xe2\x80\x94,例如,如果我们都在某处修复了相同的拼写错误\xe2\x80\x94Git 将只获取更改的一份副本。否则\xe2\x80\x94如果我们以与他们不同的方式更改了一行\xe2\x80\x94Git将为该文件产生一个合并冲突错误,并留下一个需要清理的烂摊子。

\n\n

尽其所能合并所有文件后,Git 现在要么因合并冲突而停止,要么没有任何合并冲突并继续进行合并提交。为了让事情变得简单,我们假设没有冲突。

\n\n

此合并提交的唯一特别之处在于,它有两个而不是一个父级。我们可以这样画:

\n\n
          I--J\n         /    \\\n...--G--H      M   <-- dev (HEAD)\n         \\    /\n          K--L   <-- master\n
Run Code Online (Sandbox Code Playgroud)\n\n

新提交的第一个父级是commitM,它像往常一样J将分支前进一步。新提交的第二个父级devcommitML它仍然是branch的尖端提交master。名称没有发生任何变化master现有提交也没有发生更改(因为没有一个可以更改),但是新的合并提交M使得提交KL现在也都在分支上dev,以及一直到 的提交J

\n\n

为什么要合并工作

\n\n

如果我们现在问 Git:某个特定文件 F 的某些特定行(例如第 42 行)来自哪里,Git 可以查看 中的快照M,然后查看J中的快照L如果F的第 42行在M和中匹配J,但在和中不同,则第 42 行“来自” :合并保留了来自 的行。Git 现在将后退一次提交,到,以查看F中的第 42 行是否与和中的匹配。如果它们在那里不同,Git 会说第 42 行来自进行提交的人,在他们进行提交的日期。MLJJIIJII

\n\n

如果 的第 42行在和 中F匹配,并且在 中不同,则意味着合并保留了 中的第 42 行。因此,Git 应该根据需要退回到,然后是,依此类推。MLJLLK

\n\n

如果第 42 行在ML J中匹配,则它可能来自原样不变H,Git 将继续向后移动,一次一个提交,以查看它是否在G-to-H转换中发生更改,或者是否来自更早的更改。

\n\n

查看一个特定文件的特定行的命令是git blame(或git annotate)。请注意,像许多 Git 命令一样,它必须通过提交来工作,一次一步,随着时间倒退。这些提交一次一个,存储库中的历史记录。历史就是承诺;提交已成为历史。

\n\n

你不能删除别人的更改(除非他们是错误的)

\n\n

任何合并的结果都会自动生成正确的文件。未来的合并将假设输入的任何内容都是正确的。如果你删除他们的更改,这意味着你说他们的代码很糟糕,应该被忘记。

\n\n

如果确实如此,则可以删除此代码\xe2\x80\x94,但您可能应该在另一个单独的提交中执行此操作,而不是直接在合并中执行此操作。

\n\n

关于快进合并的旁注

\n\n

虽然我们没有在这里正确介绍它,但Chuck Lu 的答案提到了快进合并。假设我们绘制了一系列这样的提交:

\n\n
...--C--D--E   <-- branch1 (HEAD)\n            \\\n             F--G--H   <-- branch2\n
Run Code Online (Sandbox Code Playgroud)\n\n

表明我们现在已经签出了分支branch1,因此也签出了提交。E如果我们运行git merge branch2,Git 会发现两个分支上的最佳公共提交是当前提交E。在这种情况下,Git 不必进行真正的合并。如果有这个选项,Git 将执行快进操作,本质上是执行 a git checkoutof commit ,但在此过程中向前H拖动分支名称:branch1

\n\n
...--C--D--E\n            \\\n             F--G--H   <-- branch1 (HEAD), branch2\n
Run Code Online (Sandbox Code Playgroud)\n\n

(现在没有理由在图中保留对角线;当您自己绘制时,请随意将其删除。)

\n\n

E当 Git 执行此操作时,它还会将旧提交中的快照与新当前提交中的快照进行比较H。对于每个更改的文件,它会告诉您有关该更改的一些信息。

\n\n

您可以通过运行看到相同的比较:

\n\n
git diff --stat <hash-of-E> HEAD\n
Run Code Online (Sandbox Code Playgroud)\n\n

由于HEAD现在命名为 commit H,这会将快照与 \xe2\x80\x94 中git diff的快照进行比较,与 \xe2\x80\x94E中的快照H完全相同,git pull因此再次打印相同的信息。

\n\n

当您进行真正的合并时(就像我们对 所做的那样M),您当时看到的信息基于您之前的提交 ( J) 和 中的比较M。由于M 结合了分支两侧的更改,但是J您的更改,因此您看到的是它们的更改。但是,您可以运行git diff --stat master dev来比较 commitL与 commit M:这一次,您将看到合并从分支的“您这边”带来了什么。

\n\n

M由于它的两个父级,所以很难看出真正的合并中的内容。实际上,您需要两个单独的git diff命令才能正确查看它。git show如果您给它标志,该命令可以自动执行此-m操作,但我们不会在这里介绍这一点。

\n