从其他项目管理系统导入数据¶
Specivo 本身不会直接读取其他项目管理系统。由单独的 迁移工具 读取旧系统,并写出一个 导入包:一个由纯文件组成的文件夹(或 .zip),包含项目、工作项(Issue)、历史记录、维基(Wiki)页面和附件。Specivo 再检查并加载这个导入包。两者分开有两个好处:迁移工具可以独立修复和发布,不必等新版 Specivo;导入包一旦生成,Specivo 就不再需要访问旧系统。
本页介绍 Specivo 这一侧的操作,对所有迁移工具都一样。导出步骤见各工具自己的指南。
迁移工具¶
| 迁移来源 | 工具 | 指南 |
|---|---|---|
| Jira Cloud 或 Jira Data Center | specivo-jira-migration |
Jira 迁移工具文档 |
| Redmine | specivo-redmine-migration |
Redmine 迁移工具 |
| Request Tracker(RT) | specivo-rt-migration |
RT 迁移工具文档 |
其他来源需要一个能写出导入包格式的工具。该格式在 Specivo 仓库的
docs/import-bundle/v1.md 中有说明,迁移 SDK 可以替你生成。
需要 Specivo 0.11.0 或更高版本
导入包加载器在 0.11.0 中引入,取代了早期版本的直接导入;参见 升级到 0.11.0。
导入包可以包含什么¶
导入包可以包含用户和用户组、项目及其成员、版本、类别、冲刺(Sprint)、自定义字段、带评论和变更历史的工作项、关联和父工作项、关注者、附件、工时记录,以及带全部修订版本的维基页面。迁移工具会填入其来源系统拥有的内容。下面是 Specivo 加载这些内容时的几个细节:
- 自定义字段 成为 工作项元数据,并沿用旧系统中的字段名显示。存放人员或版本的字段显示对应人员或版本的名称。
- 旧系统的标签(Label) 可以作为 Specivo 的 标签 导入,在每个工作项所属的项目中创建。
- 冲刺 会带着原有状态(计划中、进行中或已完成)导入。一个项目只能有一个进行中的冲刺,因此第二个进行中的冲刺会按计划中导入,并在报告中列出。已完成冲刺的速率根据其工作项记录。
- 工作项的剩余预计工时 会在预计工时旁边显示并保存。Specivo 按给定值保留,不会根据工时记录推算。
- 历史记录 保留原始作者和时间。状态、负责人、版本等的变更和其他变更一样按名称显示。
-
文本 为 Markdown。待所有记录都创建完毕后,对工作项、人员和附件的引用会用工作项新的 Specivo 编号改写为链接。
-
自定义字段值按来源原样保留,即使某个值不符合这里字段的架构(例如来源系统后来才加上校验,或后来才把某个字段设为必填)。报告会把每个这样的工作项和字段列为“Custom field value does not match its schema; kept”(自定义字段值不符合其架构;已保留)。该工作项仍可编辑,但之后再修改该字段时必须符合架构。参见 哪些值会被拒绝。
迁移工具无法迁移的内容都会列在导入报告中,不会有任何东西悄悄消失。
第 1 步:把导入包放到 Specivo 能读取的位置¶
使用 Docker 时,api 容器必须能看到该文件夹。在 docker-compose.override.yml 中挂载:
然后重启服务(docker compose up -d),把导入包复制到 ./imports。确保该文件夹对用户 ID 10001(应用运行所用的用户)可写:导入过程会在导入包旁边保存一个小的 链接文件(见 下文)。
附件很大的导入包可以把文件放在单独的文件夹中,通过 --files-path 指定。
第 2 步:校验、演练、导入¶
按顺序运行以下命令。前两条不会改动任何内容。
# 1. Check the bundle itself: schema, references, file hashes. No database is touched.
docker compose exec api python -m specivo.cli.import_data \
--source bundle --path /import/jira-export --validate
# 2. Rehearse: the whole import runs inside a transaction that is rolled back.
docker compose exec api python -m specivo.cli.import_data \
--source bundle --path /import/jira-export --dry-run
# 3. Import for real.
docker compose exec api python -m specivo.cli.import_data \
--source bundle --path /import/jira-export
继续之前先阅读演练的报告。报告会列出 Specivo 选定的项目标识、类别需要猜测的状态、需要邮箱地址的账户,以及所有无法迁移的内容。先备份,再导入。参见 备份与恢复。
值得了解的选项¶
| 选项 | 作用 |
|---|---|
--project IDS |
只导入这些来源项目(逗号分隔的来源 ID)。子项目随父项目一起导入。 |
--project-key-map ID=KEY,... |
为来源项目指定 Specivo 项目标识,不使用自动推导的标识。 |
--merge-duplicate-statuses |
复用同名的现有状态、追踪器或优先级,而不是再建一个。 |
--continue-import |
在同一来源的早先导入基础上追加(见下文)。 |
--resume RUN_ID |
继续一次中断的运行。每个阶段单独提交,已完成的部分会保留。 |
--links-file PATH |
链接文件的保存位置。 |
--files-path DIR |
从此文件夹读取附件文件,而不是导入包自带的 files/。 |
--import-passwords |
让导入的账户在首次登录前继续使用旧密码。需要匹配的 密码哈希插件。管理员始终需要设置新密码。 |
--import-admins |
让来源系统的管理员在这里也成为管理员。否则他们按普通账户导入。 |
--flatten-excess-depth |
把嵌套深度超过 Specivo 限制的项目重新挂到允许的层级。 |
--report-json PATH |
同时把报告写入文件。 |
--strict |
把每条警告都视为失败。用于测试迁移工具,不用于正式导入。 |
python -m specivo.cli.import_data --source bundle --help 会列出全部选项,包括不受信任导入包的大小限制。
没有邮箱地址的账户
有些来源系统不会提供每个人的邮箱地址。Specivo 会给这类账户一个永远收不到邮件的占位地址,并在报告中列出,方便你填入真实地址。
再次运行,以及后续追加¶
导入包中的每条记录都带有来源系统的稳定标识符。Specivo 会记住哪条来源记录变成了哪一行 Specivo 数据,并按 作用域(scope) 归档:作用域即来源系统加上导入包清单(manifest)中声明的实例。
- 再次加载同一个导入包不会创建任何新内容。
- 用
--continue-import加载 同一套安装的后续导出,会追加其间新建的记录。你可以借此先早早导入,在切换前再补齐差额。已导入的记录会被跳过,不会更新。 - 不带
--continue-import时,如果某个作用域已有数据行,Specivo 会拒绝该导入包,所以一个导入包无法通过声明来源把自己挂到另一次导入上。
分批导出¶
大型安装可以一次导出一个(或几个)项目。不同批次中的工作项仍然可以相互关联:评论中提到另一个工作项、两个工作项之间的关联、父工作项在另一批中的子工作项。
按任意顺序逐批加载,从第二批起加 --continue-import,并且每次使用同一个 --links-file。凡是指向尚未到达的工作项的内容,都会在链接文件中等待:
- 提及先写成纯文本,待另一批加载后变成链接;
- 两个工作项都存在后,才创建关联、设置父工作项。
没有待处理内容时,该文件会被删除。导入后有人编辑过的文本不会被改动。
旧工单号继续有效¶
导入后工作项会有新的编号,但它原来的编号会一并保留,因此邮件、维基页面和提交信息中的旧编号仍然能指向正确位置:
- 工作项页面在日期旁边显示它,例如 导入自:RT #123。API 中的工作项详情(
imported_from)和 MCP 工具specivo_show_issue也会显示。 - 搜索 可以通过
RT #123、#123或PROJ-7这样的编号找到该工作项。 /imported/{system}/{number}/会跳转到该工作项,例如https://specivo.example.com/imported/rt/123/。 其中 system 就是import-map list显示的系统名。如果两个导入的工作项有同一个编号(同一种系统的两套安装都可能有 123 号工单),该链接会改为打开针对它的搜索。
能看到该工作项的人都能看到它的旧编号。对于无权查看的人,编号不会指向任何位置,就像它不存在一样。这些编号不依赖导入的作用域:purge 之后它们依然保留。
要让旧链接本身继续有效,把旧主机名指向一台负责转发的 Web 服务器。这只能由你的 Web 服务器完成,因为每种系统的 URL 形式各不相同。以 nginx 为例:
# RT: /Ticket/Display.html?id=123
location = /Ticket/Display.html {
if ($arg_id !~ "^[0-9]+$") {
return 404;
}
return 301 https://specivo.example.com/imported/rt/$arg_id/;
}
# Redmine: /issues/45
location ~ ^/issues/(\d+)$ {
return 301 https://specivo.example.com/imported/redmine/$1/;
}
# Jira: /browse/PROJ-7
location ~ ^/browse/([A-Za-z0-9_-]+)$ {
return 301 https://specivo.example.com/imported/jira/$1/;
}
在此功能推出之前完成的导入,会在升级时根据导入记住的信息补上编号。这些是来源系统自己的 ID,对于另有独立键的系统可能不正确。用当前版本的导入工具重新生成同一个导入包,再加 --continue-import 运行一次即可纠正。工作项的其他内容不会改变。
查看和清理导入¶
admin 命令可以显示和管理各次导入记住的信息:
docker compose exec api python -m specivo.cli.admin import-map list
docker compose exec api python -m specivo.cli.admin import-map pending --links-file /import/specivo-links.SYSTEM.INSTANCE.sqlite
docker compose exec api python -m specivo.cli.admin import-map resolve-pending --links-file /import/... --yes
| 命令 | 作用 |
|---|---|
import-map list |
列出每个作用域及其数据行数和最后写入时间。 |
import-map pending |
统计链接文件中仍在等待的文本、关联和父工作项。只读。 |
import-map resolve-pending |
把现在能关联的内容关联起来。不带 --yes 时只报告。 |
import-map rename |
把作用域改为新的系统或实例,让下次导入能找到其数据行。工作项的旧编号会跟随新的系统名,因此 /imported/{system}/... 也随之改变。 |
import-map purge |
切换完成后删除作用域的数据行。已导入的记录及其旧编号会保留,但 之后再导入该来源会把所有内容重新创建一遍。 |
rename 和 purge 默认只是演练,加 --yes 才会真正执行。
只有在旧系统停用后才执行 purge
作用域的数据行是 Specivo 识别哪些内容已经导入的依据。purge 之后,就无法再追加该来源的后续导出。同时也要删除该作用域的链接文件。