Skip to content

fix(update): 源码部署改为暂存加改名,中断不再留下残缺 app/ - #619

Open
qiyinxi wants to merge 1 commit into
AUTO-MAS-Project:devfrom
qiyinxi:fix/sentry-deploy-20260908
Open

fix(update): 源码部署改为暂存加改名,中断不再留下残缺 app/#619
qiyinxi wants to merge 1 commit into
AUTO-MAS-Project:devfrom
qiyinxi:fix/sentry-deploy-20260908

Conversation

@qiyinxi

@qiyinxi qiyinxi commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Closes #544

问题

copyToRoot() 对每一项rmSync 掉目标、再逐文件 copyFileSync。删完之后、复制完成之前的任何中断——进程被杀、断电、单个 copyFileSync 因杀软/占用抛 EPERM/EBUSY——都会留下一个残缺的 app/,缺哪些模块取决于复制停在哪一步。没有临时目录、没有回滚、没有部署后校验。

线上表现是 ModuleNotFoundError / ImportError 家族,至今已在 Sentry 上观察到四种形态(AUTO-MAS-BACKEND-56 / 51 / 4R / 4M / 49 / 5H / 6A / 6M / 6P / 6T / 6Y …):

缺失内容 表现
app/**/xxx.py 子模块 启动即 ModuleNotFoundError
app/api/__init__.py 启动即 ImportError (unknown location)
app/task/__init__.py 后端正常启动、UI 全好,跑任务时才 AttributeError
文件在但内容是旧版本 ImportError: cannot import name 'X' from 'app.utils'

落在 lifespan 里的那几个会让后端完全起不来且无提示

改动

按 issue 里的建议改成「暂存 + 改名」:

1. 复制 src -> <item>.new        这一步耗时最长、最容易被打断,但 <item> 还原封不动
2. rename <item>     -> <item>.old
   rename <item>.new -> <item>    空窗从「整个复制耗时」缩到两次元数据操作之间
3. 换成功了才删 <item>.old
失败: 把 <item>.old 换回去,清掉 <item>.new

另外把 .git第一位挪到最后一位。原来 .git 先复制,若 .git 成功而 app 中断,后端 get_git_version() 会认为 HEAD 已是最新、标题栏不再提示「后端有更新」,用户因此失去重新拉取的入口,残缺状态可以一直留到下次启动。

抽出 removePath() 复用原先那段「是目录就 rmSync、是文件就 unlinkSync」。

磁盘占用

<item>.new 会让 app/.git/ 在切换瞬间各占两份。app/ 是源码、只有几 MB;.git/optimizeStorage() 做过 gc 之后也不大。相对「更新失败把用户装崩」,这个代价我认为值得,但如果你们觉得 .git 那份不该翻倍,可以只对 app/res 用暂存路径、.git 保持原逻辑——告诉我我来改。

没有一起改的

issue 里提到的第二个加重因素没动frontend/src/views/Initialization/index.vue:501 handleSkip() 允许用户在源码拉取失败后点「跳过此步骤」,状态被直接改成 success 并继续在残缺的树上启动后端。那是一个 UX 取舍(跳过按钮本身有存在意义),我不替你们决定,留在 #544 里没关掉的话可以单开一条。

本地验证

corepack yarn typecheck   # 退出码 0
corepack yarn lint        # 退出码 0
corepack yarn test        # Test Files 1 failed | 44 passed,Tests 1 failed | 417 passed

那条失败是 electron/services/backendService.test.ts > managed 模式 > 不传 --repo、不先跑 environment ensure,--app-root 就是用户数据根dev 上本来就红(在干净的 upstream/dev 上重跑同样失败),与本 PR 无关。

新增 electron/services/repositoryService.test.ts,4 条:

用例 说明
正常部署 内容换新、上一版残留文件被清掉、不留 .new / .old
复制中途失败 注入 EPERM,断言原有 app/ 三个文件全部完好、无残留
.git 排序 断言落盘顺序是 app.newres.new.git.new
目标此前不存在 首次部署路径

这套测试对旧代码是红的:把本 PR 的 repositoryService.ts 还原成 dev 版本后重跑,「复制中途失败」与「.git 排序」两条失败(2 failed | 2 passed)——不是写完就绿的空测试。

没有做真机手测:需要真的在部署中途杀掉 Electron 主进程来验证,本地没有安装态环境。改动集中在文件系统操作,逻辑由上面的测试覆盖,但这条路径一旦坏掉就是用户装不上,建议合并前有人在真实安装目录上跑一次完整的源码更新。

请审阅

@DLmaster361 这块是你写的,尤其请看「磁盘占用」和「没有一起改的」两节的取舍。确认后请你自行合并,我不代合。

Sourcery 摘要

使源代码部署具备原子性并支持恢复,从而确保中断的更新能够保留可正常工作的安装。

Bug 修复:

  • 通过暂存更新并在部署失败时恢复之前的内容,防止中断的源代码部署留下部分复制的应用程序目录。
  • 在应用程序文件之后部署仓库元数据,避免失败的更新错误地显示为已完全更新。

增强功能:

  • 集中处理部署清理和回滚过程中的文件及目录删除。

测试:

  • 添加仓库部署测试,涵盖成功替换、复制失败后的回滚、部署顺序以及首次安装。
Original summary in English

Summary by Sourcery

Make source deployments atomic and recoverable so interrupted updates preserve a working installation.

Bug Fixes:

  • Prevent interrupted source deployments from leaving a partially copied application directory by staging updates and restoring the previous content when deployment fails.
  • Deploy repository metadata after application files so failed updates do not falsely appear fully up to date.

Enhancements:

  • Centralize file and directory removal for deployment cleanup and rollback handling.

Tests:

  • Add repository deployment tests covering successful replacement, rollback after a copy failure, deployment ordering, and first-time installation.

copyToRoot() 原来对每一项先 rmSync 掉目标、再逐文件 copyFileSync。删完之后、
复制完成之前的任何中断——进程被杀、断电、单个 copyFileSync 因杀软/占用抛
EPERM/EBUSY——都会留下一个残缺的 app/,缺哪些模块取决于复制停在哪一步。
线上表现为 ModuleNotFoundError / ImportError 家族,落在 lifespan 里的那几个
会让后端完全起不来且无提示。

改为:
- 先把新内容整个复制到 <item>.new,这一步失败时 <item> 还原封不动
- 再 rename(<item> -> <item>.old) + rename(<item>.new -> <item>) 换上,
  空窗从「整个复制耗时」缩到两次元数据操作之间
- 换成功才删 .old;任一步失败则把 .old 换回去并清掉 .new
- .git 挪到 app/res/main.py 之后,避免中断后「版本已最新」的假象盖住残缺源码,
  否则后端 get_git_version() 认为 HEAD 已最新,用户失去重新拉取的入口

Closes AUTO-MAS-Project#544

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@qiyinxi qiyinxi added bug Something isn't working Sentry daily Sentry daily issues labels Sep 8, 2026
@qiyinxi
qiyinxi requested a review from DLmaster361 September 8, 2026 08:41

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @qiyinxi, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 4 days and 3 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@sourcery-ai

sourcery-ai Bot commented Sep 8, 2026

Copy link
Copy Markdown

审查者指南

源码部署改为逐项完整写入 .new 后通过改名切换,并在失败时回滚旧内容;同时将 .git 延后部署以避免残缺源码被误判为最新,并新增针对成功、失败回滚、顺序和首次部署的测试。

原子源码部署时序图

sequenceDiagram
    participant RepositoryService
    participant Source as SourceTree
    participant Stage as Item.new
    participant Target as Item
    participant Backup as Item.old

    RepositoryService->>Source: statSync(srcPath)
    RepositoryService->>Stage: removePath(stagePath)
    RepositoryService->>Stage: copyDirectory or copyFileSync
    alt copy succeeds
        RepositoryService->>Target: renameSync(dstPath, backupPath)
        RepositoryService->>Target: renameSync(stagePath, dstPath)
        RepositoryService->>Backup: removePath(backupPath)
    else copy or switch fails
        RepositoryService->>Stage: removePath(stagePath)
        alt target was backed up
            RepositoryService->>Target: removePath(dstPath)
            RepositoryService->>Backup: renameSync(backupPath, dstPath)
        end
    end
Loading

部署项顺序流程图

flowchart LR
    App[app.new -> app]
    Res[res.new -> res]
    Files[main.py, requirements.txt, LICENSE, README.md]
    Git[.git.new -> .git]

    App --> Res
    Res --> Files
    Files --> Git
Loading

文件级变更

变更 详细信息 文件
使用暂存目录和原子改名替换逐文件覆盖,降低源码部署中断导致残缺安装的风险。
  • 先将每个部署项完整复制到 .new,再将旧目标改名为 .old 并切换新内容
  • 切换成功后清理备份;失败时删除暂存内容并尝试恢复旧目标
  • 抽取统一的文件/目录删除辅助方法
  • 新增对 .git、文件复制失败、回滚清理和首次部署场景的测试覆盖
frontend/electron/services/repositoryService.ts
frontend/electron/services/repositoryService.test.ts
调整部署顺序,避免元数据先更新造成源码版本判断失真。
  • .git 从部署首项移至最后,确保 app/res 等源码内容先完成更新
  • 新增测试验证 app.newres.new.git.new 的落盘顺序
frontend/electron/services/repositoryService.ts
frontend/electron/services/repositoryService.test.ts
更新资源版本数据。
  • 修改版本 JSON 内容
res/version.json

可能关联的问题


提示和命令

与 Sourcery 交互

  • 触发新的审查: 在拉取请求中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的审查评论。
  • 根据审查评论生成 GitHub issue: 回复审查评论,请 Sourcery 根据该评论创建 issue。你也可以回复审查评论并输入 @sourcery-ai issue,以根据该评论创建 issue。
  • 生成拉取请求标题: 在拉取请求标题的任意位置写入 @sourcery-ai,即可随时生成标题。你也可以在拉取请求中评论 @sourcery-ai title,以随时生成或重新生成标题。
  • 生成拉取请求摘要: 在拉取请求正文的任意位置写入 @sourcery-ai summary,即可在指定位置随时生成 PR 摘要。你也可以在拉取请求中评论 @sourcery-ai summary,以随时生成或重新生成摘要。
  • 生成审查者指南: 在拉取请求中评论 @sourcery-ai guide,即可随时生成或重新生成审查者指南。
  • 解决所有 Sourcery 评论: 在拉取请求中评论 @sourcery-ai resolve,即可解决所有 Sourcery 评论。如果你已经处理了所有评论且不想再看到它们,此功能会很有用。
  • 忽略所有 Sourcery 审查: 在拉取请求中评论 @sourcery-ai dismiss,即可忽略所有现有的 Sourcery 审查。如果你想从头开始新的审查,此功能尤其有用——别忘了评论 @sourcery-ai review 以触发新的审查!

自定义使用体验

访问你的控制面板

  • 启用或禁用审查功能,例如 Sourcery 生成的拉取请求摘要、审查者指南等。
  • 更改审查语言。
  • 添加、移除或编辑自定义审查说明。
  • 调整其他审查设置。

获取帮助

Original review guide in English

Reviewer's Guide

源码部署改为逐项完整写入 .new 后通过改名切换,并在失败时回滚旧内容;同时将 .git 延后部署以避免残缺源码被误判为最新,并新增针对成功、失败回滚、顺序和首次部署的测试。

Sequence diagram for atomic source deployment

sequenceDiagram
    participant RepositoryService
    participant Source as SourceTree
    participant Stage as Item.new
    participant Target as Item
    participant Backup as Item.old

    RepositoryService->>Source: statSync(srcPath)
    RepositoryService->>Stage: removePath(stagePath)
    RepositoryService->>Stage: copyDirectory or copyFileSync
    alt copy succeeds
        RepositoryService->>Target: renameSync(dstPath, backupPath)
        RepositoryService->>Target: renameSync(stagePath, dstPath)
        RepositoryService->>Backup: removePath(backupPath)
    else copy or switch fails
        RepositoryService->>Stage: removePath(stagePath)
        alt target was backed up
            RepositoryService->>Target: removePath(dstPath)
            RepositoryService->>Backup: renameSync(backupPath, dstPath)
        end
    end
Loading

Flow diagram for deployment item ordering

flowchart LR
    App[app.new -> app]
    Res[res.new -> res]
    Files[main.py, requirements.txt, LICENSE, README.md]
    Git[.git.new -> .git]

    App --> Res
    Res --> Files
    Files --> Git
Loading

File-Level Changes

Change Details Files
用暂存目录和原子改名替换逐文件覆盖,降低源码部署中断导致残缺安装的风险。
  • 先将每个部署项完整复制到 .new,再将旧目标改名为 .old 并切换新内容
  • 切换成功后清理备份;失败时删除暂存内容并尝试恢复旧目标
  • 抽取统一的文件/目录删除辅助方法
  • 新增对 .git、文件复制失败、回滚清理和首次部署场景的测试覆盖
frontend/electron/services/repositoryService.ts
frontend/electron/services/repositoryService.test.ts
调整部署顺序,避免元数据先更新造成源码版本判断失真。
  • .git 从部署首项移至最后,确保 app/res 等源码内容先完成更新
  • 新增测试验证 app.newres.new.git.new 的落盘顺序
frontend/electron/services/repositoryService.ts
frontend/electron/services/repositoryService.test.ts
更新资源版本数据。
  • 修改版本 JSON 内容
res/version.json

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working Sentry daily Sentry daily issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant