如何将辅助分支与空主分支合并?

kes*_*edi 3 git github gitlab

我正在使用 git 提交我的项目更改。

以此目的 :-

  1. 我在 github 中创建了一个新的空存储库
  2. 除了 master 分支之外,我创建了一个名为 secondary 的辅助分支
  3. 我提交了更改并将其推送到第二个分支
  4. 现在我的所有项目及其内容都在第二个分支上,而我的主分支是空的

现在,我有两个疑问:-

a)我可以直接将任何代码推送到空主分支而不进入两个分支的合并过程吗?

b) 如何将第二个分支与空的主分支合并

tor*_*rek 8

我们必须从这个奇怪的想法开始:Git 中不存在空分支这样的东西。

\n\n

Git 中不存在空分支

\n\n

在 Git 中,分支名称(例如mastercommit\xe2\x80\x94)始终标识一个提交。这个单一提交是分支的尖端提交。如果该提交有一个或多个父提交,并且几乎每个提交都至少有一个父提交,则父提交也包含在分支内,就像父提交的父提交一样,依此类推。

\n\n

请注意,正是提交本身形成了这种向后看的结构,从末尾开始向后工作:

\n\n
A  <-B  <-C\n
Run Code Online (Sandbox Code Playgroud)\n\n

在这里,提交A是有史以来的第一次提交。它没有级:这是提交有父级的一般规则的一个例外。 A没有父母,因为它不能有父母;这是有史以来的第一次提交。但是 commitB包含 commit 的哈希 ID A,作为B\ 的父级。我们说B 指向 A. 同样,提交C包含B\的哈希ID,因此C指向回B.

\n\n

分支名称master通常会指向提交C作为其单个标识的提交:

\n\n
A--B--C   <--master\n
Run Code Online (Sandbox Code Playgroud)\n\n

Git 使用名称master来查找C、使用C来查找B、使用B来查找A

\n\n

如果我们现在创建一个新分支,例如使用git branch developor ,这会生成一个指向相同提交的git checkout -b develop名称 C

\n\n
A--B--C   <-- master, develop\n
Run Code Online (Sandbox Code Playgroud)\n\n

我们使用 告诉 Git 我们想要“位于”哪个分支git checkout。这会将名称附加HEAD到两个分支名称之一。例如,如果我们git checkout develop现在HEAD附加到develop

\n\n
A--B--C   <-- master, develop (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果我们现在创建一个新的提交\xe2\x80\x94 D,让我们称它为\xe2\x80\x94,尽管 Git 会为它发明一些又大又难看的哈希 ID\xe2\x80\x94,Git 实现这一点的方式是创建新的提交,D并将提交CD\的父级,因此D指向C

\n\n
A--B--C\n       \\\n        D\n
Run Code Online (Sandbox Code Playgroud)\n\n

作为创建此提交的最后一步,Git 更新附加到的任何分支名称 ,以便该名称现在指向新的提交:HEADD

\n\n
A--B--C   <-- master\n       \\\n        D   <-- develop\n
Run Code Online (Sandbox Code Playgroud)\n\n

请注意,任何时候都不是develop空的分支。最初,develop两者master 指向 commit C;两者都包含所有三个提交。现在develop包含四个提交,并且master仍然包含与以前相同的三个提交。提交同时在两个分支A-B-C上进行。

\n\n

那么你做了些什么?

\n\n
\n
    \n
  1. 我在 github 中创建了一个新的空存储库
  2. \n
\n
\n\n

请注意,GitHub 有两种创建存储库的方法。一种方法是为您创建初始提交,并指向master该初始提交。初始提交包含一个README文件以及可选的.gitignore和/或一个LICENSE文件。如果您选中此框,GitHub 将创建此初始提交:

\n\n
\n
\n

使用 README 初始化此存储库

\n
\n
\n\n

如果您选中该框,GitHub 确实会创建一个空存储库。没有提交,因此这个空存储库中没有分支!

\n\n
\n
    \n
  1. 除了 master 分支之外,我创建了一个名为 secondary 的辅助分支
  2. \n
\n
\n\n

因为只有存在指向分支的提交才能创建分支,所以您实际上无法执行此操作。这里的错误消息有点奇怪,但它表明这是无法完成的:

\n\n
$ mkdir er; cd er\n$ git init\nInitialized empty Git repository in .../er/.git/\n$ git branch second\nfatal: Not a valid object name: \'master\'.\n
Run Code Online (Sandbox Code Playgroud)\n\n

为什么 Git 抱怨这个名字master?答案有点令人惊讶:该分支master不存在,但我们HEAD无论如何都附加到它。也就是说,HEAD附加到不存在的分支。

\n\n

这不是一个非常明智的情况,但 Git 需要它来开始。第一次提交很奇怪:它没有父级。Git 需要保存要在某处创建的分支的名称,因此它通过将其写入HEAD. 一旦我们实际进行新的提交,就会创建分支名称。

\n\n

Git 以几种不同的方式报告此状态,将其称为未出生分支孤立分支,具体取决于 Git 的哪一部分正在执行调用。这就是什么git status在这个特定版本的 Git 中,内容

\n\n
$ git status\nOn branch master\n\nNo commits yet\n\nnothing to commit (create/copy files and use "git add" to track)\n
Run Code Online (Sandbox Code Playgroud)\n\n

相似地,git log现在比过去糟糕的日子要聪明一点:

\n\n
$ git log\nfatal: your current branch \'master\' does not have any commits yet\n
Run Code Online (Sandbox Code Playgroud)\n\n

(它曾经给出一个更神秘的错误,方式git branch仍然如此)。

\n\n

但是,我们可以要求git checkout创建一个新的分支名称,如果这样做,我们的状态就会改变:

\n\n
$ git checkout -b second\nSwitched to a new branch \'second\'\n$ git branch\n$ git status\nOn branch second\n...\n$ git log\nfatal: your current branch \'second\' does not have any commits yet\n
Run Code Online (Sandbox Code Playgroud)\n\n

输出git branch为空,正确地表明我们仍然没有分支。输出git status显示我们正处于这个未出生的分支上,并且git log告诉我们这里没有提交。

\n\n

如果我们现在进行提交,则分支名称second将出现,指向新的提交,这将是唯一的提交:

\n\n
$ echo example > README\n$ git add README\n$ git commit -m \'initial commit\'\n[second (root-commit) d4d9655] initial commit\n 1 file changed, 1 insertion(+)\n create mode 100644 README\n$ git log --all --decorate --oneline --graph\n* d4d9655 (HEAD -> second) initial commit\n$ git branch\n* second\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在我们已经看到了:现在已经有一个分支了;该名称second标识了第一个提交,在本例中具有d4d9655(缩写的)哈希 ID。

\n\n
\n
    \n
  1. 我提交了更改并将其推送到第二个分支
  2. \n
\n
\n\n

如果您能够在步骤 2 中使用 创建分支git branch,则意味着您已经拥有 amaster并且它不为空,并且您在步骤 3 中在分支上所做的提交second使名称second指向存储库中的第二次提交,其父级是名称指向的存储库中的第一个master提交。如果是这种情况,请运行:

\n\n
git log --all --decorate --oneline --graph\n
Run Code Online (Sandbox Code Playgroud)\n\n

将显示两个提交,以及两个分支名称作为装饰。

\n\n

如果不是\xe2\x80\x94,如果你真的根本没有master分支\xe2\x80\x94,那么你仍然没有master分支;您只有一个提交,位于一个名为 的分支上second

\n\n
\n
    \n
  1. 现在我的所有项目及其内容都在第二个分支上,而我的主分支是空的
  2. \n
\n
\n\n

再说一次,这实际上是不可能的。用于git log --all --decorate --oneline --graph查看您的所有提交以及分支名称的位置。该选项--graph没有执行任何操作,但是一旦您进行了合并提交,它就非常有用。

\n\n

解答您的疑问

\n\n
\n

现在,我有两个疑问:-

\n
\n\n

旁白:你的意思是两个问题。怀疑 这个词意味着你已经有了答案,你只是相信这些答案很可能是错误的。

\n\n
\n

a)我可以直接将任何代码推送到空主分支而不进入两个分支的合并过程吗?

\n
\n\n

这里也有几个重要的区别。首先,当你使用时git push,你推送的是提交。Git 的核心就是提交。正如我们上面已经看到的,每个提交都有一些父提交,除了初始(“根”)提交。提交还保存有关提交的数据:例如,您的姓名、电子邮件地址、时间戳和日志消息。并且,与此提交元数据\xe2\x80\x94parent(s)、作者/提交者和日志消息 \xe2\x80\x94 一起,每个提交都会保存一组文件的快照。

\n\n

因此,从某种意义上说,Git 存储库确实包含文件,但在更高级别上,它实际上并不涉及那么多文件:存储库是提交的集合。作为副作用,提交会拖拽文件。当然,正是这种副作用使 Git 变得有用,但请务必记住,Git 本身并不关心文件,而是关心提交

\n\n

您可以随时创建新的提交。当您这样做时,Git 将:

\n\n
    \n
  1. 收集您的日志消息;
  2. \n
  3. 打包(永久冻结)索引的内容(您可以使用git add);
  4. \n
  5. 保存您作为作者和提交者的姓名和电子邮件地址以及当前时间;
  6. \n
  7. 写出一个新的提交,并将当前提交作为新提交的父提交,以便新提交指向您运行时的当前提交git commit以及步骤 2 中创建的快照;
  8. \n
  9. 通过完成步骤 4 获取新提交的哈希 ID;
  10. \n
  11. 将该哈希 ID 写入当前分支名称,由 记录HEAD
  12. \n
\n\n

我们还没有讨论我在步骤 2 中提到的索引;我们稍后会讨论这个问题。不过,最后一步是导致分支名称更改的原因。通过前进以指向提交(现在是分支上的最后一次提交),名称会发生​​变化。所有较早的提交也在分支上,通过从新的提示提交向后工作来到达。

\n\n
\n

b) 如何将第二个分支与空的主分支合并

\n
\n\n

再说一次,分支永远不会是空的

\n\n

要将一个分支与另一个分支合并,我们使用git merge,但在此之前,我们通常需要git checkout先运行以选择一个分支。准确地了解其作用非常重要git checkout。这就涉及到提交、索引和工作树之间的区别。

\n\n

提交、索引和工作树

\n\n

在 Git 中,提交大多是永久的\xe2\x80\x94;在某些情况下,您可以完全放弃提交,这实际上非常有用\xe2\x80\x94并且完全只读。任何提交一旦做出,就无法更改,哪怕是一点点。提交有多种目的,但主要目的是让您恢复提交时的每个文件。

\n\n

每个提交都有一个永远不会改变的“真实名称”,即它的哈希 ID。您可以在不同的时间使用许多其他名称(例如分支名称)来标识提交,但这些名称最终都会解析为哈希 ID。您可以随时自行解决此问题:

\n\n
$ git rev-parse second\nd4d9655d070430e91022c1ad843267f9d05f60d1\n
Run Code Online (Sandbox Code Playgroud)\n\n

这表明我之前所做的提交的完整哈希 ID 为d4d9655d070430e91022c1ad843267f9d05f60d1. 这些哈希 ID 看起来是随机的,但实际上是提交完整内容的加密校验和。这就是为什么你和 Git 都无法更改提交中的任何内容:如果你尝试,你只会得到一个具有不同校验和的新的、不同的提交;原始提交保持不变。

\n\n

通过提交存储的文件以特殊的、冻结的(只读)、仅限 Git 的压缩格式保存。它们也永远无法更改(事实上,Git 将它们存储在哈希 ID 下,就像提交一样)。这个永不改变的属性意味着 Git 可以在新的版本中重用这些文件。如果新提交与先前提交具有​​相同的文件内容,这就是即使每次提交都存储每个文件的完整副本,Git 存储库往往不会占用大量磁盘空间的原因之一:旧文件的大量重复使用。

\n\n

当然,一个永远不允许您更改任何文件的系统并不是很有用:它对于档案检索来说很好,但对于开发来说没有好处。因此,Git 将冻结的、仅限 Git 压缩的提交文件提取为普通计算机格式的常规读/写文件。这些文件位于您的工作树中。但 Git 本身并不使用这些文件:工作树副本供您使用,而不是供 Git 使用。

\n\n

Git 的作用\xe2\x80\x94这很不寻常;大多数版本控制系统不这样做\xe2\x80\x94是在冻结的仅Git提交的文件和易于使用的工作树文件之间插入一些东西。这个“东西”就是 Git 的索引。使用 提取提交,git checkout通过将冻结文件解冻到索引中来填充此索引。索引中的副本仍然是特殊的、压缩的、仅限 Git 的格式(带有哈希 ID),但现在它可以被覆盖

\n\n

因此,git checkout所做的是:

\n\n
    \n
  • 从提交中填充索引:解冻文件,但尚未解压缩;然后(同时)
  • \n
  • 从索引填充工作树:解压缩文件;和
  • \n
  • 最后,将名称附加HEAD到您选择签出的分支名称。
  • \n
\n\n

结果是,成功后git checkout somebranch,通常会发生以下三件事:1

\n\n
    \n
  1. 该索引包含来自 的提示提交的所有文件somebranch
  2. \n
  3. 工作树包含来自 的提示提交的所有文件somebranch
  4. \n
  5. HEAD附在名称上somebranch
  6. \n
\n\n

这意味着您现在已准备好修改工作树文件并将它们复制回索引。该git add命令获取一个工作树文件并将其复制到索引中,将文件压缩(但尚未冻结)为特殊的仅 Git 形式。

\n\n

如果您在工作树中创建一个全新文件并使用git add,则会将该文件复制到索引中。此时该文件在索引中是全新的。如果您修改工作树中的某些现有文件并使用git add,则会将该文件复制到索引中,覆盖索引中之前的文件。您还可以使用git rm从索引中删除文件,但请注意,这也会删除工作树副本。

\n\n

当您运行时git commit,Git 只是冻结当时索引中的所有内容。这就是为什么您必须保持git add索引的原因。因此,索引可以概括为如果您进行另一次提交时将进入下一次提交的内容。

\n\n
\n\n

1git checkout这个规则有很多例外:这三件事只有当你处于干净状态时才成立。有关(更多)更多信息,请参阅当当前分支上存在未提交的更改时签出另一个分支。但是,如果签出成功,HEAD 则会附加到目标分支。

\n\n
\n\n

合并的简要说明

\n\n

现在让我们看看如果运行以下命令会发生什么:

\n\n
git checkout somebranch\ngit merge otherbranch\n
Run Code Online (Sandbox Code Playgroud)\n\n

正如我们所看到的,第一步将 our 附加HEAD到 name somebranch,将tip commit 获取somebranch到我们的索引和工作树中。假设一切顺利,我们的索引和工作树都与此时的提示提交完全匹配。

\n\n

第二步使用提交图\xe2\x80\x94,即我们上面绘制的同一图。现在是绘制图表的时候了。不幸的是,有很多可能的绘图,但让我们从这个简单的开始:图表可能如下所示,例如:

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

Git 在这种情况下所做的就是找到最佳公共提交\xe2\x80\x94,即“靠近提示”的两个分支上的提交。在这种情况下,如果我们从 的尖端开始somebranch,即 commit I,并向后工作,我们将到达 commit G,如果我们从 开始L并向后工作,我们也将到达 commit G。这是比 commit 更好的常见提交F,因此 commitG是这对提交的合并基础。

\n\n

或者,图表可能如下所示:

\n\n
...--F--G--H--I   <-- somebranch (HEAD)\n               \\\n                J--K--L   <-- otherbranch\n
Run Code Online (Sandbox Code Playgroud)\n\n

Git 仍然会找到最佳的共享公共提交,通过从 开始I并向后工作,同时也从 开始L并向后工作。最好的提交就是提交I本身。这实现了 Git 所说的快进,这根本不是真正的合并。

\n\n

该图可能看起来像这样:

\n\n
...--F--G--H--I   <-- otherbranch\n               \\\n                J--K--L   <-- somebranch (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n\n

在这种情况下,公共提交位于的提示后面somebranch。没有什么可以合并的,git merge会这么说,但什么也不做。

\n\n

还有更多的可能性,包括非常复杂的图表,但我们就到此为止。(对于计算复杂拓扑中的合并基,请参阅其他答案,或阅读一些适当的图论,包括Bender 等人的这篇论文。)

\n\n

为了进行快进,Git 本质上只是将分支名称从当前位置移动到新的提示,并git checkout在新的提示提交上运行,给出:

\n\n
...--F--G--H--I   [old "somebranch"]\n               \\\n                J--K--L   <-- somebranch (HEAD), otherbranch\n
Run Code Online (Sandbox Code Playgroud)\n\n

为了进行真正的合并,Git 使用合并基础。

\n\n

真正的合并

\n\n

真正合并的目标是合并更改。请记住,每次提交都是文件的快照,而不是一组更改。那么,Git 必须做的就是将基本快照与两个分支提示中的每一个进行比较。也就是说,给定:

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

Git 首先枚举commits 、和中的所有文件。Git 将 in 中的文件集与 in 中的文件集进行比较,以找出我们更改的内容:GILGI

\n\n
git diff --find-renames <hash-of-G> <hash-of-I>   # what we changed\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后将文件集与G文件集进行比较L以找出它们更改的内容:

\n\n
git diff --find-renames <hash-of-G> <hash-of-L>   # what they changed\n
Run Code Online (Sandbox Code Playgroud)\n\n

Git 现在有两个变更集,它可以尝试将它们组合起来。G对于我们都没有更改的每个文件,组合很容易:使用来自、 或 来自I、 或 来自 的文件L(哪个并不重要:所有三个副本都是相同的)。对于只有我们一个人更改的每个文件,请使用该提交中的副本,I或者L. 对于我们都更改过的文件,请从中获取合并基础副本G并添加两个更改。

\n\n

如果对该文件的两个更改发生冲突,Git 通常会声明合并冲突,将文件的所有三个副本保留在索引中,尽最大努力将更改写入工作树,并向冲突部分添加冲突标记。如果所有更改正确配合,Git 将继续将最终组合文件写入索引(和工作树)。

\n\n

将所有更改合并到所有文件后,如果没有冲突,Git 将照常从索引进行新的提交。然而,这个新提交将有两个父级,而不是通常的一个父级。然后 Git 会照常更新当前分支名称,所以我们得到:

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

其中M是与两个父级的合并提交

\n\n

如果存在 Git 无法自行解决的冲突,Git 将停止并显示合并冲突消息。然后,您的工作就是将正确的合并结果写入工作树,并将git add其复制到索引中,从而擦除 Git 留在索引中的三个副本。(在此之前,您可以提取三个输入中的任何一个或全部三个\xe2\x80\x94合并基本版本、左侧或本地或--ours版本以及右侧或远程或其他或--theirs版本\xe2\x80\ x94 也来自索引。)一旦解决了所有合并冲突并git add编辑了所有生成的文件,您就可以运行git commit(或者git merge --continuegit commit为您运行)来提交合并结果,生成与 Git 自动完成提交相同的图表。

\n