首页 > 解决方案 > Git 推送到远程而不拉取并防止远程文件被删除

问题描述

我在不同的节点上有几个本地 Git 存储库。他们都将自己的文件推送到同一个远程的同一个分支,这些文件从一个节点到另一个节点完全不同。没有节点需要访问其他节点的文件。为了节省带宽,我不想git pull在节点推送文件之前对它们进行操作。有什么方法git push --force可以将文件从节点推送到远程的单个分支而不删除其他节点提交的文件?

标签: gitpushpull

解决方案


TL;博士

为每个节点分配一个保留给该节点的私有名称。我将在下面举例说明如何使它与人类演员一起工作。您必须为机器演员想出自己的系统。然后你必须想出你自己的系统来使用结果提交——这不像选择一个提交那么简单;你需要一个真正的部署引擎。

简短的回答是 no 和 yes,或者mu。这里的基本问题是git push不推送文件; git push发送提交。这些提交由其唯一的哈希 ID 标识。例如,如果想向您发送a123456我可以运行的提交:

git push <string-that-leads-my-Git-to-your-Git> a123456:<some-name>

请记住,每个提交都列出了它自己的父提交哈希 ID,这很快就会变得很重要。这a123456必须有一些父级——一些其他的哈希 ID。但是现在让我们看看这个特定的git push命令。

当我选择运行这个git push命令时,我必须放在冒号左侧的东西,在这个git push命令中,是我要发送给你的提交的提交哈希 ID的说明符。(在这种情况下,我选择了一个我刚刚编造的缩写哈希 ID。)在同一个命令中,我必须放在冒号右侧的东西是我想要让你的 Git 使用的名称保存此提交。

我们稍后会回到这个;现在我们需要看看 Git 提交是如何工作的。

提交如何工作

Git 真正工作的方式非常简单。每个提交都有自己的唯一哈希 ID。您已经看到了这些哈希 ID,因为git log它们显示了它们。但它们又大又丑,没有人能记住它们。这就是我们让 Git 为我们记住它们的原因。 除了最后一个(分支的最新或提示提交)之外的所有哈希 ID都会其他提交中被记住。

例如,我们可能会从一个只有三个提交的小型存储库开始:

A <-B <-C

我们可以使用单个大写字母来绘制 Git 的工作原理,而不是写出它们实际的、明显随机的哈希 ID。提交C最新的提交,所以它里面有提交的实际哈希 ID B(不管它可能是什么)。所以我们说 commitC 指向commit B

同时,commitB包含 commit 的实际哈希 ID AB指向A. A是一个特例:这是第一次提交;它不能指向之前的提交,因为我们之前没有提交A。所以A无处可去,这让 Git 停止了。

分支名称如何工作

但是,不知何故,我们必须找到commit C。Git 只能通过它们的哈希 ID找到提交——至少是直接的。所以我们必须将提交的哈希 ID 保存在C某处。我们可以把它写在一张纸或白板上,然后输入,但这太糟糕了。相反,Git 允许我们将哈希 ID 存储在name中。

您可以在此处使用的名称有多种形式,但我们最常使用的是分支名称。分支名称有一个特殊属性:我们可以使用它来获取分支。当我们这样做时,Git 会将特殊名称附加该分支名称。分支名称本身仅包含实际提交的原始哈希 ID(在本例中为提交 C),因此分支名称指向该提交,就像每个提交指向其父级一样:git checkout branchHEAD

A <-B <-C   <-- master (HEAD)

现在假设我们要创建一个新分支dev。我们将创建它,指向提交C——一个提交的两个名称;这很好,它只是意味着所有三个提交现在都在两个分支上。我们也将 Git 附加HEADdev:

A--B--C   <-- dev (HEAD), master

现在我们将进行的提交。因为我们已经C签出,新提交的父级将是C. Git 将从我们决定快照的任何源代码(我们必须使用 复制到 Git 的索引中)以及我们告诉 Git使用的任何元数据以及它自己发现的任何内容(我们的名字,电子邮件地址、日志消息、当前时间和 commit 的哈希 ID ):git addC

A--B--C
       \
        D

请注意如何通过元数据中的父提交哈希 IDD指向。C

所有数据和元数据都进入加密哈希算法,因此写出提交的行为会为新提交生成D的哈希 ID 。现在到了神奇的一步:Git 简单地用新的哈希 ID覆盖分支名称 -附加的分支名称,以便当前分支名称指向新的提交:DHEAD

A--B--C   <-- master
       \
        D   <-- dev (HEAD)

其他名称没有改变。当前的分支名称 ,dev现在指向新的提交,这样就dev记住了 hash DHEAD也没有改变 - 它仍然附加到dev.

git push工作原理

当我让我的 Git 调用你的 Git 时,我告诉了我的 Git:

  • 我要发送哪个提交,以及
  • 我希望我的 Git 要求您的 Git 使用什么名称,以记住该提交。

我的 Git 询问你的 Git:嘿,你有 commita123456吗? (假设a123456是我D在上面那个简单的四提交存储库中的提交。)因为我刚刚完成,所以你没有它。所以我的 Git 会询问你的 Git 是否有C,因为我的 Git 必须发送所有导致 D的提交,而不仅仅是D它自己。你要么有,要么C没有。如果你不这样做,我的 Git 也会询问B,依此类推。但您可能已经拥有C. 可能, master对共享提交的观点C

所以我的 Git 打包只是 commit D,然后发送。然后我的 Git 要求您的 Git 设置您的分支名称之一

如果我选择让你的 Git 设置你的dev,而你没有你的devGit 将创建你的dev,现在有我做的同样的事情。

同时,假设 ErnieA-B-C在 Ernie 的 Git 中有相同的提交,但是 Ernie 做了一个新的dev和一个新的提交E。厄尼现在有:

A--B--C   <-- master
       \
        E   <-- dev

Ernie 没有,D因为 Ernie 的 Git 没有与我的 Git 对话,反之亦然。就在我向您发送 myD并要求您创建您的 dev之后,Ernie 让他的Git 向您发送他的 E并要求您将您dev的指向点设置为 commit E

你的 Git 现在有:

        E   <-- polite request to set your `dev` here
       /
A--B--C   <-- master
       \
        D   <-- dev

如果你的 Git 服从了礼貌的要求,你的 Git 会将你的devto 指向E,从而忘记如何找到D. 所以你的 Git 只是说不,我不能那样做。

如果 Ernie 改变他git push的使用--force,Ernie 将他的礼貌请求改为命令:设置dev指向E,我是认真的! 您的 Git 仍然可以拒绝该命令,但默认情况下您的 git 将服从该命令,并丢失D. 这就是你在尝试使用这种技术时所看到的。

请注意,无论我如何运行git push,我的 Git 都会要求您的 Git 设置一些名称。我在上面展示的方法——我在冒号左边选择一个哈希 ID,在右边选择一个名称——不是我们通常的拼写方式git push。我们通常写:

git push origin dev

但这意味着git push origin dev:dev。_ 左边的东西dev, 是我的 commit 名字D。右边的东西dev,,只是实际的名字dev。Git 只是让我们在没有冒号的情况下表达这一点。

问题是名称冲突,因此请避免名称冲突

不管如何提交发送到您的中央收集点选择命名它们的分支,您需要中央收集点发生的事情都很简单。在上面,我们有两个演员:一个是我,在名字下dev,发送我的提交D,厄尼,在名字下dev,发送他的提交E。这失败了,因为我D和他E都回到了 shared C,但是我们试图使用一个名字来记住两个不兼容的提交。

但是,如果你给我一个私人名字来使用呢?为简单起见,假设我的名字是 Dave(它不是)并且你让我推动我devincoming/dave而不是仅仅dev. 我会跑:

git push origin dev:incoming/dave

同时,您将让 Ernie 运行:

git push origin dev:incoming/ernie

现在在您的Git 存储库中,您将拥有以下内容:

        E   <-- incoming/ernie
       /
A--B--C   <-- master
       \
        D   <-- incoming/dave

假设现在我做了一个新的提交F,而 Ernie 做了一个新的提交G,我们每个人都在处理我们的dev. 请记住,提交哈希 ID 是通用的,因此我F的和 Ernie 的G定义是不同的大而丑陋的哈希 ID。G厄尼会和父母一起推他E。他会G他的名字找到他的 Git,dev但要求你设置你的名字incoming/ernie。你最终会得到这个:

        E--G   <-- incoming/ernie
       /
A--B--C   <-- master
       \
        D   <-- incoming/dave

incoming/ernie可以安全地更新,因为它必须G返回E,因为它必须返回。

我还没有发送我的F,但是当我发送时,这F将指向D并且我会让你更新你的incoming/dave. 没关系,因为incoming/dave将指向F哪个导致返回D:不会丢失任何提交。

显然,有人——<em>你——将不得不做一些事情来整合这些独立的开发线。但这不是戴夫和厄尼的问题,那是你的问题。


推荐阅读