如果问题只剩下 Git 是否赢得了默认心智,Mercurial changeset evolution 很容易被低估。Jordi Gutierrez Hermoso 在 2014 年 Montreal Python 的这场演讲有价值,正因为它从另一处开始:开发者一直会重写 draft work,工具却常把这种重写处理成私人技巧,或处理成破坏性操作。[1][2] 这场演讲追问的是,经过 amend、split、fold 与 replace 的 changeset,能否继续留在一条可见谱系里,而并非从模型中消失。

这个问题到 2026 年仍是实际工程问题。Mercurial 当前关于安全重写历史的帮助页把 obsolescence markers 描述为一种元数据,能够记录被删除或被新版本取代的 changeset、或许的 successor、执行重写的人,以及重写发生的时刻。[3] 同一页还说明,这条 marker history 与普通文件修改历史正交存在,这正是关键设计动作。[3] 仓库记住的不只是文件怎样变化,也能记住一个 draft patch series 在评审与协作过程中怎样改变形状。

观看这支视频时,更合适的入口,是把它看作一套围绕 mutable history 的安全契约。它的意思并非要求每个团队明天改用 Mercurial,而是暴露出许多工具会模糊的一条区分:尚未发布的工作天然可以继续修改,已经发布的工作应当硬化,二者之间的转换越明确,协作越稳定。[3][4][5]

图像说明:题图使用 Mercurial Paris 2023 工作坊的真实照片。那场工作坊由 Raphaël Gomès 与 Pierre-Yves David 带领,主题包括现代 Mercurial 用法,以及借助 Evolve 与 topics 进行 history rewriting。[6] 它并非截图,也并非图表。它适合本文,因为文章讨论的是协作工作流机制,而照片呈现了这些机制转化为操作知识时所在的现场。

开场附近,问题不在 amend,而在 amend 之后的可追责性

这场演讲最重要的框架,在于它承认评审稳定之前,重写历史是一件正常事。开发者发现一个错字,把修正折回早先提交,把混杂改动拆开,或者把一组 patch rebase 到新的父节点上。危险之处并不在编辑欲望本身,而在编辑发生之后,旧 draft 与新 draft 之间的关系从工具模型里丢失。[1][2]

Mercurial 的 evolution 文档把这种关系落到具体存储模型上。Obsolescence markers 可以说明某个 changeset 已被另一个 changeset 取代,而且这些 markers 可以在仓库之间交换。[3] 这就把“删除旧提交”和“发布从旧工作到替代工作的映射”区分开来。单人仓库里,这条差异看起来细;放到有评审队列、non-publishing servers 与并行本地编辑的团队仓库里,它就成为整套安全故事。

演讲的价值,在于它把这条谱系显示为用户体验问题。假设队友基于你的一个 draft commit 继续工作,而你随后 amend 了祖先提交,那么他的子 changeset 并非单纯“错了”。它是祖先被重写之后留下的 orphan。[3] 一个能够命名这种状态的系统,就能帮助修复它。一个只看见两个彼此无关 hash 的系统,会把意图重建工作交给聊天记录与紧张的人。

Phases 是防止可变性流入发布历史的边界

Changeset evolution 之所以能成为稳定模型,是因为 Mercurial 同时拥有 phases。Phases 文档定义了三种状态:public changesets 已经在公共服务器可见,draft changesets 尚未发布,secret changesets 不应被 push、pull 或 clone。[4] 默认情况下,新 changeset 从 draft phase 开始,在被推送到 publishing server 后走向 public。[4]

这听上去像元数据,实际是写进 VCS 的政策。Evolution 依赖这样的观念:draft 与 secret changesets 可以被修改,public changesets 则被视为已经硬化的历史。[3][4] 这让 history editing 中有用的部分,不会滑成随意重写共享发布事实的许可。用户体验也变得更诚实:“你可以重塑尚未公开的工作”,不同于“只要足够大胆,所有历史都能编辑”。

对工程团队来说,这就是采用边界。Mercurial 的模型最适合那些 review stacks 真实存在的场景:长期 feature queues、需要反复打磨的 topic branches,或者评审者要求作者重写以保持清晰,而并非永远追加 cleanup commits 的代码库。若团队没有共享的 draft 与 published 状态区分,这套模型就会变弱。在缺少纪律的地方,obsolescence markers 只是更准确地描述混乱。

中段附近,Evolve 把 trouble 变成一等对象

Evolve extension 是演讲概念进入日常机械结构的位置。PyPI 把 hg-evolve 描述为给 Mercurial 提供 evolve extension 的包,并且给 changeset evolution 提供一块成熟空间,等相关概念准备好后再移入 Mercurial core。[5] 它会启用 changeset evolution,提供分布式 history-rewriting commands,在 changeset evolution 的 “troubles” 出现时发出警告,并增加一个用于处理这些 troubles 的 hg evolve 命令。[5]

“Troubles” 这个词本身就在承担设计工作。Mercurial evolution 帮助页列出两类主要不稳定状态:orphaning 与 divergence。[3] Orphans 是祖先被重写后留下的后代 changesets;content divergence 来自同一 changeset 被独立重写后产生不同结果;phase divergence 则出现在某个 obsolete 版本进入 public 状态时。[3] 这些并非用户的道德失败,而是分布式图中的状态。

这也是视频之外仍值得带走的工程课。好的工作流工具不应该只禁止复杂状态,然后在状态出现时责怪使用者。它应当命名状态,保存足以理解状态的元数据,并提供修复路径。Evolve 中的 uncommitfoldsplitpruneprevnextevolve 等命令,与其说是各自独立的便利功能,不如说证明这个项目把 patch-stack editing 当成图维护问题处理。[5]

这套模型也有真实成本表面

演讲很有说服力,但它不应被读成魔法。Mercurial 自身文档仍把 safely rewriting history 标为 experimental,并说明这个功能仍处在开发中。[3] Evolve 包描述同样写明,changeset evolution 的完整实现仍在推进。[5] 这对采用判断很重要。团队应当把它看作一套强有力但边缘仍在移动的工作流模型,而并非对所有评审流程的无摩擦替换。

成本首先来自共享理解。所有参与者都需要知道 changeset 为什么可以处在 draft、public 或 secret phase;为什么一次重写会创建 successor relationships;为什么 orphan 或 divergence 是可修复状态,而并非重新 clone 仓库的理由。[3][4] 成本还会进入基础设施层。Non-publishing repositories、extension compatibility、server behavior,以及团队是否愿意交换 obsolescence data,都会决定这套模型显得平稳还是令人意外。[3][5]

回报在于,history editing 会少一些戏剧性。每次 amend 或 rebase 不再只是危险的私人动作,Mercurial 能把重写行为本身放进协作协议里。旧版本既不用被供奉,也不用被抹去。它可以被标记为 obsolete,连接到 successor,并参与稳定周围的图。

这场演讲至今仍澄清的问题

现在重看这场演讲,最好的理由并非怀念另一条 VCS 时间线,而是它把设计问题讲得清楚。当开发者重写尚未完成的工作时,工具应该假装旧版本从未存在,还是应该把重写本身记录为有意义事件?[1][3] Mercurial 的回答格外明确:未发布历史可以 evolve,这种 evolution 应当被表示、被交换、被修复,并获得与文件历史同等严肃的对待。

这个回答经得住时间,是因为现代代码评审正在让半成品工作在人与人之间移动得更多。Stacked changes、amend-heavy reviews、patch queues 与 branch cleanup 已经并非边缘行为,而是日常协作。Mercurial changeset evolution 仍然有趣,是因为它没有要求团队停止重写 draft work,而是要求把重写做得足够可读,让团队在重写发生之后仍能继续协作。

来源

  1. Montréal-Python,Jordi Gutierrez Hermoso 的 YouTube 视频《Changeset Evolution in Mercurial》。
  2. PyVideo,《Changeset Evolution in Mercurial》(活动元数据、讲者与演讲说明)。
  3. Mercurial help,《Safely rewriting history (EXPERIMENTAL)》(obsolescence markers、successors、orphaning 与 divergence)。
  4. Mercurial help,《Working with Phases》(public、draft 与 secret changeset states)。
  5. PyPI,hg-evolve 项目说明(extension purpose、commands、warnings 与 maturation role)。
  6. Mercurial Paris,《Mercurial Paris conference 2023 workshops》(工作坊背景与本文题图来源页)。