【发布时间】:2020-08-19 12:07:46
【问题描述】:
假设我有多个遥控器用于单个存储库。大多数时候,我使用一个 git 帐户进行开发,完成后,我将最终版本推送到另一个远程。现在,如何从第二个远程隐藏第一个远程的提交历史?
【问题讨论】:
标签: git github gitlab git-commit git-remote
假设我有多个遥控器用于单个存储库。大多数时候,我使用一个 git 帐户进行开发,完成后,我将最终版本推送到另一个远程。现在,如何从第二个远程隐藏第一个远程的提交历史?
【问题讨论】:
标签: git github gitlab git-commit git-remote
我会告诉你如何按照你的要求去做,然后告诉你为什么这是一个坏主意。 :-)
历史,在任何 Git 存储库中,只是该存储库中的 commits 集,由该存储库中的 names 集找到。这个查找过程是反向工作的,因为 Git 总是反向工作。我们稍后会看到更多关于此的内容。
请记住,每个 commit 都有一个唯一的哈希 ID。这实际上是提交的真实名称。要查看带有git log 的提交,您必须以某种方式将 Git 提示到提交的哈希 ID。然后 Git 可以从存储库数据库中检索该提交,前提是它首先在数据库中。
每次提交都有你所有文件的完整快照——这是提交中的主要数据——加上一些关于提交本身的元数据:信息,例如谁做了它,什么时候(日期-和时间戳),以及为什么(日志消息)。大多数元数据只是 Git 用git log 向你展示的东西。但是元数据中的一条重要信息,Git 本身需要的,也在这里。每个提交都有一个其 parent 提交的原始哈希 ID 列表。大多数提交在此列表中只有一个条目,因为它们只有一个父节点。
这个哈希 ID 意味着如果我们以某种方式找到一些开始提交哈希H,我们可以获取提交本身并显示它,并使用它来查找其父(早期)提交。我们称该提交为G。我们说 commit H 指向 commit G:
... G <-H
但 G 也指向一个较早的提交——我们称之为F——就像这样:
... F <-G <-H
当然F 也向后指向:
... <-F <-G <-H
所以我们真正需要的只是告诉 Git:last 提交的哈希 ID 是 _____(用哈希 ID 填空)。
这就是分支名称的作用:它提供了 last 提交的哈希 ID。分支名称指向一个提交,就像每个提交指向一个较早的提交一样。这样,我们就不必记住人类无法处理的大而丑陋的哈希 ID。我们只需要记住分支名称。 names 记住了大而丑陋的哈希 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 — 存储在远程名称 origin 下的 URL 上的 Git — 并通过哈希 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。
您现在可以进行 new 提交——使用全新的、从未见过的哈希 ID,我们将其称为 L——其中包含您喜欢的任何快照。让我们不用担心如何你这样做,然后画出结果:
L <-- other-guy (HEAD)
/
...--F--G--H <-- master
\
I--J--K <-- feature
您现在可以使用:
git push other-remote other-guy:feature
这会让你的 Git 调用存储在远程名称 other-remote 下的 Git,并为他们提供提交 L。他们不会拥有它,所以你的 Git 也会提供提交 H。他们可能有那个——它已经存在了一段时间——所以你的 Git 可能会停在那里,捆绑L,然后发送出去。
现在你的 Git 向他们的 Git 发送了以下形式的礼貌请求:如果没问题,请设置或创建你的名字 feature 指向提交 L。 如果他们接受,那么 他们在他们的存储库中有:
...--H--L <-- feature
(他们可能还有一些别的名字,比如他们的master,指向H,只是这里没有画出来)。因此,他们的 提交在 他们的 存储库中是从他们的名字feature 开始的,它标识提交L。他们将显示提交L。然后他们会回到L的父H,并显示H,以此类推。
请注意他们如何从不显示I-J-K。他们不能,因为他们没有。现在或将来,如果他们想要并有权访问,他们可以从您和/或您发送给他们的任何其他 Git 或任何与您发送给他们的 Git 具有 Git-sex 的 Git 获取它们,从而选择他们起来,依此类推;但现在,他们没有感染提交I-J-K。
(一般来说,Git 真的很喜欢接受新的提交。一般来说,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)。
如果你想开发更多的东西,你现在有一个问题:你是从commit K开始,还是从commit 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)
现在我们进行 squash-merge:
git checkout other-guy
git merge --squash feature
这合并工作——例如,比较 H 与 L 以找到“我们的”更改,并比较 H 与 N 以找到“他们的”更改,并组合更改。这通常可以正常工作...但是如果我们在 M-N 中所做的任何事情 un-does 在 I-J-K 中做某事,或者触及与我们在 @ 中所做的事情相同的 行 987654439@,我们遇到了合并冲突。
有办法解决这个问题,最后我们得到:
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 (虽然幸运的是在第一轮之后,这通常会被拒绝为“不是快进”错误)。
简而言之,秘密——隐藏的提交——通常一旦共享就不会持续。通常有一个更简单的选择。不过,我不知道你想要这一切的动机,所以很难确定。
【讨论】: