备份前空间预估;manifest 的 archive 字段只在文件真的存在时才写
需求
- 写满盘这件事只做一件事:**备份前预测本次所需大小,并提示用户存储够不够**,
不再往"更复杂的占用控制"方向做。
实现
- Backup.ps1 新增只读的"备份前空间预估",在动手之前按清单顺序模拟一遍:
* 逐个条目枚举源目录得到真实源大小/文件数,并读取现有归档大小;
* 沿用主循环那套"源未更新就跳过"的判断,所以列出来的就是**本次真的会重打**的条目;
* 估算模型:临时归档写完时旧归档还在,那一刻占用 = 当前累计净增量 + 本次预估;
原子替换后净增量 = 预估 − 旧归档大小;峰值取整个过程的最大值;
* 预估归档大小:有历史归档取 min(源大小, 旧归档 × 1.3),没有则按"不压缩"的悲观值;
* 打印:可用空间、要重打的条目数(及跳过/缺源的数量)、逐条目明细(前 15 条)、
预计峰值新增与净增量,最后给一句结论——"空间足够"或"空间可能不够!…差 Z GB"。
* 不够时**只告警、不中断**:真正放不下的条目仍由逐条目守卫跳过。
- 新增 Sync-BaknretManifestArchive(Common.psm1,Backup/Restore 都在写 manifest 前调用):
维持不变式"manifest 里写了 archive 的记录,磁盘上就一定有那个文件",
把指向不存在归档的 archive 字段清空,但保留 source / action / 历史计数。
这样"源不存在的条目"不会再让 Restore 反复报"manifest 记录的归档不存在",
人工删掉归档(例如把它并进别的条目)之后记录也会自我纠正。
整理(本机备份集)
- 按用户确认,删除了已被 scoop.7z 覆盖的 scoop-config.7z 与 scoop-persist.7z
(删除前先 7z t 复验 scoop.7z:233 MB / 29478 项 / 顶层 [persist, scoop]),
并清掉 manifest 里这两条历史记录;24 条记录的 archive 现在全部存在于磁盘上。
测试
- Pester 88 项、零依赖单元 49 项、端到端 23 项,全部通过。
新增覆盖:对象数组名录、两种写法 × :+/-、同名目录拒绝执行、行尾 # 说明、
孤儿审计(含"manifest 里还有历史记录"的说明)、archive 字段一致性、空间预估输出。
This commit is contained in:
1 parent
43fa4e52dd
commit
7173e8ae10
5 files changed
+244
-4
No files matched your search
@@ -10,6 +10,7 @@
|
||||
- 退出码可靠:有失败就返回 `1`,计划任务能正确判断成败。
|
||||
- 备份结束做**孤儿归档审计**:磁盘上有、但没有任何清单条目指向的归档会被点名(它们恢复不到,别误删)。
|
||||
- 恢复支持 `-WhatIf` / `-DryRun` / `-VerifyOnly` / `-Only` / `-Skip`;其中三种"只看不写"的模式(`-WhatIf` / `-DryRun` / `-VerifyOnly`)**一个字节都不写**。
|
||||
- 动手之前先**预估本次所需空间**并直接判断目标卷够不够(不够只告警、不中断)。
|
||||
|
||||
---
|
||||
|
||||
@@ -256,6 +257,31 @@ Edge :- !*Cache,!Crashpad # 缓存与崩溃转储都可再生,不进归档
|
||||
`Restore.ps1` **优先用 manifest 定位归档**,查不到才退回"从文件名反推路径"。
|
||||
如果 `BackupList.txt` 丢了,`Restore.ps1` 会优先用 manifest 里的 `source` 自动重建。
|
||||
|
||||
## 备份前空间预估
|
||||
|
||||
每次备份在**动手之前**先按清单顺序模拟一遍,把"这次要写多少、盘够不够"直接打出来:
|
||||
|
||||
```text
|
||||
[INFO] ==== 备份前空间预估(只读)====
|
||||
[INFO] 目标卷可用空间:7.37 GB
|
||||
[INFO] 本次要重打 8 个条目(另有 7 个源未更新会跳过、9 个源不存在)
|
||||
[INFO] 新归档合计约 2.44 GB;其中会替换掉的旧归档 1.89 GB
|
||||
[INFO] - Edge 源 2,303.8 MB / 11354 文件 现有 1,781.3 MB 预估 2,303.8 MB
|
||||
[INFO] - MiFlash_Unlock 源 231.3 MB / 153 文件 现有 70.0 MB 预估 91.0 MB
|
||||
[INFO] ...
|
||||
[INFO] 预计峰值新增占用:2.25 GB(全程净增量 0.55 GB)
|
||||
[INFO] 结论:空间足够(预计用 2.25 GB / 可用 7.37 GB)
|
||||
[INFO] ============================
|
||||
```
|
||||
|
||||
- **估算模型**:临时归档写完时旧归档还在,那一刻占用"当前累计净增量 + 本次预估",
|
||||
原子替换之后本次净增量 = 预估 − 旧归档大小。峰值取整个过程的最大值。
|
||||
- **预估归档大小**:有历史归档时取 `min(源大小, 旧归档 × 1.3)`;没有历史归档时按
|
||||
"完全不压缩"的悲观值估 —— 宁可报多不报少。
|
||||
- **结论只有两种**:空间足够,或者"空间可能不够!预计需要 X GB,可用 Y GB,差 Z GB"。
|
||||
不够时**只告警、不中断** —— 真正放不下的条目会被逐条目守卫跳过;想稳妥就腾空间或加 `-Only` / `-Skip` 分批。
|
||||
- 这一步是只读的,不改任何文件;`-DryRun` 也照跑。
|
||||
|
||||
## 日志
|
||||
|
||||
`logs/backup-<时间戳>.log` / `logs/restore-<时间戳>.log`,与控制台内容一致。
|
||||
@@ -321,7 +347,7 @@ $env:BAKNRET_PASSWORD = '...' # 或
|
||||
|
||||
| 套件 | 命令 | 需要什么 | 覆盖 |
|
||||
| --- | --- | --- | --- |
|
||||
| **Pester 套件**(推荐) | `.\tests\Run-Pester.ps1` | Pester 5.0+ 与 7z | 82 项:解析、命名、排除翻译、命令行拼接、manifest / 配置 / 名录、**两种写法 × `:+`/`:-`**,外加**用子进程真正跑 `Backup.ps1` / `Restore.ps1`** 的端到端与回归 |
|
||||
| **Pester 套件**(推荐) | `.\tests\Run-Pester.ps1` | Pester 5.0+ 与 7z | 88 项:解析、命名、排除翻译、命令行拼接、manifest / 配置 / 名录、**两种写法 × `:+`/`:-`**,外加**用子进程真正跑 `Backup.ps1` / `Restore.ps1`** 的端到端与回归 |
|
||||
| 零依赖套件 | `.\tests\Run-Tests.ps1` | 只要 PowerShell + 7z | 49 项:同样的单元面,适合没装 Pester 的机器 |
|
||||
| 端到端验收 | `.\tests\Run-E2E.ps1` | 只要 PowerShell + 7z | 23 项:备份 → 确认排除生效 → 删源 → 恢复 → 逐字节对拍 |
|
||||
| **真实归档恢复演练** | `.\tests\Restore-Drill.ps1` | 只要 PowerShell + 7z | 把 `Backups/` 里**真实的那批归档**解到临时目录,再和活源逐字节对拍(全程不碰真实目录) |
|
||||
@@ -369,7 +395,8 @@ Pester 套件里的端到端用例是**用子进程**跑 `Backup.ps1` / `Restore
|
||||
| 软件名录的多目录写法 | 只有 `@{ Dirs = @(...) }`,没有"这个目录是干什么的" | 支持**对象数组**(`Path` + `Description`),运行时逐条介绍 |
|
||||
| 多目录条目的恢复 | 把整包解压到每个位置的父目录,会在别的父目录下凭空冒出兄弟目录 | 每个源只解出**它自己那棵子树** |
|
||||
| 同一条目里两个同名目录 | 静默混成一棵树,两边的数据都错 | 打包前明确报错(退出码 1)并提示拆成两个条目 |
|
||||
| 运行时的可解释性 | 只有一行"开始备份: X" | 逐条打印目录、来源、介绍、排除/追加的出处与理由 |
|
||||
| 运行时的可解释性 | 只有一行"开始备份: X" | 逐条打印目录、来源、介绍、排除/追加的出处与理由;备份前还会预估所需空间并判断够不够 |
|
||||
| manifest 的 `archive` 字段 | 源不存在的条目也留着归档名,指向一个根本不存在的文件;恢复时白报"归档不存在" | 只在文件真的存在时才写;删掉归档后同步一次就自我纠正 |
|
||||
| 没有名录、manifest、测试、README,不是 git 仓库 | — | 都有 |
|
||||
|
||||
## 设计取舍(有意为之,不是遗漏)
|
||||
@@ -389,4 +416,6 @@ Pester 套件里的端到端用例是**用子进程**跑 `Backup.ps1` / `Restore
|
||||
- 加密归档的常规备份/恢复不依赖 `RAR`;`RAR` 与内置 `ZIP` 分支仅作降级,未做加密支持(ZIP 明确拒绝加密请求)。
|
||||
- `Variants`(同名目录分散在多处)当前打包第一个位置;恢复时每个源只解出**它自己那棵子树**,不会把兄弟目录复制到别的父目录下。
|
||||
- **`root=<名>` 标记尚未实现。** 归档内的根目录始终是源目录名(见「设计取舍」)。7z 命令行没有"入库时改名"的能力;用了该标记会打印告警,不会静默失效。
|
||||
- **磁盘空间守卫是逐条目判断的**,不预留"本次运行后续条目"的空间。`MinFreeSpaceGB` 只是告警阈值;真正拦条目的是"剩余空间 < 该条目预估大小"。往接近写满的卷上备份时请自己留意总用量。
|
||||
- **空间只做"预估 + 提示",不做全局拦截。** 备份前会打印预计峰值新增和"够不够"的结论;
|
||||
不够时**只告警不中断**,真正放不下的条目交给逐条目守卫跳过。`MinFreeSpaceGB` 是告警阈值。
|
||||
想稳妥跑完就先腾空间,或用 `-Only` / `-Skip` 分批。
|
||||
Reference in new issue
Block a user