我正在开发分支,这是一个父分支,并在分支 a1 上创建了一些东西,添加了完全独立的新功能并仅推送到 a1。现在我有另一个任务创建了一个 bew 分支 b1,但一旦我切换到 dev,我就没有得到 a1 的代码,所以如果我创建一个新的分支 b1,我需要 a1 中存在的一些文件或所有文件都很好。如何实现这一点?
- 我应该签出到 dev 并拉取 A1 并将 A1 合并到 dev 中,然后结帐 yo B1 并在那里进行更改吗?
- 我应该签出到 B1 并拉取 A1 然后合并它. 这是正确的方法吗,如果不是,请提出建议。这是什么?
还有什么时候合并开始行动?
如果我理解正确,您需要在分支 a1 中更改分支 b1?如果是这样,您需要对 b1 -> a1 执行合并请求(或在 a1 中拉取 b1 并直接合并)。完成 a1 上的所有工作后,您可以将所有更改从 a1 合并回 dev 分支。
(旁注:你用github和gitlab标记了你的问题。 别这样。 选择其中一个(无论您使用哪一个)如果适用。 在这里,目前两者都不重要,但是一旦您提出拉取请求或合并请求,就会或另一个。
我正在开发分支上工作,这是一个父分支...
你从一个糟糕的(不正确的)假设开始的。 在 Git 中,没有父分支这样的东西。 事实上,分支这个词一开始就没有任何一种特定的含义(参见">分支"到底是什么意思?),但无论你选择哪种含义,它们都没有采用修饰符"parent"。
Git 拥有的,以及关于提交的全部内容。提交确实有父提交,所以你在那里状态很好。 但是,Git 中的分支名称只是选择一个特定的提交。
。并在分支 A1 上创建了一些东西,添加了完全独立的新功能
我相信你的意思是:
-
您有一系列以当前区分的提交结尾的提交。 这个特殊的(目前)提交的名称
dev
选择它。 -
您创建了一个新的分支名称
a1
该名称将同一提交标识为dev
。 -
然后,您在"on"分支
a1
时创建了一个或多个新提交。 这些新提交实现了所需的功能。
并仅推送到 A1。
这里的事情变得更加复杂。 这些名称(dev
和a1
)是您自己的存储库的本地名称。 它们仅在您自己的存储库中有意义。
它们标识的提交具有通用唯一标识符 (UUID)。 这些名称在每个Git 存储库中都有效。 Git 将这些 UUID 称为哈希ID,或者更正式地称为对象 ID或 OID。
当您使用git push
时,您可以在自己的存储库中选择一些提交(或一组提交),您希望将其移交给其他 Git 存储库。 您将这些提交发送到其他存储库,然后要求他们的Git软件在其存储库中设置一个名称(例如分支或标记名称),以便通过该名称查找此提交。 您在他们的存储库中设置了一个名称,因为没有人喜欢输入 UUID。 它们太大太丑了,人类无法与之合作。
人类(作为人类)在另一个存储库中使用与在自己的存储库中使用的相同名称是很自然的。 对于您(人类)来说,重要的是要意识到这就是您正在做的事情:另一个Git 存储库中a1
的名称是不同的名称,即使您自己的a1
及其a1
都拼写为a1
。 这里真正通用的一件事 -在所有存储库中始终有效 - 是原始哈希ID。 但是,通过在存储库和一些遥远的其他存储库中设置相同的简短、人类可读的名称,您可以假装该名称a1
是通用的。 它不是 - 它只在有限的时间内有效,不像哈希 ID。
现在这一切都是搁置一旁,但请记住以后。 让我们继续讨论您的直接问题:
现在我有另一个任务创建了一个新的分支 b1,但一旦我切换到 dev,我就没有得到 a1 的代码
你在这里混淆了三个不同的名字。 是时候停下来,对仓库中发生的事情有一个很好的了解了。 (请记住,这是您的存储库。 它不是其他人的存储库,而是您的存储库。
每个 Git 提交都有一个唯一的哈希 ID,例如e188ec3a735ae52a0d0d3c22f9df6b29fa613b1e
。 如果有人给你这样的哈希 ID,你要么有带有该哈希 ID 的提交,要么根本没有带有该哈希ID 的Git 对象。 这就是一个 Git 存储库在需要时将内容副本提供给另一个 Git 存储库的方式:发送 Git 告诉接收 Git 一些哈希 ID,接收 Git 告诉发送 Git 它需要哪些哈希 ID。 其余的——尽管还有更多——仅仅是优化。
但是:提交内部到底是什么? 要找出答案,我们可以看一个:
$ git cat-file -p e188ec3a735ae52a0d0d3c22f9df6b29fa613b1e | sed 's/@/ /'
tree 23abcae16bcca7bf541eb62d2a0b2c1ae7f5a2a9
parent 21dd13e025aaada474fae6014f1b14799e37bedf
parent a0feb8611d4c0b2b5d954efe4e98207f62223436
author Junio C Hamano <gitster pobox.com> 1663097028 -0700
committer Junio C Hamano <gitster pobox.com> 1663097028 -0700
Sync with 'maint'
(将上述内容与点击此提交的 GitHub 副本的链接时看到的内容进行比较)。
这就是这个提交的全部内容。 它仅由元数据组成:有关此特定提交的信息,例如Junio Hamano进行提交的事实。 但请注意tree
行:每个提交都需要恰好具有这些tree
行之一,并且该行提供了与此特定提交相关的所有文件的完整快照的原始哈希 ID。
因此,提交(如果您的 Git 有)提供了为该提交提供快照的tree
对象。 这些是您在使用git switch
或git checkout
选择该提交后将看到的所有文件。
(这个特别tree
非常大:
$ git cat-file -p 23abcae16bcca7bf541eb62d2a0b2c1ae7f5a2a9 | head
100644 blob 4860bebd32f8d3f34c2382f097ac50c0b972d3a0 .cirrus.yml
100644 blob c592dda681fecfaa6bf64fb3f539eafaf4123ed8 .clang-format
100644 blob f9d819623d832113014dd5d5366e8ee44ac9666a .editorconfig
100644 blob b0044cf272fec9b987e99c600d6a95bc357261c3 .gitattributes
040000 tree 700f96ea68cfa31767d24659c4981083f65fd276 .github
100644 blob 80b530bbed2c80814ac74956d329d277d85bba86 .gitignore
100644 blob cbeebdab7a5e2c6afec338c3534930f569c90f63 .gitmodules
100644 blob 07db36a9bb949c4c911d07baeb1c4c10c13bec4c .mailmap
100644 blob 5ba86d68459e61f87dae1332c7f2402860b4280c .tsan-suppressions
100644 blob 0215b1fd4c05e668f37973549384aae24dcc65cf CODE_OF_CONDUCT.md
这种情况持续了近 500 行:此树对象中有 496 个顶级条目,其中包含更多树对象,因此快照总共包含大约 4200 个文件。但是,除了其中一个文件之外,所有这些文件实际上都是从以前的一些提交中重用的,因此尽管包含 4200 个文件,但提交本身只需要几百个字节!
因此,这就是提交对您的作用:
- 它是一个编号实体(使用唯一的哈希 ID作为其编号); 它
- 包含元数据,例如谁制作了它以及何时制作了它;
- 它保存每个文件的完整快照,但以间接和节省空间的方式。
但是,该元数据不仅仅是谁(以及何时制作)的:请参阅上面的parent
行。 这个特定的提交,在 Git 本身的 Git 存储库中,是两个父级的合并提交。 合并提交有点不寻常:大多数提交都是普通提交,只有一个父级。
看到单词后面的大丑陋数字parent
. 你能猜出它是什么吗? 您可能可以:这是另一个哈希ID。 此父编号存储在每个提交的元数据中,提供 Git 中的父/子关系。 这些关系是在提交之间,而不是在分支之间。
正如我上面提到的,分支名称只是指定一个特定提交的一种方式。 这是一个我们(人类)因为一些奇怪的、人类类型的原因而特别有趣的承诺。 在这种特殊情况下,这很有趣,因为它是最新的提交。
事实上,在 Git 中,分支名称的定义是,它是一个保存一个提交哈希 ID 的名称,然后提交是分支的提示提交。 分支的提示提交是该分支上的最新提交。 这个属性(作为最新提交)实际上并不是提交本身的一部分! 这只是一个暂时的事情:在我们说其他提交是最新的之前,该提交是最新的。
让我们看一个例子
例如,让我们将您自己的分支名称dev
和a1
。 在某些时候,有一些带有一些哈希ID的提交是您自己的dev
的最新提交。我不知道那个提交的哈希ID是什么(它是a123456...
还是b987654...
deadcab
beeffad
还是什么?),所以我只是称它为提交H
,用于哈希。 我要这样画它,还没有明显的原因:
<-H
从H
中伸出的小箭头,指向左(向后),代表提交H
元数据中的parent
线。 此parent
行保存一些早期提交的哈希 ID。 我也不知道前面提交的哈希 ID,所以我就叫它G
,这是H
之前的字母,然后我会画出那个提交:
<-G <-H
提交G
当然也是一个普通的提交,所以它向后指向一些更早的提交,所以我现在把它画进去:
... <-F <-G <-H
这里的每个提交都有元数据和快照,通过从dev
分支上的最新提交开始,我可以让 Git 向后工作并找到也在dev
分支上的每个早期提交。 您可以非常轻松地执行相同的操作:只需运行git log dev
,例如,或git switch dev
然后git log
。 无论哪种方式,Git 都会向您显示来自H
的元数据,然后后退一步以提交G
并显示名称和电子邮件地址以及G
的日志消息。 然后git log
将向后移动一步以提交F
并向您显示提交的元数据,并永久地向后移动一步,依此类推,或者更确切地说,直到它用完后退的步骤。
要实现这一切,我们需要做的就是知道提交H
的哈希 ID . 但是我不知道提交H
的哈希ID,那我该怎么办? 我要做的是使用你的名字dev
,你告诉我的。 通过使用您的dev
我可以找到哈希IDH
。
我们说你的dev
指向提交H
,我现在可以把它画进去:
...--G--H <-- dev
我(故意)懒惰,不费心在这里绘制从提交到父级的连接作为箭头,但我确实将从分支名称中伸出的箭头绘制为箭头 - 更长的箭头 - 因为这些箭头会移动。
当您创建一个新名称a1
时,您创建了名称a1
选择提交H
,就像您的名字dev
选择提交H
一样:
...--G--H <-- a1, dev
所有这些提交现在都位于两个分支上,而不仅仅是一个分支。 提交H
是a1
和dev
上的最新提交。H
本身没有任何变化:它的快照和元数据与往常相同。更改的是,我们添加了另一个名称,a1
,并将其箭头指向H
。
如果我们现在使用git switch
或git checkout
来选择名称a1
,这告诉 Git 我们希望从提交H
中的所有文件。 名称a1
指向H
,所以这就是 Git 想要H
文件的想法。 为了表明我们在分支a1
上,我这样画它:
...--G--H <-- a1 (HEAD), dev
现在,您对某些文件进行了一些更改,并git add
和git commit
进行新的提交。 这个新提交会得到一个新的、随机的(但唯一且实际上根本不是随机的)哈希 ID,任何 Git 软件都不能再次将其用于任何其他提交。1我在这里称之为"提交I
",现在把它画进去:
...--G--H <-- dev
I <-- a1 (HEAD)
a1
的名称现在指向提交I
。 没有其他名称更改;没有提交更改;2但名称a1
现在选择提交I
并提交I
因此是分支a1
上的最后一次提交。
这是根据定义! 如果我们运行git reset --hard HEAD^
丢弃提交I
,发生的事情是 Git 将哈希 IDH
存储回名称a1
中,因此提交H
成为分支a1
上的最后一次提交。 实际上没有任何事情发生来提交I
本身。 只是如果我们没有找到它的名字,我们将不得不自己想出原始哈希 ID。 快速,Git 存储库中的哈希 ID 是什么。 你已经记住了,不是吗? 好吧,我没有。 如果没有找到它的名称,我无法告诉您哈希 ID 是什么。
所以这就是为什么我们有分支名称:找到"在"该分支上的最后一个提交。 这就是提交:它是元数据 - 谁在何时提交,它的日志消息,对于 Git、它的父级或父级至关重要——加上一个永久保存所有文件的快照,或者至少只要提交本身继续存在。3
1Git 的这一部分非常神奇,或者至少是数学上的,而且也有严重的缺陷,因为这个技巧总有一天会停止工作。 但只要那一天不发生,直到我们都早已死去,没有人会太担心。
2Git 使用的神奇编号方案(提交内容的加密哈希)要求任何提交的任何部分都不会更改。
3当我们使用git reset
或类似方法从某个分支的末尾弹出一些提交时,这些提交会继续存在一段时间。 在 GitHub 上,这个时间是"永远"的,但在本地仓库中,我们通常允许 Git 在 30 到 90 天左右后清除未使用的提交。
但是一旦我切换到 dev,我就没有得到 A1 的代码
Git 存储库中dev
的名称表示"我的dev
分支上的最新提交"。 该提交有一个快照,这就是您在git switch dev
时获得的快照。
如果您不想要该快照,则只有两个选择:
- 不要使用名称
dev
或 - 创建名称
dev
选择其他提交。
我们使用这些名称来查找感兴趣的特定提交,您可能希望保留自己的dev
,以便它仍然可以找到提交H
(无论H
的真实哈希ID是什么)。 如果是这种情况,您不想使用第二个选项,只留下第一个选项:使用不同的名称。
现在,您说要从最新a1
快照中的文件开始。 执行此操作的方法是使用名称创建新分支a1
,以便新分支选择相同的提交,如下所示:
...--G--H <-- dev
I <-- a1 (HEAD), b1
您现在可以git switch b1
,您将从提交I
切换到 ,使用提交I
的所有文件,切换到...提交I
,使用提交I
的所有文件。 这根本不是变化,Git 会巧妙地不更改任何文件。四您现在可以像往常一样自行更改文件git add
和git commit
:
...--G--H <-- dev
I <-- a1
J--K <-- b1 (HEAD)
例如(一旦您又进行了两次提交)。
4你会发现,如果你忘记先切换分支,你可以在开始工作后利用 Git 的这种聪明来创建新的分支。 例如,如果您不小心继续使用分支a1
,甚至进行了一些新的提交,则可以创建新的分支b1
指向当前提交,然后使用git reset
或git branch -f
强制名称a1
选择较旧的提交。
但这稍微高级的 Git 工作,用于修复错误。 现在,您需要知道的主要事情是分支名称选择提交,并且在不更改提交的情况下更改分支名称时 Git 很聪明。
"但是分支不会有错误的父级吗?
不:分支没有父母! 鉴于:
...--G--H <-- dev
I <-- a1
J--K <-- b1 (HEAD)
根本没有分支关系。dev
选择提交H
的名称。 这就是它的作用,这就是它的用途。a1
选择提交I
的名称。b1
选择提交K
的名称。 根本没有"名字b1
的父母"。
(分支名称具有可选的上游设置。dev
的上游大概是origin/dev
,a1
的上游是origin/a1
的,b1
的上游是origin/b1
的。 但这完全是另一回事:它不是"父分支"。 没有父分支这样的东西。
有父提交和子提交:提交I
是H
的子提交,这意味着H
是H
的(注意,而不是a)的父。J
是I
的孩子,这意味着I
是J
的父母。
一旦存在某个提交,该提交的任何部分都无法更改。 提交的父级是提交本身的一部分,因此这永远不会改变。J
的父母总是I
在这里。 但是我们可以随时创建新的分支名称,然后创建新的提交,因此如果需要,我们可以创建一个新名称br3
:
...--G--H <-- dev, br3
I <-- a1
J--K <-- b1 (HEAD)
然后切换到br3
:
...--G--H <-- dev, br3 (HEAD)
I <-- a1
J--K <-- b1
我们现在有来自提交H
的文件。 如果我们更改一些文件并提交,我们会得到:
L <-- br3 (HEAD)
/
...--G--H <-- dev
I <-- a1
J--K <-- b1
承诺H
现在有两个孩子,I
和L
。 但是I
仍然只有一个父H
,并且将永远存在,因为I
现在存在,任何提交一旦做出,就无法更改。
如果我需要更改提交怎么办?
你不能,但请注意,我们通过名称找到提交。 假设提交J
中存在错误,并且您希望修复该错误。 让我们进行另一个新的提交并将其命名为J'
并使其父级I
,使用临时分支名称,fixup
:
...--G--H <-- dev
I <-- a1
|
| J' <-- fixup (HEAD)
J--K <-- b1
现在,让我们将提交K
的效果复制到一个新提交中,该提交与K
一样,但J'
作为其父级:
...--G--H <-- dev
I <-- a1
|
| J'-K' <-- fixup (HEAD)
J--K <-- b1
现在让我们强制 Git 将名称b1
指向K'
,然后切换到分支b1
:
...--G--H <-- dev
I <-- a1
|
| J'-K' <-- b1 (HEAD), fixup
J--K ???
请注意,不再有提交K
的名称。 我们找不到提交K
。 提交K
是我们找到提交J
的方式,我们找不到K
所以我们也找不到J
。 但是b1
这个名字现在找到了K'
,它找到了J'
,它找到了I
,就像J
找到I
一样。 如果我们删除临时分支名称,我们会得到:
...--G--H <-- dev
I <-- a1
|
| J'-K' <-- b1 (HEAD)
J--K ???
只要我们忘记了提交的实际哈希 ID 是什么J
和K
,看起来我们确实更改了提交J
。我们没有 - 提交是不可变的 - 但我们做了一些"同样好"的事情。
(你还不需要担心这一点,但你应该记住它。\
所以这就是你的答案:通过添加过去的分支a1
来工作
与其从分支dev
的提示提交开始新的分支b1
,不如从分支a1
的提示提交开始新的分支b1
。
开始行动?
可能永远不会。 如果您特别使用 GitHub,并且您发出了拉取请求,则对此拉取请求进行操作的任何人都可以选择MERGE以外的操作。 如果您特别使用 GitLab,并且您发出了合并请求,则在此合并请求上操作的人可以选择合并以外的操作。
你应该把它留到以后:现在,如果你需要你在a1
中所做的工作来开始处理b1
,你需要从你在a1
中离开的地方开始,而不是从dev
的最后一次提交开始。