git-cliff: 自动生成的变更日志(几乎不费力)

107 个提交。从第一天开始就是完美的约定式提交。Feat、fix、refactor、chore — 所有内容都完美标记。那么 CHANGELOG 呢?空的。不存在。一个"明天再写"的文件,已经拖了两个月。 如果这听起来很熟悉,你不是一个人。手动编写变更日志是奥林匹克级别的苦差事。不是说它难 — 而是它乏味、重复,总有更紧急的事情要做。这就是为什么 git-cliff 存在的原因。 什么是 git-cliff(30 秒版本) 这是一个用 Rust 编写的变更日志生成器,它读取你的 git 提交,根据约定式提交解析它们,然后输出按版本和类型分组的 CHANGELOG.md。没有奇怪的依赖,没有插件,没有黑魔法。一个二进制文件,一个配置文件,就完了。 简单来说:你给它你的提交,它返回你拖延了几个月的文件。 brew install git-cliff git cliff --output CHANGELOG.md 这两行字面上就是开始所需的全部。如果你的提交遵循 类型: 描述 约定,git-cliff 无需额外配置就能理解它们。 真实案例:8 个版本的回溯性变更日志 在 Tokamak(我的监控 Claude 配额的菜单栏应用)中,我正好遇到了这个问题:107 个完美提交和一个空白的 CHANGELOG。应用已经是 v1.3.0 版本,但只有一个开始时的 v0.1 标签。 计划很简单: 步骤 1:在每个版本的提交上创建回溯性标签。 git tag v0.2.0 32950f4 # Dashboard, biblioteca, achievements v1 git tag v0.3.0 9c56985 # Rename a Tokamak, 6 idiomas git tag v1.0.0 a283490 # App Store: sandbox, privacy manifest git tag v1.3.0 6248bac # HEAD: multi-provider, fetch pipeline # ... 以此类推 步骤 2:运行 git-cliff。 ...

2026年2月22日 · Fernando

macOS公证:苹果为你的应用设置的夜店门卫

凌晨两点。你的应用编译通过。签名完成。打包成DMG。执行notarytool submit。苹果说"In Progress"。你等了5分钟。10分钟。20分钟。一个小时。两个小时。提交仍然是"In Progress"。你睡觉去了。第二天早上:Invalid。 除了"The signature of the binary is invalid"之外没有更多解释。对两种架构都是如此。谢谢苹果。非常有用。 公证是那种完美运行的流程…直到它不工作为止。当它失败时,会给你留下一个Gatekeeper不会打开的.dmg文件和一个什么都不告诉你的错误。在为Tokamak(我的用于监控Claude配额的菜单栏应用)与此斗争几天后,我决定记录所学到的一切并编写一个检查器以避免再次经历这种痛苦。 什么是公证(简单来说) 想象Mac App Store是一个有保安的购物中心。但你不想在购物中心销售——你想直接分发你的应用,用你自己的DMG。就像街边摊位。 苹果说:“好的,你可以。但首先要通过门卫。” 那个门卫就是公证。这是苹果的一个自动化服务,扫描你的已签名应用,验证它不包含已知的恶意软件,如果一切正常,就给你一个票据。你把这个票据钉在你的DMG上(stapler staple),从那时起,当用户下载并尝试打开它时,Gatekeeper看到票据就说"请进"。 没有那个票据,用户会看到这个: “Tokamak.app"无法打开,因为苹果无法检查它是否不含恶意软件。 你的应用就被丢在路边了。 苹果为什么这样做 有两个原因。一个是合理的。另一个…嗯。 合理的原因:保护用户。在公证之前(2019年在macOS 10.14.5中引入),任何人都可以用Developer ID分发已签名的.app,macOS会毫无怨言地打开它。代码签名验证开发者的身份,但不扫描内容。如果你的已签名应用包含键盘记录器,签名完整的情况下也会执行。 公证增加了一层:苹果在到达用户之前扫描二进制文件寻找已知的恶意软件和可疑行为。这不是像App Store那样的人工审查——这是一个自动化系统。但至少是个保障。 另一个原因:控制。苹果希望你通过App Store Connect处理所有事情。使用Developer ID的直接分发一直是二等公民。公证是在"你可以在App Store之外分发,但我们会让你感到不便"这条道路上的又一步。 话虽如此,从macOS 10.15开始,对于所有在App Store之外分发的应用,公证是强制性的。这不是可选的。你的应用要么经过公证,要么不会打开。没有商量余地。 会让你浪费数小时的7个错误 在收集了自己的错误和开发者论坛的错误后,这些是最痛苦的: 1. 缺少--timestamp 这是经典错误。你的codesign在本地完美工作,Gatekeeper不抱怨,但公证返回"The signature of the binary is invalid.” # 错误 — 本地签名有效,苹果拒绝 codesign --force --options runtime --sign "Developer ID Application: ..." MiApp.app # 正确 — 使用苹果服务器的时间戳 codesign --force --options runtime --timestamp --sign "Developer ID Application: ..." MiApp.app 安全时间戳证明签名是在证书有效期内完成的。没有它,苹果不会信任。就像签署没有日期的合同——技术上有效,但没人会接受。 ...

2026年2月22日 · Fernando

一行命令创建 macOS 虚拟机

我正在构建一个 macOS 的菜单栏应用程序。在我的 Mac 上运行完美。现在我需要知道它是否能在干净的 macOS 环境中正常工作:没有我的配置、没有我的权限、没有我的数据。一个全新安装的用户环境。 如何测试这种情况?你需要一个虚拟机。 “简单”,我想。“我安装了 UTM。打开向导,创建一个 macOS 虚拟机,然后运行。” 事情并没有那么简单。 UTM:漂亮但难以驯服 UTM 是一个很棒的应用程序。精心设计的界面,支持在 Apple Silicon 上运行 macOS 客户机,全屏显示,共享剪贴板。手动使用确实很棒。 当你试图自动化时问题就出现了。 UTM 有一个叫做 utmctl 的命令行界面。可以列出虚拟机、启动它们、停止它们、克隆它们。它不能做的是创建虚拟机。对于 macOS 客户机,甚至 UTM 的 AppleScript 也不允许创建它们——操作系统字段被硬编码为 Linux。 简单来说:如果你想在 UTM 中创建 macOS 虚拟机,你必须通过向导手动创建。每次都是。需要点击、下载 IPSW(Apple Silicon 的 macOS 安装映像——相当于传统的 ISO,但由 Apple 打包)、等待安装。 对于需要在质量保证流程中频繁创建和销毁虚拟机的开发者来说,这真是个麻烦事。 Tart:为开发者设计的 macOS 虚拟机 Tart 是当有人在设计虚拟化工具时考虑开发者而不是最终用户的结果。 它使用与 UTM 完全相同的 Apple Virtualization.framework。相同的技术,相同的功能,相同的原生速度。区别在于界面:Tart 是命令行优先的。 brew install cirruslabs/cli/tart 就这样。没有图形界面配置,没有向导。只是在你的 PATH 中的一个二进制文件。 一个命令统治一切 创建一个使用最新可用版本的 macOS 虚拟机: tart create mi-vm --from-ipsw latest 就是这样。latest 告诉 Tart “给我这台 Mac 支持的最新 macOS 版本”。Tart 查询 Apple 的 API,下载 IPSW(约 15 GB——是的,macOS 很大),创建虚拟磁盘,安装操作系统,并为你准备好可以启动的虚拟机。去喝杯咖啡吧,因为需要一段时间——但你不需要动手做任何事情。 ...

2026年2月21日 · Fernando

Git工作树:如何让多个AI编程代理并行工作互不干扰

单一checkout的困境 我正在开发一个macOS菜单栏应用,有三个并行需求:消费趋势的走势图、原生通知和桌面小组件。这三个功能独立开发,都计划使用Claude Code实现。 问题在于:Claude Code需要在指定目录工作。而每个Git目录同一时间只能检出(checkout)一个分支。标准的git checkout就如同单车道环岛——每次只能通过一辆车。 如果想让三个功能并行开发,传统方案只有以下几种: 暂存乒乓:反复执行git stash保存更改→切换分支→git stash pop恢复→祈祷不要出现冲突…如此循环直到崩溃或退休,视哪个先到来。 克隆多个仓库:可行,但会产生三个完整的.git/目录副本、三个独立历史记录,以及三倍git fetch操作。资源浪费严重。 顺序开发:老老实实一个接一个开发。稳定可靠,但效率低得像手动执行归并排序。 这些方案都不理想。但Git其实自2015年起就内置了第四种解决方案——只是鲜为人知。 工作树(Worktree):现成的解决方案 Git工作树允许为同一仓库创建多个工作目录,无需克隆完整仓库。没有文件复制,没有魔法技巧。 类比说明:将Git仓库视作图书馆。原先你只有一张阅览桌,每次只能打开一本书。而工作树相当于增添多张阅览桌,每张桌子可打开不同的书,但所有书都来自同一个书库。 ~/code/miapp/ ← 主工作区(main分支) .git/ ← 核心书库(唯一存在) ~/code/miapp-sparkline/ ← 工作树1(feature/sparkline分支) .git ← 符号链接文件(指向主书库) ~/code/miapp-notificaciones/ ← 工作树2(feature/notifications分支) .git ← 另一符号链接 每个目录都是完整的检出副本,包含所有文件。你可以在一个目录编译代码,在另一个目录运行测试,同时让AI编程代理在第三个目录工作——真正并行不悖。 一行命令创建工作树 在项目主目录执行: git worktree add ../miapp-sparkline -b feature/sparkline git worktree add ../miapp-notificaciones -b feature/notifications 就这么简单。两个新目录立即创建完成,各自对应独立分支,共享同一Git数据库。无需克隆操作,无需配置远程仓库,无需复制历史记录。 共享与隔离机制 关键在于理解:所有工作树完全共享Git仓库数据——包括提交历史、分支、标签、远程配置和钩子脚本。当你在sparkline工作树提交代码,notifications工作树可即时看到此次提交,无需执行fetch操作。 但以下内容是独立的: 工作目录文件(每个工作树有自己的文件副本) 暂存区(各自独立的git add状态) HEAD指针(指向不同分支) 简言之:每个工作树的"当前工作状态"是私有的,其他所有仓库数据都是共享的。 与AI编程代理的协作流程 这才是工作树的精髓所在。通过工作树,你可以让多个AI编程代理真正并行工作: # 终端1:在sparkline分支运行Claude Code cd ~/code/miapp-sparkline claude # 终端2:在notifications分支运行Claude Code cd ~/code/miapp-notificaciones claude # 终端3:保持主分支运行 cd ~/code/miapp make run 每个Claude实例都在独立目录、独立分支下操作,拥有独立的.build/缓存目录。彼此互不干扰,无需竞争暂存区,更不需要反复执行stash操作。 ...

2026年2月16日 · Fernando