繁体   English   中英

如何将更改从 master 中的分支拉到子分支?

[英]How do I pull changes from a branch in master to a subbranch?

所以我有一个分支 B1,它是 master 的一个分支。 我后来从分支 B1 创建了分支 B2。 但是,当我在 B2 中执行 git 拉动时,我不会从分支 B1 拉动更改。 如何从 B1 中提取更改? 谢谢你。

我建议避免git pull ,至少(或特别是)如果您刚开始使用 Git。 所有git pull都运行git fetch ,然后为您运行第二个 Git 命令。 问题是这掩盖了真正发生的事情,阻止你学习你需要知道的东西。

您需要了解的内容从提交的真正含义和作用开始,因为 Git 完全是关于提交的。 Git与文件无关,尽管提交确实包含文件; 它甚至也不是关于分支的,尽管像b1b2这样的分支名称可以帮助你——或者至少是 Git——找到提交。 但是,虽然提交保存文件,分支名称保存提交 hash ID,但 Git 确实与提交有关。

所以,每次提交:

  • 保存所有文件的快照。 这是一个完整的快照,而不是自上次提交以来的更改。
  • 保存一些额外的信息——一些元数据——比如你的名字和 email 地址,以及你为什么提交。 其中大部分仅适用于其他人,但元数据中有一个关键元素适用于 Git 本身,我们稍后会介绍。
  • 有一个 hash ID。 hash ID 是一大串丑陋的字母和数字。 看起来是随机的,尽管它完全不是随机的。 事实上,hash ID 是提交的完整内容的加密校验和:文件快照和元数据。

所有这一切的结果是,一旦提交,任何提交的任何部分都不会改变。 您可以从提交中取出所有内容,在其他地方对其进行更改,然后从结果中进行新的提交,但是如果您在任何地方都更改了一点——您的姓名、您的日志消息、某个来源的一点文件,不管怎样——你得到的是一个新的和不同的提交。 So commit hash IDs are guaranteed to find one particular commit, and every Git uses the same hash algorithm, so all Gits everywhere use the same hash IDs for the same commits.

分支如何生长,或者,没有子分支这样的东西

接下来你需要知道的是,没有子分支这样的东西。

鉴于每个提交都有自己唯一的 hash ID,我们仍然有一个问题:我们如何找到提交? hash ID 看起来是随机的,而且肯定没有任何有用的顺序。 这就是 Git 的内部元数据和分支名称的来源。

每个提交都包含作为其元数据的一部分的父提交hash ID 的列表。 通常这里只有一个 hash ID。 那个 hash ID 是此提交的(单一)父级。 这将所有提交一个一个地反向联系在一起:

... <-F <-G <-H

其中H代表链中最后一次提交的 hash ID。 提交H本身记录了之前提交G的实际 hash ID,因此通过查看H内部,我们可以找到数字 - hash ID - 让我们找到G 提交G存储之前提交F的 hash ID,依此类推。

我们现在已将问题简化为查找提交H 我们这样做的方法是使用masterb1之类的分支名称 我们将提交H的原始 hash ID 存储到name中,给我们:

...--F--G--H   <-- b1

每个分支名称都包含一个提交 hash ID。 如果我们创建第二个分支b2 ,我们必须选择各种现有提交之一,并使b2持有 hash ID。 通常我们可能会选择当前提交,即带有 hash H的提交:

...--F--G--H   <-- b1, b2

现在我们需要记住我们使用的名称 Git 为此使用特殊名称HEAD (全大写)。 有几种方法可以绘制它; 在这里,我将HEAD附加到这些分支名称之一的括号中:

...--F--G--H   <-- b1 (HEAD), b2

或者:

...--F--G--H   <-- b1, b2 (HEAD)

此时,让我们以通常的方式进行新的提交:修改一些内容, git addgit commit Git 从 Git 的索引构建新提交的快照——我们不会在这里正确描述,但这个索引是你必须运行git add所有时间——以及适当的元数据,包括你必须编写的日志消息。 这个新的提交获得了一个新的、唯一的 hash ID。 新提交的数据是新的快照,元数据就是你说的,除了父:那来自Git知道的,也就是当前提交此时是H 所以新的提交I指向现有的提交H

...--F--G--H
            \
             I

现在 Git 更新HEAD附加到的任何分支名称 如果是b2 ,则名称b2现在包含提交I的 hash ID:

...--F--G--H   <-- b1
            \
             I   <-- b2 (HEAD)

请注意,通过H的提交现在在两个分支上,而提交I仅在b1上。 名称HEAD仍然附加到当前分支,但现在当前提交是 commit I

合并

假设您开始:

...--G--H   <-- master (HEAD)

然后创建名称b1b2 ,都指向现有的提交H 然后您 select b1继续工作,使用git checkout b1 ,并进行两次提交:

          I--J   <-- b1 (HEAD)
         /
...--G--H   <-- master, b2

请注意, b2尚未移动。 现在你再次运行git checkout b2到 select commit H 您为IJ制作的快照仍然存在,一直冻结,但现在您再次使用来自H的快照。 您现在可以在b2上进行几次提交,给出:

          I--J   <-- b1
         /
...--G--H   <-- master
         \
          K--L   <-- b2 (HEAD)

请注意,此时,您可以git checkout出 master 并在那里进行新的提交:

          o--o   <-- b1
         /
...--o--o--o--o--o--o   <-- master (HEAD)
         \
          o--o   <-- b2

我们暂时不会担心这一点,但请注意,任何现有提交或任何其他分支名称都没有发生任何事情 每次我们添加提交时,当前分支名称都会移动。 新的提交都刚刚添加到现有的所有提交图表中。 完全不可能更改任何现有的提交,但我们从未这样做过。 我们所做的只是添加链接回现有提交的提交,然后更改分支名称

不过,让我们 go 回到合并的想法。 毕竟我们没有进行任何新的master提交,如果我们git checkout b1并暂时停止绘制名称master (因为它会妨碍),我们有:

          I--J   <-- b1 (HEAD)
         /
...--G--H
         \
          K--L   <-- b2

我们现在可以运行git merge b2 ,或者我们可以运行git checkout b2; git merge b1 git checkout b2; git merge b1 Git 现在将尽力合并工作,因为git merge是关于合并工作。

现在,每个提交都包含一个快照,但组合工作需要查看更改 Git 在这里所做的是使用其内部差异引擎,您可以使用git diff调用自己。 为了完成这项工作,Git 首先必须找到最佳公共提交:两个分支上的提交,并且与每个分支上的最新提交相差不大。

在这里,哪个提交是常见的提交是非常明显的。 那是提交H 所以 Git 现在会将H中的快照与我们使用git checkout选择的任何提交中的快照进行比较——提交HEAD附加到。 如果我们假设它是b1 ,那么这个比较的提交是J

git diff --find-renames <hash-of-H> <hash-of-J>   # what we changed

这告诉 Git “我们”在b1上发生了什么变化,相对于共同的起点。 然后 Git 区分另一个配对:

git diff --find-renames <hash-of-H> <hash-of-L>   # what they changed

这告诉 Git “他们”在b2上发生了什么变化,相对于相同的起点。

合并操作现在逐个文件地组合这些更改。 如果我们更改了main.py的第 42 行并且它们根本没有触及main.py ,则 Git 会接受我们的更改。 如果他们也触及main.py ,但不是第 42 行,Git 也会接受他们的更改。 如果我们都接触了第 42 行,我们最好都以相同的方式更改它。 如果没有,Git 会声明合并冲突并给我们留下一团糟。

假设没有合并冲突——上面不是可能的冲突的完整列表,只是明显的那种——Git 将能够组合这两组更改并将所有这些更改应用于提交中出现的所有文件H 这样,Git 即将进行的新提交既有我们J的更改,也有L的更改。

此时,Git 进行新的提交。 像往常一样,新提交有一个快照:那是通过组合更改构建的 Git。 像往常一样,新提交有一个父提交; 因为我们在分支b1上,所以这个父级是J 但是——不像往常那样——新的提交也有第二个父节点。 由于我们告诉 Git 合并提交L ,所以第二个父级是L

提交后,Git 将我们当前的分支名称向前拖动:

          I--J
         /    \
...--G--H      M   <-- b1 (HEAD)
         \    /
          K--L   <-- b2

这是运行git merge的结果。

Git 分布式

现在,您可能不是唯一做出提交的人。 其他人也做出承诺。 您通常从克隆存储库开始,可能来自 GitHub,也可能来自其他地方。 这会让你得到他们所有的承诺 默认情况下,您的 Git 将调用此其他 Git origin

我们已经看到,在任何存储库中,我们都可以通过分支名称找到某个分支的最后一次提交。 所以他们的 Git——GitHub 上的那个,或者其他的——有一些分支名称:

...--o--o   <-- master
         \
          o--o--o   <-- develop

等等。 当您克隆他们的存储库时,您将获得他们的所有提交,以及他们唯一的 hash ID 保留那些 hash ID。 但是你没有得到他们的分支名称 那些是他们的 您的 Git 获取它们的名称并更改它们,以便您可以拥有自己的分支名称。

您的 Git 对其分支名称所做的更改会将其名称转换为您的远程跟踪名称 这些名称看起来像origin/masterorigin/develop 这些记住,在您的存储库中,它们的分支名称是:

...--D--E   <-- origin/master
         \
          F--G--H   <-- origin/develop

对于git clone最后一步,您的 Git 创建一个新的分支名称,指向与其分支名称之一相同的提交。 您可以选择这是哪个分支,但通常它只是master ,这样您就可以得到:

...--D--E   <-- master (HEAD), origin/master
         \
          F--G--H   <-- origin/develop

如果你现在 go 进行新的提交,你会得到:

          I--J   <-- master (HEAD)
         /
...--D--E   <-- origin/master
         \
          F--G--H   <-- origin/develop

现在,假设他们,无论他们是谁,设法在他们的master上创建两个新的提交。 您的 Git 还没有这些提交,但您可以获得它们。

你跑:

git fetch origin

(或者只是git fetch ,默认为origin )。 您的 Git 再次调用他们的 Git。 他们告诉您的 Git 他们的分支名称(和其他名称),您的 Git 发现,天哪, KL可以从他们那里获得新的提交。 因此,您的 Git 会这样做,然后您的 Git 会相应地更新您的origin/master

          I--J   <-- master (HEAD)
         /
...--D--E--K--L   <-- origin/master
         \
          F--G--H   <-- origin/develop

(我在这里假设他们没有向他们的develop添加新的提交,否则我们现在也会在origin/develop上有新的提交。)

So, this is what git fetch is all about: your Git calls up some other Git, and gets new commits from that Git (if there are any to get) and then updates your remote-tracking names . 在此git fetch之后,您可能会有新的提交。 如果你想使用它们怎么办?

要在获取后使用他们的新提交,您需要第二个 Git 命令

假设您想他们的工作与您的工作合并,以通常的方式创建合并提交M 您现在可以运行:

git merge origin/master

这将使新的合并提交M自行生成,如果可以的话,将您的J与他们的L合并,就像我们在上面看到的那样。 通过远程跟踪名称而不是分支名称找到提交L并不重要。 Git 不在乎名字; Git 关心提交 所以我们得到:

          I--J--M   <-- master (HEAD)
         /     /
...--D--E--K--L   <-- origin/master
         \
          F--G--H   <-- origin/develop

这就是git pull进来的地方

pull命令只是将两个命令合二为一:

  • 运行git fetch从他们那里获取新的提交; 然后
  • 运行第二个 Git 命令以合并这些新提交。

默认的第二个命令是git merge ,因此git fetch使用刚刚进入的提交来运行git merge 但是git pull可以运行一种故意限制的获取,并且不只是与它们的任何新提交合并。 要知道要合并什么, git pull要求你在这里要使用什么。

令人困惑的部分是,当您在git pull命令上命名要使用的内容时,您必须使用它们的分支名称,即使您的 Git 稍后将使用您自己的远程跟踪名称 因此,如果您想从origin获取,然后与他们所谓的b2合并,您需要:

git pull origin b2

这会运行一种有限的git fetch ,然后与他们通过b2命名的任何提交合并,您的 Git 通过origin/b2命名。 这里缺少的斜线是因为第一步git fetch fetch——需要知道要调用哪个 Git (位于origin的那个),而第二步需要知道要使用它们的哪个分支名称

这两个步骤git fetchgit merge都可能失败。 git fetch失败的可能性很小(如果失败, git pull将停止),但是如果您将这些作为两个单独的命令保留,您可以判断哪一个失败,如果有一个失败。 此外——对我来说,更重要的是——你可以在运行git merge之前查看git fetch获取的内容。 最后,一旦你知道它并不那么重要,你会意识到这是 Git 中两个完全独立的操作:获取提交; 合并结合工作

在任何时间,从任何分支运行git fetch都是安全的。 Fetch 只是调用另一个 Git 并从他们那里获得提交。 这不会触及你现在正在做的任何事情。 随时运行git merge并不是那么安全:将某些内容合并到您的当前分支中,并且您在任何时候都只有一个当前分支。 如果您有很多分支要更新,您可以git fetch并更新所有远程跟踪名称,但是您必须一个一个git checkout出您要更新的分支,然后git merge一个,F无论你想合并哪个提交——可能来自那个分支的远程跟踪名称,但是因为你可以查看,你可以先检查你是否真的想要那个合并。

向他们发送提交

您现在有提交IJMM有两个父母,第一个是J ,第二个是L )他们没有 要将您的提交发送给他们,您可以使用git push ,前提是他们给予您许可。 (注意:这些权限由托管软件控制,而不是由 Git 本身控制。)运行:

git push origin master

让你的 Git 调用他们的 Git,给他们提交你有他们不需要的,他们需要的 - 在这种情况下, IJM - 然后要求他们设置他们的分支名称。 请注意,您不会要求他们设置远程跟踪名称。 这个方向没有远程跟踪名称! 您只需要求他们直接设置分支名称。

有时,他们会接受这个请求。 有时他们可能会拒绝,因为他们已经设置了预防性障碍——可能是在特定的分支名称上,而且这是由托管软件控制的——或者因为他们根本不允许推送。 或者,他们可能会拒绝,因为您没有合并,或者最近没有足够的合并。 假设他们上述内容,但是当您运行git merge时,其他人添加了另一个提交。 如果你现在运行git fetch ,你会得到:

          I--J--M   <-- master (HEAD)
         /     /
...--D--E--K--L--N   <-- origin/master
         \
          F--G--H   <-- origin/develop

这意味着他们的master现在将提交N命名为,这在片刻之前并不存在。 您可能需要再次合并,或者您可能想完全删除您的提交M (这有时有点棘手)并创建一个新的合并O ,这次合并了N

您可以将分支 b1 合并或变基为分支 b2。

git checkout b2
git merge b1

或者

git checkout b2
git rebase b1

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM