什么是裸仓库,为什么我需要它?

30
这个问题可能已经有答案了,但我没有找到一个好的回答。
我来自集中式仓库,例如SVN,通常你只执行检出、更新、提交、还原、合并等操作,而不会做更多的事情。
Git让我发疯了。有大量的命令,但最难理解的是为什么很多东西能够像它们一样工作。
根据 "什么是裸的git仓库?"
使用 git init --bare 创建的仓库称为裸仓库。与工作目录不同,它们的结构略有不同。首先,它们不包含源文件的工作副本或检出副本。
...
使用 git init --bare 创建的裸仓库是用于共享的。...开发人员将克隆共享的裸仓库,在其仓库的工作副本中进行本地更改,然后推回共享的裸仓库,以使其更改对其他用户可用。
- Jon Saints, http://www.saintsjd.com/2011/01/what-is-a-bare-git-repository/ 但是,从"what's the difference between github repository and git bare repository?"的接受答案来看:
GitHub 上的 Git 仓库是裸仓库,就像您要推送到其中的任何远程仓库一样。
- VonC, https://dev59.com/u2Ei5IYBdhLWcg3w6PrZ#20855207 然而,在GitHub上有源文件,我可以看到它们。如果我创建一个裸仓库,就没有源文件了,只有工作仓库的.git目录的内容。
这怎么可能?我哪里理解错了?
你能举个例子说明为什么我需要一个裸仓库以及它的工作方式的动机吗?
更新
Edward Thomson的答案部分回答了我的问题。尽管如此,我会重新表达我的问题:
我发布的第一个链接声明("What is a bare git repository?"):
它们[裸仓库]不包含源文件的工作副本或检查副本。
VonC的答案:
GitHub上的Git仓库是裸的。
这两个陈述都意味着:
GitHub没有工作副本。
Edward Thomson说:
它根据你的浏览行为基于数据渲染网页,直接从仓库中提取数据并输出到你的浏览器,而不是先将其写入文件服务器上的磁盘。裸仓库必须包含所有数据和源代码,否则无法渲染任何内容,因为我可以看到所有更新的源代码(提交),所有分支(及其相应的源代码),整个仓库的日志等。是否总是在.git目录(或裸仓库)中保留整个仓库的所有数据,以某种格式能够随时呈现所有文件?这就是裸仓库的原因,而工作副本只有给定时间点的文件。

您提供的链接 https://dev59.com/u2Ei5IYBdhLWcg3w6PrZ#20855207 直接回答了您的问题。 - Uku Loskit
一个裸的 Git 仓库更像是你在服务器上的 SVN 仓库(共享、集中式),而一个普通的 Git 仓库更像是你的 SVN 工作副本。 - crashmstr
@larsks 不明白你为什么标记为重复。我已经发布了你提到的链接,但它并没有回答我提出的问题。 - Albert
@UkuLoskit,不是这样的。它说在 github 中的存储库是裸库(bare),但它包含了所有源代码,这与“它们不包含您的源文件的任何工作副本或检出副本”的说法相矛盾。 - Albert
1
@Albert,没有签出或工作副本。GitHub没有您的存储库的工作目录,它根据您浏览时的数据呈现网页-直接从存储库中提取数据并将其传输到您的Web浏览器,而不是首先将其写入文件服务器上的磁盘。 - Edward Thomson
显示剩余3条评论
2个回答

16
一个仓库的全部数据是否总是在 .git 目录(或裸仓库)中以某种格式存储,能够随时呈现所有文件?
是的,这些文件及其完整历史记录都存储在 .git/packed-refs.git/refs 以及 .git/objects 中。
当您克隆一个仓库(裸或非裸)时,您始终会有带有其 Git 管理和控制文件的 .git 文件夹(或按命名约定带有 .git 扩展名的文件夹,用于裸仓库)。(请参阅术语表
Git 可以使用 git unpack-objects 随时解包它所需的内容。
诀窍是:
从一个裸仓库,你可以查询日志(在 git 裸仓库中使用 git log 就可以了:不需要工作树),或者列出裸仓库中的文件
或者显示裸仓库中文件的内容
这就是 GitHub 如何能够呈现一个页面的文件,而无需检出整个仓库。
我不知道 GitHub 是否确切地这样做,因为大量的仓库数量迫使GitHub 工程团队进行各种优化。
例如,请参见他们如何优化克隆/获取仓库
使用DGit,这些裸仓库实际上被复制到多个服务器上。

这是裸仓库的原因,而工作副本只有给定时间的文件吗?

对于GitHub来说,维护一个工作树需要太多的磁盘空间,并且在更新时(当每个用户请求不同的分支时)成本太高。最好从唯一的裸仓库中提取所需内容以呈现页面。

通常情况下(在GitHub的限制之外),裸仓库用于推送,以避免工作树与刚刚推送的内容不同步。请参见“但我为什么需要一个裸仓库?”以获取具体示例。

话虽如此:

但对于GitHub来说,这是不可能的,因为它无法为存储的每个仓库维护一个(或服务器)工作树。


文章 "使用裸的 Git 仓库来进行我的 dot files 的版本控制" 来自 Greg Owen ,原作者是 报道人员 aifusenno1,补充如下:

裸仓库是一个没有快照的 Git 仓库。
它只存储历史记录。它还以稍微不同的方式存储历史记录(直接在项目根目录),但这并不那么重要。

裸仓库仍然会存储您的文件(请记住,历史记录有足够的数据来重建任何提交时的文件状态)。
您甚至可以从裸仓库创建非裸仓库:如果您从一个裸仓库进行 git clone,Git 将自动为您在新仓库中创建一个快照(如果您想要一个裸仓库,请使用 git clone --bare)。

Greg 补充道:

为什么我们要使用裸的Git存储库?
几乎所有我找到的关于裸存储库的解释都提到了它们用于集中存储想要在多个用户之间共享的存储库。
请参见Git存储库布局
一个<project>.git目录,它是一个裸存储库(即没有自己的工作树),通常用于通过将其推送和从中获取来与他人交换历史记录。
基本上,如果你想要编写自己的 GitHub/GitLab/BitBucket,你的集中式服务将把每个 repo 存储为裸仓库。
但是为什么呢?没有快照如何与共享相连呢?
答案是,如果唯一与你的 repo 交互的服务是 Git,那么就不需要快照了。
基本上,快照只是为人类和非 Git 工具提供方便,但 Git 只与历史记录交互。你的集中式 Git 托管服务只会通过 Git 命令与 repos 进行交互,所以为什么要一直生成快照呢?快照只会占用额外的空间而没有任何好处
GitHub 在访问该页面时即时生成快照,而不是永久存储在 repo 中(这意味着 GitHub 只需要在你请求时生成快照,而不是每次有人推送更改时都更新一个)。
从2022年第三季度开始,Git 2.38引入了一个名为safe.bareRepository的配置变量,允许用户禁止发现裸仓库。

请查看提交 8d1a744, 提交 6061601, 提交 5b3c650, 提交 779ea93, 提交 5f5af37 (2022年7月14日) 由Glen Choo (chooglen)提交。
(于提交 18bbc79中被Junio C Hamano -- gitster --合并,日期为2022年7月22日)

setup.c:创建safe.bareRepository

签名作者:Glen Choo

已知一种社会工程攻击利用了现有的工作树可能包括整个裸库,包括配置文件这一事实。用户可能在裸库内运行Git命令,认为将使用“外部”存储库的配置文件,但实际上使用的是裸库的配置文件(由攻击者控制),这可能导致任意代码执行。详细描述和深入讨论请参见this thread
一个简单的缓解方法是禁止裸库,除非通过--git-dirGIT_DIR指定。在不使用裸库的环境下,这将带来最小的干扰。
创建一个配置变量safe.bareRepository,告诉Git是否在使用裸库时die()。此配置是一个枚举:
- “all”:允许所有裸库(默认值)。 - “explicit”:仅允许通过--git-dirGIT_DIR指定的裸库。
如果我们想默认保护用户免受此类攻击,则两个值都不足够,“all”提供不了保护,但“explicit”对于裸库用户来说不切实际。更可用的默认值将仅允许非嵌入式裸库(this thread包含一个这样的建议),但检测存储库是否嵌入可能是不可行的,因此这项工作未在此系列中实现。

git config现在在其手册页面中包含了:

safe.bareRepository

指定 Git 将使用哪些裸仓库。目前支持的值有:

  • all:Git 使用所有的裸仓库。这是默认设置。
  • explicit:Git 仅使用通过顶级 --git-dir 命令行选项或 GIT_DIR 环境变量指定的裸仓库。

如果您的工作流程中不使用裸仓库,则将 safe.bareRepository 设置为 explicit 可能会有益。这将保护您免受涉及克隆包含裸仓库并在该目录中运行 Git 命令的存储库的攻击。

此配置设置仅在受保护的配置请参阅定义)中受到尊重。这可以防止不受信任的存储库篡改此值。


在 Git 2.41 (Q2 2023) 中,跟踪机制学会了注意并报告自动发现的裸仓库是否正在使用,因为允许这样做而没有明确说明用户打算这样做(例如通过设置 GIT_DIR)可以被用作社交工程攻击向量。

请参见 commit e35f202(2023年5月1日),作者是 Glen Choo (chooglen)
(由 Junio C Hamano -- gitster --commit fa88934 中合并,2023年5月15日)

setup:跟踪裸仓库的设置

签名作者:Glen Choo
签名作者:Josh Steadmon

safe.bareRepository=explicit 是更安全的默认操作模式,因为它可以防范 裸仓库攻击
大多数终端用户不直接使用裸仓库,因此他们应该能够设置 safe.bareRepository=explicit,并期望通过指定 GIT_DIR--git-dir 来重新启用裸仓库。

但是,用户可能会使用一个在没有设置 GIT_DIR 的情况下调用 Git 的工具(例如 "go mod" 将克隆裸仓库,请参见 go.dev/ref/mod),因此即使用户想要使用 safe.bareRepository=explicit,除非他们的工具学会设置 GIT_DIR,否则这是不可行的。

为了使这个过渡更容易,添加一个跟踪消息来记录我们在没有设置 GIT_DIR 的情况下尝试设置裸仓库时的情况。
这使得用户和工具开发人员可以审核哪些工具有问题,并报告/修复问题。
当他们足够自信时,他们将转换到 "safe.bareRepository=explicit"。

请注意,这使用了 trace2_data_string(),它不受 "normal" GIT_TRACE2 目标的支持,只有 _EVENT_PERF


有很多的阅读材料...但我相信你提供的所有这些链接都会让我明白git是如何工作的,因为它比我想象中的分布式版本系统要不同得多。非常感谢。 - Albert
@Albert 几年前,当我开始学习Git时,我发现这篇成功的Git分支模型文章非常有用。 - Paul Rougieux

4

我需要它吗?

VonC的答案中的链接"但是为什么需要一个裸仓库(bare repo)?",可以看到两个最近发现的用例。

第一个用例是我认为必须知道的,而第二个则可能受到批评。

A - 同步家目录的点文件

不再使用指向git存储库的符号链接。只需使用:

git init --bare $HOME/.myconf
alias config='/usr/bin/git --git-dir=$HOME/.myconf/ --work-tree=$HOME'
config config status.showUntrackedFiles no

我在 ~/.myconf 目录下创建了一个 Git 裸仓库,这样就可以使用普通命令对主目录中的任何文件进行版本控制,例如:

    config status
    config add .vimrc
    config commit -m "Add vimrc"
    config add .config/redshift.conf
    config commit -m "Add redshift config"
    config push

B - 在云同步文件夹中托管Git项目

创建一个嵌套的git仓库会带来很多问题,使用上述方法则可以在云同步文件夹外使用一个裸库,既可以做版本控制,又可以享受云同步的便利,非常实用。

其中一个主要的好处就是可以避免嵌套的git仓库。更多细节请见源链接


你能详细说明一下B点吗?如何将你的工作副本(在云上)链接到裸的git仓库?你仍然需要在云上有一些git仓库,这样你才能推送到裸仓库...我错过了什么? - Double_A
@Double_A 这个技术和A点是一样的。你有一个同步的文件夹(例如Nextcloud/my-repo)和一个位于Nextcloud目录之外的裸仓库。尝试理解并使用A点的技术,你可以阅读源代码 - pietrodito

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接