git - How are different branches stored locally from git on my disk?
问题描述
I have only one version repository sitting on my local HDD but multiple branches on Github. Shouldn't there be copies of code per branch? what version of code do I have sitting on my local disk?
解决方案
每个分支不应该有代码副本吗?
不,这不是 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
, so C
storesB
的哈希 ID。
每当我们有某个提交的哈希 ID 时,我们就说我们可以指向那个提交。所以C
指向B
,B
指向A
。如果我们画这个,我们得到:
A <-B <-C
现在,使用简单的单个大写字母和这张图,很明显我们需要从头开始并向C
后工作。这就是 Git 所做的,但Git 查找 commit的方式C
是使用分支名称。由于我们刚刚启动了这个存储库,我们可能仍在使用master
,所以让我们把它画在:
A <-B <-C <--master
该名称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
现在棘手的部分发生了:由于我们刚刚创建D
,并且我们正在运行master
,Git 现在使用新提交的任何真实哈希 ID 进行更新 。所以现在指向,而不是:master
D
master
D
C
A--B--C
\
D <--master
我们可以理顺图中的扭结(我也想稍微放松一下箭头):
A--B--C--D <-- master
现在假设我们决定添加一个新分支。我们运行,例如,git checkout -b develop
。这样做只是添加一个新名称,但保留所有相同的提交。新名称 ,develop
指向我们选择的提交,默认为我们现在使用的那个,即 commit D
:
A--B--C--D <-- master, develop
现在我们需要一种方法来绘制我们正在使用的分支名称。为此,我们会将单词HEAD
(全部大写)附加到两个分支之一。由于我们确实git checkout
附加HEAD
到 new develop
,让我们绘制:
A--B--C--D <-- master, develop (HEAD)
现在是时候进行另一个新的提交了。不做任何其他事情,我们修改一些文件,用于git add
将更新的文件复制到索引中,然后运行git commit
以进行新的提交,我们将调用E
它(但像往常一样,它会获得一些难以理解的哈希 ID)。当 Git 更新分支名称时,它更新的名称是HEAD
附加到它的名称,所以图表现在看起来像这样:
A--B--C--D <-- master
\
E <-- develop (HEAD)
此时,假设我克隆了您的存储库(直接从您的机器,或从您发送到 GitHub 的精确副本,具有相同的提交和相同的分支名称)。我的 Git 首先会这样做:
A--B--C--D <-- origin/master
\
E <-- origin/develop
在这里,我没有任何分支。我有所有相同的提交,但记住它们的目的不是分支名称,而是远程跟踪名称。而不是master
,我必须origin/master
记住 commitD
的哈希 ID。而不是develop
,我必须origin/develop
记住E
的哈希 ID。
作为我克隆的最后一步,我自己的 Git 尝试将git checkout master
. 而不是因为我没有失败而失败,master
这实际上创建了my ,master
使用我origin/master
的 Git 从你的 Git 复制的master
. 所以现在我有:
A--B--C--D <-- origin/master, master (HEAD)
\
E <-- origin/develop
如果我现在运行git checkout develop
,我的 Git 将查看我的存储库并且不会找到 a develop
,但它会找到一个origin/develop
. 然后我的 Git 将创建一个新名称,develop
指向 commit E
,并附HEAD
加到该名称而不是 to master
:
A--B--C--D <-- origin/master, master
\
E <-- origin/develop, develop (HEAD)
请注意,没有任何提交被复制。Git 只是添加了一个新名称,指向一些现有的提交。现有的提交已经存在,在我的本地存储库中。
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)
请注意,除了将新提交添加到您自己的存储库之外,您的 Git 所做的唯一另一件事就是更新您的所有origin/*
名称,以便它们与其他 Git 的名称匹配。也就是说,您origin/master
已经从 shared commitD
转移到 now-shared commit H
。
Fetch 总是安全的,但git push
不一样
运行总是安全的git fetch
:这会将您的 Git 连接到其他 Git,从它们那里获取任何新提交,并更新您的远程跟踪名称。因为这些名字只是记住他们的工作,而不是你的工作,所以很安全。如果你做了新的提交,你的新提交仍然在你的分支上,而不是他们的分支,除了你们共同的任何提交。
当你使用 时git push
,你的 Git 会调用他们的 Git,这两个 Git 会进行非常相似的对话。如果你有新的提交,你的 Git 会为他们的 Git 提供一些新的提交。但是,然后,您的 Git 不会更新您的远程跟踪名称,而是您的 Git 向他们的 Git 提供了一个礼貌的请求:如果可以,请更新 yourmaster
以匹配 my master
,或者如果可以,请更新 yourdevelop
以匹配 mydevelop
——或者也许甚至两者。(您可以一次推送任意数量的分支名称。)
假设在之前的 之后git fetch
,你有这个:
F--G--H <-- origin/master
/
A--B--C--D <-- master
\
E <-- origin/develop, develop (HEAD)
你一直在develop
做一些新的提交,我们称之为I
and J
,所以现在你有了这个:
F--G--H <-- origin/master
/
A--B--C--D <-- master
\
E <-- origin/develop
\
I--J <-- develop (HEAD)
但是,你不知道的是,其他人在他们的 develop
. 该提交具有您在任何地方都没有的哈希 ID。K
让我们在他们的Git 中调用它,这样他们就有了:
F--G--H <-- master
/
A--B--C--D
\
E--K <-- develop
你发送给他们I
and J
,所以现在他们有这个:
F--G--H <-- master
/
A--B--C--D
\
E--K <-- develop
\
I--J <-- (polite request to make develop go here)
如果他们接受你的请求,并移动他们的develop
,他们的 commit 会发生什么K
?他们最终得到了这个:
F--G--H <-- master
/
A--B--C--D
\
E--K ???
\
I--J <-- develop
如上所述,Git 处理提交和分支名称的方式是它使用分支名称来查找最后一个提交,然后向后工作。从 向后工作J
,他们的 Git 会转到J
. 从J
他们会回到I
, 然后E
, 然后D
, C
, B
, 和A
。这意味着提交K
丢失了!
结果,他们的 Git 会拒绝你的礼貌请求:不,如果我移动了我的develop
,我会丢失一些提交。 (您的 Git 将此报告为“拒绝”和“不是快进”。)
在这种情况下,您需要做的是git fetch
获取他们的新提交K
,然后在您自己的存储库中做一些事情来适应它。不过,这是针对另一组问题的(所有这些问题都已在 StackOverflow 上得到解答)。
推荐阅读
- python - 从熊猫系列中删除标题
- database - 我朋友的电脑可以通过我的程序访问我在 PostgreSQL 中的数据库吗?
- python-3.x - 高斯过程回归器 scikit learn 无法识别“eval_MSE=True”
- javascript - 突破 .on('click')
- javascript - 在 ReactJS 中的两个独立组件之间传递数据
- libgdx - 如何在 libgdx 中的图像或纹理上悬停时显示文本?
- python - 难以使用 python 运行外部可执行文件
- python - Python lxml:如何处理解析xml字符串的编码错误?
- python - Python MariaDB pip 安装失败,缺少 mariadb_config
- java - 有没有办法从 Android 系统设置中删除某些选项?