介绍 .discourse-compatibility:为旧版 Discourse 固定的插件/主题版本

大家好 :wave:,我刚合并了一个新功能,该功能将帮助插件和主题在安装到较旧版本的 Discourse 实例时固定特定版本。

现在,您可以在插件或主题仓库的根目录下包含一个 .discourse-compatibility 文件,用于指定在较旧版本的 Discourse 上安装时应检出的版本。


设计理由

记住哪些插件和主题与哪些 Discourse 版本兼容是一件令人头疼的事。作为管理员,应该能够轻松扫描这些变更,找到适合您 Discourse 安装的版本,而无需阅读插件的提交历史。作为插件或主题的作者,应该能够在进行向后不兼容的更改时管理安装版本,从而避免破坏旧版本的安装。

Discourse 软件更新推出得非常快,这虽然很棒,但也使得维护包含大量插件的 Discourse 实例变得非常困难,尤其是当您遵循其他发布节奏/版本(如当前稳定版)时。我的计划是建立一个生态系统,简化那些遵循稳定版或其他发布节奏的用户的更新过程,并为网站管理员提供一种快速自动获取与其目标 Discourse 版本兼容的插件版本的方法。

原始公告(现已由上方链接的文档取代)

实现方式

首先需要注意的是:我们依赖 Discourse 核心版本的标签,因为 Discourse 的测试版发布频率足够高,我们可以针对它们固定插件版本。针对 Git 哈希进行固定由于多种原因而非常棘手,因此我们可以使用 git describe 来获取最接近的测试版/稳定版标签。

在插件或主题中,我们现在支持一个名为 .discourse-compatibility 的版本兼容性文件,位于根目录下。该文件是一个降序排列(较新的 Discourse 版本在前)的列表,指定了兼容性映射。

示例

2.5.0.beta2: git-hash-1234e5f5d
2.4.4.beta6: 4444ffff33dd
2.4.2.beta1: named-git-tag-or-branch

对于每个插件/主题,升级或重建将继续检出较晚的命名提交/分支/标签,直到找到等于或晚于当前 Discourse 版本的条目。
例如,对于上述版本文件,如果当前 Discourse 版本为 2.4.6.beta12,它将扫描该文件并选择 2.5.0.beta2 的条目。

如果当前 Discourse 版本为 2.4.4.beta6,它将选择匹配的 2.4.4.beta6 条目。

如果不存在较晚的版本,则保持当前检出的版本不变。
例如,对于 2.5.0.beta3,不会发生固定。

如果不存在较早的版本,则检出版本文件中列出的最早版本。
例如,对于 2.2.1.beta22,它将检出给定“版本”中最早可能的条目,即 2.4.2.beta1 的条目。


此处的目标是减轻未来维护非严格基于“测试通过”的替代部署的痛苦,并为管理员提供在何时何地升级的灵活性。我们通过允许插件和主题作者在不影响旧版本 Discourse 安装的情况下开发向后不兼容的更改来实现这一点。

50 个赞

这是一个很棒的功能,谢谢 :slight_smile:

我喜欢这个功能的方向性,也就是说,它使得可以在插件内部直接管理这个问题,而无需站点管理员进行任何操作。

我有几个初步问题:

  • 现有的 required_version 插件元数据检查在插件激活时是否仍然保留?您如何看待它与本功能的相互关系(如果有的话)?

  • 我看到目前这是以 Rake 任务的形式添加的。它与 discourse_docker(即启动器)和 docker_manager 有什么关系?预期的用途是什么?我看到您在两个仓库中都做了修改,能否解释一下它在这两种环境中的预期工作方式?

12 个赞

是的,这正是我们的构想——让插件作者能够添加向后兼容性,这样管理员就不必为此担心了!

目前没有任何计划更改或移除 required_version 插件元数据。它们虽然相关,但在我脑海中仍是两个独立的概念:required_version 的最小/最大版本号限制会通过抛出错误来阻止插件安装,并且是在此兼容性拉取操作之后加载的。如果你想防止极其古老的 Discourse 实例使用你的插件,我建议仍然为第一个最低版本包含 required_version;再多的兼容性排查也无法解决那种情况 :wink:

在正常使用中,应该不需要任何配置更改,系统会自动检测变化。Rake 任务在 discourse_docker 中于插件克隆后调用,因此在启动器中的顺序如下:

  • 克隆插件
  • 检查兼容性并检出相应版本(如适用)
  • 迁移

docker_manager 中,较旧版本的 Discourse 将能够更新插件至兼容版本。此处的 UI 保持不变,但现在“已是最新”的插件状态是根据兼容性文件来判断的。

简而言之,如果你想知道是否需要操作,那么无论哪种使用场景,都不需要进行任何更改即可开始利用此功能。

底层实现上,它使用 git show HEAD@{upstream}:.discourse-compatibility 读取最新的兼容性文件,并使用 git reset --hard #{checkout_version} 检出正确的版本。这样我们既能利用最新的兼容性信息,又能确保旧版 Discourse 不会卡在旧版(可能无效)的兼容性文件上。

11 个赞

太好了,谢谢你的解释。

于是我给自己倒了一杯 :wine_glass:,然后尝试使用了 Custom Wizard Plugin

我按逆序逐个检查了每个标签,从 v2.6.0.beta1 开始。我发现以下 git 命令很有帮助:

git tag --list \\ 例如:git tag --list 'v2.5.0*'
git checkout tags/tag \\ 例如:git checkout tags/v2.5.0.beta7

没过多久,我就找到了一个与当前插件版本不兼容的标签:v2.5.0.beta7 中不包含 discourse/app/components/d-textarea,而自定义向导插件试图导入该文件。

于是,我找到了插件中添加该导入的 提交记录,获取了 前一个提交 的 sha1 值,检出该提交并进行了测试(运行正常),然后将以下内容添加到 .discourse-compatibility 文件中:

v2.5.0.beta7: 802d74bab2ebe19a106f75275342dc2e9cc6066a

接着,我将该更改推送到 包含最新插件代码的分支(这是一个用于测试的分支,通常并不需要),并使用该插件分支和设置为 v2.5.0.beta7 的 version 重新构建了一个 Docker 化的测试服务器。

但这次失败了。随后我意识到,当然,plugin:pull_compatible_all 这个 rake 任务在 v2.5.0.beta7 中并不存在,因此无法向后兼容(这要怪那杯 :wine_glass:)。果然,在启动器日志中我看到了以下错误:

Don't know how to build task 'plugin:pull_compatible_all' (See the list of available tasks with `rake --tasks`)

不过,这是否就是你设想的使用方式的核心要点呢?

关于 required_version 方面,我在此也遇到了问题,因为测试服务器上安装了 discourse-legal-tools 插件,该插件的 required_version 设置为 v2.5.0,因此在 v2.5.0.beta7 上最初就失败了。我打算将该插件迁移到这个新系统中。我仍然认为 required_version 在设定绝对基准方面是有用的,正如你所说。

11 个赞

是的,没错——因为这一功能直到目前才集成到 Discourse 核心中,因此它无法在低于 2.6.0.beta1 的版本上运行(当前情况)。我仍需将其移植到稳定版和最新的测试版,这样我们就能在 2.5.0 上使用它,但我们不打算将其移植到更早的版本。

11 个赞

跟进一下,我已在 Custom Wizard 主仓库中添加了兼容性文件,并将在移植时使用该文件锁定插件版本,包括当前测试版 v2.6.0.beta1 和当前稳定版 v2.5.0。详见:

https://meta.discourse.org/t/custom-wizard-plugin/73345/562?u=angus

5 个赞

这里有一个备注和一个问题。

首先,备注一下:.discourse-compatibility 文件中 Discourse 版本标签的语法应为 2.5.0(如 @featheredtoast 的示例所示),而不是 v2.5.0。如果您使用了 v 前缀,将会收到以下错误:

Malformed version number string v2.6.0.beta1

其次,@featheredtoast 指出 锁定机制现已向后移植到 stable 分支。我之所以错过这一点,是因为我当时查看的是 v2.5.0 标签。

这引发了我一点疑问。Discourse 的分支并不直接等同于 Discourse 的发布标签。大多数运行旧版本 Discourse 的网站可能使用的是某个分支(例如 stable),而不是某个发布版本(例如 v2.5.0)。

如果针对插件的破坏性变更也被添加到了“较旧”的分支(例如 stable)中,这对锁定机制意味着什么?我可能只是还没有完全理解版本控制系统的运作方式。

6 个赞

版本号对应的是应用中定义的 Discourse 版本,而非 Git 标签。

因此,此处的 “2.5.0” 指的是所有声明为 2.5.0 的版本,直到新版本升级至 2.5.1(或 2.6.0.beta1)之前。

如果针对插件的破坏性变更也被添加到稳定分支,那确实也会导致插件失效。版本控制的根本目的,正是为了确保我们不会向该版本引入破坏性变更,因此如有此类情况,请单独提出。我们对稳定分支的意图是仅向后移植安全修复和关键修复(以及在适当情况下的次要功能)。

此举的目的是让我们能够根据旧版 Discourse 计算出旧版插件的可用版本。这绝非万能方案,但它为我们提供了一个可行的平台,前提是我们必须谨慎选择向后移植的内容。

7 个赞

这通常能正常工作,除非你使用 --depth=1 进行克隆。

我很快会实现一个钩子,在初始找不到目标时调用 git fetch --depth 1 {upstream} commit,否则我们将被迫进行部分或完整克隆,这并不理想。我们应该能够获取所需的内容。

编辑:已在此处更新:

我已将此修复反向移植到 Beta 和 Stable 版本——因此现在即使使用浅克隆,我们也应该能够进行固定。

6 个赞

先道个歉,我常在深夜才求助于此,所以不能保证自己 100% 正确,也可能您早已知晓此事。我在此记录, partly 是为了让自己安心,因为这个问题已经让我困扰了好几次。

我认为,如果插件被用于您无法控制的实例(即开源实例),并且该实例按常规方式更新,那么这一机制在 beta 分支上实际上是行不通的。常规更新方式是站点管理员在管理 UI 提示时进行更新。

鉴于以下情况:

以及:

并且 tests-passedbeta 具有相同的 Discourse 版本,但代码不同,例如目前两者都是 2.6.0.beta2

由此可得出:

  1. 要支持 beta 分支,需要将提交锁定到最新的 beta 发布版本,因为使用 beta 分支的站点将采用该版本。

  2. 然而,如果最新 beta 发布版本出现在兼容性文件中,那么运行 tests-passed 的实例也会使用该锁定提交。

这意味着,对于开源插件,您无法同时支持 tests-passedbeta 的标准用法。鉴于大多数安装插件的用户都在 tests-passed 上,您实际上无法通过此方法支持 beta

需要注意的是,这种方法在 stable 分支上是可行的,因为 stable 的 Discourse 版本与 betatests-passed 不同。

4 个赞

对,这件事我确实知道,它要到下一个 beta 版本发布才能真正解决。我们确实也应该想办法区分 beta 版本和测试通过的版本。

我在设置这个功能时,只考虑到了稳定版和异常分支,后来才发现 latest 和 stable 共享了版本号。

6 个赞

感谢您的确认。

看起来实际结果是:如果实例正在运行 tests-passed,则不应执行该过程。由于上述原因,您无法在文件中包含 tests-passed 版本,因此在 tests-passed 上排除该过程不会改变其当前行为。

实现这一点的一种方法是:

def self.find_compatible_resource(version_list, version = ::Discourse::VERSION::STRING)
 
   return if Discourse.git_branch === 'tests-passed'

   ...
end

这样,您就可以在文件中使用最新的 beta 版本,以便为运行 beta 的网站固定插件版本。同时,您仍可以在插件的最新提交中继续支持 tests-passed,即不使用此文件。如果您同意这个方案,我可以提交一个 PR。

另外,我觉得这里根本的问题是:

我相信你们之前已经讨论过这个问题,但能否考虑如下方案:

  • tests-passed:2.6.0.tests-passed,即在 tests-passed 分支上将 PRE 设置为 tests-passed

  • beta:2.6.0.beta2,即保持现状。

@jomaxro 您能帮我理解一下这一点吗?

7 个赞

是的,我支持为预发布版本和正式测试版本添加额外的标记,前提是实施起来足够简单。这将有助于解决当前的根本问题。我见过其他软件项目在发布前将版本号提升到下一个版本(例如,在发布 beta1 后,将“版本”提升至 beta2)。

不过,Discourse 传统上并未这样做,因此具体取决于哪种方法对项目来说最容易采用。

6 个赞

我又遇到了一个问题。

此更改 在 2.6.0beta2 中引入了 $danger-low-mid

这导致 discourse-styleguide 插件 出现错误,因此该插件已更新,并引入了 .discourse-compatibility 文件,以将插件锁定在 Discourse 2.5.0 之前的提交版本

这在 Discourse 2.5.1 上会出错。由于此更改永远不会被反向移植到稳定版,因此 .discourse-compatibility 文件需要在每个新的 2.5.x 稳定版发布时进行更新。

每个插件的每个 .discourse-compatibility 文件都需要在每个新的 2.5.x 稳定版发布时进行更新。

另一种方案是让兼容性文件指向 2.5.999 版本,这样在整个 2.5.x 生命周期内,插件将始终保持在提交 1f86468b2c81b40e97f1bcd16ea4bb780634e2c7。但这在我看来非常取巧。

6 个赞

听起来锁定到 2.6.0beta1 是安全的,而且也更准确,因为该问题是在 beta2 中引入的,这样理解对吗?这样也能覆盖所有 2.5 版本。

你提出的“权宜之计”——使用 2.5.999——听起来像是一个不错的变通方案(尽管正如你所说,这样比较杂乱)。我尽量让这里的锁定逻辑保持简单,但你说得对,我们目前确实还缺少一种真正能表示 2.5.x 作为有效锁定版本的方法。

针对锁定最后一个稳定版但保留下一个版本的测试版不锁定的特定场景,还有一个可能更微妙、但在我看来对锁定整个 2.5.x 分支来说远不那么“权宜”的变通方案:将锁定点设为 2.6.0.beta02.6.0.alpha1。从语义上讲,这表示锁定 2.5.x 与 2.6.0.beta1 之间(含)的所有版本。这意味着:

  • 所有 2.5.x 版本都被该锁定覆盖。
  • 所有 2.6.0.beta(1+) 版本仍保持未锁定状态。
6 个赞

是的,我也觉得这样更自然一些。

但是,无论是 2.5.999 还是 2.6.0beta0 的解决方案,都无法涵盖类似的情况:如果在 2.6.0beta3 中引入了一个问题,并将其反向移植到 2.5.2 中,该怎么办?

更多的边界情况与隐藏特性:你实际上可以通过留空条目来“取消”锁定:

以如下兼容性文件为例:

        2.6.0.beta1: twofiveall
        2.4.4.beta6: ~
        2.4.2.beta1: twofourtwobetaone

2.4.2.beta1 到 2.4.4.beta6 之间的任何版本都不会被回退锁定。从该范围之后直到 2.6.0.beta1 的版本将被锁定。超过该版本后,又将恢复为未锁定状态。

底层机制中,任何评估为 nil 的值将保持原样或指向最新版本。空条目或 ~ 会通过 Ruby 的 YAML 解析器评估为 nil,从而绕过锁定逻辑。

8 个赞

是否可以在自托管实例上使用旧版本插件?

1 个赞

是的,您可以在您的 app.yml 中调整 git clone 命令来克隆您想要的版本。请注意,您需要确保 Discourse 也固定到您想要的版本。您可能还需要将 discourse_docker 固定到与您正在运行的 Discourse 版本兼容的版本。如果您正在做所有这些,那么不升级会更容易。

2 个赞