多个 Git 推入一个命令

Multiple Git push in one command

我有一个应用程序需要 运行 git add/commit/push 我想推送的每个文件,以便触发每个 Gitlab 作业。

我的问题是 git 实际上花了很多时间来执行 git push 命令。

这是我正在使用的命令:

git add myFile.json
git commit myFile.json -m "commitMessage"
variable1 = git rev-parse HEAD  # Storing last commit hash into a variable
# Pushing only one specific commit to (maybe) make it faster
git push $variable1:master

我想做的是让整个过程“更快”。 我想到了什么:

有没有人知道如何通过使用我的想法之一或您的全新想法来加快此过程!

注意:我使用的是 HTTPS,因此 SSH 解决方案可能不适合这里。

推送命令的输出:

Enumerating objects: 8, done.
Counting objects: 100% (8/8), done.
Delta compression using up to 2 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), 521 bytes | 521.00 KiB/s, done.
Total 6 (delta 2), reused 0 (delta 0)
To https://gitlab/root/xxx.git
   bd0a7c1..9671b26  master -> master

提前致谢!

编辑 1:

这是GIT_TRACE=true GIT_TRACE_PACKET=true git push

的输出
12:47:38.387505 git.c:418               trace: built-in: git push https://root:password@gitlab/root/xxx.git
12:47:38.405394 run-command.c:643       trace: run_command: GIT_DIR=.git git-remote-https https://root:password@gitlab/root/xxx.git https://root:password@gitlab/root/xxx.git
12:47:39.926614 pkt-line.c:80           packet:          git< # service=git-receive-pack
12:47:39.926654 pkt-line.c:80           packet:          git< 0000
12:47:39.926664 pkt-line.c:80           packet:          git< 4a902e3cdd3c06ba7fe9aa0345e510ce7c7ebb73 refs/heads/master[=13=]report-status report-status-v2 delete-refs side-band-64k quiet atomic ofs-delta push-options object-format=sha1 agent=git/2.33.1.gl3
12:47:39.926678 pkt-line.c:80           packet:          git< 0000
12:47:39.931365 pkt-line.c:80           packet:          git> refs/heads/master:refs/heads/master
12:47:39.934089 pkt-line.c:80           packet:          git> 0000
12:47:39.934131 run-command.c:643       trace: run_command: git send-pack --stateless-rpc --helper-status --thin --progress https://root:password@gitlab/root/xxx.git/ --stdin
12:47:39.954005 git.c:418               trace: built-in: git send-pack --stateless-rpc --helper-status --thin --progress https://root:password@gitlab/root/xxx.git/ --stdin
12:47:39.962814 pkt-line.c:80           packet:          git< refs/heads/master:refs/heads/master
12:47:39.962841 pkt-line.c:80           packet:          git< 0000
12:47:39.965723 pkt-line.c:80           packet:          git< 4a902e3cdd3c06ba7fe9aa0345e510ce7c7ebb73 refs/heads/master[=13=]report-status report-status-v2 delete-refs side-band-64k quiet atomic ofs-delta push-options object-format=sha1 agent=git/2.33.1.gl3
12:47:39.965792 pkt-line.c:80           packet:          git< 0000
12:47:39.998180 pkt-line.c:80           packet:          git> shallow 67769ed9405583783a372dc7731d5c1be8801a63
12:47:39.998324 pkt-line.c:80           packet:          git> 4a902e3cdd3c06ba7fe9aa0345e510ce7c7ebb73 4f72bdb2e340f297c5c88a3433bf1700a010f721 refs/heads/master[=13=] report-status side-band-64k agent=git/2.20.1
12:47:39.998420 pkt-line.c:80           packet:          git> 0000
12:47:39.998841 pkt-line.c:80           packet:          git< 0035shallow 67769ed9405583783a372dc7731d5c1be8801a6300954a902e3cdd3c06ba7fe9aa0345e510ce7c7ebb73 4f72bdb2e340f297c5c88a3433bf1700a010f721 refs/heads/master[=13=] report-status side-band-64k agent=git/2.20.10000
12:47:39.998986 run-command.c:643       trace: run_command: git pack-objects --all-progress-implied --revs --stdout --thin --delta-base-offset --progress --shallow
12:47:40.007547 git.c:418               trace: built-in: git pack-objects --all-progress-implied --revs --stdout --thin --delta-base-offset --progress --shallow
Enumerating objects: 8, done.
Counting objects: 100% (8/8), done.
Delta compression using up to 2 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), 530 bytes | 530.00 KiB/s, done.
Total 6 (delta 2), reused 0 (delta 0)
12:47:40.202197 pkt-line.c:80           packet:          git> 0000
12:47:40.202196 pkt-line.c:80           packet:          git< PACK ...
12:47:41.680994 pkt-line.c:80           packet:     sideband< 00eunpack ok0019ok refs/heads/master0000
12:47:41.681027 pkt-line.c:80           packet:     sideband< 0000
12:47:41.681108 pkt-line.c:80           packet:          git< unpack ok
12:47:41.681132 pkt-line.c:80           packet:          git< ok refs/heads/master
12:47:41.681148 pkt-line.c:80           packet:          git< 0000
12:47:41.681153 pkt-line.c:80           packet:          git> 0000
To https://gitlab/root/xxx.git
   4a902e3..4f72bdb  master -> master

长的部分好像是run_command: GIT_DIR=.git git-remote-httpsgit<PACK ...

此命令用于 depth=2 git clone

注意:第一个push总是很快的(好像在1-2秒之间),后面的大多在3-4秒之间。我试图在我的推送之间放置一个 sleep,但持续时间没有改变,所以我想这不是我的代码的问题。

这是我的第一个推送的输出:

12:46:16.834461 git.c:418               trace: built-in: git push https://root:password@gitlab/root/xxx.git
12:46:16.836817 run-command.c:643       trace: run_command: GIT_DIR=.git git-remote-https https://root:password@gitlab/root/xxx.git https://root:password@gitlab/root/xxx.git
12:46:17.307690 pkt-line.c:80           packet:          git< # service=git-receive-pack
12:46:17.307951 pkt-line.c:80           packet:          git< 0000
12:46:17.308003 pkt-line.c:80           packet:          git< 6725c964319de48c963746e976a97634f0ee0e7c refs/heads/master[=14=]report-status report-status-v2 delete-refs side-band-64k quiet atomic ofs-delta push-options object-format=sha1 agent=git/2.33.1.gl3
12:46:17.308024 pkt-line.c:80           packet:          git< 0000
12:46:17.309205 pkt-line.c:80           packet:          git> refs/heads/master:refs/heads/master
12:46:17.309223 pkt-line.c:80           packet:          git> 0000
12:46:17.309271 run-command.c:643       trace: run_command: git send-pack --stateless-rpc --helper-status --thin --progress https://root:password@gitlab/root/xxx.git/ --stdin
12:46:17.312085 git.c:418               trace: built-in: git send-pack --stateless-rpc --helper-status --thin --progress https://root:password@gitlab/root/xxx.git/ --stdin
12:46:17.313673 pkt-line.c:80           packet:          git< refs/heads/master:refs/heads/master
12:46:17.313707 pkt-line.c:80           packet:          git< 0000
12:46:17.313722 pkt-line.c:80           packet:          git< 6725c964319de48c963746e976a97634f0ee0e7c refs/heads/master[=14=]report-status report-status-v2 delete-refs side-band-64k quiet atomic ofs-delta push-options object-format=sha1 agent=git/2.33.1.gl3
12:46:17.314002 pkt-line.c:80           packet:          git< 0000
12:46:17.316186 pkt-line.c:80           packet:          git> shallow 67769ed9405583783a372dc7731d5c1be8801a63
12:46:17.316209 pkt-line.c:80           packet:          git> 6725c964319de48c963746e976a97634f0ee0e7c 6665f070f909ed946bf8223516f69691c15e9c29 refs/heads/master[=14=] report-status side-band-64k agent=git/2.20.1
12:46:17.316316 pkt-line.c:80           packet:          git> 0000
12:46:17.316423 pkt-line.c:80           packet:          git< 0035shallow 67769ed9405583783a372dc7731d5c1be8801a6300956725c964319de48c963746e976a97634f0ee0e7c 6665f070f909ed946bf8223516f69691c15e9c29 refs/heads/master[=14=] report-status side-band-64k agent=git/2.20.10000
12:46:17.316509 run-command.c:643       trace: run_command: git pack-objects --all-progress-implied --revs --stdout --thin --delta-base-offset --progress --shallow
12:46:17.318598 git.c:418               trace: built-in: git pack-objects --all-progress-implied --revs --stdout --thin --delta-base-offset --progress --shallow
Enumerating objects: 8, done.
Counting objects: 100% (8/8), done.
Delta compression using up to 2 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), 521 bytes | 173.00 KiB/s, done.
Total 6 (delta 2), reused 0 (delta 0)
12:46:17.339570 pkt-line.c:80           packet:          git< PACK ...
12:46:17.339830 pkt-line.c:80           packet:          git> 0000
[03/May/2022 12:46:17] "POST /api/getStatus/ HTTP/1.1" 200 854
12:46:18.029134 pkt-line.c:80           packet:     sideband< 00eunpack ok0019ok refs/heads/master0000
12:46:18.029164 pkt-line.c:80           packet:     sideband< 0000
12:46:18.029277 pkt-line.c:80           packet:          git< unpack ok
12:46:18.029285 pkt-line.c:80           packet:          git< ok refs/heads/master
12:46:18.029293 pkt-line.c:80           packet:          git< 0000
12:46:18.029297 pkt-line.c:80           packet:          git> 0000
To https://gitlab/root/xxx.git
   6725c96..6665f07  master -> master

GIT_DIR部分的持续时间似乎更快,git< PACK ...部分几乎是瞬间。

编辑 2:

这似乎只是我服务器的性能问题,但我认为@torek 的回答可以帮助人们搜索这个主题,所以我将其标记为已接受。

编辑:此答案适用于 ssh 连接。由于问题谈论 https,@torek 的回答更合适。


提高速度的一种方法是使用 ssh ControlMaster 选项。避免ssh每次重新打开一个连接。

ssh_config 手册页中的 ControlMaster 选项:

Enables the sharing of multiple sessions over a single network connection. When set to yes, ssh(1) will listen for connections on a control socket specified using the ControlPath argument. Additional sessions can connect to this socket using the same ControlPath with ControlMaster set to no (the default). These sessions will try to reuse the master instance's network connection rather than initiating new ones, but will fall back to connecting normally if the control socket does not exist, or is not listening.

例如在我的 ~/.ssh/config 我有:

ControlMaster auto
ControlPath ~/.ssh/mux-%r@%h_%p
ControlPersist 15m

TL;DR

我的猜测是您让 GitLab 使用浅层克隆,这通常会使事情变得更快,但在这种情况下,它会让事情变得 很多 变慢。

This大概是重点评论:

The long parts seems to be before Enumerating Objects, and during Writing Objects. The rest is almost instant.

其中很多都进入了 Git 内部结构的杂草中,可能会在没有警告的情况下随时更改。因此,过分依赖这些是不明智的。尽管如此,还是发生了什么:

  • 一个Gitrepository主要由两个数据库组成。一个数据库保存 Git objects,另一个保存名称:refsreferences。 object 已编号(按哈希 ID 或 object ID、OID)。 refs 将 human-usable 个名称,例如 refs/heads/main for b运行ch main,转换为 OID,每个名称只存储一个 OID。

  • OID 是通用的:每个 object 都有一个唯一的 OID,所有 Git 的编号都相同 object。这意味着任何两个 Git 存储库都可以相遇——无论是第一次,还是 nth 具有较大的价值n——但几乎可以立即找出 other 存储库是否有一些 object,只需将 object的ID。发送 Git 列出了某些关键 OID,接收 Git 以“我有那个”或“我想要那个”响应来响应。

  • 接下来我们需要做的事情在技术上很复杂,但可能足够容易可视化或理解。 commit object 包含已知的元数据值,包括 parent 提交列表和一个(单个)object。 tree object 由重复的元组组成,给出了组件名称、object 类型(或“模式”)和 object ID。在 一棵树中 列出的 object 通常是另一棵树,或者是 blob object。一个 blob object 表示一个文件的内容:该文件的 name 是通过将提交到该 blob 的树 object 的名称串在一起生成的.

    任何给定提交的 parent(s) 是提交本身进行时存在的提交。一次提交一个。这意味着从提交到提交的连接中不能有循环:如果提交 H link 向后提交到提交 G,提交 G 不能 link 转发到提交 H 因为 H 在创建 G 时不存在。 G 可以 link 返回到 still-earlier 提交,但是那些不能 link 转发到 GH.

    同样,一棵树 object 不能引用它自己,树 object 中的任何 sub-tree 也不能引用树 object 或任何 sub-tree 位于进行引用的 sub-tree 的“上方”。也就是说,如果树 OID 9876543 存在,它的条目 none 可以引用 object 9876543 和它的子树的 none——比如说,5566778——也可以指代9876543。因此,从提交开始发现的任何树集中都不会存在循环。这些规则意味着树实际上是 ,它是 DAG 的子集:参见 What's the difference between the data structure Tree and Graph?

    (Blob objects 表示文件内容,在这个级别对 Git 是不透明的:Git 不必检查它们,也不会这样做。)

    所有这一切的最终结果是提交本身形成了有向无环图或 DAG。同时,每个提交中的顶层 tree object 形成树的树 objects。所以我们有一个树的 DAG,或 DAG 的 DAG,但是你想引用它;这样的组合本身就是一个 DAG。 (请注意,提交可以 re-use 顶级或 sub-level 早期提交的树:这在这里非常好,因为它不会违反 DAG 规则。)

    (在所有这些之上,我们可以有 带注释的标签 object,它存储一个 目标的哈希 ID object。因为它们仅限于单个目标,并且 Git 的哈希 ID 计算规则禁止循环,所以这些只是向整体 [=313= 添加了几个 leads-into-an-object 节点]。它们为可视化添加了一点点复杂性,但不会扰乱整体 DAGginess。)

最终,所有这一切归结为我们拥有具有约束的整体图结构:方向性和缺乏循环。任何这样的 DAG 都具有 可达性 属性:也就是说,从图中的某个节点开始,图中可能还有我们 可以 到达,并且图中可能还有我们 无法 到达的其他节点,通过 one-way 连接:提交 b789abc 有 parent a123456,因此 a123456 可从 b789abc 访问。由于没有循环,根据定义,这意味着 b789abc 不能从 a123456 到达 。 (但是,您不能推断出se:如果节点 X 不能从节点 Y 到达,这并不意味着 Y 可以从 X 到达。也许 W 或 Z 可以到达 X 和 Y,但是 X 和 Y 只是树中的兄弟姐妹,例如。)

为此,我们通常再添加一个约束:Git 存储库中永远不会有“漏洞” .通过这个,我的意思是如果我们有一些节点 图中,我们必须总是 每个节点都可以从该节点 到达。如果 a123456b789abc 的 parent,并且我们有 b789abc,我们也必须有 a123456。这反过来意味着我们必须拥有 a123456 的完整 快照。如果 a123456 有一个 parent 提交,我们也必须有 提交的整个快照,依此类推。

注意上面通常这个词的强调。在这种情况下,如果我们是发送者并且我们正在做 git push,我们通常可以通过知道哪些提交是 receiving 中的最新提交来判断Git 存储库,关于这些提交的所有内容。也就是说,如果我们有新的提交 b789abc 并且他们有它的 parent a123456 我们自己已经有 a123456。我们也有 一切可以从 a123456 到达。所以我们知道他们拥有的每个文件的一切,至少就 a123456 所有祖先而言是这样。

这给发送 Git 一个巨大的优势:它告诉接收 Git 我可以向你发送提交 b789abc,你喜欢吗? 接收方 Git 可能会回答 我已经有 b789abc,在这种情况下我们知道我们需要知道的关于接收方的一切 Git,或者它可能会说 是的,我想要 。如果是后者,我们作为发送者Git,现在必须提供parenta123456。他们会回复 我已经拥有它请发送它,之后我们将提供它 parent(s),等等。

在某些时候,我们要么 运行 没有要发送的提交——它们什么都没有,我们必须发送 每个 object——或者我们命中一些提交他们拥有的,这意味着他们拥有那个提交 和每个更早的提交 而我们,发件人,现在也确切地知道他们拥有什么文件。因此我们可以很好地向他们发送只是他们需要的提交,只是他们需要的文件for 那些提交, 我们可以压缩这些文件,知道他们已经拥有这些文件的早期版本。

请注意,这里有一个很大的总体假设,即 CPU 时间很便宜,但网络带宽很贵。我们使用这个 OID-exchange 过程来找到他们已经拥有的东西,然后我们准备新的 objects 并将它们与已知的旧 objects 进行压缩。这(“压缩 objects”)部分 可能会 花费很多时间,具体取决于我们自己的计算速度。但它通常很快,因为通常我们只发送一个或几个提交,每个提交只有一个或几个新的或修改的文件,所以没有太多要压缩的。然后我们发送那些 objects,那部分和网络一样慢,但如果我们压缩得很好,我们就不必发送很多 objects 我们已经很好地压缩了它们,而不是他们已经拥有的其他 object。

但是请注意,如果我们git push当前(HEAD)提交,我们必须发送它们全部 parent 回到我们的历史和他们的历史交汇点。这保持了“完整性”或“缺孔”属性。所以你的代码在这里:

variable1 = git rev-parse HEAD  # Storing last commit hash into a variable
# Pushing only one specific commit to (maybe) make it faster
git push $variable1:master

没有用;你可以 git push HEAD:master,或者如果你当前的 b运行ch 名称 master 在你的(发件人)“一方”,你可以 git push master.

我在上面提到过,存储库由两个 数据库组成。到目前为止我所描述的过程都是关于更新 object 数据库的。这才是真正重要的,因为名称数据库主要是为了帮助弱小的人类:机器 需要的只是原始哈希 ID。 Git 不会像人类那样试图分辨 720100ac47678fa31f0844a413f05bd0305d179f720100ac47678fae1f0844a413f05bd0305d179f 之间的区别(我在这里做了一个小改动:你能发现它吗?)。但是我们也需要更新名称数据库,所以按照上面的步骤,发送 Git 将发送礼貌请求或强制命令(或者可能不止一个一个或两个,空格中的名称不同):

  • 如果可以,请创建或更新您的姓名 _______ 以保留 ID _______;或
  • 设置你的名字_______持有_______!

(还有第三种,有条件地强制更新,发件人说我认为你的名字_______持有_______ . 如果是这样,设置为 _______! 就是 --force-with-lease 选项,它只是启用一种更安全的执行命令的方法。)

接收方 Git 根据各种规则随意服从或不服从(托管服务器通常会在 super-simple 之上添加一堆控制规则 out-of-the-box Git 提供)。发件人的工作只是提供名称和哈希 ID,以及礼貌的请求或强制命令,并从接收方那里取回回复 Git:“OK”或“拒绝”,以及任何附带的消息可能来自托管服务器,例如,告诉用户他没有设置该名称的访问权限。然后发件人将结果报告给 运行 git push 的人或进程,并更新实际成功的 git push 操作的任何 remote-tracking 名称。

因此,如果您有几件东西要发送,您可以将它们一起发送:

git push origin master mybranch:theirbranch

例如。这让你的发送 Git 收集你这边 mastermybranch 的 OID,发送他们的 Git 所需的任何提交和支持 object,然后询问(礼貌地)他们将 mastertheirbranch 设置为您的 Git 为您的 masteryourbranch.

找到的 OID

出错的地方:太多的名字和肤浅的克隆

这是正常的流程。现在让我们看看有什么方法可以混淆它。

首先,一些(不是全部)现有的发送和接收进程有时会遍历名称数据库中的每个 名称。例如,对于具有数万个标签名称的存储库,这可能会花费很多时间。这发生在“计数 objects”阶段甚至开始之前。如果你有网络监控器或跟踪器,你会看到很多来自接收方的数据,列出他们所有的 b运行ch 和标签名称以及相应的哈希 ID,即使发送方并不真正需要所有那个。 Git 的 C 版本中 worked-on 有一些技术改进,但它们已经进行了很长时间(我想现在已经很多年了),但目前还没有。如果这个 一个问题,最简单的解决方案是 p运行 返回很多名称,但这通常需要存档,或者至少重命名现有的存储库和制作一个新的(因为某些哈希 ID 只能通过旧名称找到,您可能不想永远丢失它们)。

更重要的是,回到通常这个词,我在上面输入bold-italics。我们可以制作一种 Git 克隆,它 Git 称为 浅层 克隆,其中通常的约束——图中没有“洞” ——被故意侵犯。为了实现这一点,Git 将某些提交哈希 ID 写入文件,说 假定存在此提交,但我们没有它,所以我们对此一无所知.

当areceivingGit浅时,sendingGit有问题,当a sendingGit浅薄,sendingGit有问题。这意味着浅层克隆可以作为 one-time 的东西,但对于正在进行的工作来说是个坏主意,你将要 运行 git fetch (“从某些地方获得更多 objects其他存储库”)或 git push(“将 object 发送到其他存储库”)。特别是,通常的假设是,如果有人提交 b789abc,其 parent 是 a123456,那么他们不仅有 a123456,而且还有每个较早的提交,因此每个版本到那时为止的每个文件都完全崩溃了。

由于发送方在发送 接收方时使用的 have/want 协议,深度 -two 浅克隆对完整 (non-shallow) 克隆进行 git push 比深度 1 克隆要好得多。 “更深”克隆中的额外提交允许发送方更好地理解接收方拥有的内容,即使发送方不具备进行最佳压缩所需的一切。

最佳 发送结果是在您首先拥有完整克隆时实现的,但是如果您要进行浅克隆,然后提交并推送,开始至少有一个深度为 2 的克隆。 CI 系统经常故意使用深度为 1 的浅克隆,但通常有旋钮来调整深度,或制作完整 (non-shallow) 克隆。一些深度妥协(除了 2 个)可能最适合你的情况;没有实际测试很难说。