GitLab Integration
GitLab Integration 模块是系统和 GitLab issue 世界之间的“转换枢纽”。它的价值不在于“会调几个 REST API”,而在于把两套本质不同的语义模型对齐:GitLab 偏 API/标签驱动(例如 opened、priority::high、IID),而系统内部偏领域对象驱动(types.Issue、结构化 Status/Priority/IssueType、统一 tracker 契约)。没有这层,主同步引擎就会被 GitLab 细节污染,最终变成难以维护的条件分支森林。
架构总览
叙事化走读(它是怎么“思考”的)
可以把这个模块想成机场里的“GitLab 专用边检通道”:
Tracker是柜台本身,负责接待上游统一流程(IssueTracker接口);Client是外事窗口,专门和 GitLab API 通信;gitlabFieldMapper + mapping.go是翻译官,把 GitLab 词汇翻译成内部词汇;types.go里的结构体是“护照格式”,用于承载 GitLab 原生字段与同步过程中的中间语义。
典型 pull 流程:
- 上游调用
(*Tracker).FetchIssues,传入tracker.FetchOptions。 Tracker先做状态归一(例如open -> opened),再调用Client.FetchIssues或Client.FetchIssuesSince。- 返回的 GitLab
Issue被gitlabToTrackerIssue包装成tracker.TrackerIssue(保留Raw)。 - 上游再通过
FieldMapper().IssueToBeads进入GitLabIssueToBeads,产出*types.Issue和依赖信息。
典型 push 流程:
- 上游给
(*Tracker).CreateIssue/(*Tracker).UpdateIssue传入*types.Issue。 BeadsIssueToGitLabFields把内部结构编码成 GitLab 字段 map(labels,weight,state_event等)。Tracker调Client.CreateIssue/Client.UpdateIssue发请求。- 响应再次进入
gitlabToTrackerIssue,保证回传结构和 pull 路径一致。
1) 这个模块解决了什么问题?
问题空间
GitLab 集成不是“字段重命名”问题,而是“三重不一致”问题:
- 模型不一致:GitLab 用标签承载很多业务语义(
priority::,status::,type::),内部系统用结构化字段。 - 标识不一致:GitLab 同时有
ID(全局)和IID(项目内);内部系统需要稳定 external ref 与来源标识。 - 流程不一致:依赖链接常常不能在 issue 首次导入时立刻建立,需要分阶段处理。
为什么不把逻辑塞进同步引擎?
这是一个刻意避免的方案。若把 GitLab 细节塞进通用引擎,会导致:
- 引擎与平台耦合升级,新增 Jira/Linear 时重复劳动;
- 测试维度膨胀(通用流程测试混入平台特例);
- 平台 API 变化影响核心路径,回归风险更大。
当前设计把“平台差异”隔离在 GitLab 插件边界,属于典型 adapter boundary 决策。
2) 心智模型:你该在脑中保留哪些抽象?
对新同学最有效的模型是“四层”:
- 协议镜像层(types):
Issue,User,Project,IssueLink等,尽量贴近 GitLab JSON。 - 策略层(MappingConfig):优先级/状态/类型/关系的映射规则。
- 转换层(mapping + fieldmapper):整单转换与标量转换。
- 适配层(Tracker):实现
tracker.IssueTracker,向上提供统一行为。
这四层对应不同变化源:
- GitLab API 变化,多数在第1层;
- 团队语义约定变化,多数在第2层;
- 同步行为变化,多数在第3层;
- 框架契约变化,多数在第4层。
3) 关键数据流(基于实际调用关系)
A. 初始化与配置装配
(*Tracker).Init 的依赖链:
Tracker.Init->Tracker.getConfig->storage.Storage.GetConfig- 同时回退环境变量:
GITLAB_TOKEN,GITLAB_URL,GITLAB_PROJECT_ID - 成功后:
NewClient(token, baseURL, projectID)+DefaultMappingConfig()
隐含合同:
gitlab.token和gitlab.project_id缺失会直接失败;gitlab.url缺失默认https://gitlab.com。
B. 拉取 issues
FetchIssues 路径:
Tracker.FetchIssues->Client.FetchIssues/Client.FetchIssuesSince- 每条
*Issue->gitlabToTrackerIssue - 上游再进入
gitlabFieldMapper.IssueToBeads->GitLabIssueToBeads
关键点:
open被翻译为opened;TrackerIssue.Raw保存*gitlab.Issue,后续转换依赖它做类型断言。
C. 推送 create/update
CreateIssue:
Tracker.CreateIssue->BeadsIssueToGitLabFields->Client.CreateIssue
UpdateIssue:
Tracker.UpdateIssue->BeadsIssueToGitLabFields->Client.UpdateIssue
关键点:
externalID被当作 GitLabIID(字符串数字)解析;StatusClosed会生成state_event=close,而非仅靠标签。
D. 外部引用识别
IsExternalRef(ref):同时要求包含gitlab且匹配/issues/(\d+)ExtractIdentifier(ref):从 URL 提取 IIDBuildExternalRef(issue):优先 URL,否则gitlab:<identifier>
这三者共同定义了“外部引用协议”;它影响增量更新和冲突路径的可定位性。
4) 关键设计决策与取舍
决策1:使用标签编码业务语义(而非扩展 schema)
已选方案:priority::x, status::x, type::x。
- 优点:兼容 GitLab 原生能力,部署简单,不依赖额外字段。
- 代价:语义是“约定式”的,容易受标签命名漂移影响。
为什么合理:对于跨团队、跨实例 GitLab,这是最现实的低门槛方案。
决策2:默认值优先容错(而非严格失败)
例如优先级默认 2、类型默认 task、未知关系默认 related。
- 优点:同步连续性更强,不因单条脏数据阻塞全量同步。
- 代价:配置错误可能被“静默降级”。
决策3:IssueConversion 分离 dependencies(两阶段)
- 优点:避免“目标 issue 还没导入”的顺序问题。
- 代价:流程复杂度增加,需要后续链接阶段。
决策4:边界内外双模型并存
GitLab 包内有自己的 IssueConversion/Conflict/SyncResult,tracker 框架也有同名概念。
- 优点:插件内部可贴近 GitLab 语义;
- 代价:需要适配转换,存在模型重复。
这是一种“边界隔离换演进自由”的取舍。
5) 新贡献者应重点关注的隐含合同与坑
IIDvsID不可混用:FetchIssue/UpdateIssue路径依赖IID(字符串数字)。Raw必须保留原始*Issue:否则IssueToBeads会因类型断言失败返回nil。- 状态双通道:
closed/opened用 state,in_progress/blocked/deferred用标签;改一边不改另一边会出偏差。 - 外部引用格式一致性:
BuildExternalRef的回退格式gitlab:<id>不匹配当前 URL 正则识别规则,URL 缺失时要特别小心。 - 估算精度损失:
EstimatedMinutes / 60写入weight,小于 60 分钟会被截断。 FetchOptions.Limit当前未在Tracker.FetchIssues使用(从现有代码可见),如果上游依赖限量抓取,需要补实现。
子模块导读
-
gitlab_types
讲清Client、GitLab 协议结构体(Issue/User/Project/...)以及 API 边界的设计哲学。 -
gitlab_tracker
讲清Tracker如何实现IssueTracker契约、如何装配配置、如何连接 Client 与映射层。 -
gitlab_fieldmapper
讲清gitlabFieldMapper的具体机制,它如何在 GitLab 数据模型和 Beads 内部模型之间进行转换。 -
gitlab_mapping
讲清MappingConfig、GitLabIssueToBeads、BeadsIssueToGitLabFields等映射配置与转换函数的细节。
跨模块依赖与耦合点
- 对上游框架:依赖 Tracker Integration Framework 的
IssueTracker/FieldMapper契约。 - 对核心领域:读写 Core Domain Types 中的
types.Issue,types.Status,types.IssueType。 - 对配置与存储:初始化依赖 Storage Interfaces 的
Storage.GetConfig。 - 对路由/CLI:间接被 CLI 和同步命令调用,但这些调用通过 tracker 框架间接发生,而不是直接调用 GitLab client。
从架构角色看,GitLab Integration 是一个边界适配模块(gateway + transformer):它不主导业务流程,但决定了系统与 GitLab 的语义接缝是否稳定。