@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.js
CHANGED
|
@@ -499,9 +499,22 @@ function isGenerationCommit(commit) {
|
|
|
499
499
|
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.
|
|
500
500
|
// The default PR title is "SDK Generation" (from GithubStep's commitMessage default).
|
|
501
501
|
// GitHub appends "(#N)" for the PR number, e.g. "SDK Generation (#70)".
|
|
502
|
-
commit.message.startsWith("SDK Generation")
|
|
502
|
+
commit.message.startsWith("SDK Generation") || // Merge commit (GitHub's "Create a merge commit" option) of a fern-bot
|
|
503
|
+
// regen PR. First-parent walking lands on this commit, not on the
|
|
504
|
+
// generation commit on the side branch, so the classifier must treat
|
|
505
|
+
// it as a generation boundary. GitHub's default subject is
|
|
506
|
+
// "Merge pull request #N from <owner>/<branch>"; fern-bot branches are
|
|
507
|
+
// named "<owner>/fern-bot/<...>".
|
|
508
|
+
/^Merge pull request #\d+ from [^/]+\/fern-bot\//.test(commit.message);
|
|
503
509
|
return isBotAuthor || hasGenerationMarker;
|
|
504
510
|
}
|
|
511
|
+
function isGenerationBoundary(commit) {
|
|
512
|
+
const isFernSupport = FERN_SUPPORT_NAMES.includes(commit.authorName);
|
|
513
|
+
const isBotAuthor = !isFernSupport && (commit.authorLogin === FERN_BOT_LOGIN || commit.authorEmail === FERN_BOT_EMAIL || commit.authorName === FERN_BOT_NAME);
|
|
514
|
+
const isAutoversion = commit.message.startsWith("[fern-autoversion]");
|
|
515
|
+
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");
|
|
516
|
+
return isBotAuthor && !isAutoversion || hasBoundaryMarker;
|
|
517
|
+
}
|
|
505
518
|
function isReplayCommit(commit) {
|
|
506
519
|
return commit.message.startsWith("[fern-replay]");
|
|
507
520
|
}
|
|
@@ -829,8 +842,48 @@ var ReplayDetector = class {
|
|
|
829
842
|
this.lockManager = lockManager;
|
|
830
843
|
this.sdkOutputDir = sdkOutputDir;
|
|
831
844
|
}
|
|
845
|
+
/**
|
|
846
|
+
* Derive the previous-generation SHA from git history instead of trusting
|
|
847
|
+
* `lock.current_generation`.
|
|
848
|
+
*
|
|
849
|
+
* Walks `git log HEAD --first-parent` and returns the SHA of the most recent
|
|
850
|
+
* commit that `isGenerationBoundary` classifies as a customization-baseline
|
|
851
|
+
* boundary, or null if no such commit is reachable from HEAD on the mainline.
|
|
852
|
+
*
|
|
853
|
+
* `isGenerationBoundary` is intentionally narrower than `isGenerationCommit`:
|
|
854
|
+
* it excludes `[fern-autoversion]` (lightweight bumps that don't reset the
|
|
855
|
+
* baseline) and the GitHub merge-commit subject for fern-bot regen PRs
|
|
856
|
+
* (which sits on first-parent but where the customer fix-up commits live
|
|
857
|
+
* on second-parent — stopping at the merge would hide them).
|
|
858
|
+
*
|
|
859
|
+
* See docs/adr/0001-derived-scan-boundary.md.
|
|
860
|
+
*/
|
|
861
|
+
async findPreviousGenerationFromHistory() {
|
|
862
|
+
let log;
|
|
863
|
+
try {
|
|
864
|
+
log = await this.git.exec([
|
|
865
|
+
"log",
|
|
866
|
+
"--first-parent",
|
|
867
|
+
"--format=%H%x00%an%x00%ae%x00%s",
|
|
868
|
+
"HEAD"
|
|
869
|
+
]);
|
|
870
|
+
} catch {
|
|
871
|
+
return null;
|
|
872
|
+
}
|
|
873
|
+
for (const commit of this.parseGitLog(log)) {
|
|
874
|
+
if (isGenerationBoundary(commit)) {
|
|
875
|
+
return commit.sha;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
return null;
|
|
879
|
+
}
|
|
832
880
|
async detectNewPatches() {
|
|
833
881
|
const lock = this.lockManager.read();
|
|
882
|
+
const derivedSha = await this.findPreviousGenerationFromHistory();
|
|
883
|
+
if (derivedSha !== null) {
|
|
884
|
+
const derivedRecord = await this.synthesizeGenerationRecord(derivedSha);
|
|
885
|
+
return this.detectPatchesInRange(derivedSha, derivedRecord, lock);
|
|
886
|
+
}
|
|
834
887
|
const lastGen = this.getLastGeneration(lock);
|
|
835
888
|
if (!lastGen) {
|
|
836
889
|
return { patches: [], revertedPatchIds: [] };
|
|
@@ -838,7 +891,7 @@ var ReplayDetector = class {
|
|
|
838
891
|
const exists = await this.git.commitExists(lastGen.commit_sha);
|
|
839
892
|
if (!exists) {
|
|
840
893
|
this.warnings.push(
|
|
841
|
-
`Generation commit ${lastGen.commit_sha.slice(0, 7)} not found in git history. Falling back to
|
|
894
|
+
`Generation commit ${lastGen.commit_sha.slice(0, 7)} not found in git history. Falling back to tree-diff detection (likely a shallow clone).`
|
|
842
895
|
);
|
|
843
896
|
return this.detectPatchesViaTreeDiff(
|
|
844
897
|
lastGen,
|
|
@@ -846,44 +899,8 @@ var ReplayDetector = class {
|
|
|
846
899
|
true
|
|
847
900
|
);
|
|
848
901
|
}
|
|
849
|
-
const isAncestor = await this.git.isAncestor(lastGen.commit_sha, "HEAD");
|
|
850
|
-
if (!isAncestor) {
|
|
851
|
-
return this.detectPatchesViaMergeBase(lastGen, lock);
|
|
852
|
-
}
|
|
853
902
|
return this.detectPatchesInRange(lastGen.commit_sha, lastGen, lock);
|
|
854
903
|
}
|
|
855
|
-
/**
|
|
856
|
-
* Non-linear-history detection via `merge-base(prevGen, HEAD)`.
|
|
857
|
-
*
|
|
858
|
-
* After a squash-merge of a Fern-bot PR, `lastGen.commit_sha` (the previous
|
|
859
|
-
* `[fern-generated]`) is orphaned but still in git's object database. The
|
|
860
|
-
* merge-base is the commit on main from which the bot's branch was created —
|
|
861
|
-
* a reachable ancestor of HEAD. Walking that range and classifying each
|
|
862
|
-
* commit via `isGenerationCommit` mirrors the linear path and eliminates
|
|
863
|
-
* the bug class where pipeline-driven changes (autoversion, replay,
|
|
864
|
-
* generation) get bundled as customer customizations.
|
|
865
|
-
*
|
|
866
|
-
* Falls through to the legacy composite path when no common ancestor exists
|
|
867
|
-
* (truly disjoint history — orphan branches with no shared root).
|
|
868
|
-
*/
|
|
869
|
-
async detectPatchesViaMergeBase(lastGen, lock) {
|
|
870
|
-
let mergeBase = "";
|
|
871
|
-
try {
|
|
872
|
-
mergeBase = (await this.git.exec(["merge-base", lastGen.commit_sha, "HEAD"])).trim();
|
|
873
|
-
} catch {
|
|
874
|
-
}
|
|
875
|
-
if (!mergeBase) {
|
|
876
|
-
this.warnings.push(
|
|
877
|
-
`No common ancestor between previous generation ${lastGen.commit_sha.slice(0, 7)} and HEAD. Falling back to composite tree-diff detection.`
|
|
878
|
-
);
|
|
879
|
-
return this.detectPatchesViaTreeDiff(
|
|
880
|
-
lastGen,
|
|
881
|
-
/* commitKnownMissing */
|
|
882
|
-
false
|
|
883
|
-
);
|
|
884
|
-
}
|
|
885
|
-
return this.detectPatchesInRange(mergeBase, lastGen, lock);
|
|
886
|
-
}
|
|
887
904
|
/**
|
|
888
905
|
* Walk `${rangeStart}..HEAD`, classify each commit, emit one
|
|
889
906
|
* `StoredPatch` per customer commit. Body shared by the linear path
|
|
@@ -896,7 +913,7 @@ var ReplayDetector = class {
|
|
|
896
913
|
async detectPatchesInRange(rangeStart, lastGen, lock) {
|
|
897
914
|
const log = await this.git.exec([
|
|
898
915
|
"log",
|
|
899
|
-
"--
|
|
916
|
+
"--no-merges",
|
|
900
917
|
"--format=%H%x00%an%x00%ae%x00%s",
|
|
901
918
|
`${rangeStart}..HEAD`,
|
|
902
919
|
"--",
|
|
@@ -916,18 +933,6 @@ var ReplayDetector = class {
|
|
|
916
933
|
continue;
|
|
917
934
|
}
|
|
918
935
|
const parents = await this.git.getCommitParents(commit.sha);
|
|
919
|
-
if (parents.length > 1) {
|
|
920
|
-
const compositePatch = await this.createMergeCompositePatch({
|
|
921
|
-
mergeCommit: commit,
|
|
922
|
-
firstParent: parents[0],
|
|
923
|
-
lastGen,
|
|
924
|
-
lock
|
|
925
|
-
});
|
|
926
|
-
if (compositePatch) {
|
|
927
|
-
newPatches.push(compositePatch);
|
|
928
|
-
}
|
|
929
|
-
continue;
|
|
930
|
-
}
|
|
931
936
|
let patchContent;
|
|
932
937
|
try {
|
|
933
938
|
patchContent = await this.git.formatPatch(commit.sha);
|
|
@@ -1065,9 +1070,7 @@ var ReplayDetector = class {
|
|
|
1065
1070
|
* within the current linear detection window, and are absent from
|
|
1066
1071
|
* HEAD at the end of the window.
|
|
1067
1072
|
*
|
|
1068
|
-
* Rationale:
|
|
1069
|
-
* firstParent..mergeCommit so net-zero files never enter the composite. On
|
|
1070
|
-
* linear history each commit becomes its own patch, so a file that was
|
|
1073
|
+
* Rationale: each commit becomes its own patch, so a file that was
|
|
1071
1074
|
* briefly added and later removed survives as a create+delete pair whose
|
|
1072
1075
|
* cumulative diff is empty. We drop such patches before they reach the
|
|
1073
1076
|
* lockfile.
|
|
@@ -1134,54 +1137,6 @@ var ReplayDetector = class {
|
|
|
1134
1137
|
return /* @__PURE__ */ new Set();
|
|
1135
1138
|
}
|
|
1136
1139
|
}
|
|
1137
|
-
/**
|
|
1138
|
-
* Create a single composite patch for a merge commit by diffing the merge
|
|
1139
|
-
* commit against its first parent. This captures the net change of the
|
|
1140
|
-
* entire merged branch as one patch, eliminating accumulator chaining and
|
|
1141
|
-
* zero-net-change ghost conflicts.
|
|
1142
|
-
*/
|
|
1143
|
-
async createMergeCompositePatch(input) {
|
|
1144
|
-
const { mergeCommit, firstParent, lastGen, lock } = input;
|
|
1145
|
-
const forgottenHashes = new Set(lock.forgotten_hashes ?? []);
|
|
1146
|
-
const filesOutput = await this.git.exec([
|
|
1147
|
-
"diff",
|
|
1148
|
-
"--name-only",
|
|
1149
|
-
firstParent,
|
|
1150
|
-
mergeCommit.sha
|
|
1151
|
-
]);
|
|
1152
|
-
const { files, sensitiveFiles } = filterInfrastructureAndSensitiveFiles(filesOutput);
|
|
1153
|
-
if (sensitiveFiles.length > 0) {
|
|
1154
|
-
this.warnings.push(
|
|
1155
|
-
`Sensitive file(s) excluded from merge patch for commit ${mergeCommit.sha.slice(0, 7)}: ${sensitiveFiles.join(", ")}. These files will not be tracked by replay.`
|
|
1156
|
-
);
|
|
1157
|
-
}
|
|
1158
|
-
if (files.length === 0) return null;
|
|
1159
|
-
const diff = await this.git.exec([
|
|
1160
|
-
"diff",
|
|
1161
|
-
firstParent,
|
|
1162
|
-
mergeCommit.sha,
|
|
1163
|
-
"--",
|
|
1164
|
-
...files
|
|
1165
|
-
]);
|
|
1166
|
-
if (!diff.trim()) return null;
|
|
1167
|
-
const contentHash = this.computeContentHash(diff);
|
|
1168
|
-
if (lock.patches.some((p) => p.content_hash === contentHash) || forgottenHashes.has(contentHash)) {
|
|
1169
|
-
return null;
|
|
1170
|
-
}
|
|
1171
|
-
const theirsSnapshot = await capturePatchSnapshot(this.git, files, mergeCommit.sha);
|
|
1172
|
-
return {
|
|
1173
|
-
id: `patch-merge-${mergeCommit.sha.slice(0, 8)}`,
|
|
1174
|
-
content_hash: contentHash,
|
|
1175
|
-
original_commit: mergeCommit.sha,
|
|
1176
|
-
original_message: mergeCommit.message,
|
|
1177
|
-
original_author: `${mergeCommit.authorName} <${mergeCommit.authorEmail}>`,
|
|
1178
|
-
base_generation: lastGen.commit_sha,
|
|
1179
|
-
files,
|
|
1180
|
-
patch_content: diff,
|
|
1181
|
-
...Object.keys(theirsSnapshot).length > 0 ? { theirs_snapshot: theirsSnapshot } : {},
|
|
1182
|
-
...this.isCreationOnlyPatch(diff) ? { user_owned: true } : {}
|
|
1183
|
-
};
|
|
1184
|
-
}
|
|
1185
1140
|
/**
|
|
1186
1141
|
* Detect patches via tree diff for non-linear history. Returns a composite patch.
|
|
1187
1142
|
* Revert reconciliation is skipped here because tree-diff produces a single composite
|
|
@@ -1381,6 +1336,26 @@ var ReplayDetector = class {
|
|
|
1381
1336
|
getLastGeneration(lock) {
|
|
1382
1337
|
return lock.generations.find((g) => g.commit_sha === lock.current_generation);
|
|
1383
1338
|
}
|
|
1339
|
+
/**
|
|
1340
|
+
* Build a transient `GenerationRecord` from a SHA derived by walking git
|
|
1341
|
+
* history. Only `commit_sha` (and `tree_hash` when needed downstream) is
|
|
1342
|
+
* load-bearing here; the rest are filled with defensible placeholders.
|
|
1343
|
+
* This record is consumed by `detectPatchesInRange` and is never persisted.
|
|
1344
|
+
*/
|
|
1345
|
+
async synthesizeGenerationRecord(sha) {
|
|
1346
|
+
let tree_hash = "";
|
|
1347
|
+
try {
|
|
1348
|
+
tree_hash = await this.git.getTreeHash(sha);
|
|
1349
|
+
} catch {
|
|
1350
|
+
}
|
|
1351
|
+
return {
|
|
1352
|
+
commit_sha: sha,
|
|
1353
|
+
tree_hash,
|
|
1354
|
+
timestamp: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
1355
|
+
cli_version: "",
|
|
1356
|
+
generator_versions: {}
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1384
1359
|
};
|
|
1385
1360
|
|
|
1386
1361
|
// src/ThreeWayMerge.ts
|
|
@@ -2520,8 +2495,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
2520
2495
|
tree_hash: treeHash,
|
|
2521
2496
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2522
2497
|
cli_version: options?.cliVersion ?? "unknown",
|
|
2523
|
-
generator_versions: options?.generatorVersions ?? {}
|
|
2524
|
-
base_branch_head: options?.baseBranchHead
|
|
2498
|
+
generator_versions: options?.generatorVersions ?? {}
|
|
2525
2499
|
};
|
|
2526
2500
|
}
|
|
2527
2501
|
async stageAll() {
|
|
@@ -2908,7 +2882,6 @@ var FirstGenerationFlow = class {
|
|
|
2908
2882
|
flow: "first-generation",
|
|
2909
2883
|
previousGenerationSha: null,
|
|
2910
2884
|
currentGenerationSha: headSha,
|
|
2911
|
-
baseBranchHead: options.baseBranchHead ?? null,
|
|
2912
2885
|
_prepared: {
|
|
2913
2886
|
kind: "terminal",
|
|
2914
2887
|
flow: "first-generation",
|
|
@@ -2918,8 +2891,7 @@ var FirstGenerationFlow = class {
|
|
|
2918
2891
|
}
|
|
2919
2892
|
const commitOpts = options ? {
|
|
2920
2893
|
cliVersion: options.cliVersion ?? "unknown",
|
|
2921
|
-
generatorVersions: options.generatorVersions ?? {}
|
|
2922
|
-
baseBranchHead: options.baseBranchHead
|
|
2894
|
+
generatorVersions: options.generatorVersions ?? {}
|
|
2923
2895
|
} : void 0;
|
|
2924
2896
|
await this.service.committer.commitGeneration("Initial SDK generation", commitOpts);
|
|
2925
2897
|
const genRecord = await this.service.committer.createGenerationRecord(commitOpts);
|
|
@@ -2928,7 +2900,6 @@ var FirstGenerationFlow = class {
|
|
|
2928
2900
|
flow: "first-generation",
|
|
2929
2901
|
previousGenerationSha: null,
|
|
2930
2902
|
currentGenerationSha: genRecord.commit_sha,
|
|
2931
|
-
baseBranchHead: options?.baseBranchHead ?? null,
|
|
2932
2903
|
_prepared: {
|
|
2933
2904
|
kind: "active",
|
|
2934
2905
|
flow: "first-generation",
|
|
@@ -2965,8 +2936,7 @@ var SkipApplicationFlow = class {
|
|
|
2965
2936
|
async prepare(options) {
|
|
2966
2937
|
const commitOpts = options ? {
|
|
2967
2938
|
cliVersion: options.cliVersion ?? "unknown",
|
|
2968
|
-
generatorVersions: options.generatorVersions ?? {}
|
|
2969
|
-
baseBranchHead: options.baseBranchHead
|
|
2939
|
+
generatorVersions: options.generatorVersions ?? {}
|
|
2970
2940
|
} : void 0;
|
|
2971
2941
|
await this.service.committer.commitGeneration("Update SDK (replay skipped)", commitOpts);
|
|
2972
2942
|
await this.service.cleanupStaleConflictMarkers();
|
|
@@ -2988,7 +2958,6 @@ var SkipApplicationFlow = class {
|
|
|
2988
2958
|
flow: "skip-application",
|
|
2989
2959
|
previousGenerationSha,
|
|
2990
2960
|
currentGenerationSha: genRecord.commit_sha,
|
|
2991
|
-
baseBranchHead: options?.baseBranchHead ?? null,
|
|
2992
2961
|
_prepared: {
|
|
2993
2962
|
kind: "active",
|
|
2994
2963
|
flow: "skip-application",
|
|
@@ -3049,7 +3018,6 @@ var NoPatchesFlow = class {
|
|
|
3049
3018
|
flow: "no-patches",
|
|
3050
3019
|
previousGenerationSha,
|
|
3051
3020
|
currentGenerationSha: headSha,
|
|
3052
|
-
baseBranchHead: options.baseBranchHead ?? null,
|
|
3053
3021
|
_prepared: {
|
|
3054
3022
|
kind: "terminal",
|
|
3055
3023
|
flow: "no-patches",
|
|
@@ -3081,8 +3049,7 @@ var NoPatchesFlow = class {
|
|
|
3081
3049
|
}
|
|
3082
3050
|
const commitOpts = options ? {
|
|
3083
3051
|
cliVersion: options.cliVersion ?? "unknown",
|
|
3084
|
-
generatorVersions: options.generatorVersions ?? {}
|
|
3085
|
-
baseBranchHead: options.baseBranchHead
|
|
3052
|
+
generatorVersions: options.generatorVersions ?? {}
|
|
3086
3053
|
} : void 0;
|
|
3087
3054
|
await this.service.committer.commitGeneration("Update SDK", commitOpts);
|
|
3088
3055
|
await this.service.cleanupStaleConflictMarkers();
|
|
@@ -3092,7 +3059,6 @@ var NoPatchesFlow = class {
|
|
|
3092
3059
|
flow: "no-patches",
|
|
3093
3060
|
previousGenerationSha,
|
|
3094
3061
|
currentGenerationSha: genRecord.commit_sha,
|
|
3095
|
-
baseBranchHead: options?.baseBranchHead ?? null,
|
|
3096
3062
|
_prepared: {
|
|
3097
3063
|
kind: "active",
|
|
3098
3064
|
flow: "no-patches",
|
|
@@ -3182,7 +3148,6 @@ var NormalRegenerationFlow = class {
|
|
|
3182
3148
|
flow: "normal-regeneration",
|
|
3183
3149
|
previousGenerationSha,
|
|
3184
3150
|
currentGenerationSha: headSha,
|
|
3185
|
-
baseBranchHead: options.baseBranchHead ?? null,
|
|
3186
3151
|
_prepared: {
|
|
3187
3152
|
kind: "terminal",
|
|
3188
3153
|
flow: "normal-regeneration",
|
|
@@ -3200,14 +3165,16 @@ var NormalRegenerationFlow = class {
|
|
|
3200
3165
|
existingPatches = this.service.lockManager.getPatches();
|
|
3201
3166
|
const seenHashes = /* @__PURE__ */ new Map();
|
|
3202
3167
|
for (const p of existingPatches) {
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3168
|
+
if (!p.original_commit || p.original_commit === "") {
|
|
3169
|
+
const priorPatchId = seenHashes.get(p.content_hash);
|
|
3170
|
+
if (priorPatchId !== void 0) {
|
|
3171
|
+
this.service.lockManager.removePatch(p.id);
|
|
3172
|
+
this.service.warnings.push(
|
|
3173
|
+
`Consolidated patch ${p.id} into prior patch ${priorPatchId}: byte-identical patch_content. Lockfile collapsed to avoid duplicate-apply corruption on next regen.`
|
|
3174
|
+
);
|
|
3175
|
+
} else {
|
|
3176
|
+
seenHashes.set(p.content_hash, p.id);
|
|
3177
|
+
}
|
|
3211
3178
|
}
|
|
3212
3179
|
}
|
|
3213
3180
|
existingPatches = this.service.lockManager.getPatches();
|
|
@@ -3236,8 +3203,7 @@ var NormalRegenerationFlow = class {
|
|
|
3236
3203
|
const allPatches = [...existingPatches, ...newPatches];
|
|
3237
3204
|
const commitOpts = options ? {
|
|
3238
3205
|
cliVersion: options.cliVersion ?? "unknown",
|
|
3239
|
-
generatorVersions: options.generatorVersions ?? {}
|
|
3240
|
-
baseBranchHead: options.baseBranchHead
|
|
3206
|
+
generatorVersions: options.generatorVersions ?? {}
|
|
3241
3207
|
} : void 0;
|
|
3242
3208
|
await this.service.committer.commitGeneration("Update SDK", commitOpts);
|
|
3243
3209
|
await this.service.cleanupStaleConflictMarkers();
|
|
@@ -3247,7 +3213,6 @@ var NormalRegenerationFlow = class {
|
|
|
3247
3213
|
flow: "normal-regeneration",
|
|
3248
3214
|
previousGenerationSha,
|
|
3249
3215
|
currentGenerationSha: genRecord.commit_sha,
|
|
3250
|
-
baseBranchHead: options?.baseBranchHead ?? null,
|
|
3251
3216
|
_prepared: {
|
|
3252
3217
|
kind: "active",
|
|
3253
3218
|
flow: "normal-regeneration",
|
|
@@ -3440,68 +3405,6 @@ var ReplayService = class {
|
|
|
3440
3405
|
const prep = await this.prepareReplay(options);
|
|
3441
3406
|
return this.applyPreparedReplay(prep, options);
|
|
3442
3407
|
}
|
|
3443
|
-
/**
|
|
3444
|
-
* Sync the lockfile after a divergent PR was squash-merged.
|
|
3445
|
-
* Call this BEFORE runReplay() when the CLI detects a merged divergent PR.
|
|
3446
|
-
*
|
|
3447
|
-
* After updating the generation record, re-detects customer patches via
|
|
3448
|
-
* tree diff between the generation tag (pure generation tree) and HEAD
|
|
3449
|
-
* (which includes customer customizations after squash merge). This ensures
|
|
3450
|
-
* patches survive the squash merge → regenerate cycle even when the lockfile
|
|
3451
|
-
* was restored from the base branch during conflict PR creation.
|
|
3452
|
-
*/
|
|
3453
|
-
async syncFromDivergentMerge(generationCommitSha, options) {
|
|
3454
|
-
const treeHash = await this.git.getTreeHash(generationCommitSha);
|
|
3455
|
-
const timestamp = (await this.git.exec(["log", "-1", "--format=%aI", generationCommitSha])).trim();
|
|
3456
|
-
const record = {
|
|
3457
|
-
commit_sha: generationCommitSha,
|
|
3458
|
-
tree_hash: treeHash,
|
|
3459
|
-
timestamp,
|
|
3460
|
-
cli_version: options?.cliVersion ?? "unknown",
|
|
3461
|
-
generator_versions: options?.generatorVersions ?? {},
|
|
3462
|
-
base_branch_head: options?.baseBranchHead
|
|
3463
|
-
};
|
|
3464
|
-
let resolvedPatches;
|
|
3465
|
-
if (!this.lockManager.exists()) {
|
|
3466
|
-
this.lockManager.initializeInMemory(record);
|
|
3467
|
-
} else {
|
|
3468
|
-
this.lockManager.read();
|
|
3469
|
-
const unresolvedPatches = [
|
|
3470
|
-
...this.lockManager.getUnresolvedPatches(),
|
|
3471
|
-
...this.lockManager.getResolvingPatches()
|
|
3472
|
-
];
|
|
3473
|
-
resolvedPatches = this.lockManager.getPatches().filter((p) => p.status == null);
|
|
3474
|
-
this.lockManager.addGeneration(record);
|
|
3475
|
-
this.lockManager.clearPatches();
|
|
3476
|
-
for (const patch of unresolvedPatches) {
|
|
3477
|
-
this.lockManager.addPatch(patch);
|
|
3478
|
-
}
|
|
3479
|
-
}
|
|
3480
|
-
try {
|
|
3481
|
-
const { patches: redetectedPatches } = await this.detector.detectNewPatches();
|
|
3482
|
-
if (redetectedPatches.length > 0) {
|
|
3483
|
-
const redetectedFiles = new Set(redetectedPatches.flatMap((p) => p.files));
|
|
3484
|
-
const currentPatches = this.lockManager.getPatches();
|
|
3485
|
-
for (const patch of currentPatches) {
|
|
3486
|
-
if (patch.status != null && patch.files.some((f) => redetectedFiles.has(f))) {
|
|
3487
|
-
this.lockManager.removePatch(patch.id);
|
|
3488
|
-
}
|
|
3489
|
-
}
|
|
3490
|
-
for (const patch of redetectedPatches) {
|
|
3491
|
-
this.lockManager.addPatch(patch);
|
|
3492
|
-
}
|
|
3493
|
-
}
|
|
3494
|
-
} catch (error) {
|
|
3495
|
-
for (const patch of resolvedPatches ?? []) {
|
|
3496
|
-
this.lockManager.addPatch(patch);
|
|
3497
|
-
}
|
|
3498
|
-
this.detector.warnings.push(
|
|
3499
|
-
`Patch re-detection failed after divergent merge sync. ${(resolvedPatches ?? []).length} previously resolved patch(es) preserved. Error: ${error instanceof Error ? error.message : String(error)}`
|
|
3500
|
-
);
|
|
3501
|
-
}
|
|
3502
|
-
this.lockManager.save();
|
|
3503
|
-
this.detector.warnings.push(...this.lockManager.warnings);
|
|
3504
|
-
}
|
|
3505
3408
|
determineFlow() {
|
|
3506
3409
|
try {
|
|
3507
3410
|
const lock = this.lockManager.read();
|