试图解决 Git --bare repo 的问题是什么?

What problem is trying to solve a Git --bare repo?

我想我理解 实用 非裸 回购之间的区别在 Git 中,但我真的不明白为什么 逻辑上 这种区别存在:为什么 Git 必须实施裸和非裸回购的概念?我知道已经有大量关于该主题的帖子和文章,但我确实缺少一些具体示例来完全理解该主题。

回顾一下,非裸仓库和裸仓库之间的实际区别(即文件方面)如下:

问题是:为什么我需要一个中间裸仓库来方便地同步两个非裸仓库?许多线程和文章都在回答说没有中央裸仓库会导致中央工作树不同步(参见 )。好的,但是为什么呢?有人可以提供一个具体的例子吗?

我能想象的情况是这样的:

  1. A 是本地回购,B 是本地回购,C 是中央远程仓库。都是非裸。
  2. A 提交 c 并将其推送到 CC 根据 c 中的更改更新其 .git 文件夹和工作树。 重要:“更新工作树”在我看来意味着替换C工作树(即C 个文件和文件夹)与 A 工作树。
  3. BC 中提取更改并根据 C 中的更改相应地更新其 .git 文件夹和工作树=11=]

像上面描述的这种情况怎么会导致C的工作树不同步呢? C 不同步是什么意思?

到目前为止,我所了解的唯一真正优势是,对于像 Github 或 Gitlab 这样的服务,没有为每个 repo 和每个分支保存存储非常方便space。他们可以利用 Git 工具即时重建工作树。

确实比较简单。裸存储库没有工作树,因此它 不能 有一个活动的检出。1 而且,正如您在其他地方看到的那样,问题是将 推送到 某些分支的主动签出会导致 out-of-sync 签出。所以 Git 禁止推送到 checked-out 分支。2 由于裸存储库没有工作树,因此没有 checked-out 分支,裸存储库回避了这个问题。

What does even mean that [the non-bare central repo] goes out of sync?

让我们放弃第三台机器:我们只需要 client,一个 non-bare 存储库,和 server,应该是但不是裸机的存储库。

server 上,分支 main 处于活动状态 checked-out。有人可能登录也可能没有登录 server 并在那里编辑文件。

与此同时,在 client,您进行了一些新的提交,并且您 运行 git push 并将新的提交发送到 server。如果服务器接受这个提交,现在有两种可能性:

  1. server的Git存储库不会更新checked-out工作树,或者
  2. server 的 Git 存储库 确实 更新了 checked-out 工作树。

两种情况都可能产生不好的结果。在我们开始之前,让我们稍微探索一下 Git 的工作原理。


1这个 在添加 git worktree add 之前是 正确的,现在不是了。所以简单性一直存在到 Git 2.5,现在不是了。

2这个原来的Git,在发明各种配置项之前。现在不是了。所以简单性曾经存在,现在不存在了。 (receive.denyCurrentBranch 的东西发生在 git worktree 命令之前,但我不记得那是哪个版本。)


Git 是关于提交的;提交已编号;分支名称查找提交

一个Git 存储库主要由两个数据库组成,一个通常比另一个大得多。较大的数据库包含提交和支持 Git 对象。较小的数据库包含名称,例如分支和标记名称。

提交对象已编号,编号表示为 hexadecimal 哈希 ID。 Git 需要 哈希 ID 来查找提交:大数据库仅由哈希 ID 索引。

提交本身包含两件事:

  • 每个文件的完整快照,截至提交时 应有的状态;和
  • 元数据:有关提交本身的信息,例如提交人、时间和原因(日志消息)。

在任何给定提交的元数据中,Git 存储该提交的 父级或父级 的原始哈希 ID。因此,提交有一个 previous-commit 哈希 ID 列表,存储在其元数据中。这形成了存储库中的历史记录。

为了能够获得任何给定分支的最新提交,Git存储在分支名称(例如refs/heads/main)中, 最新 提交的原始哈希 ID。该提交在其元数据中包含前一个(父)提交的哈希 ID,后者又包含另一个父提交的另一个哈希 ID,依此类推。

当我们将 git checkoutgit switch 与分支名称一起使用时,我们告诉 Git: 提取 latest 为该分支提交 。这是其哈希 ID 存储在分支名称中的那个。因此,使用 git switch main,Git 查找 refs/heads/main,找到一个哈希 ID,例如 a123456...,然后在数据库中查找该提交。该提交有一组与之关联的文件。 Git 将这些文件从提交中复制出来—— 中的那些文件通常不能被 OS 使用,因为它们在 [=229] =], compressed, Git-only, de-duplicated 形式——到你的工作树。

但是,Git 将文件——或者更确切地说,关于文件的信息(名称和 blob 哈希 ID)——复制到 Git 中index,它与工作树一起出现。这定义了哪些文件被 tracked,帮助 Git 快速运行,并且通常需要知道在 next 提交中放入什么。

一旦一切就绪,Git 设置特殊名称 HEAD 以包含 分支名称 。 (在最初的 Git 中,这是 refs/heads/main 文件的符号 link,但与 Git 的许多位一样,它在十多年前就被废除了。 )

现在有一组well-defined,carefully-coordinated数据:

  • HEAD包含当前分支名称;
  • 分支名称包含哈希ID;
  • Git 的索引包含 next 提交的文件名和 blob 哈希 ID,跟踪工作树中的文件;和
  • 工作树包含从提交中复制的文件。

你处理文件,运行 git add 告诉 Git 更新 Git 索引中的内容,最终 运行 git commit。在此刻Git:

  • 读取 HEAD,然后读取分支名称以查找当前提交;
  • 收集所有必要的元数据;
  • 打包索引的内容;
  • 将所有这些写入 new 提交,这将获得一个新的唯一哈希 ID;和
  • 将哈希 ID 写入分支名称。

carefully-coordinated 数据让 Git 完成所有这些,并且仍然是 carefully-coordinated。

如果其他人在您工作时做出承诺...

现在假设我们在服务器上工作,客户端 上有人提交。这不是问题,因为客户端 Git 存储库有 自己的分支名称 。它在 its 提交数据库中获得一个新的提交,并且 its 分支名称 main 存储一个新的哈希 ID。但是在 server 上, 我们的 Git 数据库没有变化。

但是如果他们现在 运行 git push main 发送他们的提交 ,我们的 Git 必须接受他们的提交或拒绝它。如果我们拒绝它,那很好:我们的数据库保持不变,一切仍然协调一致。

让我们说,不过,我们接受推送。服务器 Git 更新 refs/heads/main 以存储 他们的 提交哈希 ID。我们的两种可能性是:

  1. 不要更新索引和工作树;
  2. 更新索引和工作树。

如果我们选择可能性#1,那么我们有一个“过时的签出”:我们的文件来自之前的提交。但是 分支名称 持有新的提交哈希 ID。所以我们不同步。如果我们更新任何文件然后提交,我们将恢复其他人的工作(请记住我们的Git 软件使用索引中的内容,这与我们的工作树相匹配)。这不太好,所以让我们继续选项 2。

如果我们选择选项 2,我们的文件就会被撕掉并被替换。我们的索引和工作树 re-synchronized 具有更新的分支名称。那更好... 除了,如果我们积极地处理某些文件,我们的工作会怎样?也许我们的编辑注意到底层文件已经改变,给了我们修复问题的机会。也许它只是覆盖了底层文件。无论哪种方式,它都可能是一个问题。

因此,更新服务器存储库的工作树 可能比不更新更好,这就是 receive.denyCurrentBranchupdateInstead 设置所做的.不过,它并不完美。 “完美”只是没有工作树,这样就不会出错,我们通过--bare.

得到了它