全部笔记

提交、推送、上线:三个不同的完成状态

一次本地提交并不意味着网站已经更新。用小而明确的检查闭合发布流程。

假设一次网站修改已经提交到 Git,维护者发出「上线完成」,读者却仍然看到旧标题。可能是提交没有推送,可能是部署了另一个目录,也可能新版本已经上线而浏览器仍使用旧内容。这些现象外观相似,处置方式却完全不同。

本文把本地提交、远端推送、产物构建、Cloudflare 部署和用户路径验证拆开。例子采用 Astro 静态博客,不涉及数据库迁移或有状态服务。命令用于说明检查方法,执行推送、部署或回滚前仍须确认仓库、分支和 Cloudflare 目标;检查清单不是对任何生产环境的自动操作授权。

1. 先定义每个完成状态需要的证据

本地提交保存了一份源代码快照,远端推送让指定 Git 引用得到提交,构建把源码转换成资源,部署让平台接收某份产物,线上验证才说明用户访问路径返回预期结果。链路中任何一段成功,都不能替剩余阶段作证。

可以把一次发布记成一个小记录,而不是只保留一张成功截图:

repository: botao/taotao
branch: main
source_commit: <完整 Git 提交标识>
worker: taotao-dev
deployment_version: <平台返回的版本标识>
verified_url: https://taotao.dev/writing/verify-deployment/
verified_at: <实际检查时间与时区>

占位符必须来自本次操作,不能把历史输出复制成新发布证据。这个记录表达来源、目标和观察时点;它不自动保证内容事实或安全性。若发布分为准备与完成两个状态,也要解释哪些检查尚未通过,避免后续工具仅凭「部署接口返回成功」把任务标成已验证。

2. 提交前检查范围,避免混入别人的修改

先确认当前目录与分支,再查看差异。共享工作区里,未提交内容不一定都属于本次任务。文章修改、依赖升级和域名配置可能来自不同工作,应分别理解;为了让工作区看起来干净而丢弃不认识的修改,会直接破坏协作。

git status --short --branch
git diff --check
git diff -- src/content/writing/verify-deployment.md
git diff --cached --stat
git diff --cached

未暂存差异与已暂存差异分开显示。最后两条检查真正准备进入提交的内容,不能用工作区差异替代。只看文件名还不够:一个看似普通配置文件可能包含私人地址、令牌或错误的生产目标。范围确认完成后,再按文件明确暂存,而不是无条件把整个目录加入。

构建目录、依赖、临时截图和私人草稿通常不进入源码历史。锁文件用于固定依赖解析,不能仅因为它自动生成就忽略。秘密曾经进入历史时,当前删除和增加忽略规则都不等于撤回已传播内容;需要按实际泄漏范围处理凭证与历史副本,这与普通发布回退是不同任务。

3. 验证当前提交,而不是任意一次测试结果

一次测试通过只对那份输入有意义。测试后继续编辑源文件,再引用刚才结果作为新修改的证明,会让证据与提交分离。提交前完成相关检查,提交后记录 HEAD,并确保准备部署的内容没有额外改变。

如果允许从未提交工作区构建,提交标识就只能表达基础版本,不能完整描述输入。报告需要同时保存已理解的差异或产物身份,不将 HEAD 写成全部来源。相反,要求干净工作区的发布器应在发现额外修改时停止,不能为了继续而自动暂存、提交或丢弃他人的内容。不同流程可以有不同政策,但证据不能超出政策实际覆盖的范围。

npm run check
npm test
npm run build
git rev-parse HEAD
git status --short

具体项目是否在提交前或提交后构建,可以依据流程选择;关键是能说明被验证的源代码与被部署的源代码关系。若测试会生成非忽略文件,需要区分正常产物与源码变更。不能为了通过干净检查就把新出现的文件全部删除,也不能把未知文件偷偷带入部署。

内容网站还需要产物级检查。文章正文、标题、RSS 与 sitemap 都由构建生成,源文件正确不代表所有消费者都使用同一规则。对私人草稿,检查未发布内容没有进入这些产物。对公开文章,确认指定路由与内容存在。类型检查本身覆盖不了这两类承诺。

4. 上游引用与真实远端是两层证据

本地分支可能跟踪错误远端,甚至没有上游。推送前查看配置,不凭仓库目录名推断账户与目的地。多个 GitHub 账户共用机器时,还要核对 SSH 主机别名与对应身份,不能通过一次默认身份认证就认为所有仓库写权限正确。

git remote -v
git branch --show-current
git rev-parse --abbrev-ref --symbolic-full-name '@{upstream}'
git rev-list --count '@{upstream}..HEAD'

最后一条计算本地 HEAD 相对当前上游跟踪引用领先多少提交。结果为零,表示本地视角没有领先,不表示刚刚与网络远端通信过。其他人在远端修改了分支,而本机没有更新引用时,这个检查不能说明远端最新状态。

如果没有上游,先决定目标分支再建立关联;如果远端不同步,先读取与理解历史差异。普通发布不应为了消除错误就强制推送。强制操作可能覆盖其他人的历史,必须是单独明确授权的协作动作,而不是检查失败后的默认修复。

5. 推送结果还要与指定分支关联

在确认目标后执行推送,并保存命令结果:

git push
git rev-list --count '@{upstream}..HEAD'
git ls-remote --heads origin refs/heads/main

第三条示范读取实际远端分支,分支名和远端应按项目调整。返回标识与预期提交比较,才能知道检查的是哪条引用。若协作中远端已出现后续提交,不应简单要求永远等于本机 HEAD;需要确认自己的提交是否已经成为目标历史的一部分,避免把合法前进误判为丢失。

推送被拒绝时,区分认证、权限、网络与历史冲突。网络超时也可能留下未知结果,下一步应读取目标状态,而不是立即改凭证或覆盖历史。成功输出、读取远端与本地跟踪检查一起使用,比单个计数更容易形成可信结论。

普通网站内容修改不会因为 GitHub 分支更新自动获得部署,除非项目确实配置了相应 CI。手动部署和自动部署都有各自入口;报告「已推送」时,不把一个没有触发的流水线当成已经运行,也不把检查状态的预期结果写成实际结果。

远端前进时怎样判断自己的提交仍在

假设维护者发布的提交是 A,检查时另一人已经向目标分支追加 B。远端标识不再等于 A,并不自动说明 A 未推送;需要读取对应目标历史,判断 A 是否在它的祖先链中。比较之前确认远端、分支和本地读到的目标标识对应同一次观察,不能把另一条分支的后续提交当成生产目标历史。

浅克隆或不完整对象库可能缺少祖先信息。某个祖先查询不能确认关系时,先区分是真正不包含,还是本地证据不足;不能立即强推 A 来“修复”。读取需要的目标历史是诊断,覆盖目标分支是协作变更,两者授权不同。记录远端观察标识与时间,也方便解释随后又有新提交时为什么两次检查结果不同。

推送成功与长期保留也不是相同承诺。后来别人重写分支或删除引用,先前观察仍真实,但不再代表当前目标。正常发布记录应保存已验证的来源与结果,持续保护分支则依赖相应仓库策略。不要试图让一次推送命令证明提交永远不会从任何远端历史中消失。

6. 固定构建输入,再核对上传目录

对于 Astro 静态网站,构建产物通常位于 dist/,Wrangler 的静态资源目录也必须指向它。一个旧构建目录仍然存在时,直接上传可能正常返回部署版本,却完全没有包含最新文章。部署成功并不能发现你选择了错误输入。

自动化环境可以从指定提交和锁文件安装依赖,再构建产物。手动流程也要记录当前源码状态、Node 与依赖条件,确认构建后没有其他源文件被同时改动。文章目录、配置、图片和生成索引都是输入,不能只看正文文件就认为整个产物来源固定。

npm ci
npm run check
npm test
npm run build

这些步骤应在适合项目的工作区执行;已有工作区出现依赖不一致时,先理解原因。构建结果至少核对一篇目标文章、内容列表、RSS 和不存在路径策略。特别要检查私人资料不是由搜索索引或复制脚本间接带入,安全边界应以最终上传内容为准。

源码相同仍要核对构建输入

锁文件固定依赖解析的一部分,不能固定所有环境。构建使用的 Node、环境配置、外部获取数据和工具行为,仍可能改变页面。尤其是一个以环境变量决定正式地址的布局:同一提交在预览与正式环境生成的 canonical 可以不同。这个差异可能是设计,也可能是误配置,应把实际值与目标环境一起检查。

产物身份可以帮助区分“上传了什么”和“源码是什么”,但散列只确认所比较字节一致,不证明它安全或正确。若记录的是压缩包散列,打包时间戳等也可能让同内容的包不同;若记录文件清单,又要定义哪些文件和路径参与。选择足以追溯本项目的方式,避免建立看似精确却无人知道如何重算的版本编号。

需要回滚时保留一份已验收产物,可以减少再次依赖外部构建输入的变数;是否采用取决于存储和平台流程。重新从旧提交构建则应说明可能与当年产物不同,特别是外部资源已经改变。不能看到 Git commit 相同就把未比较的新构建当成过去验证过的那一包文件。

7. 部署版本与域名绑定分别检查

Wrangler 部署前确认账户、Worker 名称、静态资源目录和自定义域名。相同本地项目可以被部署到不同账户或环境,不能只见一个版本号就确认生产网站已更新。记录目标是排查的必要输入,不是形式上的附加字段。

npx wrangler whoami
npx wrangler deploy

示例中的部署命令会改变远端状态,只在已授权目标执行。若命令结果无法确认,先读取平台版本与部署状态,再决定是否重试。盲目重复虽然对静态资源通常不像数据库写入那样复杂,仍可能混淆版本历史,或把后续本地变化一起上传。

Worker 默认地址返回新版,而正式域名返回旧版时,应继续检查域名绑定、路由和访问链路。根域与 www 可能由不同规则处理,TLS 成功也不代表返回同一应用。控制台中显示绑定完成,只说明平台配置状态的一部分;正式 URL 的实际内容才说明用户走到了哪里。

两次发布同时进行时保护完成记录

设发布任务 A 构建较慢,任务 B 后启动却先部署成功。随后 A 完成上传,线上可能回到 A 的内容,尽管两次命令都显示成功。这不是测试单次部署能发现的情况。发布系统需要定义串行、只允许特定目标版本推进,或明确接受哪个顺序,而不是默认“最后启动的任务就是最终上线任务”。

完成记录也不能被旧任务迟到覆盖。写入验证结论时关联部署版本与预期来源,确认观察到的确实是该版本的内容;发现线上已被另一份发布取代,应报告被替换或重新验证,而不是拿一次成功 GET 证明自己的产物仍在。锁只是某种协调机制,锁过期或任务恢复后仍需要版本条件保护实际结果。

静态博客的手动流程可以用更简单的约定:同一时段只有一人执行部署,开始前确认没有其他发布,完成后保存版本与正式地址证据。约定不能自动阻止失误,但比完全没有规则更可解释。需要自动化时,再把约定实现成可观察的状态检查,不把“大家应该不会同时操作”写成平台保证。

8. 状态码与正文身份都需要正负例

发布检查不应只访问首页。为本次修改选择一个可区分的正文标识,例如新的标题或新增小节,再获取指定文章;同时访问不存在路径和私人草稿路径,检查它们没有被成功页面掩盖。

curl -I https://taotao.dev/writing/verify-deployment/
curl -I https://taotao.dev/rss.xml
curl -I https://taotao.dev/writing/not-a-real-article/
curl -I https://taotao.dev/.drafts/index.html

HEAD 适合快速看状态和头部,正文需要 GET 检查。文章路由返回成功但显示首页,是路由契约错误;RSS 返回 XML 但缺少最新文章,是内容消费者错误。负例路径不应返回私人正文,不能用导航没有入口替代不可访问检查。

浏览器还要检查资源加载、长代码行、手机标题换行和链接。网络响应正确但页面被固定导航遮挡,仍会影响阅读。对缓存导致的旧内容,先比较部署与响应,不把一次强制刷新结果直接推广到所有访客;第三方订阅阅读器的刷新周期也不完全由本站控制。

9. 回滚是一项新的发布,不是抹去历史

静态博客没有业务数据库回滚,恢复旧内容相对简单,但仍要决定恢复哪份已经验证的产物。可以从已知提交重新构建部署,或按平台支持的版本流程恢复;选择哪种方式取决于产物是否可追溯以及当前平台配置。先核对能力与目标,不依据记忆执行恢复命令。

回滚后保留失败版本与原因,重新检查正式域名、文章和订阅源。Git 分支仍可能包含新提交,线上却已恢复旧版本,这不是不允许的状态,而是必须明确记录的状态。后续维护者需要知道差异,否则下一次自动部署可能再次把错误内容送上线。

动态应用中的数据库迁移、外部消息和支付效果不随静态版本一起撤回,不能把本站的恢复经验套用到所有服务。即使是内容网站,外部已经抓取或引用的文字也不会被回滚抹去;涉及事实更正时,可能还需要透明说明,而不是只恢复旧文件。

失败窗口决定恢复哪一段

推送前失败,只能说明远端阶段尚未证明,不要同时执行部署绕过来源确认。构建失败时保留明确错误与源版本,不能直接上传还留在目录里的旧产物。部署请求超时则属于结果可能未知,先读取平台和正式内容,再决定是否重试。验证失败又要区分应用错误与观察路径错误:错误正文已经上线,与证书或网络暂不可达,需要不同处理。

一个合理的恢复报告保存最后已证明阶段、目标身份、未完成检查与下一步。不要将所有失败都清零重新跑,因为重新选择输入可能把另一个工作区的变化带进去;也不要永远沿用上次成功证据,让恢复只剩形式。每次恢复都回答“本次仍在处理哪份来源、哪份产物、哪个部署”,才能避免状态混合。

Git 引用已到远端,不代表外部附件一并到达

仓库若使用子模块或独立大文件存储,主仓库提交记录的是相关引用或指针,发布所需内容还有其他取得边界。不能仅凭主分支标识匹配就宣称干净环境能重建全部资源。本项目未使用这些机制,其他仓库需要按实际依赖核对。

验收从受控清洁环境取得目标来源,检查附件是否完整;不复制私人凭据进仓库来修复访问。缺失附件应阻止依赖它的发布,而非静默替换占位图后报告原产物已经上线。

10. 报告已证明的阶段与剩余风险

完成报告先列最后已证明的阶段,再给来源、目标与证据。例如本地提交已完成而远端不可读,保存提交身份和读取失败,下一步是确认指定远端,不写“全部上线”。部署版本存在而正式文章不符,保留入口响应,下一步核对绑定与交付,不重新挑选来源掩盖差异。

报告区分实际检查与待执行检查,附观察时点。一次 GET 证明当时该入口的结果,不保证另一网络、所有缓存或未来部署都相同;这些未覆盖范围按风险决定是否追加验证或监控,不用一句“已验证”全部略过。

最终每个状态都有对应材料:源提交、远端引用观察、产物身份、平台版本和正式正文结果。后续维护者凭这些材料继续同一份发布,不必重跑所有命令,得到另一份无法与原任务对账的结果。

参考资料