无需代码托管平台玩转 Git

[Simon Tatham, 2025-03-05]

简介

我一生中编写了很多自由软件。 大部分都是从头开始的:我自己启动的项目。 因此,我可以选择在哪里托管它们 - 或者更确切地说,我 必须 选择在哪里托管它们。 现在,我所有的项目都保存在 Git 中。 而且,我通常将它们放在我个人网站上的“裸” git 仓库中。 我没有使用任何构建在 Git 之上的 git "代码托管平台" 系统,例如 Gitlab 或 Github,它们会自动为每个项目创建一个错误跟踪数据库,并为用户提供一个方便的按钮来打开合并请求/pull request。 我只是使用纯 Git。 人们可以 'git clone' 我的代码,并且有一个基于 Web 的浏览界面(基本的 gitweb)可以浏览,而无需完全克隆它。 但这就是您获得的全部自动化工具。 偶尔这会让人们感到困惑,所以我认为我应该写一些关于它的东西。

本文的目的

有时人们根本无法弄清楚如何向我发送 patch。 或者他们可以想到几种方法,但不确定哪种方法最好。 因此,本文的目的之一是公开声明我自己的偏好,当人们问到这个问题时,我可以链接到它。 但这也是对 为什么 我不使用像 Gitlab 或 Github 这样的“代码托管平台”风格系统的思考。 人们有时也会问我这个问题——“你为什么不做别人都在做的事情?” 或者类似的话。

如何与裸 Git 仓库交互

习惯于 git 代码托管平台的人会寻找“pull request”按钮。 当他们没有找到它时,他们有时会感到困惑。 如果您在网站上找不到提交 patch 的按钮,您该如何将您的 patch 发送给维护者? 您向作者发送电子邮件。 在电子邮件中,您放入以下内容之一:

  1. 指向您自己仓库克隆的 URL,其中包含您在“上游”代码之上的 patch。
  2. 实际的 patch,以某种电子邮件附件的形式。

这两种方法都有效。 对于选项 2,有很多方法可以详细说明它,并且所有 这些 方法也有效。 它也不必通过电子邮件。 将此数据发送给维护者的任何方法都可以。 例如,我在 Mastodon 上 - 所以如果您真的想这样做,您可以通过 Mastodon 向我发送仓库 URL(前提是您不介意我的回复非常简短)。 或者,如果您和维护者都在其他任何通信媒介上,并且允许您将文件附加到消息中,则可以通过这些媒介发送 patch。

我特别喜欢哪种方式?

但有些人不仅仅想知道发送 patch 的 任何 方式。 他们想知道哪个是 最好 的方式,或者至少是我喜欢的方式。 所以这是我自己的列表,按降序排列:最喜欢的在顶部,最不喜欢的在底部。

最佳:Git 仓库的 URL + 分支名称

这是我 绝对 喜欢的接收 patch 的方式。 如果您可以在 Internet 上的任何位置放置我的 git 仓库的克隆,其中包含一些额外的 patch,那么最好的事情就是这样做,并向我发送一封电子邮件,内容如下:

我有一些 [项目] 的 patch,用于 [进行一些更改]。 您可以在此处名为 [任何名称] 的分支中找到它们:[URL] 该 URL 可以是任何方便的东西,只要它是我可以提供给 git clone 的东西,或者是一个人类可读的网页,其中包含 我可以提供给 git clone 的东西。 它可以是来自 git 代码托管平台页面的任何内容(仅仅因为 没有在 Github 上托管我的代码并不意味着您不能将您打过 patch 的版本放在那里供我查看),也可以是您上传了仓库并运行 git update-server-info 的静态站点。 当您真正做到这一点时,这正是 git 代码托管平台系统中正式的“pull request”或“merge request” 的内容。 这就是为什么您首先 fork(即克隆)目标仓库并将您的更改放入您的 fork 的一个分支中。 代码托管平台中的正式 PR 按钮是一种一键执行此操作的方式,但包含所有相同信息的简短电子邮件同样好。 为什么我喜欢它:这是我最喜欢的接收 patch 的方式,因为 patch 本身不会通过我的电子邮件。 这从长远来看为我节省了空间(我保留我所有的电子邮件),并且节省了我摆弄移动附件的时间(我在与我开发的机器不同的机器上阅读电子邮件)。 我所需要的只是将电子邮件中的 URL 粘贴到我的 git 命令行中,然后我就可以以一种我可以查看、审核并可能合并的形式获得 patch。 此外,如果我发表审核意见并且您想更新 patch,如果您不必发送一整套更新的 patch 文件,而是推送修改后的分支版本并仅发送另一封电子邮件,则可以 再次 节省空间,内容如下: 好的,我已经处理了那些审核意见。 新的 patch 与以前在同一个地方。

增量 Git bundle

Git bundle 似乎不是很广为人知。 我认为这很遗憾,因为它们太棒了。 最简单的 git bundle 类型——完整 的 bundle——是一个完整的 git 仓库,包装成一个单独的文件。 它包含 git 对象的集合,以及引用的集合(通常是分支头)。 您可以通过与访问实际 git 远程仓库相同的任何方法来访问它,方法是将文件名传递给 git fetchgit pull,或者可以从 git ls-remote 开始,以查看 bundle 文件中存在哪些分支并确定要提取哪个分支。 您唯一不能有效地做的是修改它。 如果您希望 git bundle 包含不同的内容,您只需从头开始制作一个新的。 但是 bundle 也可以是 增量 的,这意味着缺少一些对象,因为它期望 bundle 接收者已经拥有这些对象。 这正是您在针对现有仓库发送 patch 时所需要的:您知道接收者拥有您从原始仓库获得的所有对象,并且您只需要发送新对象。 所以,假设您已经针对我的一个仓库的 main 分支准备了一系列提交(或只是一个提交)。 然后您可以执行以下操作:

git bundle create fix-weasel-rotator.bundle origin/main..HEAD

这将创建一个名为“fix-weasel-rotator.bundle”的文件(仅示例名称!),其中包含您在 origin/main(我的上游分支)之上进行的所有额外提交。 现在您可以将该文件作为附件发送。 为什么我喜欢它:如果我完全要以电子邮件附件的形式接收 patch,这是我最喜欢的方式,原因有很多。 首先,它是 一个 文件,无论您在其中放置多少 patch。 这意味着我可以一次性将其下载到我的开发机器上,而不必放牧一整套较小的文件。 其次,git bundle 很小:明显小于相应的文本 patch 文件。 (它们使用与 git 的 packed object 格式相同的压缩。) 第三,git bundle 是 二进制 的:压缩将它们变成完全无法理解的二进制废话。 这听起来 像是一件好事,但它确实让电子邮件客户端有最大的机会完全不变地传输它们,而无需尝试变得聪明(字符集转换,“有帮助地”重新包装长行)。 也许最重要的是,git bundle 中的提交被详细描述:我可以查看您针对哪个基本提交准备了它们,因为它们带有它们的父链接。 因此,如果我需要针对不同的父级重新应用 patch,我知道我从哪个父级开始,以及从那时起发生了什么变化。 这有助于我正确地 rebase patch。 事实上,patch 已经以 git 提交的形式存在,我可以使用 git rebase,并获得其非常好的冲突处理(优于 git am),这也很有帮助。

来自 git format-patch 的一组 patch 文件

这似乎是人们实际选择的最流行的选项:运行 'git format-patch' 以生成一系列文本 patch 文件,每个提交一个,名称以 0001、0002、0003 等开头,以便接收者可以看到它们的顺序。 然后将该批次作为一堆单独的电子邮件附件发送,或者(很少)在像 zip 文件这样的单个容器中发送。 我认为这不如 git bundle 好,原因有以下几个: 多个文件需要放牧。 当我收到一封带有五个 patch 附件的电子邮件时,我有五个文件需要复制而不是一个,文件名又长又笨拙。 文本容易受到攻击。 因为 patch 是文本文件,所以至少有 一些 机会 MUA 在传输过程中对它们做了一些“有帮助的”(实际上没有帮助)的事情。 更难处理冲突。 以这种格式应用 patch 的 git am 命令不会以我最喜欢的方式处理冲突,即应用有效的 patch 部分并在出现问题的地方留下文件内冲突标记。 此外,由于 git format-patch 没有提及 patch 确实 应用于哪个提交,因此在尝试应用它们时我更有可能首先遇到冲突。 因此,由于所有这些原因,我更喜欢引用其父提交的单个二进制 git bundle,而不是少数几个文本 patch 文件。 但是这些缺点 通常 不会导致问题:format-patch 方法通常运行良好,如果这是发送者满意的方式,我不会花任何时间试图说服他们以不同的方式做事。

git diff 生成的裸 diff 文件

普通的 git diff 具有 git format-patch 的所有相同缺点,外加一个额外的缺点:它不包含提交元数据:作者身份和提交消息。 如果您向我发送一个普通的 git diff,我必须自己编写提交消息。 这要么意味着充分理解您的 patch,以便了解您的 意图 是什么(可能与您实际所做的不同!),要么从您随 patch 发送的电子邮件中复制文本。 我的 一般建议 是,通过电子邮件提交 patch 时,如果您对为什么 patch 是可取的或安全的,或者两者兼而有之,有一个解释,最好将其放入提交消息中,以便为以后阅读 git 历史记录的人员保留它。 所以您不妨首先将其放在那里,然后我就不必移动它了!

最差:由 git send-email 生成的一系列独立电子邮件

哦,拜托,别这样。 我真的不喜欢接收 git send-email 输出。 如果您可以 可能 以任何其他方式执行此操作,请执行。 为什么我不喜欢它:因为 patch 系列被分成多封电子邮件,它们以随机顺序到达我的收件箱,然后我必须将它们一个一个地保存到文件中,然后手动将这些文件按主题行排序回正确的顺序。 使用 git format-patch,这些文件作为同一封电子邮件的附件到达,因此我可以一次性保存它们,然后它们的名称使我可以轻松地对它们进行排序。 git send-email 没有这两个优点。

为什么我不使用 Git 代码托管平台?

我答应过我也会谈谈我为什么做出这个选择。 如今,大多数人都喜欢 git 代码托管平台:为什么我不喜欢呢?

信任

对我来说,在决定在哪里托管我的代码时,首先要考虑的问题不是它提供了什么工具,而是谁在运行它。 我希望我的代码不要受制于我不信任的人。 我并不是说我对负责主要 Git 代码托管平台网站的组织有任何特别的 信任。 但我不亲自认识他们,我更喜欢信任我信任的人。 因此,我的 git 托管安排住在由朋友运行的服务器上,而不是由公司运行的服务器上。 这是否过分偏执? 我不这么认为。 也许会,如果我所有的项目都是低风险的 - 不处理重要的秘密,并且用户很少,因此它们不是任何人攻击的有吸引力的目标。 但我维护一个安全项目,而且我的一些东西已经变得非常流行。 即使是像电子游戏这样完全无聊的东西,如果它安装在很多机器上,也可能成为一个有吸引力的目标。 诚然,在 Git 出现之前,这是一个更严重的问题:Git 的提交哈希系统的性质是,很难在不让已经拥有它的每个人都注意到的情况下,悄悄地将仓库的内容更改为恶意内容。 在 Subversion 时代,如果您拥有管理员访问权限,则更容易悄悄地破解仓库的内容。 但是“困难”并非 不可能,因此仍然值得谨慎对待。 信任一家公司也是危险的,因为管理层会发生变化:即使您信任 现在 的负责人,他们明年可能就不负责了,而负责人可能是完全不同的人。 现在吸引用户到 Github 的相同设施在几十年前也会吸引人们到 Sourceforge - 而 Sourceforge 现在名声很差。

重量级

当然,使用著名的代码托管平台 网站 和使用代码托管平台 软件 并不是同一回事。 如果我不想在 gitlab.com 上托管我的代码,我仍然可以安排在我自己的控制下运行我自己的 Gitlab 软件 实例,并使用它。 根据我所听到的一切,这比托管普通的 git 仓库要付出更多的努力。 我认为总体便利性的提高不值得我花大量精力来运行这样的东西。 这会占用我宁愿花在实际代码上的时间。

账户管理

我特别不喜欢 git 代码托管平台网站的一件事是它们让您创建帐户的方式。 即使要 报告 针对其他人项目的 bug - 更不用说发送 patch - 如果它托管在一些我以前没有使用过的 Gitlab 实例中,我必须在该实例上创建一个帐户,因为在我这样做之前,我根本无法与系统交互。 并且不仅仅是 我以前没有使用过的 实例:至少有一些 Gitlab 实例会删除旧帐户,因此即使我 之前 与项目交互过,当我在几年后在同一软件中发现另一个 bug 时,我可能仍然必须创建一个新帐户。 创建帐户是 一件坏事。 它们中的每一个都是您在密码管理器中要跟踪的额外内容; 如果网站坚持要求,则需要设置某种 2FA; 定期需要花费精力(比如网站通知您他们已经受到威胁并且您需要重新确认某些内容); 彻头彻尾的风险(比如 诈骗者 假装是该网站向您发送该类型的欺诈性通知); 要跟踪的在线身份的额外方面。 每一个都是单独的,但它们加起来,并且管理大量的帐户很烦人。 我不喜欢自己做这件事,我也不想把它强加给别人!

你会被强加一种工作流程

Git 代码托管平台网站带有一堆超出普通 git 仓库的内容。 这就是他们的全部意义。 特别是,您的项目会自动获得一个 bug 跟踪器 - 并且您无法选择使用哪个 bug 跟踪器,或者它的外观。 如果您使用 Gitlab,您将使用 Gitlab bug 跟踪器。 pull request/merge request 系统也是如此。 当我开始使用 Git 时,PuTTY 已经 一个 bug 跟踪器。 一个非常简单的 bug 跟踪器 - 不仅仅是一组文本文件和一个将它们变成一组网页的脚本 - 但它就在那里,并且它以我们熟悉的方式与我们的源代码控制、发布和网站集成。 为了支持与托管系统相关的并且行为方式与我们精心选择的方式不同的东西而将其丢弃? 不,谢谢。 更一般地说,我不希望关于我的开发工作流程的这种决定是某些不相关的事情的结果,例如我正在使用什么版本控制。 我想 首先 决定如何处理 patch 和 bug 报告,然后决定哪种软件最能满足这些需求 - 而不是相反。

纯粹的惯性

我不希望任何人认为我在 隐瞒 这个原因,或者自欺欺人地认为它不是我的动机的一部分,所以我应该确保大声说出来。 我不使用代码托管平台的一个原因仅仅是因为我没有 一开始 就使用代码托管平台,并且将我的所有东西移入一个会很费力。 自从 Git 出现之前,以及代码托管平台本身出现之前,我一直在提供公共源代码仓库。 (好的,不是 完全 在 Git 之前的 Sourceforge 实际成立之前,而是在它广为人知之前。)在 Git 之前,我的东西托管在特定 Linux 机器上的 SVN 中; 当我将我的东西从 SVN 移动到 Git 时,尽可能少地更改的路径是在同一台 Linux 机器上以 Git 托管它。 当然,这本身并不是一个好理由。 这没关系 - 这不是我的 唯一 理由。 但我不能否认它是我的 其中一个 理由。 改变是需要付出努力的,并且应该带来足够的好处才能值得付出努力。

特别提及:尤其不是 Github

由于所有这些原因,我真的不想使用 任何 git 代码托管平台。 但我 特别 不想使用 Github。 最大的原因是它本身不是自由软件。 如果您想通过将其移动到同一系统的另一个实例(甚至设置您自己的实例)来重新控制您的项目,您可以使用 Gitlab 来完成此操作 - 将您所有的 bug 记录与 git 仓库一起迁移 - 但不能使用 Github。 您被锁定了。 另外,我也可以出来说:我不希望使用 Github 的一个原因是 因为 它是托管您的代码最受欢迎的地方。 几乎不可能拥有 任何东西 的单一文化,而由一家公司控制的单一文化尤其危险。 我宁愿为互联网的分布式做出贡献,也不愿为互联网的集中化做出贡献。 (当人们要求我为此原因而转移到 Github 时,我特别恼火。“每个人都在 Github 上,跟上进度!符合!” 我实际上不太可能这样做 因为 你说了这句话。呃。) 当然,如今 Github 归微软所有,并且有很多人不 100% 信任微软。 我不能声称完全不受此影响,但我 已经 在它被收购之前不想使用 Github 了,所以这不是我的唯一理由。 甚至不是我的主要原因。

我应该开始使用 Git 代码托管平台吗?

有时我会收到来自希望我开始以“正常”方式做事的人的电子邮件。 这 并不总是 仅仅是因为我的方式不是他们已经习惯的方式。 他们中的一些人有更有趣和周到的理由。 我收到的关于这些方面的最有趣的评论是,代码托管平台将您与贡献者的所有互动都公开了。 如果有人正在考虑为您的代码库做出贡献,他们可以查看过去的 MR/PR 并查看发生了什么:似乎是否有一个活跃的社区,开发人员是否及时响应贡献,审核似乎是否具有建设性,是否存在任何令人讨厌或有毒的行为。 然后,如果它看起来不热情或没有帮助,则可能会决定不要浪费时间在该项目上。 通过我的方法,每次对贡献的讨论都发生在私人电子邮件中,因此确实,做出贡献的过程的公开性要低得多。 我可能会对一位贡献者非常粗鲁,而其他任何人都不一定能发现它(除非假设的受害者在他们的博客上咆哮或类似的事情)。 我可能需要很长时间才能做任何事情(出于任何原因 - 懒惰或超载或介于两者之间的任何事情)。 我可以向每位贡献者谎报其他贡献者正在做什么(尽管我无法想到有任何理由它甚至符合我自己的利益!) 因此,我接受以我的方式做事存在缺点,也存在优点。 代码托管平台并非完全浪费时间。 我只是尚未确信代码托管平台的优势超过了劣势。 但我对如何获得两全其美的方法更感兴趣。 如果有一个系统允许贡献及其审核和讨论公开进行,使用比 Gitlab 轻量级且更易于运行的软件,允许贡献而无需任何人创建和管理另一个帐户,具有高度可配置的工作流程管理形式(如果有的话),并且在讨论论坛层和实际的 git 仓库之间有严格的分离,以便妥协不会允许将恶意软件注入目标项目,我将有兴趣查看它! (当然,只是偶尔的贡献 确实 必须通过私人通信进行,例如,因为它是针对尚未公开的安全问题的修复。 如果有必要,或者只是如果那是他们喜欢的,我当然不希望 阻止 人们私下贡献。)