Dagger Changeset.withChangeset 的 onConflict 冲突合并策略:从 TypeScript API 到引擎源码全解析

Dagger Changeset.withChangeset 的 onConflict 冲突合并策略:从 TypeScript API 到引擎源码全解析 Dagger Changeset.withChangeset 的 onConflict 冲突合并策略从 TypeScript API 到引擎源码全解析【免费下载链接】daggerAutomation engine to build, test and ship any codebase. Runs locally, in CI, or directly in the cloud项目地址: https://gitcode.com/GitHub_Trending/da/dagger导读本文以 Dagger v0.19 TypeScript SDK 的ChangesetWithChangesetOpts类型别名为切入点系统讲解 Dagger Changeset 的合并语义与冲突处理策略。你将理解onConflict各取值FAIL、FAIL_EARLY、LEAVE_CONFLICT_MARKERS、PREFER_OURS、PREFER_THEIRS的确切行为差异掌握在多分支流水线、并行变更聚合等场景下如何通过 TypeScript SDK 安全合并 Changeset并了解这一 API 在引擎层的 Git 三路合并3-way merge实现原理与版本演进细节。Changeset 是什么先厘清合并的对象在 Dagger 中Changeset是两个目录快照之间的差异比较结果它可以被看作一份可应用的变更集合。从 core/schema/testdata/base_schema.graphqls 的 GraphQL schema 可以看出 A comparison between two directories representing changes that can be applied. type Changeset implements Exportable Node Syncer {一个 Changeset 记录了三类路径变更对应 changeset.go 中的路径冲突检查逻辑addedPaths新目录中新增的文件与目录modifiedPaths旧目录中已存在、但在新目录中被更新的文件与目录removedPaths被移除的文件与目录目录以尾斜杠标识且不包含其子路径。同时它提供before旧快照、after新快照、asPatchGit 兼容补丁、layer仅包含创建与修改文件等派生能力。Changeset.withChangeset的作用正是把另一个 Changeset 合并进当前 Changeset其 schema 描述为 Add changes to an existing changeset By default the operation will fail in case of conflicts, for instance a file modified in both changesets. The behavior can be adjusted using onConflict argument withChangeset( changes: ID! expectedType(name: Changeset) onConflict: ChangesetMergeConflict FAIL ): Changeset!注意默认值onConflict缺省为FAIL。也就是说默认情况下只要两个 Changeset 存在冲突如同一文件都被修改合并就会失败。ChangesetWithChangesetOptsTypeScript 类型别名全景本文主角ChangesetWithChangesetOpts定义于 docs/versioned_docs/version-0.19/reference/typescript/api/client.gen/type-aliases/ChangesetWithChangesetOpts.md它由 codegen 从上述 GraphQL 的withChangeset字段自动生成是 TypeScript 调用端的参数对象类型type ChangesetWithChangesetOpts { /** * What to do on a merge conflict */ onConflict?: ChangesetMergeConflict; };该类型只有一个可选属性onConflict其取值类型ChangesetMergeConflict是一个枚举。需要说明的是changes待合并的 Changeset在生成的 SDK 中通常是作为独立参数传入的例如withChangeset(changes, opts?)形态而ChangesetWithChangesetOpts专门承载策略参数onConflict。在 TypeScript 中对应生成的客户端方法签名位于 sdk/typescript 的 codegen 产物形如withChangeset(changes: Changeset, opts?: ChangesetWithChangesetOpts): Changeset因此实际调用方式是const merged mine.withChangeset(theirs, { onConflict: ChangesetMergeConflict.PreferOurs, });省略opts时等价于传入{ onConflict: ChangesetMergeConflict.Fail }。ChangesetMergeConflict 枚举五种冲突策略详解ChangesetMergeConflict枚举的完整定义见 enumerations/ChangesetMergeConflict.md引擎侧对应的 GraphQL 枚举与 Go 实现位于 core/schema/testdata/base_schema.graphqls 与 core/changeset.go。枚举成员TypeScript底层字符串值GraphQL行为说明FailFAIL尝试执行合并若 Git 合并因冲突失败则整体失败默认策略FailEarlyFAIL_EARLY在尝试合并之前先检测文件级冲突若检测到冲突立即失败不执行合并LeaveConflictMarkersLEAVE_CONFLICT_MARKERS让 Git 在文件中写入冲突标记对修改/删除冲突保留修改后的版本遇到二进制冲突则失败PreferOursPREFER_OURS冲突以**调用方当前 Changeset**的版本为准PreferTheirsPREFER_THEIRS冲突以**对方被合并的 Changeset**的版本为准策略的引擎层注释Go 源码原话core/changeset.go 中定义了内部策略常量注释即是对五种行为的权威解释// FailEarlyOnConflict fails before attempting merge if file-level conflicts are detected. FailEarlyOnConflict WithChangesetMergeConflict iota // FailOnConflict attempts the merge and fails if git merge fails due to conflicts. FailOnConflict // LeaveConflictMarkers lets git create conflict markers in files. For modify/delete // conflicts, keeps the modified version. Fails on binary conflicts. LeaveConflictMarkers // PreferOursOnConflict uses -X ours strategy and resolves modify/delete by preferring ours. PreferOursOnConflict // PreferTheirsOnConflict uses -X theirs strategy and resolves modify/delete by preferring theirs. PreferTheirsOnConflict其中-X ours/-X theirs直接对应 Git merge 的-X--strategy-option参数说明PREFER_OURS/PREFER_THEIRS最终是透传给 Git 底层合并器的策略选项。版本演进注意点FAIL 与 FAIL_EARLY 的 SDK 暴露范围从 core/changeset.go 可以看到一个容易被忽视的细节FAIL与FAIL_EARLY这两个枚举值通过RegisterView注册并带有AfterVersion(v0.15.0)版本门槛而LEAVE_CONFLICT_MARKERS、PREFER_OURS、PREFER_THEIRS三个值则无版本限制。源码注释说明starting with engine version 0.15.0 Go codegen only exposes scoped enum values... Ensure those enum values are only exposed on engines 0.15.0即FAIL与FAIL_EARLY仅在引擎版本 ≥ 0.15.0 时才暴露给各 SDK 的 codegen 产物以避免与多 Changeset 合并枚举ChangesetsMergeConflict中的同名值FAIL、FAIL_EARLY发生命名冲突。如果你的项目基于更早引擎版本且 codegen 生成结果里看不到这两个成员属于预期行为。引擎层执行流程三路合并是如何发生的Changeset.withChangeset的 Go 实现位于 core/changeset.go整个流程可以概括为 5 步计算路径集合分别对两个 Changeset 调用ComputePaths得到我们的路径与他们的路径文件级冲突预检调用CheckConflicts检测路径级冲突如同一路径一边新增一边修改、一边修改一边删除等参见 changeset.go 中的ErrModifiedRemoved等冲突类型FailEarly提前失败若检测到冲突且策略为FailEarlyOnConflict直接返回冲突错误不进入合并if !conflicts.IsEmpty() onConflictStrategy FailEarlyOnConflict { return nil, conflicts.Error() }Git 三路合并先通过mergeBeforeDirectories构造合并基线before再物化双方内容调用gitMergeChangesets执行 git 3-way merge生成新 Changeset基于合并后的目录与原before快照通过newChangesetFromMerge返回新的 Changeset 对象。可见FAIL_EARLY与FAIL的核心区别在于失败发生的时机前者在路径级预检阶段即失败开销小、反馈快后者把判断交给 Git 合并器只有真正发生冲突内容合并失败时才失败。这也解释了为什么FAIL_EARLY适合在合并前就明确知道存在重叠路径的场景而FAIL允许路径重叠但内容可自动合并的情况通过。多 Changeset 批量合并的差异WithChangesets需要区分的是引擎还提供WithChangesets对应 GraphQL 的withChangesets用于使用 Git octopus merge 策略一次合并多个 Changeset其策略枚举是独立的ChangesetsMergeConflict且仅支持FAIL_EARLY与FAIL两种取值core/changeset.go 注释明确不支持-X ours/theirs// WithChangesetsMergeConflict specifies how to handle conflicts when merging multiple changesets // using gits octopus merge strategy. Only FAIL_EARLY and FAIL are supported (no -X ours/theirs). type WithChangesetsMergeConflict int而本文的ChangesetWithChangesetOpts/ChangesetMergeConflict只服务于两两合并的withChangeset两者不要混淆。另外两两合并逻辑通过 changeset.go 的parallel.Jobs与maxParallelChangesets 8上限控制并发说明批量合并时引擎会限制同时挂载的 snapshot 数量避免 I/O 资源被过度占用。实战TypeScript 中如何选择冲突策略场景一严格串行流水线——使用默认 Fail当你的两个 Changeset 来自同一分支上的顺序变更理论上不应冲突此时保持默认策略一旦出现意外重叠例如上游改动了相同文件立即报错便于尽早暴露问题import { client } from ./client.ts; const base client.host().directory(src); const changesA client.directory().withDirectory(., base).diff(base); const changesB client.directory().withDirectory(., base).diff(base); // 不传 opts等价于 { onConflict: ChangesetMergeConflict.Fail } const merged changesA.withChangeset(changesB);场景二希望快速失败——使用 FailEarly当合并的 Changeset 数量多、文件路径重叠概率高且你希望避免不必要的 Git 合并计算时选择FailEarly。它在路径级预检阶段就拒绝合并import { ChangesetMergeConflict } from dagger.io/dagger; const merged changesA.withChangeset(changesB, { onConflict: ChangesetMergeConflict.FailEarly, });引擎行为对应上文第 3 步检测到任何文件级冲突立即返回错误不挂载快照、不执行 merge。场景三人工介入解决冲突——使用 LeaveConflictMarkers当两个 Changeset 确实存在内容冲突但你希望保留冲突现场、由后续流程或人手动处理时使用const merged changesA.withChangeset(changesB, { onConflict: ChangesetMergeConflict.LeaveConflictMarkers, });此时 Git 会在冲突文件中写入标准的//冲突标记。需要留意两个限制来自枚举注释对于修改/删除modify/delete冲突保留的是修改后的版本对于二进制文件冲突会直接失败。场景四以我方为准——使用 PreferOurs当被合并的 Changeset 是辅助性变更如格式化、代码风格调整你希望当前 Changeset调用方的内容始终胜出const merged changesA.withChangeset(changesB, { onConflict: ChangesetMergeConflict.PreferOurs, });引擎透传git merge -X ours语义冲突区域一律采用changesA的版本。场景五以对方为准——使用 PreferTheirs当当前 Changeset 只是待合并的基础上下文被合并的 Changeset对方才是权威来源时const merged changesA.withChangeset(changesB, { onConflict: ChangesetMergeConflict.PreferTheirs, });引擎透传git merge -X theirs语义冲突区域一律采用changesB的版本。底层验证与测试依据引擎的冲突检测与合并行为有完整的测试覆盖可进一步追踪changeset 合并相关测试core/integration/changeset_test.go 覆盖withChangeset/withChangesets的集成行为路径冲突检测core/changeset_patch_test.go 验证CheckConflicts对新增/修改/删除重叠路径的判定Git 集成测试core/integration/git_test.go 中与 changeset 相关的用例验证三路合并结果GraphQL schema 基线core/schema/testdata/base_schema.graphqls 中的withChangeset定义是 codegen 生成ChangesetWithChangesetOpts的唯一事实来源。这些测试与 schema 基线共同保证了文档、SDK 类型与引擎实现三者的一致性本文档中描述的每个枚举值都能在 GraphQL schema 中找到同名字符串值并在 Go 引擎中找到对应处理分支。总结ChangesetWithChangesetOpts虽小却是 Dagger Changeset 合并能力的关键开关。理解onConflict的五种策略本质上是理解合并失败时机FAIL_EARLYvsFAIL与冲突裁决倾向LEAVE_CONFLICT_MARKERS、PREFER_OURS、PREFER_THEIRS两个维度。结合 core/changeset.go 的三路合并实现你可以在 CI 聚合、多分支并行变更、自动化重构等场景中准确选择策略避免因默认FAIL行为而中断整个流水线或因盲目使用PREFER_*而静默丢失数据。【免费下载链接】daggerAutomation engine to build, test and ship any codebase. Runs locally, in CI, or directly in the cloud项目地址: https://gitcode.com/GitHub_Trending/da/dagger创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考