Sar*_*tel 5 git version-control github
我的本地硬盘上只有一个版本存储库,但 Github 上有多个分支。每个分支不应该有代码副本吗?我的本地磁盘上有什么版本的代码?
tor*_*rek 10
每个分支不应该有代码副本吗?
不。这不是 Git 的分支名称的工作方式。
从某种意义上说,Git 存储的根本不是分支。Git 存储提交。提交几乎是 Git 中的一切。分支机构进入的时间要晚得多,它们的作用相当小。当然,它们对人类很重要,因为任何提交的实际名称都是一个对人类无用的大而丑陋的哈希 ID。分支名称让我们可以使用名称而不是哈希 ID。但是 Git 最关心的是提交,以及它们的哈希 ID。
我的本地硬盘上只有一个版本存储库,但 Github 上有多个分支。
通常,本地驱动器上的存储库是 GitHub 上存储库的克隆。或者,我们同样可以说 GitHub 上的存储库是本地驱动器上存储库的克隆。两个存储库都不比另一个“更好”,它们只是拥有不同的计算机来保存它们,并且可能略有不同的提交集,以及它们中更可能不同的分支名称(但由于 Git 更关心提交,这并不对 Git 来说太重要了)。
要了解这是如何工作的,请从提交开始。每个提交——由一些丑陋的大哈希 ID 命名,例如——8a0ba68f6dab2c8b1f297a0d46b710bb9af3237a
存储一些文件集的完整快照。除了那个快照,每个提交都有一些元数据,一些关于提交的数据。如果你做的全心投入,例如,提交存储您的姓名和电子邮件地址,以及时间戳记,当你做出的承诺。而且,每个提交通常存储其前一个或父提交的哈希 ID 。(例如,8a0ba68f6dab2c8b1f297a0d46b710bb9af3237a
上面的父对象是15cc2da0b5aaf5350f180951450e0a5318f7d34d
。)
Git 使用这些父链接来查找提交。让我们考虑一下您最近创建的一个小型存储库,您仅在其中进行了 3 次提交。每个提交都有一些丑陋的哈希 ID,但让我们使用单个大写字母代替。因此,您所做的第一次提交是 commit A
。因为它是第一次提交,所以它没有父级。
然后,通过提交A
,您提交了B
。它有A
作为它的父级。同样,您曾经使用C
make B
,因此C
存储了B
的哈希 ID。
每当我们拥有某个提交的哈希 ID 时,我们就说我们可以指向该提交。所以C
指向B
,B
指向A
。如果我们画这个,我们得到:
A <-B <-C
Run Code Online (Sandbox Code Playgroud)
现在,简单的单大写字母和此图中,很明显,我们需要开始在C
和工作倒退。这就是 Git 所做的,但Git 查找 commit的方式C
是使用分支名称。由于我们刚刚启动这个存储库,我们可能仍在使用master
,所以让我们将其绘制在:
A <-B <-C <--master
Run Code Online (Sandbox Code Playgroud)
该名称master
包含 commit 的哈希 ID C
,以便 Git 可以找到C
. 从那里,Git 可以向后工作,到B
然后A
.
如果我们想添加一个新的提交,我们运行git checkout master
,它会为我们提供一份C
我们可以处理的副本,并记住我们现在在master
哪个 commit 上C
。我们完成我们的工作,git add
根据需要创建文件,然后运行git commit
. Git 进行新的提交D
(来自index 中的任何内容,我不会在这里定义),并设置D
为指向C
:
A--B--C <--master
\
D
Run Code Online (Sandbox Code Playgroud)
现在棘手的问题发生了:因为我们刚刚创建的D
,而且我们的master
,现在的Git更新 master
与任何真正的哈希ID是新的提交D
。所以现在master
指向D
,而不是C
:
A--B--C
\
D <--master
Run Code Online (Sandbox Code Playgroud)
我们可以理顺图中的扭结(我也喜欢稍微松开箭头):
A--B--C--D <-- master
Run Code Online (Sandbox Code Playgroud)
现在假设我们决定添加一个新分支。我们运行,例如,git checkout -b develop
。这只是添加一个新名称,但保留所有相同的提交。新名称 ,develop
指向我们选择的提交,默认为我们现在使用的提交,即 commit D
:
A--B--C--D <-- master, develop
Run Code Online (Sandbox Code Playgroud)
现在我们需要一种方法来绘制我们正在使用的分支名称。为此,我们将把单词HEAD
(全部大写)附加到两个分支之一。由于我们确实git checkout
附加HEAD
到了 new develop
,让我们画出来:
A--B--C--D <-- master, develop (HEAD)
Run Code Online (Sandbox Code Playgroud)
现在是时候进行另一个新的提交了。没有做任何其他事情,我们修改了一些文件,用于git add
将更新的文件复制到索引中,然后运行git commit
以进行新的提交,我们将调用它E
(但它会像往常一样获得一些难以理解的哈希 ID)。当 Git 更新一个分支名称时,它更新的名称是HEAD
附加到它的名称,所以图形现在看起来像这样:
A--B--C--D <-- master
\
E <-- develop (HEAD)
Run Code Online (Sandbox Code Playgroud)
在这一点上,假设我克隆了您的存储库(直接从您的机器,或从您发送到 GitHub 的精确副本,具有相同的提交和相同的分支名称)。我的 Git 首先会这样做:
A--B--C--D <-- origin/master
\
E <-- origin/develop
Run Code Online (Sandbox Code Playgroud)
在这里,我没有任何分支。我有所有相同的提交,但记住它们的目的不是分支名称,而是远程跟踪名称。取而代之的是master
,我必须origin/master
记住 commitD
的哈希 ID。取而代之的是develop
,我必须origin/develop
记住E
的哈希 ID。
作为我克隆的最后一步,我自己的 Git 尝试将git checkout master
. 相反,失败,因为我不的有一个master
,这实际上造成我的master
,用我的origin/master
,我的Git从Git的复制master
。所以现在我有:
A--B--C--D <-- origin/master, master (HEAD)
\
E <-- origin/develop
Run Code Online (Sandbox Code Playgroud)
如果我现在运行git checkout develop
,我的 Git 将查看我的存储库develop
,但不会找到origin/develop
. 然后,我的 Git 将创建一个新名称, develop
,指向 commit E
,并附HEAD
加到该名称而不是 to master
:
A--B--C--D <-- origin/master, master
\
E <-- origin/develop, develop (HEAD)
Run Code Online (Sandbox Code Playgroud)
请注意,没有复制任何提交。Git 只是添加了一个新的name,指向一些现有的提交。现有的提交已经存在,在我的本地存储库中。
fetch
和连接两个 Git 存储库push
这是您从 GitHub 存储库获取更新的方式。如果他们有一些你没有的新提交,你可以git fetch
在你的 Git 存储库中运行。您的 Git 使用origin
您的 Git 在原始git clone
操作期间创建的名称来查找 GitHub 的 URL。然后你的 Git 调用 GitHub,你的 Git 和他们的 Git 有一些对话。
使用git fetch
,您的 Git 会向他们的 Git 询问他们的分支名称以及每个名称的最终提交。他们的 Git 可能会说:我master
指出一些带有丑陋的大散列的提交——让我们称之为H
,而不是试图猜测实际的散列 ID。您的 Git 查看您的存储库并自言自语:我没有H
,我最好要求它。 你的 Git 询问他们的 Git H
;他们说H
的父母是G
,谁的父母是F
,谁的父母是D
。你们都有D
,所以这部分对话就完成了。他们的 Git 也可能会说:my develop
points to commit E
。 你的 Git 已经有了E
,所以这部分对话也完成了。有左担心没有其他的名字,所以现在你的Git有自己的Git送过来的提交F
,G
和H
,你的Git的版本库中保存走:
F--G--H <-- origin/master
/
A--B--C--D <-- master
\
E <-- origin/develop, develop (HEAD)
Run Code Online (Sandbox Code Playgroud)
请注意,除了向您自己的存储库添加新提交之外,您的 Git 所做的唯一另一件事就是更新您的所有origin/*
名称,以便它们与其他 Git 的名称匹配。也就是说,您origin/master
已经从共享提交D
转移到了现在共享的提交H
。
git push
不同运行总是安全的git fetch
:这会将您的 Git 连接到其他某个 Git,从它们获取任何新提交,并更新您的远程跟踪名称。因为这些名字只是记住他们的工作,而不是你的工作,所以这是安全的。如果你做了新的提交,你的新提交仍然在你的分支上,而不是你的分支,除了你们共同的任何提交。
当您使用 时git push
,您的 Git 会调用他们的 Git,并且这两个 Git 会进行非常相似的对话。如果你有新的提交,你的 Git 会向他们的 Git 提供一些新的提交。但是,你的 Git 不是更新你的远程跟踪名称,而是向他们的 Git 提供一个礼貌的请求:请,如果可以,更新你的master
以匹配我的master
,或者,如果可以,更新你develop
的匹配我的develop
- 或者也许甚至两者。(您可以一次推送任意数量的分支名称。)
假设在之前的 之后git fetch
,你有这个:
F--G--H <-- origin/master
/
A--B--C--D <-- master
\
E <-- origin/develop, develop (HEAD)
Run Code Online (Sandbox Code Playgroud)
你继续你的develop
并做了一些新的提交,我们称之为I
and J
,所以现在你有这个:
F--G--H <-- origin/master
/
A--B--C--D <-- master
\
E <-- origin/develop
\
I--J <-- develop (HEAD)
Run Code Online (Sandbox Code Playgroud)
但是,您不知道的是,其他人向他们的 develop
. 该提交具有您在任何地方都没有的哈希 ID。让我们K
在他们的Git 中调用它,以便他们拥有:
F--G--H <-- master
/
A--B--C--D
\
E--K <-- develop
Run Code Online (Sandbox Code Playgroud)
你发送他们I
和J
,所以现在他们有这个:
F--G--H <-- master
/
A--B--C--D
\
E--K <-- develop
\
I--J <-- (polite request to make develop go here)
Run Code Online (Sandbox Code Playgroud)
如果他们接受你的请求,并移动他们的develop
,他们的提交会发生什么K
?他们最终是这样的:
F--G--H <-- master
/
A--B--C--D
\
E--K ???
\
I--J <-- develop
Run Code Online (Sandbox Code Playgroud)
正如我们上面所描述的,Git 处理提交和分支名称的方式是它使用分支名称来查找最后一次提交,然后向后工作。从 向后工作J
,他们的 Git 将转到J
. 从J
他们会回去I
,然后E
,然后D
,C
,B
,和A
。是,犯下这是什么意思K
是输了!
其结果是,他们的Git会说没有你有礼貌的请求:不,如果我感动我的develop
,我会失去一些承诺(S)。 (您的 Git 将此报告为“被拒绝”和“不是快进”。)
在这种情况下,您需要做的是git fetch
获取他们的新提交K
,然后在您自己的存储库中执行一些操作以适应它。不过,这是针对另一组问题的(所有这些问题都已经在 StackOverflow 上得到了回答)。