@fern-api/replay 0.15.1 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +92 -189
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +92 -189
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -47
- package/dist/index.d.ts +25 -47
- package/dist/index.js +92 -189
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -554,9 +554,22 @@ function isGenerationCommit(commit) {
|
|
|
554
554
|
const hasGenerationMarker = commit.message.startsWith("[fern-generated]") || commit.message.startsWith("[fern-replay]") || commit.message.startsWith("[fern-autoversion]") || commit.message.includes("Generated by Fern") || commit.message.includes("\u{1F916} Generated with Fern") || // Squash merge of a Fern-generated PR uses the PR title as commit message.
|
|
555
555
|
// The default PR title is "SDK Generation" (from GithubStep's commitMessage default).
|
|
556
556
|
// GitHub appends "(#N)" for the PR number, e.g. "SDK Generation (#70)".
|
|
557
|
-
commit.message.startsWith("SDK Generation")
|
|
557
|
+
commit.message.startsWith("SDK Generation") || // Merge commit (GitHub's "Create a merge commit" option) of a fern-bot
|
|
558
|
+
// regen PR. First-parent walking lands on this commit, not on the
|
|
559
|
+
// generation commit on the side branch, so the classifier must treat
|
|
560
|
+
// it as a generation boundary. GitHub's default subject is
|
|
561
|
+
// "Merge pull request #N from <owner>/<branch>"; fern-bot branches are
|
|
562
|
+
// named "<owner>/fern-bot/<...>".
|
|
563
|
+
/^Merge pull request #\d+ from [^/]+\/fern-bot\//.test(commit.message);
|
|
558
564
|
return isBotAuthor || hasGenerationMarker;
|
|
559
565
|
}
|
|
566
|
+
function isGenerationBoundary(commit) {
|
|
567
|
+
const isFernSupport = FERN_SUPPORT_NAMES.includes(commit.authorName);
|
|
568
|
+
const isBotAuthor = !isFernSupport && (commit.authorLogin === FERN_BOT_LOGIN || commit.authorEmail === FERN_BOT_EMAIL || commit.authorName === FERN_BOT_NAME);
|
|
569
|
+
const isAutoversion = commit.message.startsWith("[fern-autoversion]");
|
|
570
|
+
const hasBoundaryMarker = commit.message.startsWith("[fern-generated]") || commit.message.startsWith("[fern-replay]") || commit.message.includes("Generated by Fern") || commit.message.includes("\u{1F916} Generated with Fern") || commit.message.startsWith("SDK Generation");
|
|
571
|
+
return isBotAuthor && !isAutoversion || hasBoundaryMarker;
|
|
572
|
+
}
|
|
560
573
|
function isReplayCommit(commit) {
|
|
561
574
|
return commit.message.startsWith("[fern-replay]");
|
|
562
575
|
}
|
|
@@ -884,8 +897,48 @@ var ReplayDetector = class {
|
|
|
884
897
|
this.lockManager = lockManager;
|
|
885
898
|
this.sdkOutputDir = sdkOutputDir;
|
|
886
899
|
}
|
|
900
|
+
/**
|
|
901
|
+
* Derive the previous-generation SHA from git history instead of trusting
|
|
902
|
+
* `lock.current_generation`.
|
|
903
|
+
*
|
|
904
|
+
* Walks `git log HEAD --first-parent` and returns the SHA of the most recent
|
|
905
|
+
* commit that `isGenerationBoundary` classifies as a customization-baseline
|
|
906
|
+
* boundary, or null if no such commit is reachable from HEAD on the mainline.
|
|
907
|
+
*
|
|
908
|
+
* `isGenerationBoundary` is intentionally narrower than `isGenerationCommit`:
|
|
909
|
+
* it excludes `[fern-autoversion]` (lightweight bumps that don't reset the
|
|
910
|
+
* baseline) and the GitHub merge-commit subject for fern-bot regen PRs
|
|
911
|
+
* (which sits on first-parent but where the customer fix-up commits live
|
|
912
|
+
* on second-parent — stopping at the merge would hide them).
|
|
913
|
+
*
|
|
914
|
+
* See docs/adr/0001-derived-scan-boundary.md.
|
|
915
|
+
*/
|
|
916
|
+
async findPreviousGenerationFromHistory() {
|
|
917
|
+
let log;
|
|
918
|
+
try {
|
|
919
|
+
log = await this.git.exec([
|
|
920
|
+
"log",
|
|
921
|
+
"--first-parent",
|
|
922
|
+
"--format=%H%x00%an%x00%ae%x00%s",
|
|
923
|
+
"HEAD"
|
|
924
|
+
]);
|
|
925
|
+
} catch {
|
|
926
|
+
return null;
|
|
927
|
+
}
|
|
928
|
+
for (const commit of this.parseGitLog(log)) {
|
|
929
|
+
if (isGenerationBoundary(commit)) {
|
|
930
|
+
return commit.sha;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
return null;
|
|
934
|
+
}
|
|
887
935
|
async detectNewPatches() {
|
|
888
936
|
const lock = this.lockManager.read();
|
|
937
|
+
const derivedSha = await this.findPreviousGenerationFromHistory();
|
|
938
|
+
if (derivedSha !== null) {
|
|
939
|
+
const derivedRecord = await this.synthesizeGenerationRecord(derivedSha);
|
|
940
|
+
return this.detectPatchesInRange(derivedSha, derivedRecord, lock);
|
|
941
|
+
}
|
|
889
942
|
const lastGen = this.getLastGeneration(lock);
|
|
890
943
|
if (!lastGen) {
|
|
891
944
|
return { patches: [], revertedPatchIds: [] };
|
|
@@ -893,7 +946,7 @@ var ReplayDetector = class {
|
|
|
893
946
|
const exists = await this.git.commitExists(lastGen.commit_sha);
|
|
894
947
|
if (!exists) {
|
|
895
948
|
this.warnings.push(
|
|
896
|
-
`Generation commit ${lastGen.commit_sha.slice(0, 7)} not found in git history. Falling back to
|
|
949
|
+
`Generation commit ${lastGen.commit_sha.slice(0, 7)} not found in git history. Falling back to tree-diff detection (likely a shallow clone).`
|
|
897
950
|
);
|
|
898
951
|
return this.detectPatchesViaTreeDiff(
|
|
899
952
|
lastGen,
|
|
@@ -901,44 +954,8 @@ var ReplayDetector = class {
|
|
|
901
954
|
true
|
|
902
955
|
);
|
|
903
956
|
}
|
|
904
|
-
const isAncestor = await this.git.isAncestor(lastGen.commit_sha, "HEAD");
|
|
905
|
-
if (!isAncestor) {
|
|
906
|
-
return this.detectPatchesViaMergeBase(lastGen, lock);
|
|
907
|
-
}
|
|
908
957
|
return this.detectPatchesInRange(lastGen.commit_sha, lastGen, lock);
|
|
909
958
|
}
|
|
910
|
-
/**
|
|
911
|
-
* Non-linear-history detection via `merge-base(prevGen, HEAD)`.
|
|
912
|
-
*
|
|
913
|
-
* After a squash-merge of a Fern-bot PR, `lastGen.commit_sha` (the previous
|
|
914
|
-
* `[fern-generated]`) is orphaned but still in git's object database. The
|
|
915
|
-
* merge-base is the commit on main from which the bot's branch was created —
|
|
916
|
-
* a reachable ancestor of HEAD. Walking that range and classifying each
|
|
917
|
-
* commit via `isGenerationCommit` mirrors the linear path and eliminates
|
|
918
|
-
* the bug class where pipeline-driven changes (autoversion, replay,
|
|
919
|
-
* generation) get bundled as customer customizations.
|
|
920
|
-
*
|
|
921
|
-
* Falls through to the legacy composite path when no common ancestor exists
|
|
922
|
-
* (truly disjoint history — orphan branches with no shared root).
|
|
923
|
-
*/
|
|
924
|
-
async detectPatchesViaMergeBase(lastGen, lock) {
|
|
925
|
-
let mergeBase = "";
|
|
926
|
-
try {
|
|
927
|
-
mergeBase = (await this.git.exec(["merge-base", lastGen.commit_sha, "HEAD"])).trim();
|
|
928
|
-
} catch {
|
|
929
|
-
}
|
|
930
|
-
if (!mergeBase) {
|
|
931
|
-
this.warnings.push(
|
|
932
|
-
`No common ancestor between previous generation ${lastGen.commit_sha.slice(0, 7)} and HEAD. Falling back to composite tree-diff detection.`
|
|
933
|
-
);
|
|
934
|
-
return this.detectPatchesViaTreeDiff(
|
|
935
|
-
lastGen,
|
|
936
|
-
/* commitKnownMissing */
|
|
937
|
-
false
|
|
938
|
-
);
|
|
939
|
-
}
|
|
940
|
-
return this.detectPatchesInRange(mergeBase, lastGen, lock);
|
|
941
|
-
}
|
|
942
959
|
/**
|
|
943
960
|
* Walk `${rangeStart}..HEAD`, classify each commit, emit one
|
|
944
961
|
* `StoredPatch` per customer commit. Body shared by the linear path
|
|
@@ -951,7 +968,7 @@ var ReplayDetector = class {
|
|
|
951
968
|
async detectPatchesInRange(rangeStart, lastGen, lock) {
|
|
952
969
|
const log = await this.git.exec([
|
|
953
970
|
"log",
|
|
954
|
-
"--
|
|
971
|
+
"--no-merges",
|
|
955
972
|
"--format=%H%x00%an%x00%ae%x00%s",
|
|
956
973
|
`${rangeStart}..HEAD`,
|
|
957
974
|
"--",
|
|
@@ -971,18 +988,6 @@ var ReplayDetector = class {
|
|
|
971
988
|
continue;
|
|
972
989
|
}
|
|
973
990
|
const parents = await this.git.getCommitParents(commit.sha);
|
|
974
|
-
if (parents.length > 1) {
|
|
975
|
-
const compositePatch = await this.createMergeCompositePatch({
|
|
976
|
-
mergeCommit: commit,
|
|
977
|
-
firstParent: parents[0],
|
|
978
|
-
lastGen,
|
|
979
|
-
lock
|
|
980
|
-
});
|
|
981
|
-
if (compositePatch) {
|
|
982
|
-
newPatches.push(compositePatch);
|
|
983
|
-
}
|
|
984
|
-
continue;
|
|
985
|
-
}
|
|
986
991
|
let patchContent;
|
|
987
992
|
try {
|
|
988
993
|
patchContent = await this.git.formatPatch(commit.sha);
|
|
@@ -1120,9 +1125,7 @@ var ReplayDetector = class {
|
|
|
1120
1125
|
* within the current linear detection window, and are absent from
|
|
1121
1126
|
* HEAD at the end of the window.
|
|
1122
1127
|
*
|
|
1123
|
-
* Rationale:
|
|
1124
|
-
* firstParent..mergeCommit so net-zero files never enter the composite. On
|
|
1125
|
-
* linear history each commit becomes its own patch, so a file that was
|
|
1128
|
+
* Rationale: each commit becomes its own patch, so a file that was
|
|
1126
1129
|
* briefly added and later removed survives as a create+delete pair whose
|
|
1127
1130
|
* cumulative diff is empty. We drop such patches before they reach the
|
|
1128
1131
|
* lockfile.
|
|
@@ -1189,54 +1192,6 @@ var ReplayDetector = class {
|
|
|
1189
1192
|
return /* @__PURE__ */ new Set();
|
|
1190
1193
|
}
|
|
1191
1194
|
}
|
|
1192
|
-
/**
|
|
1193
|
-
* Create a single composite patch for a merge commit by diffing the merge
|
|
1194
|
-
* commit against its first parent. This captures the net change of the
|
|
1195
|
-
* entire merged branch as one patch, eliminating accumulator chaining and
|
|
1196
|
-
* zero-net-change ghost conflicts.
|
|
1197
|
-
*/
|
|
1198
|
-
async createMergeCompositePatch(input) {
|
|
1199
|
-
const { mergeCommit, firstParent, lastGen, lock } = input;
|
|
1200
|
-
const forgottenHashes = new Set(lock.forgotten_hashes ?? []);
|
|
1201
|
-
const filesOutput = await this.git.exec([
|
|
1202
|
-
"diff",
|
|
1203
|
-
"--name-only",
|
|
1204
|
-
firstParent,
|
|
1205
|
-
mergeCommit.sha
|
|
1206
|
-
]);
|
|
1207
|
-
const { files, sensitiveFiles } = filterInfrastructureAndSensitiveFiles(filesOutput);
|
|
1208
|
-
if (sensitiveFiles.length > 0) {
|
|
1209
|
-
this.warnings.push(
|
|
1210
|
-
`Sensitive file(s) excluded from merge patch for commit ${mergeCommit.sha.slice(0, 7)}: ${sensitiveFiles.join(", ")}. These files will not be tracked by replay.`
|
|
1211
|
-
);
|
|
1212
|
-
}
|
|
1213
|
-
if (files.length === 0) return null;
|
|
1214
|
-
const diff = await this.git.exec([
|
|
1215
|
-
"diff",
|
|
1216
|
-
firstParent,
|
|
1217
|
-
mergeCommit.sha,
|
|
1218
|
-
"--",
|
|
1219
|
-
...files
|
|
1220
|
-
]);
|
|
1221
|
-
if (!diff.trim()) return null;
|
|
1222
|
-
const contentHash = this.computeContentHash(diff);
|
|
1223
|
-
if (lock.patches.some((p) => p.content_hash === contentHash) || forgottenHashes.has(contentHash)) {
|
|
1224
|
-
return null;
|
|
1225
|
-
}
|
|
1226
|
-
const theirsSnapshot = await capturePatchSnapshot(this.git, files, mergeCommit.sha);
|
|
1227
|
-
return {
|
|
1228
|
-
id: `patch-merge-${mergeCommit.sha.slice(0, 8)}`,
|
|
1229
|
-
content_hash: contentHash,
|
|
1230
|
-
original_commit: mergeCommit.sha,
|
|
1231
|
-
original_message: mergeCommit.message,
|
|
1232
|
-
original_author: `${mergeCommit.authorName} <${mergeCommit.authorEmail}>`,
|
|
1233
|
-
base_generation: lastGen.commit_sha,
|
|
1234
|
-
files,
|
|
1235
|
-
patch_content: diff,
|
|
1236
|
-
...Object.keys(theirsSnapshot).length > 0 ? { theirs_snapshot: theirsSnapshot } : {},
|
|
1237
|
-
...this.isCreationOnlyPatch(diff) ? { user_owned: true } : {}
|
|
1238
|
-
};
|
|
1239
|
-
}
|
|
1240
1195
|
/**
|
|
1241
1196
|
* Detect patches via tree diff for non-linear history. Returns a composite patch.
|
|
1242
1197
|
* Revert reconciliation is skipped here because tree-diff produces a single composite
|
|
@@ -1436,6 +1391,26 @@ var ReplayDetector = class {
|
|
|
1436
1391
|
getLastGeneration(lock) {
|
|
1437
1392
|
return lock.generations.find((g) => g.commit_sha === lock.current_generation);
|
|
1438
1393
|
}
|
|
1394
|
+
/**
|
|
1395
|
+
* Build a transient `GenerationRecord` from a SHA derived by walking git
|
|
1396
|
+
* history. Only `commit_sha` (and `tree_hash` when needed downstream) is
|
|
1397
|
+
* load-bearing here; the rest are filled with defensible placeholders.
|
|
1398
|
+
* This record is consumed by `detectPatchesInRange` and is never persisted.
|
|
1399
|
+
*/
|
|
1400
|
+
async synthesizeGenerationRecord(sha) {
|
|
1401
|
+
let tree_hash = "";
|
|
1402
|
+
try {
|
|
1403
|
+
tree_hash = await this.git.getTreeHash(sha);
|
|
1404
|
+
} catch {
|
|
1405
|
+
}
|
|
1406
|
+
return {
|
|
1407
|
+
commit_sha: sha,
|
|
1408
|
+
tree_hash,
|
|
1409
|
+
timestamp: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
1410
|
+
cli_version: "",
|
|
1411
|
+
generator_versions: {}
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1439
1414
|
};
|
|
1440
1415
|
|
|
1441
1416
|
// src/ThreeWayMerge.ts
|
|
@@ -2575,8 +2550,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
2575
2550
|
tree_hash: treeHash,
|
|
2576
2551
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2577
2552
|
cli_version: options?.cliVersion ?? "unknown",
|
|
2578
|
-
generator_versions: options?.generatorVersions ?? {}
|
|
2579
|
-
base_branch_head: options?.baseBranchHead
|
|
2553
|
+
generator_versions: options?.generatorVersions ?? {}
|
|
2580
2554
|
};
|
|
2581
2555
|
}
|
|
2582
2556
|
async stageAll() {
|
|
@@ -2963,7 +2937,6 @@ var FirstGenerationFlow = class {
|
|
|
2963
2937
|
flow: "first-generation",
|
|
2964
2938
|
previousGenerationSha: null,
|
|
2965
2939
|
currentGenerationSha: headSha,
|
|
2966
|
-
baseBranchHead: options.baseBranchHead ?? null,
|
|
2967
2940
|
_prepared: {
|
|
2968
2941
|
kind: "terminal",
|
|
2969
2942
|
flow: "first-generation",
|
|
@@ -2973,8 +2946,7 @@ var FirstGenerationFlow = class {
|
|
|
2973
2946
|
}
|
|
2974
2947
|
const commitOpts = options ? {
|
|
2975
2948
|
cliVersion: options.cliVersion ?? "unknown",
|
|
2976
|
-
generatorVersions: options.generatorVersions ?? {}
|
|
2977
|
-
baseBranchHead: options.baseBranchHead
|
|
2949
|
+
generatorVersions: options.generatorVersions ?? {}
|
|
2978
2950
|
} : void 0;
|
|
2979
2951
|
await this.service.committer.commitGeneration("Initial SDK generation", commitOpts);
|
|
2980
2952
|
const genRecord = await this.service.committer.createGenerationRecord(commitOpts);
|
|
@@ -2983,7 +2955,6 @@ var FirstGenerationFlow = class {
|
|
|
2983
2955
|
flow: "first-generation",
|
|
2984
2956
|
previousGenerationSha: null,
|
|
2985
2957
|
currentGenerationSha: genRecord.commit_sha,
|
|
2986
|
-
baseBranchHead: options?.baseBranchHead ?? null,
|
|
2987
2958
|
_prepared: {
|
|
2988
2959
|
kind: "active",
|
|
2989
2960
|
flow: "first-generation",
|
|
@@ -3020,8 +2991,7 @@ var SkipApplicationFlow = class {
|
|
|
3020
2991
|
async prepare(options) {
|
|
3021
2992
|
const commitOpts = options ? {
|
|
3022
2993
|
cliVersion: options.cliVersion ?? "unknown",
|
|
3023
|
-
generatorVersions: options.generatorVersions ?? {}
|
|
3024
|
-
baseBranchHead: options.baseBranchHead
|
|
2994
|
+
generatorVersions: options.generatorVersions ?? {}
|
|
3025
2995
|
} : void 0;
|
|
3026
2996
|
await this.service.committer.commitGeneration("Update SDK (replay skipped)", commitOpts);
|
|
3027
2997
|
await this.service.cleanupStaleConflictMarkers();
|
|
@@ -3043,7 +3013,6 @@ var SkipApplicationFlow = class {
|
|
|
3043
3013
|
flow: "skip-application",
|
|
3044
3014
|
previousGenerationSha,
|
|
3045
3015
|
currentGenerationSha: genRecord.commit_sha,
|
|
3046
|
-
baseBranchHead: options?.baseBranchHead ?? null,
|
|
3047
3016
|
_prepared: {
|
|
3048
3017
|
kind: "active",
|
|
3049
3018
|
flow: "skip-application",
|
|
@@ -3104,7 +3073,6 @@ var NoPatchesFlow = class {
|
|
|
3104
3073
|
flow: "no-patches",
|
|
3105
3074
|
previousGenerationSha,
|
|
3106
3075
|
currentGenerationSha: headSha,
|
|
3107
|
-
baseBranchHead: options.baseBranchHead ?? null,
|
|
3108
3076
|
_prepared: {
|
|
3109
3077
|
kind: "terminal",
|
|
3110
3078
|
flow: "no-patches",
|
|
@@ -3136,8 +3104,7 @@ var NoPatchesFlow = class {
|
|
|
3136
3104
|
}
|
|
3137
3105
|
const commitOpts = options ? {
|
|
3138
3106
|
cliVersion: options.cliVersion ?? "unknown",
|
|
3139
|
-
generatorVersions: options.generatorVersions ?? {}
|
|
3140
|
-
baseBranchHead: options.baseBranchHead
|
|
3107
|
+
generatorVersions: options.generatorVersions ?? {}
|
|
3141
3108
|
} : void 0;
|
|
3142
3109
|
await this.service.committer.commitGeneration("Update SDK", commitOpts);
|
|
3143
3110
|
await this.service.cleanupStaleConflictMarkers();
|
|
@@ -3147,7 +3114,6 @@ var NoPatchesFlow = class {
|
|
|
3147
3114
|
flow: "no-patches",
|
|
3148
3115
|
previousGenerationSha,
|
|
3149
3116
|
currentGenerationSha: genRecord.commit_sha,
|
|
3150
|
-
baseBranchHead: options?.baseBranchHead ?? null,
|
|
3151
3117
|
_prepared: {
|
|
3152
3118
|
kind: "active",
|
|
3153
3119
|
flow: "no-patches",
|
|
@@ -3237,7 +3203,6 @@ var NormalRegenerationFlow = class {
|
|
|
3237
3203
|
flow: "normal-regeneration",
|
|
3238
3204
|
previousGenerationSha,
|
|
3239
3205
|
currentGenerationSha: headSha,
|
|
3240
|
-
baseBranchHead: options.baseBranchHead ?? null,
|
|
3241
3206
|
_prepared: {
|
|
3242
3207
|
kind: "terminal",
|
|
3243
3208
|
flow: "normal-regeneration",
|
|
@@ -3255,14 +3220,16 @@ var NormalRegenerationFlow = class {
|
|
|
3255
3220
|
existingPatches = this.service.lockManager.getPatches();
|
|
3256
3221
|
const seenHashes = /* @__PURE__ */ new Map();
|
|
3257
3222
|
for (const p of existingPatches) {
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3223
|
+
if (!p.original_commit || p.original_commit === "") {
|
|
3224
|
+
const priorPatchId = seenHashes.get(p.content_hash);
|
|
3225
|
+
if (priorPatchId !== void 0) {
|
|
3226
|
+
this.service.lockManager.removePatch(p.id);
|
|
3227
|
+
this.service.warnings.push(
|
|
3228
|
+
`Consolidated patch ${p.id} into prior patch ${priorPatchId}: byte-identical patch_content. Lockfile collapsed to avoid duplicate-apply corruption on next regen.`
|
|
3229
|
+
);
|
|
3230
|
+
} else {
|
|
3231
|
+
seenHashes.set(p.content_hash, p.id);
|
|
3232
|
+
}
|
|
3266
3233
|
}
|
|
3267
3234
|
}
|
|
3268
3235
|
existingPatches = this.service.lockManager.getPatches();
|
|
@@ -3291,8 +3258,7 @@ var NormalRegenerationFlow = class {
|
|
|
3291
3258
|
const allPatches = [...existingPatches, ...newPatches];
|
|
3292
3259
|
const commitOpts = options ? {
|
|
3293
3260
|
cliVersion: options.cliVersion ?? "unknown",
|
|
3294
|
-
generatorVersions: options.generatorVersions ?? {}
|
|
3295
|
-
baseBranchHead: options.baseBranchHead
|
|
3261
|
+
generatorVersions: options.generatorVersions ?? {}
|
|
3296
3262
|
} : void 0;
|
|
3297
3263
|
await this.service.committer.commitGeneration("Update SDK", commitOpts);
|
|
3298
3264
|
await this.service.cleanupStaleConflictMarkers();
|
|
@@ -3302,7 +3268,6 @@ var NormalRegenerationFlow = class {
|
|
|
3302
3268
|
flow: "normal-regeneration",
|
|
3303
3269
|
previousGenerationSha,
|
|
3304
3270
|
currentGenerationSha: genRecord.commit_sha,
|
|
3305
|
-
baseBranchHead: options?.baseBranchHead ?? null,
|
|
3306
3271
|
_prepared: {
|
|
3307
3272
|
kind: "active",
|
|
3308
3273
|
flow: "normal-regeneration",
|
|
@@ -3495,68 +3460,6 @@ var ReplayService = class {
|
|
|
3495
3460
|
const prep = await this.prepareReplay(options);
|
|
3496
3461
|
return this.applyPreparedReplay(prep, options);
|
|
3497
3462
|
}
|
|
3498
|
-
/**
|
|
3499
|
-
* Sync the lockfile after a divergent PR was squash-merged.
|
|
3500
|
-
* Call this BEFORE runReplay() when the CLI detects a merged divergent PR.
|
|
3501
|
-
*
|
|
3502
|
-
* After updating the generation record, re-detects customer patches via
|
|
3503
|
-
* tree diff between the generation tag (pure generation tree) and HEAD
|
|
3504
|
-
* (which includes customer customizations after squash merge). This ensures
|
|
3505
|
-
* patches survive the squash merge → regenerate cycle even when the lockfile
|
|
3506
|
-
* was restored from the base branch during conflict PR creation.
|
|
3507
|
-
*/
|
|
3508
|
-
async syncFromDivergentMerge(generationCommitSha, options) {
|
|
3509
|
-
const treeHash = await this.git.getTreeHash(generationCommitSha);
|
|
3510
|
-
const timestamp = (await this.git.exec(["log", "-1", "--format=%aI", generationCommitSha])).trim();
|
|
3511
|
-
const record = {
|
|
3512
|
-
commit_sha: generationCommitSha,
|
|
3513
|
-
tree_hash: treeHash,
|
|
3514
|
-
timestamp,
|
|
3515
|
-
cli_version: options?.cliVersion ?? "unknown",
|
|
3516
|
-
generator_versions: options?.generatorVersions ?? {},
|
|
3517
|
-
base_branch_head: options?.baseBranchHead
|
|
3518
|
-
};
|
|
3519
|
-
let resolvedPatches;
|
|
3520
|
-
if (!this.lockManager.exists()) {
|
|
3521
|
-
this.lockManager.initializeInMemory(record);
|
|
3522
|
-
} else {
|
|
3523
|
-
this.lockManager.read();
|
|
3524
|
-
const unresolvedPatches = [
|
|
3525
|
-
...this.lockManager.getUnresolvedPatches(),
|
|
3526
|
-
...this.lockManager.getResolvingPatches()
|
|
3527
|
-
];
|
|
3528
|
-
resolvedPatches = this.lockManager.getPatches().filter((p) => p.status == null);
|
|
3529
|
-
this.lockManager.addGeneration(record);
|
|
3530
|
-
this.lockManager.clearPatches();
|
|
3531
|
-
for (const patch of unresolvedPatches) {
|
|
3532
|
-
this.lockManager.addPatch(patch);
|
|
3533
|
-
}
|
|
3534
|
-
}
|
|
3535
|
-
try {
|
|
3536
|
-
const { patches: redetectedPatches } = await this.detector.detectNewPatches();
|
|
3537
|
-
if (redetectedPatches.length > 0) {
|
|
3538
|
-
const redetectedFiles = new Set(redetectedPatches.flatMap((p) => p.files));
|
|
3539
|
-
const currentPatches = this.lockManager.getPatches();
|
|
3540
|
-
for (const patch of currentPatches) {
|
|
3541
|
-
if (patch.status != null && patch.files.some((f) => redetectedFiles.has(f))) {
|
|
3542
|
-
this.lockManager.removePatch(patch.id);
|
|
3543
|
-
}
|
|
3544
|
-
}
|
|
3545
|
-
for (const patch of redetectedPatches) {
|
|
3546
|
-
this.lockManager.addPatch(patch);
|
|
3547
|
-
}
|
|
3548
|
-
}
|
|
3549
|
-
} catch (error) {
|
|
3550
|
-
for (const patch of resolvedPatches ?? []) {
|
|
3551
|
-
this.lockManager.addPatch(patch);
|
|
3552
|
-
}
|
|
3553
|
-
this.detector.warnings.push(
|
|
3554
|
-
`Patch re-detection failed after divergent merge sync. ${(resolvedPatches ?? []).length} previously resolved patch(es) preserved. Error: ${error instanceof Error ? error.message : String(error)}`
|
|
3555
|
-
);
|
|
3556
|
-
}
|
|
3557
|
-
this.lockManager.save();
|
|
3558
|
-
this.detector.warnings.push(...this.lockManager.warnings);
|
|
3559
|
-
}
|
|
3560
3463
|
determineFlow() {
|
|
3561
3464
|
try {
|
|
3562
3465
|
const lock = this.lockManager.read();
|