假设我有多个用于单个存储库的远程。大多数情况下,我使用一个 git 帐户进行开发,完成后,我将最终版本推送到另一个远程。现在,如何从第二个远程隐藏第一个远程的提交历史记录?
我会告诉你如何做你要求的事情,然后告诉你为什么这是一个坏主意。
在任何 Git 存储库中,历史记录只是该存储库中的提交集,由该存储库中的名称集找到。 此查找过程向后工作,因为 Git 始终向后工作。 我们稍后将看到更多关于此的内容。
背景
请记住,每个提交都有一个唯一的哈希 ID。 这实际上是提交的真实名称。 要查看带有git log
的提交,您必须以某种方式将 Git 告知提交的哈希 ID。 然后,Git 可以从存储库数据库中检索该提交,前提是它首先在数据库中。
每次提交都有所有文件的完整快照(这是提交中的主要数据)以及一些元数据:有关提交本身的信息,例如谁进行了提交,何时(日期和时间戳)以及原因(日志消息)。 这些元数据的大部分只是 Git 向您展示的内容git log
. 但是 Git 本身需要的元数据中的一个关键信息也在这里。 每个提交都有一个其父提交的原始哈希 ID 列表。 大多数提交在此列表中只有一个条目,因为它们具有单个父项。
这个哈希 ID 意味着如果我们以某种方式找到一些起始提交哈希H
,我们可以获取提交本身并显示它,并使用它来查找其父(早期)提交。 我们称之为提交G
。 我们说提交H
点来提交G
:
... G <-H
但 G 也指出了较早的提交——我们称之为F
——如下所示:
... F <-G <-H
当然,F
也指向倒退:
... <-F <-G <-H
所以我们真正需要的只是一种告诉 Git 的方法:最后一个提交的哈希 ID 是 _____(用哈希 ID填空)。
这就是分支名称的含义:它提供最后一次提交的哈希 ID。 分支名称指向一个提交,就像每个提交指向一个较早的提交一样。 这样,我们就不必记住人类无法处理的大丑陋哈希ID。 我们只需要记住分支名称。这些名字记住了丑陋的大哈希ID:
... <-F <-G <-H <-- master
。当我完成[进行新提交]时...
让我们看一下进行新提交的过程。 让我们创建一个新的分支名称,例如feature
,现在。 分支名称必须指向某个现有提交- 这是 Git 中的规则:分支名称指向某个提交。 在...--F--G--H
系列中,显而易见的是...最后一个:
...--F--G--H <-- feature (HEAD), master
我们需要一种方法来记住我们使用的分支名称,因此我将特殊名称HEAD
附加到新名称feature
。 如果我们这样做,这就是我们得到的:
git checkout -b feature master
我们仍在使用提交H
,但现在我们on branch feature
,正如git status
所说。 特殊名称HEAD
现在附加到feature
,而不是master
。
当我们进行新的提交时,它会得到一个新的、从未在其他地方使用过、从未在其他地方使用过的、从未在其他地方使用过的提交哈希I
。 新提交I
指向现有提交H
:
...--F--G--H <-- master
I <-- feature (HEAD)
重复几次,你就会得到这个:
...--F--G--H <-- master
I--J--K <-- feature (HEAD)
最终,您完成了提交。 您现在可以git push
到某些远程,例如origin
。 这种工作方式是,你的 Git 调用另一个 Git(存储在远程名称下的 URL 上的 Git,origin
个),并通过哈希 ID 为他们提供一些提交。
他们在存储库中查看他们是否具有该哈希 ID。 如果你向他们提供提交K
,他们就不会有它。 这迫使你的 Git 也向他们提供提交J
,因为J
是K
的父级,这也是 Git 规则的一部分。 他们不会有,所以你的 Git 会提供I
,他们不会有,所以你的 Git 会提供H
。 在这里,他们很可能有H
! 假设他们这样做。 这让你的 Git 停止提供哈希 ID。
现在,您的 Git 必须打包新提交的提交,I-J-K
并发送它们。 你将在此处看到有关计数和压缩的消息,然后你的 Git 发送提交。
git push
现在进入最后阶段:它向他们发送一个礼貌的请求:如果没问题,请将您的分支名称 ______ 设置为指向提交K
。只要这会将提交添加到其分支之一,而不从该分支中删除任何提交,他们就可能会遵守此请求。 如果这是一个全新的分支名称,他们更有可能遵守此请求。
最终结果是,现在它们的分支名称指向提交链中的最后一个提交K
。 从K
,他们会找到J
,然后是I
,然后是H
,等等。这是他们现在在存储库中拥有的历史记录。
你想要什么
。如何从第二个遥控器隐藏第一个遥控器的提交历史记录?
你不能,或者,不完全是。 但是,您可以进行具有不同历史记录的新提交,并改为发送这些提交。
假设您在自己的存储库中,使用以下方法创建一个新的不同的分支名称:
git checkout -b other-guy master
这会在您的 Git 存储库中为您提供以下一系列名称和提交:
...--F--G--H <-- master, other-guy (HEAD)
I--J--K <-- feature
您当前的提交现在是提交H
。 您当前的分支名称现在为other-guy
。
您现在可以进行新的提交 - 使用一个全新的、前所未见的哈希 ID,我们称之为L
- 其中包含您喜欢的任何快照。 让我们不要担心你是如何做到这一点的,只是画出结果:
L <-- other-guy (HEAD)
/
...--F--G--H <-- master
I--J--K <-- feature
您现在可以使用:
git push other-remote other-guy:feature
这会让您的 Git 调用存储在远程名称下的 Gitother-remote
并为他们提供提交L
。 他们不会拥有它,所以你的 Git 也会提供提交H
。 他们可能有那个 - 它已经存在了一段时间 - 所以你的 Git 可能会停在那里,捆绑L
,然后发送它。
现在,您的 Git 向他们的 Git 发送了一个礼貌的表单请求:如果没问题,请在指向提交L
feature
设置或创建您的名称。如果他们接受,他们仓库中的内容是:
...--H--L <-- feature
(他们可能还有其他名字,比如他们的master
,指向H
,我们只是没有在这里画)。 因此,它们在存储库中的提交是通过从它们的名称feature
开始找到的,该名称标识提交L
。 他们将显示提交L
。 然后它们将移回L
的父H
,并显示H
,依此类推。
请注意他们如何从不显示I-J-K
. 他们不能,因为他们没有。如果他们想要并有权访问,他们现在可以现在或将来从您和/或您发送给它们的任何其他 Git 中获取它们,或者与您发送给他们的 Git 有 Git 性关系的任何 Git,从而拾取它们,等等;但是现在,他们没有感染提交I-J-K
。
(一般来说,Gits 真的很喜欢接受新的提交。 一般来说,Git 不喜欢放弃提交。 像感染一样传播提交非常容易。
使提交L
的简单方法
我答应教你如何做你想做的事。 在I-J-K
之后,有一种简单的方法可以使提交L
,那就是使用git merge --squash
。
鉴于此:
...--F--G--H <-- master, other-guy (HEAD)
I--J--K <-- feature
您可以运行git merge --squash feature
,然后git commit
.git merge --squash
告诉 Git:为真正的合并做所有你想做的事情,但随后停止而不提交。 当我进行提交时,将其作为常规的日常单父提交,而不是与其两个父级的合并提交。
Git 现在结合了从提交H
到提交H
的差异(完全没有变化)以及从H
到K
的差异,并将所有这些更改应用于H
中的快照,从而在K
中生成快照。 此快照尚未提交,但您运行git commit
,根据需要填写提交消息,现在是:
L <-- other-guy (HEAD)
/
...--F--G--H <-- master
I--J--K <-- feature
并且您已准备好git push
提交L
并让其他人将其称为feature
。
为什么你可能不应该这样做
一旦你完成了一次,你在你自己的仓库中的下一个起始位置是这样的:
L <-- other-guy (HEAD)
/
...--F--G--H <-- master
I--J--K <-- feature
您的两个遥控器之一具有相同的设置,只是它完全缺少提交L
。 如果你想在那里发送提交L
,这次你需要使用feature
以外的名称:他们的名字feature
记住提交K
。 你可以告诉他们强行放弃I-J-K
以支持L
,但如果你这样做,你已经放弃了你要求做的事情:现在其他两个遥控器只能找到提交L
(至少,通过他们的名字feature
)。
如果你想开发更多的东西,你现在有一个问题:你是从提交K
开始,还是从提交L
开始? 如果你从L
开始,你自己做的新工作的历史,没有I-J-K
的历史。 毕竟,历史记录是由某个名称发现并向后工作的一组提交。
因此,您最终要做的是以下两件事之一:
- 创建许多您忽略的历史记录(您自己的
feature
分支,在这种情况下,您可以通过从提交L
开始下一个分支来放弃这些历史记录),或者 - 当你做
git merge --squash
时,开始不得不做git merge --squash
。
让我们看看后者是如何工作的:
git checkout feature
现在的结果是:
L <-- other-guy
/
...--F--G--H <-- master
I--J--K <-- feature (HEAD)
我们做出更多承诺:
L <-- other-guy
/
...--F--G--H <-- master
I--J--K--M--N <-- feature (HEAD)
现在我们去挤压合并:
git checkout other-guy
git merge --squash feature
这会合并工作 — 例如,比较H
与L
以查找"我们的"更改,H
与N
以查找"他们的"更改,并合并更改。 这通常工作正常...但是,如果我们在M-N
中所做的任何事情撤消了I-J-K
中的某些操作,或者与我们在I-J-K
中所做的内容相同,则会出现合并冲突。
有很多方法可以解决这个问题,最后我们得到:
L--O <-- other-guy
/
...--F--G--H <-- master
I--J--K--M--N <-- feature (HEAD)
其中O
具有将M
和N
相结合的挤压结果. 您现在可以将两个不同的历史git push
到两个不同的地方。
这真的可以工作。 随着时间的推移,它只会变得痛苦。 还有另一个"类似感染"的问题:你已经将提交I-J-K-M-N
发送到其他一些 Git。 这些提交很有可能会传播到更多的 Git 克隆中,然后从那里到达您试图保持这些秘密的克隆。 即使这没有发生,通过做git push other-guy feature
,你自己也很容易搞砸(尽管幸运的是,在第一轮之后,这通常会被拒绝,因为"不是快进"错误)。
简而言之,秘密 - 隐藏的提交 - 通常不会持续一次共享。 通常有一个更简单的选择。 不过,我不知道你想要这一切的动机,所以很难确定。