@fern-api/fern-api-dev 5.27.5 → 5.27.6
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/cli.cjs +1734 -1567
- package/package.json +1 -1
package/cli.cjs
CHANGED
|
@@ -360342,9 +360342,9 @@ var require_diff3 = __commonJS({
|
|
|
360342
360342
|
}
|
|
360343
360343
|
});
|
|
360344
360344
|
|
|
360345
|
-
// ../../../node_modules/.pnpm/@fern-api+replay@0.15.
|
|
360345
|
+
// ../../../node_modules/.pnpm/@fern-api+replay@0.15.1/node_modules/@fern-api/replay/dist/index.cjs
|
|
360346
360346
|
var require_dist14 = __commonJS({
|
|
360347
|
-
"../../../node_modules/.pnpm/@fern-api+replay@0.15.
|
|
360347
|
+
"../../../node_modules/.pnpm/@fern-api+replay@0.15.1/node_modules/@fern-api/replay/dist/index.cjs"(exports2, module4) {
|
|
360348
360348
|
"use strict";
|
|
360349
360349
|
var __defProp4 = Object.defineProperty;
|
|
360350
360350
|
var __getOwnPropDesc4 = Object.getOwnPropertyDescriptor;
|
|
@@ -360757,24 +360757,24 @@ var require_dist14 = __commonJS({
|
|
|
360757
360757
|
async function applyPatchToContent(base4, patchContent, filePath) {
|
|
360758
360758
|
const fileDiff = extractFileDiff(patchContent, filePath);
|
|
360759
360759
|
if (!fileDiff) return null;
|
|
360760
|
-
const tempDir2 = await (0,
|
|
360760
|
+
const tempDir2 = await (0, import_promises510.mkdtemp)((0, import_node_path72.join)((0, import_node_os32.tmpdir)(), "replay-migrate-"));
|
|
360761
360761
|
const tempGit = new GitClient(tempDir2);
|
|
360762
360762
|
try {
|
|
360763
360763
|
await tempGit.exec(["init"]);
|
|
360764
360764
|
await tempGit.exec(["config", "user.email", "migrate@fern.com"]);
|
|
360765
360765
|
await tempGit.exec(["config", "user.name", "Fern Replay Migration"]);
|
|
360766
360766
|
await tempGit.exec(["config", "commit.gpgSign", "false"]);
|
|
360767
|
-
const tempFilePath = (0,
|
|
360768
|
-
await (0,
|
|
360769
|
-
await (0,
|
|
360767
|
+
const tempFilePath = (0, import_node_path72.join)(tempDir2, filePath);
|
|
360768
|
+
await (0, import_promises510.mkdir)((0, import_node_path72.dirname)(tempFilePath), { recursive: true });
|
|
360769
|
+
await (0, import_promises510.writeFile)(tempFilePath, base4);
|
|
360770
360770
|
await tempGit.exec(["add", filePath]);
|
|
360771
360771
|
await tempGit.exec(["commit", "-m", `base for ${filePath}`, "--allow-empty"]);
|
|
360772
360772
|
await tempGit.execWithInput(["apply", "--allow-empty"], fileDiff);
|
|
360773
|
-
return await (0,
|
|
360773
|
+
return await (0, import_promises510.readFile)(tempFilePath, "utf-8");
|
|
360774
360774
|
} catch {
|
|
360775
360775
|
return null;
|
|
360776
360776
|
} finally {
|
|
360777
|
-
await (0,
|
|
360777
|
+
await (0, import_promises510.rm)(tempDir2, { recursive: true, force: true }).catch(() => {
|
|
360778
360778
|
});
|
|
360779
360779
|
}
|
|
360780
360780
|
}
|
|
@@ -360796,15 +360796,15 @@ var require_dist14 = __commonJS({
|
|
|
360796
360796
|
}
|
|
360797
360797
|
return out.length > 0 ? out.join("\n") + "\n" : null;
|
|
360798
360798
|
}
|
|
360799
|
-
var
|
|
360799
|
+
var import_promises510;
|
|
360800
360800
|
var import_node_os32;
|
|
360801
|
-
var
|
|
360801
|
+
var import_node_path72;
|
|
360802
360802
|
var init_PatchApplyTheirs = __esm3({
|
|
360803
360803
|
"src/PatchApplyTheirs.ts"() {
|
|
360804
360804
|
"use strict";
|
|
360805
|
-
|
|
360805
|
+
import_promises510 = require("fs/promises");
|
|
360806
360806
|
import_node_os32 = require("os");
|
|
360807
|
-
|
|
360807
|
+
import_node_path72 = require("path");
|
|
360808
360808
|
init_GitClient();
|
|
360809
360809
|
}
|
|
360810
360810
|
});
|
|
@@ -360898,7 +360898,7 @@ var require_dist14 = __commonJS({
|
|
|
360898
360898
|
return match3?.[1];
|
|
360899
360899
|
}
|
|
360900
360900
|
var import_node_fs32 = require("fs");
|
|
360901
|
-
var
|
|
360901
|
+
var import_node_path42 = require("path");
|
|
360902
360902
|
var import_yaml18 = require_dist11();
|
|
360903
360903
|
var LOCKFILE_HEADER = "# DO NOT EDIT MANUALLY - Managed by Fern Replay\n";
|
|
360904
360904
|
var LockfileNotFoundError = class extends Error {
|
|
@@ -360915,10 +360915,10 @@ var require_dist14 = __commonJS({
|
|
|
360915
360915
|
this.outputDir = outputDir;
|
|
360916
360916
|
}
|
|
360917
360917
|
get lockfilePath() {
|
|
360918
|
-
return (0,
|
|
360918
|
+
return (0, import_node_path42.join)(this.outputDir, ".fern", "replay.lock");
|
|
360919
360919
|
}
|
|
360920
360920
|
get customizationsPath() {
|
|
360921
|
-
return (0,
|
|
360921
|
+
return (0, import_node_path42.join)(this.outputDir, ".fern", "replay.yml");
|
|
360922
360922
|
}
|
|
360923
360923
|
exists() {
|
|
360924
360924
|
return (0, import_node_fs32.existsSync)(this.lockfilePath);
|
|
@@ -360990,7 +360990,7 @@ var require_dist14 = __commonJS({
|
|
|
360990
360990
|
}
|
|
360991
360991
|
}
|
|
360992
360992
|
this.lock.patches = cleanPatches;
|
|
360993
|
-
const dir = (0,
|
|
360993
|
+
const dir = (0, import_node_path42.dirname)(this.lockfilePath);
|
|
360994
360994
|
if (!(0, import_node_fs32.existsSync)(dir)) {
|
|
360995
360995
|
(0, import_node_fs32.mkdirSync)(dir, { recursive: true });
|
|
360996
360996
|
}
|
|
@@ -361091,218 +361091,7 @@ var require_dist14 = __commonJS({
|
|
|
361091
361091
|
}
|
|
361092
361092
|
};
|
|
361093
361093
|
var import_node_crypto7 = require("crypto");
|
|
361094
|
-
var import_promises195 = require("fs/promises");
|
|
361095
|
-
var import_node_os15 = require("os");
|
|
361096
361094
|
var import_node_path210 = require("path");
|
|
361097
|
-
var import_minimatch8 = require_commonjs3();
|
|
361098
|
-
var CONFLICT_OPENER = "<<<<<<< Generated";
|
|
361099
|
-
var CONFLICT_SEPARATOR = "=======";
|
|
361100
|
-
var CONFLICT_CLOSER = ">>>>>>> Your customization";
|
|
361101
|
-
function trimCR(line) {
|
|
361102
|
-
return line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
361103
|
-
}
|
|
361104
|
-
function findConflictRanges(lines) {
|
|
361105
|
-
const ranges = [];
|
|
361106
|
-
let i9 = 0;
|
|
361107
|
-
while (i9 < lines.length) {
|
|
361108
|
-
if (trimCR(lines[i9]) === CONFLICT_OPENER) {
|
|
361109
|
-
let separatorIdx = -1;
|
|
361110
|
-
let j7 = i9 + 1;
|
|
361111
|
-
let found = false;
|
|
361112
|
-
while (j7 < lines.length) {
|
|
361113
|
-
const trimmed2 = trimCR(lines[j7]);
|
|
361114
|
-
if (trimmed2 === CONFLICT_OPENER) {
|
|
361115
|
-
break;
|
|
361116
|
-
}
|
|
361117
|
-
if (separatorIdx === -1 && trimmed2 === CONFLICT_SEPARATOR) {
|
|
361118
|
-
separatorIdx = j7;
|
|
361119
|
-
} else if (separatorIdx !== -1 && trimmed2 === CONFLICT_CLOSER) {
|
|
361120
|
-
ranges.push({ start: i9, separator: separatorIdx, end: j7 });
|
|
361121
|
-
i9 = j7;
|
|
361122
|
-
found = true;
|
|
361123
|
-
break;
|
|
361124
|
-
}
|
|
361125
|
-
j7++;
|
|
361126
|
-
}
|
|
361127
|
-
if (!found) {
|
|
361128
|
-
i9++;
|
|
361129
|
-
continue;
|
|
361130
|
-
}
|
|
361131
|
-
}
|
|
361132
|
-
i9++;
|
|
361133
|
-
}
|
|
361134
|
-
return ranges;
|
|
361135
|
-
}
|
|
361136
|
-
function stripConflictMarkers(content5) {
|
|
361137
|
-
const lines = content5.split(/\r?\n/);
|
|
361138
|
-
const ranges = findConflictRanges(lines);
|
|
361139
|
-
if (ranges.length === 0) {
|
|
361140
|
-
return content5;
|
|
361141
|
-
}
|
|
361142
|
-
const result = [];
|
|
361143
|
-
let rangeIdx = 0;
|
|
361144
|
-
for (let i9 = 0; i9 < lines.length; i9++) {
|
|
361145
|
-
if (rangeIdx < ranges.length) {
|
|
361146
|
-
const range2 = ranges[rangeIdx];
|
|
361147
|
-
if (i9 === range2.start) {
|
|
361148
|
-
continue;
|
|
361149
|
-
}
|
|
361150
|
-
if (i9 > range2.start && i9 < range2.separator) {
|
|
361151
|
-
result.push(lines[i9]);
|
|
361152
|
-
continue;
|
|
361153
|
-
}
|
|
361154
|
-
if (i9 === range2.separator) {
|
|
361155
|
-
continue;
|
|
361156
|
-
}
|
|
361157
|
-
if (i9 > range2.separator && i9 < range2.end) {
|
|
361158
|
-
continue;
|
|
361159
|
-
}
|
|
361160
|
-
if (i9 === range2.end) {
|
|
361161
|
-
rangeIdx++;
|
|
361162
|
-
continue;
|
|
361163
|
-
}
|
|
361164
|
-
}
|
|
361165
|
-
result.push(lines[i9]);
|
|
361166
|
-
}
|
|
361167
|
-
return result.join("\n");
|
|
361168
|
-
}
|
|
361169
|
-
var import_node_diff3 = require_diff3();
|
|
361170
|
-
function threeWayMerge(base4, ours, theirs) {
|
|
361171
|
-
const baseLines = base4.split("\n");
|
|
361172
|
-
const oursLines = ours.split("\n");
|
|
361173
|
-
const theirsLines = theirs.split("\n");
|
|
361174
|
-
const regions = (0, import_node_diff3.diff3Merge)(oursLines, baseLines, theirsLines);
|
|
361175
|
-
const outputLines = [];
|
|
361176
|
-
const conflicts2 = [];
|
|
361177
|
-
let currentLine = 1;
|
|
361178
|
-
for (const region of regions) {
|
|
361179
|
-
if (region.ok) {
|
|
361180
|
-
outputLines.push(...region.ok);
|
|
361181
|
-
currentLine += region.ok.length;
|
|
361182
|
-
} else if (region.conflict) {
|
|
361183
|
-
const resolved = tryResolveConflict(
|
|
361184
|
-
region.conflict.a,
|
|
361185
|
-
// ours (generator)
|
|
361186
|
-
region.conflict.o,
|
|
361187
|
-
// base
|
|
361188
|
-
region.conflict.b
|
|
361189
|
-
// theirs (user)
|
|
361190
|
-
);
|
|
361191
|
-
if (resolved !== null) {
|
|
361192
|
-
outputLines.push(...resolved);
|
|
361193
|
-
currentLine += resolved.length;
|
|
361194
|
-
} else {
|
|
361195
|
-
const startLine = currentLine;
|
|
361196
|
-
outputLines.push("<<<<<<< Generated");
|
|
361197
|
-
outputLines.push(...region.conflict.a);
|
|
361198
|
-
outputLines.push("=======");
|
|
361199
|
-
outputLines.push(...region.conflict.b);
|
|
361200
|
-
outputLines.push(">>>>>>> Your customization");
|
|
361201
|
-
const conflictLines = region.conflict.a.length + region.conflict.b.length + 3;
|
|
361202
|
-
conflicts2.push({
|
|
361203
|
-
startLine,
|
|
361204
|
-
endLine: startLine + conflictLines - 1,
|
|
361205
|
-
ours: region.conflict.a,
|
|
361206
|
-
theirs: region.conflict.b
|
|
361207
|
-
});
|
|
361208
|
-
currentLine += conflictLines;
|
|
361209
|
-
}
|
|
361210
|
-
}
|
|
361211
|
-
}
|
|
361212
|
-
const result = {
|
|
361213
|
-
content: outputLines.join("\n"),
|
|
361214
|
-
hasConflicts: conflicts2.length > 0,
|
|
361215
|
-
conflicts: conflicts2
|
|
361216
|
-
};
|
|
361217
|
-
if (conflicts2.length === 0) {
|
|
361218
|
-
const dropped = computeDroppedContextLines(baseLines, theirsLines, outputLines);
|
|
361219
|
-
if (dropped.length > 0) {
|
|
361220
|
-
result.droppedContextLines = dropped;
|
|
361221
|
-
}
|
|
361222
|
-
}
|
|
361223
|
-
return result;
|
|
361224
|
-
}
|
|
361225
|
-
function computeDroppedContextLines(baseLines, theirsLines, mergedLines) {
|
|
361226
|
-
const baseCounts = countLines2(baseLines);
|
|
361227
|
-
const theirsCounts = countLines2(theirsLines);
|
|
361228
|
-
const mergedCounts = countLines2(mergedLines);
|
|
361229
|
-
const dropped = [];
|
|
361230
|
-
const seen = /* @__PURE__ */ new Set();
|
|
361231
|
-
for (const line of baseLines) {
|
|
361232
|
-
if (seen.has(line)) continue;
|
|
361233
|
-
const inBase = baseCounts.get(line) ?? 0;
|
|
361234
|
-
const inTheirs = theirsCounts.get(line) ?? 0;
|
|
361235
|
-
const inMerged = mergedCounts.get(line) ?? 0;
|
|
361236
|
-
if (inBase > 0 && inTheirs > 0 && inMerged < Math.min(inBase, inTheirs)) {
|
|
361237
|
-
dropped.push(line);
|
|
361238
|
-
seen.add(line);
|
|
361239
|
-
}
|
|
361240
|
-
}
|
|
361241
|
-
return dropped;
|
|
361242
|
-
}
|
|
361243
|
-
function countLines2(lines) {
|
|
361244
|
-
const counts = /* @__PURE__ */ new Map();
|
|
361245
|
-
for (const line of lines) {
|
|
361246
|
-
counts.set(line, (counts.get(line) ?? 0) + 1);
|
|
361247
|
-
}
|
|
361248
|
-
return counts;
|
|
361249
|
-
}
|
|
361250
|
-
function tryResolveConflict(oursLines, baseLines, theirsLines) {
|
|
361251
|
-
if (baseLines.length === 0) {
|
|
361252
|
-
return null;
|
|
361253
|
-
}
|
|
361254
|
-
const oursPatches = (0, import_node_diff3.diffPatch)(baseLines, oursLines);
|
|
361255
|
-
const theirsPatches = (0, import_node_diff3.diffPatch)(baseLines, theirsLines);
|
|
361256
|
-
if (oursPatches.length === 0) return theirsLines;
|
|
361257
|
-
if (theirsPatches.length === 0) return oursLines;
|
|
361258
|
-
if (patchesOverlap(oursPatches, theirsPatches)) {
|
|
361259
|
-
return null;
|
|
361260
|
-
}
|
|
361261
|
-
return applyBothPatches(baseLines, oursPatches, theirsPatches);
|
|
361262
|
-
}
|
|
361263
|
-
function patchesOverlap(oursPatches, theirsPatches) {
|
|
361264
|
-
for (const op4 of oursPatches) {
|
|
361265
|
-
const oStart = op4.buffer1.offset;
|
|
361266
|
-
const oEnd = oStart + op4.buffer1.length;
|
|
361267
|
-
for (const tp3 of theirsPatches) {
|
|
361268
|
-
const tStart = tp3.buffer1.offset;
|
|
361269
|
-
const tEnd = tStart + tp3.buffer1.length;
|
|
361270
|
-
if (op4.buffer1.length === 0 && tp3.buffer1.length === 0 && oStart === tStart) {
|
|
361271
|
-
return true;
|
|
361272
|
-
}
|
|
361273
|
-
if (tp3.buffer1.length === 0 && tStart === oEnd) {
|
|
361274
|
-
return true;
|
|
361275
|
-
}
|
|
361276
|
-
if (op4.buffer1.length === 0 && oStart === tEnd) {
|
|
361277
|
-
return true;
|
|
361278
|
-
}
|
|
361279
|
-
if (oStart < tEnd && tStart < oEnd) {
|
|
361280
|
-
return true;
|
|
361281
|
-
}
|
|
361282
|
-
}
|
|
361283
|
-
}
|
|
361284
|
-
return false;
|
|
361285
|
-
}
|
|
361286
|
-
function applyBothPatches(baseLines, oursPatches, theirsPatches) {
|
|
361287
|
-
const allPatches = [
|
|
361288
|
-
...oursPatches.map((p14) => ({
|
|
361289
|
-
offset: p14.buffer1.offset,
|
|
361290
|
-
length: p14.buffer1.length,
|
|
361291
|
-
replacement: p14.buffer2.chunk
|
|
361292
|
-
})),
|
|
361293
|
-
...theirsPatches.map((p14) => ({
|
|
361294
|
-
offset: p14.buffer1.offset,
|
|
361295
|
-
length: p14.buffer1.length,
|
|
361296
|
-
replacement: p14.buffer2.chunk
|
|
361297
|
-
}))
|
|
361298
|
-
];
|
|
361299
|
-
allPatches.sort((a3, b8) => b8.offset - a3.offset);
|
|
361300
|
-
const result = [...baseLines];
|
|
361301
|
-
for (const p14 of allPatches) {
|
|
361302
|
-
result.splice(p14.offset, p14.length, ...p14.replacement);
|
|
361303
|
-
}
|
|
361304
|
-
return result;
|
|
361305
|
-
}
|
|
361306
361095
|
var BINARY_EXTENSIONS2 = /* @__PURE__ */ new Set([
|
|
361307
361096
|
".png",
|
|
361308
361097
|
".jpg",
|
|
@@ -361352,728 +361141,10 @@ var require_dist14 = __commonJS({
|
|
|
361352
361141
|
".pyo",
|
|
361353
361142
|
".DS_Store"
|
|
361354
361143
|
]);
|
|
361355
|
-
var ReplayApplicator = class {
|
|
361356
|
-
git;
|
|
361357
|
-
lockManager;
|
|
361358
|
-
outputDir;
|
|
361359
|
-
renameCache = /* @__PURE__ */ new Map();
|
|
361360
|
-
treeExistsCache = /* @__PURE__ */ new Map();
|
|
361361
|
-
fileTheirsAccumulator = /* @__PURE__ */ new Map();
|
|
361362
|
-
/**
|
|
361363
|
-
* Apply mode for the current `applyPatches` invocation. Set at the start
|
|
361364
|
-
* of `applyPatches`, read by `mergeFile` to decide:
|
|
361365
|
-
* - whether to skip the intra-loop marker strip (kept in `applyPatches`)
|
|
361366
|
-
* - whether to populate the accumulator after a conflicted merge
|
|
361367
|
-
* (resolve mode populates so subsequent patches on the same file
|
|
361368
|
-
* can use patch A's THEIRS as a structurally-correct merge base
|
|
361369
|
-
* when their diff was authored against post-A structure)
|
|
361370
|
-
* - whether to retry THEIRS reconstruction against the accumulator
|
|
361371
|
-
* when the BASE-relative reconstruction produced markers from a
|
|
361372
|
-
* cross-patch context mismatch
|
|
361373
|
-
*/
|
|
361374
|
-
currentApplyMode = "replay";
|
|
361375
|
-
/**
|
|
361376
|
-
* Set of files that appear in 2+ patches in the current `applyPatches`
|
|
361377
|
-
* invocation. Computed at the top of `applyPatches`. Read by `mergeFile`
|
|
361378
|
-
* and `populateAccumulatorForPatch` to decide whether to use the patch's
|
|
361379
|
-
* `theirs_snapshot` directly as THEIRS (snapshot-as-primary) or fall
|
|
361380
|
-
* back to line-anchored reconstruction (FER-9525 cross-patch isolation).
|
|
361381
|
-
*
|
|
361382
|
-
* Snapshot-as-primary is correct when the patch owns its file (no other
|
|
361383
|
-
* patch in this run touches it): the snapshot is what the customer
|
|
361384
|
-
* intends for that file post-regen, regardless of generator structural
|
|
361385
|
-
* changes that would invalidate the diff's line anchors.
|
|
361386
|
-
*
|
|
361387
|
-
* When a file IS shared, snapshots are CUMULATIVE across the patches
|
|
361388
|
-
* touching it (each one's snapshot includes prior patches' contributions).
|
|
361389
|
-
* Using a later patch's cumulative snapshot as its individual THEIRS
|
|
361390
|
-
* would propagate prior patches' edits into the merge — surfacing nested
|
|
361391
|
-
* conflicts at regions the patch alone never touched. Reconstruction
|
|
361392
|
-
* via `applyPatchToContent` is required there.
|
|
361393
|
-
*/
|
|
361394
|
-
sharedFiles = /* @__PURE__ */ new Set();
|
|
361395
|
-
constructor(git, lockManager, outputDir) {
|
|
361396
|
-
this.git = git;
|
|
361397
|
-
this.lockManager = lockManager;
|
|
361398
|
-
this.outputDir = outputDir;
|
|
361399
|
-
}
|
|
361400
|
-
/**
|
|
361401
|
-
* Resolve the GenerationRecord for a patch's base_generation.
|
|
361402
|
-
* Falls back to constructing an ad-hoc record from the commit's tree
|
|
361403
|
-
* when base_generation isn't a tracked generation (commit-scan patches).
|
|
361404
|
-
*/
|
|
361405
|
-
async resolveBaseGeneration(baseGeneration) {
|
|
361406
|
-
const gen = this.lockManager.getGeneration(baseGeneration);
|
|
361407
|
-
if (gen) return gen;
|
|
361408
|
-
try {
|
|
361409
|
-
const treeHash = await this.git.getTreeHash(baseGeneration);
|
|
361410
|
-
return {
|
|
361411
|
-
commit_sha: baseGeneration,
|
|
361412
|
-
tree_hash: treeHash,
|
|
361413
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
361414
|
-
cli_version: "unknown",
|
|
361415
|
-
generator_versions: {}
|
|
361416
|
-
};
|
|
361417
|
-
} catch {
|
|
361418
|
-
return void 0;
|
|
361419
|
-
}
|
|
361420
|
-
}
|
|
361421
|
-
/** Reset inter-patch accumulator for a new cycle. */
|
|
361422
|
-
resetAccumulator() {
|
|
361423
|
-
this.fileTheirsAccumulator.clear();
|
|
361424
|
-
}
|
|
361425
|
-
/**
|
|
361426
|
-
* @internal Test-only.
|
|
361427
|
-
* Returns a defensive copy of the inter-patch accumulator. Used by the
|
|
361428
|
-
* adversarial test suite (FER-9791) to assert invariant I2: after every
|
|
361429
|
-
* applied patch, the accumulator has an entry for each file the patch
|
|
361430
|
-
* touched, and the entry's content matches on-disk.
|
|
361431
|
-
*/
|
|
361432
|
-
getAccumulatorSnapshot() {
|
|
361433
|
-
const snapshot = /* @__PURE__ */ new Map();
|
|
361434
|
-
for (const [path111, entry] of this.fileTheirsAccumulator) {
|
|
361435
|
-
snapshot.set(path111, { content: entry.content, baseGeneration: entry.baseGeneration });
|
|
361436
|
-
}
|
|
361437
|
-
return snapshot;
|
|
361438
|
-
}
|
|
361439
|
-
/**
|
|
361440
|
-
* Apply all patches, returning results for each.
|
|
361441
|
-
* Skips patches that match exclude patterns in replay.yml
|
|
361442
|
-
*
|
|
361443
|
-
* `applyMode` controls the post-conflict marker strategy:
|
|
361444
|
-
* - `"replay"` (default): strip conflict markers from disk between
|
|
361445
|
-
* iterations whenever a later patch touches the same file. Lets the
|
|
361446
|
-
* follow-up patch's `git apply --3way` see clean OURS content,
|
|
361447
|
-
* which is what the silent-loss fix (PR #73) relies on. The replay
|
|
361448
|
-
* pipeline calls `revertConflictingFiles` after the loop to clean
|
|
361449
|
-
* any markers that survive.
|
|
361450
|
-
* - `"resolve"`: keep markers on disk. The resolve command needs the
|
|
361451
|
-
* customer to see them. Slow-path 3-way merge naturally preserves
|
|
361452
|
-
* A's markers and B's clean writes when their regions don't overlap;
|
|
361453
|
-
* when they do overlap, the customer gets nested markers and
|
|
361454
|
-
* resolves manually. Either way they aren't silently dropped.
|
|
361455
|
-
*/
|
|
361456
|
-
async applyPatches(patches, opts) {
|
|
361457
|
-
const applyMode = opts?.applyMode ?? "replay";
|
|
361458
|
-
this.currentApplyMode = applyMode;
|
|
361459
|
-
this.resetAccumulator();
|
|
361460
|
-
const fileFreq = /* @__PURE__ */ new Map();
|
|
361461
|
-
for (const p14 of patches) {
|
|
361462
|
-
for (const f4 of p14.files) {
|
|
361463
|
-
fileFreq.set(f4, (fileFreq.get(f4) ?? 0) + 1);
|
|
361464
|
-
}
|
|
361465
|
-
}
|
|
361466
|
-
this.sharedFiles = new Set(
|
|
361467
|
-
Array.from(fileFreq.entries()).filter(([, n19]) => n19 > 1).map(([f4]) => f4)
|
|
361468
|
-
);
|
|
361469
|
-
const results = [];
|
|
361470
|
-
for (let i9 = 0; i9 < patches.length; i9++) {
|
|
361471
|
-
const patch5 = patches[i9];
|
|
361472
|
-
if (this.isExcluded(patch5)) {
|
|
361473
|
-
results.push({
|
|
361474
|
-
patch: patch5,
|
|
361475
|
-
status: "skipped",
|
|
361476
|
-
method: "git-am"
|
|
361477
|
-
});
|
|
361478
|
-
continue;
|
|
361479
|
-
}
|
|
361480
|
-
const result = await this.applyPatchWithFallback(patch5);
|
|
361481
|
-
results.push(result);
|
|
361482
|
-
if (applyMode !== "resolve" && result.status === "conflict" && result.fileResults) {
|
|
361483
|
-
const laterFiles = /* @__PURE__ */ new Set();
|
|
361484
|
-
for (let j7 = i9 + 1; j7 < patches.length; j7++) {
|
|
361485
|
-
for (const f4 of patches[j7].files) {
|
|
361486
|
-
laterFiles.add(f4);
|
|
361487
|
-
}
|
|
361488
|
-
}
|
|
361489
|
-
const resolvedToOriginal = /* @__PURE__ */ new Map();
|
|
361490
|
-
if (result.resolvedFiles) {
|
|
361491
|
-
for (const [orig, resolved] of Object.entries(result.resolvedFiles)) {
|
|
361492
|
-
resolvedToOriginal.set(resolved, orig);
|
|
361493
|
-
}
|
|
361494
|
-
}
|
|
361495
|
-
for (const fileResult of result.fileResults) {
|
|
361496
|
-
if (fileResult.status !== "conflict") continue;
|
|
361497
|
-
const originalPath = resolvedToOriginal.get(fileResult.file) ?? fileResult.file;
|
|
361498
|
-
if (laterFiles.has(fileResult.file) || laterFiles.has(originalPath)) {
|
|
361499
|
-
const filePath = (0, import_node_path210.join)(this.outputDir, fileResult.file);
|
|
361500
|
-
try {
|
|
361501
|
-
const content5 = await (0, import_promises195.readFile)(filePath, "utf-8");
|
|
361502
|
-
const stripped = stripConflictMarkers(content5);
|
|
361503
|
-
await (0, import_promises195.writeFile)(filePath, stripped);
|
|
361504
|
-
} catch {
|
|
361505
|
-
}
|
|
361506
|
-
}
|
|
361507
|
-
}
|
|
361508
|
-
}
|
|
361509
|
-
}
|
|
361510
|
-
return results;
|
|
361511
|
-
}
|
|
361512
|
-
/**
|
|
361513
|
-
* Populate accumulator after git apply succeeds, AND collect per-file
|
|
361514
|
-
* results including any "dropped context lines" — lines that were
|
|
361515
|
-
* present in BASE and THEIRS (unchanged context) but absent from the
|
|
361516
|
-
* final on-disk state because OURS deleted them. This is the fast-path
|
|
361517
|
-
* counterpart to ThreeWayMerge.computeDroppedContextLines used by the
|
|
361518
|
-
* 3-way slow path; both must surface the same warning.
|
|
361519
|
-
*/
|
|
361520
|
-
async populateAccumulatorForPatch(patch5, baseGen, currentTreeHash) {
|
|
361521
|
-
const fileResults = [];
|
|
361522
|
-
if (!baseGen) return fileResults;
|
|
361523
|
-
const tempDir2 = await (0, import_promises195.mkdtemp)((0, import_node_path210.join)((0, import_node_os15.tmpdir)(), "replay-acc-"));
|
|
361524
|
-
const { GitClient: GitClient2 } = await Promise.resolve().then(() => (init_GitClient(), GitClient_exports));
|
|
361525
|
-
const tempGit = new GitClient2(tempDir2);
|
|
361526
|
-
await tempGit.exec(["init"]);
|
|
361527
|
-
await tempGit.exec(["config", "user.email", "replay@fern.com"]);
|
|
361528
|
-
await tempGit.exec(["config", "user.name", "Fern Replay"]);
|
|
361529
|
-
try {
|
|
361530
|
-
for (const filePath of patch5.files) {
|
|
361531
|
-
if (isBinaryFile(filePath)) continue;
|
|
361532
|
-
const resolvedPath = await this.resolveFilePath(filePath, baseGen.tree_hash, currentTreeHash);
|
|
361533
|
-
const base4 = await this.git.showFile(baseGen.tree_hash, filePath);
|
|
361534
|
-
const snapshotForFile = patch5.theirs_snapshot?.[resolvedPath] ?? patch5.theirs_snapshot?.[filePath];
|
|
361535
|
-
const fileIsShared = this.sharedFiles.has(resolvedPath) || this.sharedFiles.has(filePath);
|
|
361536
|
-
const theirs = !fileIsShared && snapshotForFile != null ? snapshotForFile : await this.applyPatchToContent(base4, patch5.patch_content, filePath, tempGit, tempDir2);
|
|
361537
|
-
const finalOnDisk = await (0, import_promises195.readFile)((0, import_node_path210.join)(this.outputDir, resolvedPath), "utf-8").catch(() => null);
|
|
361538
|
-
const effectiveTheirs = theirs ?? finalOnDisk;
|
|
361539
|
-
if (effectiveTheirs != null) {
|
|
361540
|
-
this.fileTheirsAccumulator.set(resolvedPath, {
|
|
361541
|
-
content: effectiveTheirs,
|
|
361542
|
-
baseGeneration: patch5.base_generation
|
|
361543
|
-
});
|
|
361544
|
-
}
|
|
361545
|
-
if (base4 != null && effectiveTheirs != null && finalOnDisk != null) {
|
|
361546
|
-
const dropped = computeDroppedContextLinesForFile(base4, effectiveTheirs, finalOnDisk);
|
|
361547
|
-
if (dropped.length > 0) {
|
|
361548
|
-
fileResults.push({
|
|
361549
|
-
file: resolvedPath,
|
|
361550
|
-
status: "merged",
|
|
361551
|
-
droppedContextLines: dropped
|
|
361552
|
-
});
|
|
361553
|
-
}
|
|
361554
|
-
}
|
|
361555
|
-
}
|
|
361556
|
-
} finally {
|
|
361557
|
-
await (0, import_promises195.rm)(tempDir2, { recursive: true }).catch(() => {
|
|
361558
|
-
});
|
|
361559
|
-
}
|
|
361560
|
-
return fileResults;
|
|
361561
|
-
}
|
|
361562
|
-
async applyPatchWithFallback(patch5) {
|
|
361563
|
-
const baseGen = await this.resolveBaseGeneration(patch5.base_generation);
|
|
361564
|
-
const lock = this.lockManager.read();
|
|
361565
|
-
const currentGen = lock.generations.find((g8) => g8.commit_sha === lock.current_generation);
|
|
361566
|
-
const currentTreeHash = currentGen?.tree_hash ?? baseGen?.tree_hash ?? "";
|
|
361567
|
-
const needsAccumulation = await Promise.all(
|
|
361568
|
-
patch5.files.map(async (f4) => {
|
|
361569
|
-
if (!baseGen) return false;
|
|
361570
|
-
const resolved = await this.resolveFilePath(f4, baseGen.tree_hash, currentTreeHash);
|
|
361571
|
-
return this.fileTheirsAccumulator.has(resolved);
|
|
361572
|
-
})
|
|
361573
|
-
).then((results) => results.some(Boolean));
|
|
361574
|
-
const resolvedFiles = {};
|
|
361575
|
-
for (const filePath of patch5.files) {
|
|
361576
|
-
const resolvedPath = baseGen ? await this.resolveFilePath(filePath, baseGen.tree_hash, currentTreeHash) : filePath;
|
|
361577
|
-
if (resolvedPath !== filePath) {
|
|
361578
|
-
resolvedFiles[filePath] = resolvedPath;
|
|
361579
|
-
}
|
|
361580
|
-
}
|
|
361581
|
-
const patchIsRenameAware = /^rename from /m.test(patch5.patch_content);
|
|
361582
|
-
const hasGeneratorRename = Object.keys(resolvedFiles).length > 0 && !patchIsRenameAware;
|
|
361583
|
-
if (!needsAccumulation && !hasGeneratorRename) {
|
|
361584
|
-
const snapshots = /* @__PURE__ */ new Map();
|
|
361585
|
-
for (const filePath of patch5.files) {
|
|
361586
|
-
const fullPath = (0, import_node_path210.join)(this.outputDir, filePath);
|
|
361587
|
-
snapshots.set(filePath, await (0, import_promises195.readFile)(fullPath, "utf-8").catch(() => null));
|
|
361588
|
-
}
|
|
361589
|
-
try {
|
|
361590
|
-
await this.git.execWithInput(["apply", "--3way"], patch5.patch_content);
|
|
361591
|
-
const fastPathFileResults = await this.populateAccumulatorForPatch(patch5, baseGen, currentTreeHash);
|
|
361592
|
-
return {
|
|
361593
|
-
patch: patch5,
|
|
361594
|
-
status: "applied",
|
|
361595
|
-
method: "git-am",
|
|
361596
|
-
...fastPathFileResults.length > 0 && { fileResults: fastPathFileResults },
|
|
361597
|
-
...Object.keys(resolvedFiles).length > 0 && { resolvedFiles }
|
|
361598
|
-
};
|
|
361599
|
-
} catch {
|
|
361600
|
-
for (const [filePath, content5] of snapshots) {
|
|
361601
|
-
const fullPath = (0, import_node_path210.join)(this.outputDir, filePath);
|
|
361602
|
-
if (content5 != null) {
|
|
361603
|
-
await (0, import_promises195.writeFile)(fullPath, content5);
|
|
361604
|
-
} else {
|
|
361605
|
-
await (0, import_promises195.unlink)(fullPath).catch(() => {
|
|
361606
|
-
});
|
|
361607
|
-
}
|
|
361608
|
-
}
|
|
361609
|
-
}
|
|
361610
|
-
}
|
|
361611
|
-
return this.applyWithThreeWayMerge(patch5);
|
|
361612
|
-
}
|
|
361613
|
-
async applyWithThreeWayMerge(patch5) {
|
|
361614
|
-
const fileResults = [];
|
|
361615
|
-
const resolvedFiles = {};
|
|
361616
|
-
const tempDir2 = await (0, import_promises195.mkdtemp)((0, import_node_path210.join)((0, import_node_os15.tmpdir)(), "replay-"));
|
|
361617
|
-
const { GitClient: GitClient2 } = await Promise.resolve().then(() => (init_GitClient(), GitClient_exports));
|
|
361618
|
-
const tempGit = new GitClient2(tempDir2);
|
|
361619
|
-
await tempGit.exec(["init"]);
|
|
361620
|
-
await tempGit.exec(["config", "user.email", "replay@fern.com"]);
|
|
361621
|
-
await tempGit.exec(["config", "user.name", "Fern Replay"]);
|
|
361622
|
-
try {
|
|
361623
|
-
for (const filePath of patch5.files) {
|
|
361624
|
-
if (isBinaryFile(filePath)) {
|
|
361625
|
-
fileResults.push({
|
|
361626
|
-
file: filePath,
|
|
361627
|
-
status: "skipped",
|
|
361628
|
-
reason: "binary-file"
|
|
361629
|
-
});
|
|
361630
|
-
continue;
|
|
361631
|
-
}
|
|
361632
|
-
const result = await this.mergeFile(patch5, filePath, tempGit, tempDir2);
|
|
361633
|
-
if (result.file !== filePath) {
|
|
361634
|
-
resolvedFiles[filePath] = result.file;
|
|
361635
|
-
}
|
|
361636
|
-
fileResults.push(result);
|
|
361637
|
-
}
|
|
361638
|
-
} finally {
|
|
361639
|
-
await (0, import_promises195.rm)(tempDir2, { recursive: true }).catch(() => {
|
|
361640
|
-
});
|
|
361641
|
-
}
|
|
361642
|
-
const conflictFiles = fileResults.filter((r5) => r5.status === "conflict");
|
|
361643
|
-
const hasConflicts = conflictFiles.length > 0;
|
|
361644
|
-
const conflictReason = hasConflicts ? conflictFiles.some((f4) => f4.conflictReason === "base-generation-mismatch") ? "base-generation-mismatch" : conflictFiles[0]?.conflictReason : void 0;
|
|
361645
|
-
return {
|
|
361646
|
-
patch: patch5,
|
|
361647
|
-
status: hasConflicts ? "conflict" : "applied",
|
|
361648
|
-
method: "3way-merge",
|
|
361649
|
-
fileResults,
|
|
361650
|
-
conflictReason,
|
|
361651
|
-
...Object.keys(resolvedFiles).length > 0 && { resolvedFiles }
|
|
361652
|
-
};
|
|
361653
|
-
}
|
|
361654
|
-
async mergeFile(patch5, filePath, tempGit, tempDir2) {
|
|
361655
|
-
try {
|
|
361656
|
-
const baseGen = await this.resolveBaseGeneration(patch5.base_generation);
|
|
361657
|
-
if (!baseGen) {
|
|
361658
|
-
return { file: filePath, status: "skipped", reason: "base-generation-not-found" };
|
|
361659
|
-
}
|
|
361660
|
-
const lock = this.lockManager.read();
|
|
361661
|
-
const currentGen = lock.generations.find((g8) => g8.commit_sha === lock.current_generation);
|
|
361662
|
-
const currentTreeHash = currentGen?.tree_hash ?? baseGen.tree_hash;
|
|
361663
|
-
const resolvedPath = await this.resolveFilePath(filePath, baseGen.tree_hash, currentTreeHash);
|
|
361664
|
-
const metadata = {
|
|
361665
|
-
patchId: patch5.id,
|
|
361666
|
-
patchMessage: patch5.original_message,
|
|
361667
|
-
baseGeneration: patch5.base_generation,
|
|
361668
|
-
currentGeneration: lock.current_generation
|
|
361669
|
-
};
|
|
361670
|
-
let base4 = await this.git.showFile(baseGen.tree_hash, filePath);
|
|
361671
|
-
let renameSourcePath;
|
|
361672
|
-
if (!base4) {
|
|
361673
|
-
const renameSource = this.extractRenameSource(patch5.patch_content, filePath);
|
|
361674
|
-
if (renameSource) {
|
|
361675
|
-
base4 = await this.git.showFile(baseGen.tree_hash, renameSource);
|
|
361676
|
-
renameSourcePath = renameSource;
|
|
361677
|
-
}
|
|
361678
|
-
}
|
|
361679
|
-
const oursPath = (0, import_node_path210.join)(this.outputDir, resolvedPath);
|
|
361680
|
-
const ours = await (0, import_promises195.readFile)(oursPath, "utf-8").catch(() => null);
|
|
361681
|
-
let ghostReconstructed = false;
|
|
361682
|
-
let theirs = null;
|
|
361683
|
-
if (!base4 && ours && !renameSourcePath) {
|
|
361684
|
-
const treeReachable = await this.isTreeReachable(baseGen.tree_hash);
|
|
361685
|
-
if (!treeReachable) {
|
|
361686
|
-
const fileDiff = this.extractFileDiff(patch5.patch_content, filePath);
|
|
361687
|
-
if (fileDiff) {
|
|
361688
|
-
const { reconstructFromGhostPatch: reconstructFromGhostPatch2 } = await Promise.resolve().then(() => (init_HybridReconstruction(), HybridReconstruction_exports));
|
|
361689
|
-
const result = reconstructFromGhostPatch2(fileDiff, ours);
|
|
361690
|
-
if (result) {
|
|
361691
|
-
base4 = result.base;
|
|
361692
|
-
theirs = result.theirs;
|
|
361693
|
-
ghostReconstructed = true;
|
|
361694
|
-
}
|
|
361695
|
-
}
|
|
361696
|
-
}
|
|
361697
|
-
}
|
|
361698
|
-
const snapshotForFile = patch5.theirs_snapshot?.[resolvedPath] ?? patch5.theirs_snapshot?.[filePath];
|
|
361699
|
-
const fileIsShared = this.sharedFiles.has(resolvedPath) || this.sharedFiles.has(filePath);
|
|
361700
|
-
const useSnapshotAsPrimary = !fileIsShared && snapshotForFile != null;
|
|
361701
|
-
if (!ghostReconstructed) {
|
|
361702
|
-
if (useSnapshotAsPrimary) {
|
|
361703
|
-
theirs = snapshotForFile;
|
|
361704
|
-
} else {
|
|
361705
|
-
theirs = await this.applyPatchToContent(
|
|
361706
|
-
base4,
|
|
361707
|
-
patch5.patch_content,
|
|
361708
|
-
filePath,
|
|
361709
|
-
tempGit,
|
|
361710
|
-
tempDir2,
|
|
361711
|
-
renameSourcePath
|
|
361712
|
-
);
|
|
361713
|
-
const reconstructionHasMarkers = theirs != null && (theirs.includes("<<<<<<< Generated") || theirs.includes(">>>>>>> Your customization"));
|
|
361714
|
-
const baseHadMarkers = base4 != null && (base4.includes("<<<<<<< Generated") || base4.includes(">>>>>>> Your customization"));
|
|
361715
|
-
if (reconstructionHasMarkers && !baseHadMarkers && snapshotForFile != null) {
|
|
361716
|
-
theirs = snapshotForFile;
|
|
361717
|
-
}
|
|
361718
|
-
}
|
|
361719
|
-
}
|
|
361720
|
-
const accumulatorEntry = this.fileTheirsAccumulator.get(resolvedPath);
|
|
361721
|
-
if (theirs) {
|
|
361722
|
-
const theirsHasMarkers = theirs.includes("<<<<<<< Generated") || theirs.includes(">>>>>>> Your customization");
|
|
361723
|
-
const baseHasMarkers = base4 != null && (base4.includes("<<<<<<< Generated") || base4.includes(">>>>>>> Your customization"));
|
|
361724
|
-
if (theirsHasMarkers && !baseHasMarkers) {
|
|
361725
|
-
if (accumulatorEntry) {
|
|
361726
|
-
theirs = null;
|
|
361727
|
-
} else {
|
|
361728
|
-
return {
|
|
361729
|
-
file: resolvedPath,
|
|
361730
|
-
status: "skipped",
|
|
361731
|
-
reason: "stale-conflict-markers"
|
|
361732
|
-
};
|
|
361733
|
-
}
|
|
361734
|
-
}
|
|
361735
|
-
}
|
|
361736
|
-
let useAccumulatorAsMergeBase = false;
|
|
361737
|
-
if (accumulatorEntry && (theirs == null || base4 == null)) {
|
|
361738
|
-
theirs = await this.applyPatchToContent(
|
|
361739
|
-
accumulatorEntry.content,
|
|
361740
|
-
patch5.patch_content,
|
|
361741
|
-
filePath,
|
|
361742
|
-
tempGit,
|
|
361743
|
-
tempDir2
|
|
361744
|
-
);
|
|
361745
|
-
if (theirs != null) {
|
|
361746
|
-
useAccumulatorAsMergeBase = true;
|
|
361747
|
-
} else if (await this.isPatchAlreadyApplied(
|
|
361748
|
-
patch5.patch_content,
|
|
361749
|
-
filePath,
|
|
361750
|
-
accumulatorEntry.content,
|
|
361751
|
-
tempGit,
|
|
361752
|
-
tempDir2
|
|
361753
|
-
)) {
|
|
361754
|
-
theirs = accumulatorEntry.content;
|
|
361755
|
-
useAccumulatorAsMergeBase = true;
|
|
361756
|
-
}
|
|
361757
|
-
}
|
|
361758
|
-
if (theirs) {
|
|
361759
|
-
const theirsHasMarkers = theirs.includes("<<<<<<< Generated") || theirs.includes(">>>>>>> Your customization");
|
|
361760
|
-
const accBaseHasMarkers = accumulatorEntry != null && (accumulatorEntry.content.includes("<<<<<<< Generated") || accumulatorEntry.content.includes(">>>>>>> Your customization"));
|
|
361761
|
-
if (theirsHasMarkers && !accBaseHasMarkers && !(base4 != null && (base4.includes("<<<<<<< Generated") || base4.includes(">>>>>>> Your customization")))) {
|
|
361762
|
-
return {
|
|
361763
|
-
file: resolvedPath,
|
|
361764
|
-
status: "skipped",
|
|
361765
|
-
reason: "stale-conflict-markers"
|
|
361766
|
-
};
|
|
361767
|
-
}
|
|
361768
|
-
}
|
|
361769
|
-
let effective_theirs = theirs;
|
|
361770
|
-
let baseMismatchSkipped = false;
|
|
361771
|
-
if (theirs != null && base4 != null && !useAccumulatorAsMergeBase) {
|
|
361772
|
-
if (accumulatorEntry && accumulatorEntry.baseGeneration === patch5.base_generation) {
|
|
361773
|
-
try {
|
|
361774
|
-
const preMerged = threeWayMerge(base4, accumulatorEntry.content, theirs);
|
|
361775
|
-
if (!preMerged.hasConflicts) {
|
|
361776
|
-
effective_theirs = preMerged.content;
|
|
361777
|
-
} else {
|
|
361778
|
-
effective_theirs = theirs;
|
|
361779
|
-
}
|
|
361780
|
-
} catch {
|
|
361781
|
-
effective_theirs = theirs;
|
|
361782
|
-
}
|
|
361783
|
-
} else if (accumulatorEntry) {
|
|
361784
|
-
baseMismatchSkipped = true;
|
|
361785
|
-
}
|
|
361786
|
-
}
|
|
361787
|
-
if (base4 == null && ours == null && effective_theirs != null) {
|
|
361788
|
-
this.fileTheirsAccumulator.set(resolvedPath, {
|
|
361789
|
-
content: effective_theirs,
|
|
361790
|
-
baseGeneration: patch5.base_generation
|
|
361791
|
-
});
|
|
361792
|
-
const outDir2 = (0, import_node_path210.dirname)(oursPath);
|
|
361793
|
-
await (0, import_promises195.mkdir)(outDir2, { recursive: true });
|
|
361794
|
-
await (0, import_promises195.writeFile)(oursPath, effective_theirs);
|
|
361795
|
-
return { file: resolvedPath, status: "merged", reason: "new-file" };
|
|
361796
|
-
}
|
|
361797
|
-
if (base4 == null && ours != null && effective_theirs != null && !useAccumulatorAsMergeBase) {
|
|
361798
|
-
const merged2 = threeWayMerge("", ours, effective_theirs);
|
|
361799
|
-
const outDir2 = (0, import_node_path210.dirname)(oursPath);
|
|
361800
|
-
await (0, import_promises195.mkdir)(outDir2, { recursive: true });
|
|
361801
|
-
await (0, import_promises195.writeFile)(oursPath, merged2.content);
|
|
361802
|
-
if (merged2.hasConflicts) {
|
|
361803
|
-
return {
|
|
361804
|
-
file: resolvedPath,
|
|
361805
|
-
status: "conflict",
|
|
361806
|
-
conflicts: merged2.conflicts,
|
|
361807
|
-
conflictReason: "new-file-both",
|
|
361808
|
-
conflictMetadata: metadata
|
|
361809
|
-
};
|
|
361810
|
-
}
|
|
361811
|
-
this.fileTheirsAccumulator.set(resolvedPath, {
|
|
361812
|
-
content: merged2.content,
|
|
361813
|
-
baseGeneration: patch5.base_generation
|
|
361814
|
-
});
|
|
361815
|
-
return { file: resolvedPath, status: "merged" };
|
|
361816
|
-
}
|
|
361817
|
-
if (effective_theirs == null) {
|
|
361818
|
-
return {
|
|
361819
|
-
file: resolvedPath,
|
|
361820
|
-
status: "skipped",
|
|
361821
|
-
reason: "missing-content"
|
|
361822
|
-
};
|
|
361823
|
-
}
|
|
361824
|
-
if (base4 == null && !useAccumulatorAsMergeBase || ours == null) {
|
|
361825
|
-
return {
|
|
361826
|
-
file: resolvedPath,
|
|
361827
|
-
status: "skipped",
|
|
361828
|
-
reason: "missing-content"
|
|
361829
|
-
};
|
|
361830
|
-
}
|
|
361831
|
-
const mergeBase = useAccumulatorAsMergeBase && accumulatorEntry ? accumulatorEntry.content : base4;
|
|
361832
|
-
if (mergeBase == null) {
|
|
361833
|
-
return {
|
|
361834
|
-
file: resolvedPath,
|
|
361835
|
-
status: "skipped",
|
|
361836
|
-
reason: "missing-content"
|
|
361837
|
-
};
|
|
361838
|
-
}
|
|
361839
|
-
const merged = threeWayMerge(mergeBase, ours, effective_theirs);
|
|
361840
|
-
const outDir = (0, import_node_path210.dirname)(oursPath);
|
|
361841
|
-
await (0, import_promises195.mkdir)(outDir, { recursive: true });
|
|
361842
|
-
await (0, import_promises195.writeFile)(oursPath, merged.content);
|
|
361843
|
-
const populateAccumulator = effective_theirs != null && (!merged.hasConflicts || this.currentApplyMode === "resolve");
|
|
361844
|
-
if (populateAccumulator) {
|
|
361845
|
-
this.fileTheirsAccumulator.set(resolvedPath, {
|
|
361846
|
-
content: effective_theirs,
|
|
361847
|
-
baseGeneration: patch5.base_generation
|
|
361848
|
-
});
|
|
361849
|
-
}
|
|
361850
|
-
if (merged.hasConflicts) {
|
|
361851
|
-
return {
|
|
361852
|
-
file: resolvedPath,
|
|
361853
|
-
status: "conflict",
|
|
361854
|
-
conflicts: merged.conflicts,
|
|
361855
|
-
conflictReason: baseMismatchSkipped ? "base-generation-mismatch" : "same-line-edit",
|
|
361856
|
-
conflictMetadata: metadata
|
|
361857
|
-
};
|
|
361858
|
-
}
|
|
361859
|
-
return {
|
|
361860
|
-
file: resolvedPath,
|
|
361861
|
-
status: "merged",
|
|
361862
|
-
...merged.droppedContextLines && merged.droppedContextLines.length > 0 ? { droppedContextLines: merged.droppedContextLines } : {}
|
|
361863
|
-
};
|
|
361864
|
-
} catch (error50) {
|
|
361865
|
-
return {
|
|
361866
|
-
file: filePath,
|
|
361867
|
-
status: "skipped",
|
|
361868
|
-
reason: `error: ${error50 instanceof Error ? error50.message : String(error50)}`
|
|
361869
|
-
};
|
|
361870
|
-
}
|
|
361871
|
-
}
|
|
361872
|
-
async isTreeReachable(treeHash) {
|
|
361873
|
-
let result = this.treeExistsCache.get(treeHash);
|
|
361874
|
-
if (result === void 0) {
|
|
361875
|
-
result = await this.git.treeExists(treeHash);
|
|
361876
|
-
this.treeExistsCache.set(treeHash, result);
|
|
361877
|
-
}
|
|
361878
|
-
return result;
|
|
361879
|
-
}
|
|
361880
|
-
isExcluded(patch5) {
|
|
361881
|
-
const config5 = this.lockManager.getCustomizationsConfig();
|
|
361882
|
-
if (!config5.exclude) return false;
|
|
361883
|
-
return patch5.files.some((file4) => config5.exclude.some((pattern) => (0, import_minimatch8.minimatch)(file4, pattern)));
|
|
361884
|
-
}
|
|
361885
|
-
async resolveFilePath(filePath, baseTreeHash, currentTreeHash) {
|
|
361886
|
-
const config5 = this.lockManager.getCustomizationsConfig();
|
|
361887
|
-
if (config5.moves) {
|
|
361888
|
-
for (const move of config5.moves) {
|
|
361889
|
-
if ((0, import_minimatch8.minimatch)(filePath, move.from) || filePath === move.from) {
|
|
361890
|
-
if (filePath === move.from) {
|
|
361891
|
-
return move.to;
|
|
361892
|
-
}
|
|
361893
|
-
const fromBase = move.from.replace(/\*\*.*$/, "");
|
|
361894
|
-
const toBase = move.to.replace(/\*\*.*$/, "");
|
|
361895
|
-
if (filePath.startsWith(fromBase)) {
|
|
361896
|
-
return toBase + filePath.slice(fromBase.length);
|
|
361897
|
-
}
|
|
361898
|
-
}
|
|
361899
|
-
}
|
|
361900
|
-
}
|
|
361901
|
-
const cacheKey = `${baseTreeHash}:${currentTreeHash}`;
|
|
361902
|
-
let renames = this.renameCache.get(cacheKey);
|
|
361903
|
-
if (!renames) {
|
|
361904
|
-
renames = await this.git.detectRenames(baseTreeHash, currentTreeHash);
|
|
361905
|
-
this.renameCache.set(cacheKey, renames);
|
|
361906
|
-
}
|
|
361907
|
-
const gitRename = renames.find((r5) => r5.from === filePath);
|
|
361908
|
-
if (gitRename) {
|
|
361909
|
-
return gitRename.to;
|
|
361910
|
-
}
|
|
361911
|
-
return filePath;
|
|
361912
|
-
}
|
|
361913
|
-
async applyPatchToContent(base4, patchContent, filePath, tempGit, tempDir2, sourceFilePath) {
|
|
361914
|
-
if (!base4) {
|
|
361915
|
-
return this.extractNewFileFromPatch(patchContent, filePath);
|
|
361916
|
-
}
|
|
361917
|
-
const fileDiff = this.extractFileDiff(patchContent, filePath);
|
|
361918
|
-
if (!fileDiff) return null;
|
|
361919
|
-
try {
|
|
361920
|
-
if (sourceFilePath) {
|
|
361921
|
-
const tempSourcePath = (0, import_node_path210.join)(tempDir2, sourceFilePath);
|
|
361922
|
-
await (0, import_promises195.mkdir)((0, import_node_path210.dirname)(tempSourcePath), { recursive: true });
|
|
361923
|
-
await (0, import_promises195.writeFile)(tempSourcePath, base4);
|
|
361924
|
-
await tempGit.exec(["add", sourceFilePath]);
|
|
361925
|
-
await tempGit.exec([
|
|
361926
|
-
"commit",
|
|
361927
|
-
"-m",
|
|
361928
|
-
`base for rename ${sourceFilePath} -> ${filePath}`,
|
|
361929
|
-
"--allow-empty"
|
|
361930
|
-
]);
|
|
361931
|
-
await tempGit.execWithInput(["apply", "--allow-empty"], fileDiff);
|
|
361932
|
-
const tempTargetPath = (0, import_node_path210.join)(tempDir2, filePath);
|
|
361933
|
-
return await (0, import_promises195.readFile)(tempTargetPath, "utf-8");
|
|
361934
|
-
}
|
|
361935
|
-
const tempFilePath = (0, import_node_path210.join)(tempDir2, filePath);
|
|
361936
|
-
await (0, import_promises195.mkdir)((0, import_node_path210.dirname)(tempFilePath), { recursive: true });
|
|
361937
|
-
await (0, import_promises195.writeFile)(tempFilePath, base4);
|
|
361938
|
-
await tempGit.exec(["add", filePath]);
|
|
361939
|
-
await tempGit.exec(["commit", "-m", `base for ${filePath}`, "--allow-empty"]);
|
|
361940
|
-
await tempGit.execWithInput(["apply", "--allow-empty"], fileDiff);
|
|
361941
|
-
return await (0, import_promises195.readFile)(tempFilePath, "utf-8");
|
|
361942
|
-
} catch {
|
|
361943
|
-
return null;
|
|
361944
|
-
}
|
|
361945
|
-
}
|
|
361946
|
-
/**
|
|
361947
|
-
* Detects whether a patch's additions are already present in `content`.
|
|
361948
|
-
* Uses `git apply --reverse --check` — if the reverse patch applies cleanly,
|
|
361949
|
-
* the forward patch is effectively a no-op (its +lines are already there and
|
|
361950
|
-
* its -lines are already gone). Used to distinguish "patch already applied"
|
|
361951
|
-
* from "patch base mismatch" after a forward-apply fails.
|
|
361952
|
-
*/
|
|
361953
|
-
async isPatchAlreadyApplied(patchContent, filePath, content5, tempGit, tempDir2) {
|
|
361954
|
-
const fileDiff = this.extractFileDiff(patchContent, filePath);
|
|
361955
|
-
if (!fileDiff) return false;
|
|
361956
|
-
try {
|
|
361957
|
-
const tempFilePath = (0, import_node_path210.join)(tempDir2, filePath);
|
|
361958
|
-
await (0, import_promises195.mkdir)((0, import_node_path210.dirname)(tempFilePath), { recursive: true });
|
|
361959
|
-
await (0, import_promises195.writeFile)(tempFilePath, content5);
|
|
361960
|
-
await tempGit.exec(["add", filePath]);
|
|
361961
|
-
await tempGit.exec(["commit", "-m", `check already-applied ${filePath}`, "--allow-empty"]);
|
|
361962
|
-
await tempGit.execWithInput(["apply", "--reverse", "--check", "--allow-empty"], fileDiff);
|
|
361963
|
-
return true;
|
|
361964
|
-
} catch {
|
|
361965
|
-
return false;
|
|
361966
|
-
}
|
|
361967
|
-
}
|
|
361968
|
-
extractFileDiff(patchContent, filePath) {
|
|
361969
|
-
const lines = patchContent.split("\n");
|
|
361970
|
-
const diffLines = [];
|
|
361971
|
-
let inTargetFile = false;
|
|
361972
|
-
for (const line of lines) {
|
|
361973
|
-
if (line.startsWith("diff --git")) {
|
|
361974
|
-
if (inTargetFile) {
|
|
361975
|
-
break;
|
|
361976
|
-
}
|
|
361977
|
-
if (isDiffLineForFile(line, filePath)) {
|
|
361978
|
-
inTargetFile = true;
|
|
361979
|
-
diffLines.push(line);
|
|
361980
|
-
}
|
|
361981
|
-
continue;
|
|
361982
|
-
}
|
|
361983
|
-
if (inTargetFile) {
|
|
361984
|
-
diffLines.push(line);
|
|
361985
|
-
}
|
|
361986
|
-
}
|
|
361987
|
-
return diffLines.length > 0 ? diffLines.join("\n") + "\n" : null;
|
|
361988
|
-
}
|
|
361989
|
-
extractRenameSource(patchContent, targetFilePath) {
|
|
361990
|
-
const lines = patchContent.split("\n");
|
|
361991
|
-
let inTargetFile = false;
|
|
361992
|
-
for (const line of lines) {
|
|
361993
|
-
if (line.startsWith("diff --git")) {
|
|
361994
|
-
if (inTargetFile) break;
|
|
361995
|
-
inTargetFile = isDiffLineForFile(line, targetFilePath);
|
|
361996
|
-
continue;
|
|
361997
|
-
}
|
|
361998
|
-
if (!inTargetFile) continue;
|
|
361999
|
-
if (line.startsWith("@@")) break;
|
|
362000
|
-
if (line.startsWith("rename from ")) {
|
|
362001
|
-
return line.slice("rename from ".length);
|
|
362002
|
-
}
|
|
362003
|
-
}
|
|
362004
|
-
return null;
|
|
362005
|
-
}
|
|
362006
|
-
extractNewFileFromPatch(patchContent, filePath) {
|
|
362007
|
-
const lines = patchContent.split("\n");
|
|
362008
|
-
const addedLines = [];
|
|
362009
|
-
let inTargetFile = false;
|
|
362010
|
-
let inHunk = false;
|
|
362011
|
-
let isNewFile = false;
|
|
362012
|
-
let noTrailingNewline = false;
|
|
362013
|
-
for (const line of lines) {
|
|
362014
|
-
if (line.startsWith("diff --git")) {
|
|
362015
|
-
if (inTargetFile) break;
|
|
362016
|
-
inTargetFile = isDiffLineForFile(line, filePath);
|
|
362017
|
-
inHunk = false;
|
|
362018
|
-
isNewFile = false;
|
|
362019
|
-
continue;
|
|
362020
|
-
}
|
|
362021
|
-
if (!inTargetFile) continue;
|
|
362022
|
-
if (!inHunk) {
|
|
362023
|
-
if (line === "--- /dev/null" || line.startsWith("new file mode")) {
|
|
362024
|
-
isNewFile = true;
|
|
362025
|
-
}
|
|
362026
|
-
}
|
|
362027
|
-
if (line.startsWith("@@")) {
|
|
362028
|
-
if (!isNewFile) return null;
|
|
362029
|
-
inHunk = true;
|
|
362030
|
-
continue;
|
|
362031
|
-
}
|
|
362032
|
-
if (!inHunk) continue;
|
|
362033
|
-
if (line === "\") {
|
|
362034
|
-
noTrailingNewline = true;
|
|
362035
|
-
continue;
|
|
362036
|
-
}
|
|
362037
|
-
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
362038
|
-
addedLines.push(line.slice(1));
|
|
362039
|
-
}
|
|
362040
|
-
}
|
|
362041
|
-
if (addedLines.length === 0) return null;
|
|
362042
|
-
return addedLines.join("\n") + (noTrailingNewline ? "" : "\n");
|
|
362043
|
-
}
|
|
362044
|
-
};
|
|
362045
361144
|
function isBinaryFile(filePath) {
|
|
362046
361145
|
const ext2 = (0, import_node_path210.extname)(filePath).toLowerCase();
|
|
362047
361146
|
return BINARY_EXTENSIONS2.has(ext2);
|
|
362048
361147
|
}
|
|
362049
|
-
function computeDroppedContextLinesForFile(base4, theirs, finalContent) {
|
|
362050
|
-
const baseLines = base4.split("\n");
|
|
362051
|
-
const theirsLines = theirs.split("\n");
|
|
362052
|
-
const finalLines = finalContent.split("\n");
|
|
362053
|
-
const baseCounts = /* @__PURE__ */ new Map();
|
|
362054
|
-
const theirsCounts = /* @__PURE__ */ new Map();
|
|
362055
|
-
const finalCounts = /* @__PURE__ */ new Map();
|
|
362056
|
-
for (const l9 of baseLines) baseCounts.set(l9, (baseCounts.get(l9) ?? 0) + 1);
|
|
362057
|
-
for (const l9 of theirsLines) theirsCounts.set(l9, (theirsCounts.get(l9) ?? 0) + 1);
|
|
362058
|
-
for (const l9 of finalLines) finalCounts.set(l9, (finalCounts.get(l9) ?? 0) + 1);
|
|
362059
|
-
const dropped = [];
|
|
362060
|
-
const seen = /* @__PURE__ */ new Set();
|
|
362061
|
-
for (const line of baseLines) {
|
|
362062
|
-
if (seen.has(line)) continue;
|
|
362063
|
-
const inBase = baseCounts.get(line) ?? 0;
|
|
362064
|
-
const inTheirs = theirsCounts.get(line) ?? 0;
|
|
362065
|
-
const inFinal = finalCounts.get(line) ?? 0;
|
|
362066
|
-
if (inBase > 0 && inTheirs > 0 && inFinal < Math.min(inBase, inTheirs)) {
|
|
362067
|
-
dropped.push(line);
|
|
362068
|
-
seen.add(line);
|
|
362069
|
-
}
|
|
362070
|
-
}
|
|
362071
|
-
return dropped;
|
|
362072
|
-
}
|
|
362073
|
-
function isDiffLineForFile(diffLine, filePath) {
|
|
362074
|
-
const match3 = diffLine.match(/^diff --git a\/.+ b\/(.+)$/);
|
|
362075
|
-
return match3 !== null && match3[1] === filePath;
|
|
362076
|
-
}
|
|
362077
361148
|
var INFRASTRUCTURE_FILES = /* @__PURE__ */ new Set([".fernignore"]);
|
|
362078
361149
|
function parsePatchFileHeaders(patchContent) {
|
|
362079
361150
|
const results = [];
|
|
@@ -362106,6 +361177,21 @@ var require_dist14 = __commonJS({
|
|
|
362106
361177
|
flush3();
|
|
362107
361178
|
return results;
|
|
362108
361179
|
}
|
|
361180
|
+
async function capturePatchSnapshot(git, files, treeish) {
|
|
361181
|
+
const snapshot = {};
|
|
361182
|
+
for (const file4 of files) {
|
|
361183
|
+
if (isBinaryFile(file4)) continue;
|
|
361184
|
+
const content5 = await git.showFile(treeish, file4).catch(() => null);
|
|
361185
|
+
if (content5 != null) snapshot[file4] = content5;
|
|
361186
|
+
}
|
|
361187
|
+
return snapshot;
|
|
361188
|
+
}
|
|
361189
|
+
function filterInfrastructureAndSensitiveFiles(rawFilesOutput) {
|
|
361190
|
+
const allFiles = rawFilesOutput.trim().split("\n").filter(Boolean).filter((f4) => !INFRASTRUCTURE_FILES.has(f4)).filter((f4) => !f4.startsWith(".fern/"));
|
|
361191
|
+
const sensitiveFiles = allFiles.filter((f4) => isSensitiveFile(f4));
|
|
361192
|
+
const files = allFiles.filter((f4) => !isSensitiveFile(f4));
|
|
361193
|
+
return { files, sensitiveFiles };
|
|
361194
|
+
}
|
|
362109
361195
|
var ReplayDetector = class {
|
|
362110
361196
|
git;
|
|
362111
361197
|
lockManager;
|
|
@@ -362140,7 +361226,7 @@ var require_dist14 = __commonJS({
|
|
|
362140
361226
|
return this.detectPatchesInRange(lastGen.commit_sha, lastGen, lock);
|
|
362141
361227
|
}
|
|
362142
361228
|
/**
|
|
362143
|
-
*
|
|
361229
|
+
* Non-linear-history detection via `merge-base(prevGen, HEAD)`.
|
|
362144
361230
|
*
|
|
362145
361231
|
* After a squash-merge of a Fern-bot PR, `lastGen.commit_sha` (the previous
|
|
362146
361232
|
* `[fern-generated]`) is orphaned but still in git's object database. The
|
|
@@ -362172,7 +361258,7 @@ var require_dist14 = __commonJS({
|
|
|
362172
361258
|
return this.detectPatchesInRange(mergeBase, lastGen, lock);
|
|
362173
361259
|
}
|
|
362174
361260
|
/**
|
|
362175
|
-
*
|
|
361261
|
+
* Walk `${rangeStart}..HEAD`, classify each commit, emit one
|
|
362176
361262
|
* `StoredPatch` per customer commit. Body shared by the linear path
|
|
362177
361263
|
* (rangeStart = lastGen.commit_sha) and the merge-base path.
|
|
362178
361264
|
*
|
|
@@ -362229,9 +361315,7 @@ var require_dist14 = __commonJS({
|
|
|
362229
361315
|
continue;
|
|
362230
361316
|
}
|
|
362231
361317
|
const filesOutput = await this.git.exec(["diff-tree", "--no-commit-id", "--name-only", "-r", commit.sha]);
|
|
362232
|
-
const
|
|
362233
|
-
const sensitiveFiles = allFiles.filter((f4) => isSensitiveFile(f4));
|
|
362234
|
-
const files = allFiles.filter((f4) => !isSensitiveFile(f4));
|
|
361318
|
+
const { files, sensitiveFiles } = filterInfrastructureAndSensitiveFiles(filesOutput);
|
|
362235
361319
|
if (sensitiveFiles.length > 0) {
|
|
362236
361320
|
this.warnings.push(
|
|
362237
361321
|
`Sensitive file(s) excluded from patch for commit ${commit.sha.slice(0, 7)}: ${sensitiveFiles.join(", ")}. These files will not be tracked by replay.`
|
|
@@ -362252,12 +361336,7 @@ var require_dist14 = __commonJS({
|
|
|
362252
361336
|
if (files.length === 0) {
|
|
362253
361337
|
continue;
|
|
362254
361338
|
}
|
|
362255
|
-
const theirsSnapshot =
|
|
362256
|
-
for (const file4 of files) {
|
|
362257
|
-
if (isBinaryFile(file4)) continue;
|
|
362258
|
-
const content5 = await this.git.showFile(commit.sha, file4).catch(() => null);
|
|
362259
|
-
if (content5 != null) theirsSnapshot[file4] = content5;
|
|
362260
|
-
}
|
|
361339
|
+
const theirsSnapshot = await capturePatchSnapshot(this.git, files, commit.sha);
|
|
362261
361340
|
newPatches.push({
|
|
362262
361341
|
id: `patch-${commit.sha.slice(0, 8)}`,
|
|
362263
361342
|
content_hash: contentHash,
|
|
@@ -362345,7 +361424,7 @@ var require_dist14 = __commonJS({
|
|
|
362345
361424
|
*
|
|
362346
361425
|
* This ensures content hashes match across the no-patches and normal-
|
|
362347
361426
|
* regeneration flows, which store formatPatch and git-diff formats
|
|
362348
|
-
* respectively
|
|
361427
|
+
* respectively.
|
|
362349
361428
|
*/
|
|
362350
361429
|
computeContentHash(patchContent) {
|
|
362351
361430
|
const lines = patchContent.split("\n");
|
|
@@ -362355,8 +361434,8 @@ var require_dist14 = __commonJS({
|
|
|
362355
361434
|
return `sha256:${(0, import_node_crypto7.createHash)("sha256").update(normalized).digest("hex")}`;
|
|
362356
361435
|
}
|
|
362357
361436
|
/**
|
|
362358
|
-
*
|
|
362359
|
-
*
|
|
361437
|
+
* Drop patches whose files were transiently created and then deleted
|
|
361438
|
+
* within the current linear detection window, and are absent from
|
|
362360
361439
|
* HEAD at the end of the window.
|
|
362361
361440
|
*
|
|
362362
361441
|
* Rationale: on a merge commit, createMergeCompositePatch already diffs
|
|
@@ -362381,8 +361460,8 @@ var require_dist14 = __commonJS({
|
|
|
362381
361460
|
* single files from a multi-file patch would require a follow-up that
|
|
362382
361461
|
* regenerates the patch content via `git format-patch -- <files>`. No
|
|
362383
361462
|
* current failing test exercises this, and leaving the patch intact
|
|
362384
|
-
* preserves today's behaviour.
|
|
362385
|
-
*
|
|
361463
|
+
* preserves today's behaviour. Revisit if a future adversarial test
|
|
361464
|
+
* demands per-file stripping.
|
|
362386
361465
|
*/
|
|
362387
361466
|
async collapseNetZeroFiles(patches) {
|
|
362388
361467
|
if (patches.length === 0) return patches;
|
|
@@ -362443,12 +361522,10 @@ var require_dist14 = __commonJS({
|
|
|
362443
361522
|
firstParent,
|
|
362444
361523
|
mergeCommit.sha
|
|
362445
361524
|
]);
|
|
362446
|
-
const
|
|
362447
|
-
|
|
362448
|
-
const files = allMergeFiles.filter((f4) => !isSensitiveFile(f4));
|
|
362449
|
-
if (sensitiveMergeFiles.length > 0) {
|
|
361525
|
+
const { files, sensitiveFiles } = filterInfrastructureAndSensitiveFiles(filesOutput);
|
|
361526
|
+
if (sensitiveFiles.length > 0) {
|
|
362450
361527
|
this.warnings.push(
|
|
362451
|
-
`Sensitive file(s) excluded from merge patch for commit ${mergeCommit.sha.slice(0, 7)}: ${
|
|
361528
|
+
`Sensitive file(s) excluded from merge patch for commit ${mergeCommit.sha.slice(0, 7)}: ${sensitiveFiles.join(", ")}. These files will not be tracked by replay.`
|
|
362452
361529
|
);
|
|
362453
361530
|
}
|
|
362454
361531
|
if (files.length === 0) return null;
|
|
@@ -362464,12 +361541,7 @@ var require_dist14 = __commonJS({
|
|
|
362464
361541
|
if (lock.patches.some((p14) => p14.content_hash === contentHash) || forgottenHashes.has(contentHash)) {
|
|
362465
361542
|
return null;
|
|
362466
361543
|
}
|
|
362467
|
-
const theirsSnapshot =
|
|
362468
|
-
for (const file4 of files) {
|
|
362469
|
-
if (isBinaryFile(file4)) continue;
|
|
362470
|
-
const content5 = await this.git.showFile(mergeCommit.sha, file4).catch(() => null);
|
|
362471
|
-
if (content5 != null) theirsSnapshot[file4] = content5;
|
|
362472
|
-
}
|
|
361544
|
+
const theirsSnapshot = await capturePatchSnapshot(this.git, files, mergeCommit.sha);
|
|
362473
361545
|
return {
|
|
362474
361546
|
id: `patch-merge-${mergeCommit.sha.slice(0, 8)}`,
|
|
362475
361547
|
content_hash: contentHash,
|
|
@@ -362493,13 +361565,24 @@ var require_dist14 = __commonJS({
|
|
|
362493
361565
|
if (!diffBase) {
|
|
362494
361566
|
return this.detectPatchesViaCommitScan();
|
|
362495
361567
|
}
|
|
361568
|
+
const lockForGuard = this.lockManager.read();
|
|
361569
|
+
const knownGenerations = new Set(lockForGuard.generations.map((g8) => g8.commit_sha));
|
|
361570
|
+
let resolvedBaseGeneration = commitKnownMissing ? diffBase : lastGen.commit_sha;
|
|
361571
|
+
if (commitKnownMissing && !knownGenerations.has(diffBase)) {
|
|
361572
|
+
const fallback2 = lockForGuard.current_generation ?? lockForGuard.generations[lockForGuard.generations.length - 1]?.commit_sha;
|
|
361573
|
+
if (!fallback2) {
|
|
361574
|
+
this.warnings.push(
|
|
361575
|
+
`Composite-patch fallback skipped: diff base ${diffBase.slice(0, 7)} is not a recorded generation and the lockfile has no recorded generations to anchor on. Customer customizations on disk are preserved; tracked patches will resume on the next regeneration.`
|
|
361576
|
+
);
|
|
361577
|
+
return { patches: [], revertedPatchIds: [] };
|
|
361578
|
+
}
|
|
361579
|
+
resolvedBaseGeneration = fallback2;
|
|
361580
|
+
}
|
|
362496
361581
|
const filesOutput = await this.git.exec(["diff", "--name-only", diffBase, "HEAD"]);
|
|
362497
|
-
const
|
|
362498
|
-
|
|
362499
|
-
const files = allTreeFiles.filter((f4) => !isSensitiveFile(f4));
|
|
362500
|
-
if (sensitiveTreeFiles.length > 0) {
|
|
361582
|
+
const { files, sensitiveFiles } = filterInfrastructureAndSensitiveFiles(filesOutput);
|
|
361583
|
+
if (sensitiveFiles.length > 0) {
|
|
362501
361584
|
this.warnings.push(
|
|
362502
|
-
`Sensitive file(s) excluded from composite patch: ${
|
|
361585
|
+
`Sensitive file(s) excluded from composite patch: ${sensitiveFiles.join(", ")}. These files will not be tracked by replay.`
|
|
362503
361586
|
);
|
|
362504
361587
|
}
|
|
362505
361588
|
if (files.length === 0) return { patches: [], revertedPatchIds: [] };
|
|
@@ -362511,21 +361594,19 @@ var require_dist14 = __commonJS({
|
|
|
362511
361594
|
return { patches: [], revertedPatchIds: [] };
|
|
362512
361595
|
}
|
|
362513
361596
|
const headSha = (await this.git.exec(["rev-parse", "HEAD"])).trim();
|
|
362514
|
-
const theirsSnapshot =
|
|
362515
|
-
for (const file4 of files) {
|
|
362516
|
-
if (isBinaryFile(file4)) continue;
|
|
362517
|
-
const content5 = await this.git.showFile("HEAD", file4).catch(() => null);
|
|
362518
|
-
if (content5 != null) theirsSnapshot[file4] = content5;
|
|
362519
|
-
}
|
|
361597
|
+
const theirsSnapshot = await capturePatchSnapshot(this.git, files, "HEAD");
|
|
362520
361598
|
const compositePatch = {
|
|
362521
361599
|
id: `patch-composite-${headSha.slice(0, 8)}`,
|
|
362522
361600
|
content_hash: contentHash,
|
|
362523
361601
|
original_commit: headSha,
|
|
362524
361602
|
original_message: "Customer customizations (composite)",
|
|
362525
361603
|
original_author: "composite",
|
|
362526
|
-
//
|
|
362527
|
-
//
|
|
362528
|
-
|
|
361604
|
+
// Anchor `base_generation` on a SHA the applicator's
|
|
361605
|
+
// `resolveBaseGeneration` can find — always a member of
|
|
361606
|
+
// `lock.generations`. When `diffBase` is a recorded
|
|
361607
|
+
// generation, use it directly; otherwise the guard above
|
|
361608
|
+
// has already mapped it onto `current_generation`.
|
|
361609
|
+
base_generation: resolvedBaseGeneration,
|
|
362529
361610
|
files,
|
|
362530
361611
|
patch_content: diff2,
|
|
362531
361612
|
...Object.keys(theirsSnapshot).length > 0 ? { theirs_snapshot: theirsSnapshot } : {},
|
|
@@ -362543,6 +361624,8 @@ var require_dist14 = __commonJS({
|
|
|
362543
361624
|
*/
|
|
362544
361625
|
async detectPatchesViaCommitScan() {
|
|
362545
361626
|
const lock = this.lockManager.read();
|
|
361627
|
+
const knownGenerations = new Set(lock.generations.map((g8) => g8.commit_sha));
|
|
361628
|
+
const fallbackAnchor = lock.current_generation ?? lock.generations[lock.generations.length - 1]?.commit_sha;
|
|
362546
361629
|
const log4 = await this.git.exec([
|
|
362547
361630
|
"log",
|
|
362548
361631
|
"--max-count=200",
|
|
@@ -362584,12 +361667,10 @@ var require_dist14 = __commonJS({
|
|
|
362584
361667
|
continue;
|
|
362585
361668
|
}
|
|
362586
361669
|
const filesOutput = await this.git.exec(["diff-tree", "--no-commit-id", "--name-only", "-r", commit.sha]);
|
|
362587
|
-
const
|
|
362588
|
-
|
|
362589
|
-
const files = allScanFiles.filter((f4) => !isSensitiveFile(f4));
|
|
362590
|
-
if (sensitiveScanFiles.length > 0) {
|
|
361670
|
+
const { files, sensitiveFiles } = filterInfrastructureAndSensitiveFiles(filesOutput);
|
|
361671
|
+
if (sensitiveFiles.length > 0) {
|
|
362591
361672
|
this.warnings.push(
|
|
362592
|
-
`Sensitive file(s) excluded from patch for commit ${commit.sha.slice(0, 7)}: ${
|
|
361673
|
+
`Sensitive file(s) excluded from patch for commit ${commit.sha.slice(0, 7)}: ${sensitiveFiles.join(", ")}. These files will not be tracked by replay.`
|
|
362593
361674
|
);
|
|
362594
361675
|
if (files.length > 0) {
|
|
362595
361676
|
if (parents.length === 0) {
|
|
@@ -362611,19 +361692,25 @@ var require_dist14 = __commonJS({
|
|
|
362611
361692
|
continue;
|
|
362612
361693
|
}
|
|
362613
361694
|
const parentSha = parents[0];
|
|
362614
|
-
|
|
362615
|
-
|
|
362616
|
-
|
|
362617
|
-
|
|
362618
|
-
|
|
361695
|
+
let baseGeneration;
|
|
361696
|
+
if (knownGenerations.has(parentSha)) {
|
|
361697
|
+
baseGeneration = parentSha;
|
|
361698
|
+
} else if (fallbackAnchor) {
|
|
361699
|
+
baseGeneration = fallbackAnchor;
|
|
361700
|
+
} else {
|
|
361701
|
+
this.warnings.push(
|
|
361702
|
+
`Skipping commit ${commit.sha.slice(0, 7)} (${commit.message.split("\n")[0]}): no recorded generation in the lockfile to anchor on. The customer's state on disk is preserved; tracked patches will resume on the next regeneration.`
|
|
361703
|
+
);
|
|
361704
|
+
continue;
|
|
362619
361705
|
}
|
|
361706
|
+
const theirsSnapshot = await capturePatchSnapshot(this.git, files, commit.sha);
|
|
362620
361707
|
newPatches.push({
|
|
362621
361708
|
id: `patch-${commit.sha.slice(0, 8)}`,
|
|
362622
361709
|
content_hash: contentHash,
|
|
362623
361710
|
original_commit: commit.sha,
|
|
362624
361711
|
original_message: commit.message,
|
|
362625
361712
|
original_author: `${commit.authorName} <${commit.authorEmail}>`,
|
|
362626
|
-
base_generation:
|
|
361713
|
+
base_generation: baseGeneration,
|
|
362627
361714
|
files,
|
|
362628
361715
|
patch_content: patchContent,
|
|
362629
361716
|
...Object.keys(theirsSnapshot).length > 0 ? { theirs_snapshot: theirsSnapshot } : {}
|
|
@@ -362668,6 +361755,1040 @@ var require_dist14 = __commonJS({
|
|
|
362668
361755
|
return lock.generations.find((g8) => g8.commit_sha === lock.current_generation);
|
|
362669
361756
|
}
|
|
362670
361757
|
};
|
|
361758
|
+
var import_node_diff3 = require_diff3();
|
|
361759
|
+
function threeWayMerge(base4, ours, theirs) {
|
|
361760
|
+
const baseLines = base4.split("\n");
|
|
361761
|
+
const oursLines = ours.split("\n");
|
|
361762
|
+
const theirsLines = theirs.split("\n");
|
|
361763
|
+
const regions = (0, import_node_diff3.diff3Merge)(oursLines, baseLines, theirsLines);
|
|
361764
|
+
const outputLines = [];
|
|
361765
|
+
const conflicts2 = [];
|
|
361766
|
+
let currentLine = 1;
|
|
361767
|
+
for (const region of regions) {
|
|
361768
|
+
if (region.ok) {
|
|
361769
|
+
outputLines.push(...region.ok);
|
|
361770
|
+
currentLine += region.ok.length;
|
|
361771
|
+
} else if (region.conflict) {
|
|
361772
|
+
const resolved = tryResolveConflict(
|
|
361773
|
+
region.conflict.a,
|
|
361774
|
+
// ours (generator)
|
|
361775
|
+
region.conflict.o,
|
|
361776
|
+
// base
|
|
361777
|
+
region.conflict.b
|
|
361778
|
+
// theirs (user)
|
|
361779
|
+
);
|
|
361780
|
+
if (resolved !== null) {
|
|
361781
|
+
outputLines.push(...resolved);
|
|
361782
|
+
currentLine += resolved.length;
|
|
361783
|
+
} else {
|
|
361784
|
+
const startLine = currentLine;
|
|
361785
|
+
outputLines.push("<<<<<<< Generated");
|
|
361786
|
+
outputLines.push(...region.conflict.a);
|
|
361787
|
+
outputLines.push("=======");
|
|
361788
|
+
outputLines.push(...region.conflict.b);
|
|
361789
|
+
outputLines.push(">>>>>>> Your customization");
|
|
361790
|
+
const conflictLines = region.conflict.a.length + region.conflict.b.length + 3;
|
|
361791
|
+
conflicts2.push({
|
|
361792
|
+
startLine,
|
|
361793
|
+
endLine: startLine + conflictLines - 1,
|
|
361794
|
+
ours: region.conflict.a,
|
|
361795
|
+
theirs: region.conflict.b
|
|
361796
|
+
});
|
|
361797
|
+
currentLine += conflictLines;
|
|
361798
|
+
}
|
|
361799
|
+
}
|
|
361800
|
+
}
|
|
361801
|
+
const result = {
|
|
361802
|
+
content: outputLines.join("\n"),
|
|
361803
|
+
hasConflicts: conflicts2.length > 0,
|
|
361804
|
+
conflicts: conflicts2
|
|
361805
|
+
};
|
|
361806
|
+
if (conflicts2.length === 0) {
|
|
361807
|
+
const dropped = computeDroppedContextLines(baseLines, theirsLines, outputLines);
|
|
361808
|
+
if (dropped.length > 0) {
|
|
361809
|
+
result.droppedContextLines = dropped;
|
|
361810
|
+
}
|
|
361811
|
+
}
|
|
361812
|
+
return result;
|
|
361813
|
+
}
|
|
361814
|
+
function computeDroppedContextLines(baseLines, theirsLines, mergedLines) {
|
|
361815
|
+
const baseCounts = countLines2(baseLines);
|
|
361816
|
+
const theirsCounts = countLines2(theirsLines);
|
|
361817
|
+
const mergedCounts = countLines2(mergedLines);
|
|
361818
|
+
const dropped = [];
|
|
361819
|
+
const seen = /* @__PURE__ */ new Set();
|
|
361820
|
+
for (const line of baseLines) {
|
|
361821
|
+
if (seen.has(line)) continue;
|
|
361822
|
+
const inBase = baseCounts.get(line) ?? 0;
|
|
361823
|
+
const inTheirs = theirsCounts.get(line) ?? 0;
|
|
361824
|
+
const inMerged = mergedCounts.get(line) ?? 0;
|
|
361825
|
+
if (inBase > 0 && inTheirs > 0 && inMerged < Math.min(inBase, inTheirs)) {
|
|
361826
|
+
dropped.push(line);
|
|
361827
|
+
seen.add(line);
|
|
361828
|
+
}
|
|
361829
|
+
}
|
|
361830
|
+
return dropped;
|
|
361831
|
+
}
|
|
361832
|
+
function countLines2(lines) {
|
|
361833
|
+
const counts = /* @__PURE__ */ new Map();
|
|
361834
|
+
for (const line of lines) {
|
|
361835
|
+
counts.set(line, (counts.get(line) ?? 0) + 1);
|
|
361836
|
+
}
|
|
361837
|
+
return counts;
|
|
361838
|
+
}
|
|
361839
|
+
function tryResolveConflict(oursLines, baseLines, theirsLines) {
|
|
361840
|
+
if (baseLines.length === 0) {
|
|
361841
|
+
return null;
|
|
361842
|
+
}
|
|
361843
|
+
const oursPatches = (0, import_node_diff3.diffPatch)(baseLines, oursLines);
|
|
361844
|
+
const theirsPatches = (0, import_node_diff3.diffPatch)(baseLines, theirsLines);
|
|
361845
|
+
if (oursPatches.length === 0) return theirsLines;
|
|
361846
|
+
if (theirsPatches.length === 0) return oursLines;
|
|
361847
|
+
if (patchesOverlap(oursPatches, theirsPatches)) {
|
|
361848
|
+
return null;
|
|
361849
|
+
}
|
|
361850
|
+
return applyBothPatches(baseLines, oursPatches, theirsPatches);
|
|
361851
|
+
}
|
|
361852
|
+
function patchesOverlap(oursPatches, theirsPatches) {
|
|
361853
|
+
for (const op4 of oursPatches) {
|
|
361854
|
+
const oStart = op4.buffer1.offset;
|
|
361855
|
+
const oEnd = oStart + op4.buffer1.length;
|
|
361856
|
+
for (const tp3 of theirsPatches) {
|
|
361857
|
+
const tStart = tp3.buffer1.offset;
|
|
361858
|
+
const tEnd = tStart + tp3.buffer1.length;
|
|
361859
|
+
if (op4.buffer1.length === 0 && tp3.buffer1.length === 0 && oStart === tStart) {
|
|
361860
|
+
return true;
|
|
361861
|
+
}
|
|
361862
|
+
if (tp3.buffer1.length === 0 && tStart === oEnd) {
|
|
361863
|
+
return true;
|
|
361864
|
+
}
|
|
361865
|
+
if (op4.buffer1.length === 0 && oStart === tEnd) {
|
|
361866
|
+
return true;
|
|
361867
|
+
}
|
|
361868
|
+
if (oStart < tEnd && tStart < oEnd) {
|
|
361869
|
+
return true;
|
|
361870
|
+
}
|
|
361871
|
+
}
|
|
361872
|
+
}
|
|
361873
|
+
return false;
|
|
361874
|
+
}
|
|
361875
|
+
function applyBothPatches(baseLines, oursPatches, theirsPatches) {
|
|
361876
|
+
const allPatches = [
|
|
361877
|
+
...oursPatches.map((p14) => ({
|
|
361878
|
+
offset: p14.buffer1.offset,
|
|
361879
|
+
length: p14.buffer1.length,
|
|
361880
|
+
replacement: p14.buffer2.chunk
|
|
361881
|
+
})),
|
|
361882
|
+
...theirsPatches.map((p14) => ({
|
|
361883
|
+
offset: p14.buffer1.offset,
|
|
361884
|
+
length: p14.buffer1.length,
|
|
361885
|
+
replacement: p14.buffer2.chunk
|
|
361886
|
+
}))
|
|
361887
|
+
];
|
|
361888
|
+
allPatches.sort((a3, b8) => b8.offset - a3.offset);
|
|
361889
|
+
const result = [...baseLines];
|
|
361890
|
+
for (const p14 of allPatches) {
|
|
361891
|
+
result.splice(p14.offset, p14.length, ...p14.replacement);
|
|
361892
|
+
}
|
|
361893
|
+
return result;
|
|
361894
|
+
}
|
|
361895
|
+
var import_promises310 = require("fs/promises");
|
|
361896
|
+
var import_node_os15 = require("os");
|
|
361897
|
+
var import_node_path52 = require("path");
|
|
361898
|
+
var import_minimatch8 = require_commonjs3();
|
|
361899
|
+
var MergeAccumulator = class {
|
|
361900
|
+
constructor(mode, sharedFiles) {
|
|
361901
|
+
this.mode = mode;
|
|
361902
|
+
this.sharedFiles = sharedFiles;
|
|
361903
|
+
}
|
|
361904
|
+
entries = /* @__PURE__ */ new Map();
|
|
361905
|
+
/** Was this file touched by 2+ patches in the current `applyPatches` invocation? */
|
|
361906
|
+
isShared(path111) {
|
|
361907
|
+
return this.sharedFiles.has(path111);
|
|
361908
|
+
}
|
|
361909
|
+
/** Has any prior patch already recorded a result for this path? */
|
|
361910
|
+
has(path111) {
|
|
361911
|
+
return this.entries.has(path111);
|
|
361912
|
+
}
|
|
361913
|
+
/** Look up the accumulated content + base generation for this path. */
|
|
361914
|
+
lookup(path111) {
|
|
361915
|
+
return this.entries.get(path111);
|
|
361916
|
+
}
|
|
361917
|
+
/**
|
|
361918
|
+
* True if any of the patch's files have a prior accumulator entry — in
|
|
361919
|
+
* which case the fast path (`git apply --3way`) cannot honor prior
|
|
361920
|
+
* patches' contributions and we must route through the slow path.
|
|
361921
|
+
*/
|
|
361922
|
+
shouldSkipFastPath(patch5) {
|
|
361923
|
+
return patch5.files.some((f4) => this.entries.has(f4));
|
|
361924
|
+
}
|
|
361925
|
+
/** Record a successful (clean) merge result. */
|
|
361926
|
+
recordMerge(path111, content5, baseGeneration) {
|
|
361927
|
+
this.entries.set(path111, { content: content5, baseGeneration });
|
|
361928
|
+
}
|
|
361929
|
+
/**
|
|
361930
|
+
* Record a conflicted merge result. Populates only in `"resolve"` mode
|
|
361931
|
+
* — replay mode keeps the accumulator clean of marker-laden content
|
|
361932
|
+
* since markers will be stripped from disk after the apply loop.
|
|
361933
|
+
*/
|
|
361934
|
+
recordConflict(path111, content5, baseGeneration) {
|
|
361935
|
+
if (this.mode === "resolve") {
|
|
361936
|
+
this.entries.set(path111, { content: content5, baseGeneration });
|
|
361937
|
+
}
|
|
361938
|
+
}
|
|
361939
|
+
/**
|
|
361940
|
+
* Defensive copy for test introspection. Used by invariant I2 to assert
|
|
361941
|
+
* that after every applied patch, the accumulator has an entry for each
|
|
361942
|
+
* file the patch touched (see `__tests__/invariants/i2-accumulator-correctness.ts`).
|
|
361943
|
+
*
|
|
361944
|
+
* @internal
|
|
361945
|
+
*/
|
|
361946
|
+
snapshot() {
|
|
361947
|
+
const copy = /* @__PURE__ */ new Map();
|
|
361948
|
+
for (const [path111, entry] of this.entries) {
|
|
361949
|
+
copy.set(path111, { content: entry.content, baseGeneration: entry.baseGeneration });
|
|
361950
|
+
}
|
|
361951
|
+
return copy;
|
|
361952
|
+
}
|
|
361953
|
+
};
|
|
361954
|
+
var import_promises195 = require("fs/promises");
|
|
361955
|
+
var import_node_path310 = require("path");
|
|
361956
|
+
async function resolveInputs(args) {
|
|
361957
|
+
const {
|
|
361958
|
+
patch: patch5,
|
|
361959
|
+
filePath,
|
|
361960
|
+
git,
|
|
361961
|
+
lockManager,
|
|
361962
|
+
outputDir,
|
|
361963
|
+
isTreeReachable,
|
|
361964
|
+
resolveBaseGeneration,
|
|
361965
|
+
resolveFilePath,
|
|
361966
|
+
extractRenameSource,
|
|
361967
|
+
extractFileDiff: extractFileDiff2
|
|
361968
|
+
} = args;
|
|
361969
|
+
const baseGen = await resolveBaseGeneration(patch5.base_generation);
|
|
361970
|
+
if (!baseGen) {
|
|
361971
|
+
return {
|
|
361972
|
+
resolvedPath: filePath,
|
|
361973
|
+
metadata: {
|
|
361974
|
+
patchId: patch5.id,
|
|
361975
|
+
patchMessage: patch5.original_message,
|
|
361976
|
+
baseGeneration: patch5.base_generation,
|
|
361977
|
+
currentGeneration: lockManager.read().current_generation
|
|
361978
|
+
},
|
|
361979
|
+
base: null,
|
|
361980
|
+
ours: null,
|
|
361981
|
+
oursPath: (0, import_node_path310.join)(outputDir, filePath),
|
|
361982
|
+
renameSourcePath: void 0,
|
|
361983
|
+
ghostTheirs: null,
|
|
361984
|
+
earlyExit: { status: "skipped", reason: "base-generation-not-found" }
|
|
361985
|
+
};
|
|
361986
|
+
}
|
|
361987
|
+
const lock = lockManager.read();
|
|
361988
|
+
const currentGen = lock.generations.find((g8) => g8.commit_sha === lock.current_generation);
|
|
361989
|
+
const currentTreeHash = currentGen?.tree_hash ?? baseGen.tree_hash;
|
|
361990
|
+
const resolvedPath = await resolveFilePath(filePath, baseGen.tree_hash, currentTreeHash);
|
|
361991
|
+
const metadata = {
|
|
361992
|
+
patchId: patch5.id,
|
|
361993
|
+
patchMessage: patch5.original_message,
|
|
361994
|
+
baseGeneration: patch5.base_generation,
|
|
361995
|
+
currentGeneration: lock.current_generation
|
|
361996
|
+
};
|
|
361997
|
+
let base4 = await git.showFile(baseGen.tree_hash, filePath);
|
|
361998
|
+
let renameSourcePath;
|
|
361999
|
+
if (!base4) {
|
|
362000
|
+
const renameSource = extractRenameSource(patch5.patch_content, filePath);
|
|
362001
|
+
if (renameSource) {
|
|
362002
|
+
base4 = await git.showFile(baseGen.tree_hash, renameSource);
|
|
362003
|
+
renameSourcePath = renameSource;
|
|
362004
|
+
}
|
|
362005
|
+
}
|
|
362006
|
+
const oursPath = (0, import_node_path310.join)(outputDir, resolvedPath);
|
|
362007
|
+
const ours = await (0, import_promises195.readFile)(oursPath, "utf-8").catch(() => null);
|
|
362008
|
+
let ghostTheirs = null;
|
|
362009
|
+
if (!base4 && ours && !renameSourcePath) {
|
|
362010
|
+
const treeReachable = await isTreeReachable(baseGen.tree_hash);
|
|
362011
|
+
if (!treeReachable) {
|
|
362012
|
+
const fileDiff = extractFileDiff2(patch5.patch_content, filePath);
|
|
362013
|
+
if (fileDiff) {
|
|
362014
|
+
const { reconstructFromGhostPatch: reconstructFromGhostPatch2 } = await Promise.resolve().then(() => (init_HybridReconstruction(), HybridReconstruction_exports));
|
|
362015
|
+
const result = reconstructFromGhostPatch2(fileDiff, ours);
|
|
362016
|
+
if (result) {
|
|
362017
|
+
base4 = result.base;
|
|
362018
|
+
ghostTheirs = result.theirs;
|
|
362019
|
+
}
|
|
362020
|
+
}
|
|
362021
|
+
}
|
|
362022
|
+
}
|
|
362023
|
+
return {
|
|
362024
|
+
resolvedPath,
|
|
362025
|
+
metadata,
|
|
362026
|
+
base: base4,
|
|
362027
|
+
ours,
|
|
362028
|
+
oursPath,
|
|
362029
|
+
renameSourcePath,
|
|
362030
|
+
ghostTheirs
|
|
362031
|
+
};
|
|
362032
|
+
}
|
|
362033
|
+
var CONFLICT_OPENER = "<<<<<<< Generated";
|
|
362034
|
+
var CONFLICT_SEPARATOR = "=======";
|
|
362035
|
+
var CONFLICT_CLOSER = ">>>>>>> Your customization";
|
|
362036
|
+
function trimCR(line) {
|
|
362037
|
+
return line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
362038
|
+
}
|
|
362039
|
+
function findConflictRanges(lines) {
|
|
362040
|
+
const ranges = [];
|
|
362041
|
+
let i9 = 0;
|
|
362042
|
+
while (i9 < lines.length) {
|
|
362043
|
+
if (trimCR(lines[i9]) === CONFLICT_OPENER) {
|
|
362044
|
+
let separatorIdx = -1;
|
|
362045
|
+
let j7 = i9 + 1;
|
|
362046
|
+
let found = false;
|
|
362047
|
+
while (j7 < lines.length) {
|
|
362048
|
+
const trimmed2 = trimCR(lines[j7]);
|
|
362049
|
+
if (trimmed2 === CONFLICT_OPENER) {
|
|
362050
|
+
break;
|
|
362051
|
+
}
|
|
362052
|
+
if (separatorIdx === -1 && trimmed2 === CONFLICT_SEPARATOR) {
|
|
362053
|
+
separatorIdx = j7;
|
|
362054
|
+
} else if (separatorIdx !== -1 && trimmed2 === CONFLICT_CLOSER) {
|
|
362055
|
+
ranges.push({ start: i9, separator: separatorIdx, end: j7 });
|
|
362056
|
+
i9 = j7;
|
|
362057
|
+
found = true;
|
|
362058
|
+
break;
|
|
362059
|
+
}
|
|
362060
|
+
j7++;
|
|
362061
|
+
}
|
|
362062
|
+
if (!found) {
|
|
362063
|
+
i9++;
|
|
362064
|
+
continue;
|
|
362065
|
+
}
|
|
362066
|
+
}
|
|
362067
|
+
i9++;
|
|
362068
|
+
}
|
|
362069
|
+
return ranges;
|
|
362070
|
+
}
|
|
362071
|
+
function stripConflictMarkers(content5) {
|
|
362072
|
+
const lines = content5.split(/\r?\n/);
|
|
362073
|
+
const ranges = findConflictRanges(lines);
|
|
362074
|
+
if (ranges.length === 0) {
|
|
362075
|
+
return content5;
|
|
362076
|
+
}
|
|
362077
|
+
const result = [];
|
|
362078
|
+
let rangeIdx = 0;
|
|
362079
|
+
for (let i9 = 0; i9 < lines.length; i9++) {
|
|
362080
|
+
if (rangeIdx < ranges.length) {
|
|
362081
|
+
const range2 = ranges[rangeIdx];
|
|
362082
|
+
if (i9 === range2.start) {
|
|
362083
|
+
continue;
|
|
362084
|
+
}
|
|
362085
|
+
if (i9 > range2.start && i9 < range2.separator) {
|
|
362086
|
+
result.push(lines[i9]);
|
|
362087
|
+
continue;
|
|
362088
|
+
}
|
|
362089
|
+
if (i9 === range2.separator) {
|
|
362090
|
+
continue;
|
|
362091
|
+
}
|
|
362092
|
+
if (i9 > range2.separator && i9 < range2.end) {
|
|
362093
|
+
continue;
|
|
362094
|
+
}
|
|
362095
|
+
if (i9 === range2.end) {
|
|
362096
|
+
rangeIdx++;
|
|
362097
|
+
continue;
|
|
362098
|
+
}
|
|
362099
|
+
}
|
|
362100
|
+
result.push(lines[i9]);
|
|
362101
|
+
}
|
|
362102
|
+
return result.join("\n");
|
|
362103
|
+
}
|
|
362104
|
+
function hasConflictMarkerStrings(content5) {
|
|
362105
|
+
if (content5 == null) return false;
|
|
362106
|
+
return content5.includes(CONFLICT_OPENER) || content5.includes(CONFLICT_CLOSER);
|
|
362107
|
+
}
|
|
362108
|
+
async function buildMergePlan(args) {
|
|
362109
|
+
const {
|
|
362110
|
+
inputs,
|
|
362111
|
+
patch: patch5,
|
|
362112
|
+
filePath,
|
|
362113
|
+
accumulator,
|
|
362114
|
+
tempGit,
|
|
362115
|
+
tempDir: tempDir2,
|
|
362116
|
+
applyPatchToContent: applyPatchToContent2,
|
|
362117
|
+
isPatchAlreadyApplied
|
|
362118
|
+
} = args;
|
|
362119
|
+
const { base: base4, ours, resolvedPath, renameSourcePath, ghostTheirs } = inputs;
|
|
362120
|
+
let theirs = ghostTheirs;
|
|
362121
|
+
const ghostReconstructed = ghostTheirs != null;
|
|
362122
|
+
const snapshotForFile = patch5.theirs_snapshot?.[resolvedPath] ?? patch5.theirs_snapshot?.[filePath];
|
|
362123
|
+
const fileIsShared = accumulator.isShared(resolvedPath) || accumulator.isShared(filePath);
|
|
362124
|
+
const useSnapshotAsPrimary = !fileIsShared && snapshotForFile != null;
|
|
362125
|
+
let primarySource = ghostReconstructed ? "ghost" : "reconstructed";
|
|
362126
|
+
if (!ghostReconstructed) {
|
|
362127
|
+
if (useSnapshotAsPrimary) {
|
|
362128
|
+
theirs = snapshotForFile;
|
|
362129
|
+
primarySource = "snapshot";
|
|
362130
|
+
} else {
|
|
362131
|
+
theirs = await applyPatchToContent2(
|
|
362132
|
+
base4,
|
|
362133
|
+
patch5.patch_content,
|
|
362134
|
+
filePath,
|
|
362135
|
+
tempGit,
|
|
362136
|
+
tempDir2,
|
|
362137
|
+
renameSourcePath
|
|
362138
|
+
);
|
|
362139
|
+
const reconstructionHasMarkers = hasConflictMarkerStrings(theirs);
|
|
362140
|
+
const baseHadMarkers = hasConflictMarkerStrings(base4);
|
|
362141
|
+
if (reconstructionHasMarkers && !baseHadMarkers && snapshotForFile != null) {
|
|
362142
|
+
theirs = snapshotForFile;
|
|
362143
|
+
}
|
|
362144
|
+
}
|
|
362145
|
+
}
|
|
362146
|
+
const accumulatorEntry = accumulator.lookup(resolvedPath);
|
|
362147
|
+
if (theirs) {
|
|
362148
|
+
const theirsHasMarkers = hasConflictMarkerStrings(theirs);
|
|
362149
|
+
const baseHasMarkers = hasConflictMarkerStrings(base4);
|
|
362150
|
+
if (theirsHasMarkers && !baseHasMarkers) {
|
|
362151
|
+
if (accumulatorEntry) {
|
|
362152
|
+
theirs = null;
|
|
362153
|
+
} else {
|
|
362154
|
+
return { kind: "skipped", reason: "stale-conflict-markers" };
|
|
362155
|
+
}
|
|
362156
|
+
}
|
|
362157
|
+
}
|
|
362158
|
+
let useAccumulatorAsMergeBase = false;
|
|
362159
|
+
if (accumulatorEntry && (theirs == null || base4 == null)) {
|
|
362160
|
+
theirs = await applyPatchToContent2(
|
|
362161
|
+
accumulatorEntry.content,
|
|
362162
|
+
patch5.patch_content,
|
|
362163
|
+
filePath,
|
|
362164
|
+
tempGit,
|
|
362165
|
+
tempDir2
|
|
362166
|
+
);
|
|
362167
|
+
if (theirs != null) {
|
|
362168
|
+
useAccumulatorAsMergeBase = true;
|
|
362169
|
+
} else if (await isPatchAlreadyApplied(
|
|
362170
|
+
patch5.patch_content,
|
|
362171
|
+
filePath,
|
|
362172
|
+
accumulatorEntry.content,
|
|
362173
|
+
tempGit,
|
|
362174
|
+
tempDir2
|
|
362175
|
+
)) {
|
|
362176
|
+
theirs = accumulatorEntry.content;
|
|
362177
|
+
useAccumulatorAsMergeBase = true;
|
|
362178
|
+
}
|
|
362179
|
+
}
|
|
362180
|
+
if (theirs) {
|
|
362181
|
+
const theirsHasMarkers = hasConflictMarkerStrings(theirs);
|
|
362182
|
+
const accBaseHasMarkers = accumulatorEntry != null && hasConflictMarkerStrings(accumulatorEntry.content);
|
|
362183
|
+
if (theirsHasMarkers && !accBaseHasMarkers && !hasConflictMarkerStrings(base4)) {
|
|
362184
|
+
return { kind: "skipped", reason: "stale-conflict-markers" };
|
|
362185
|
+
}
|
|
362186
|
+
}
|
|
362187
|
+
let effective_theirs = theirs;
|
|
362188
|
+
let conflictReasonHint;
|
|
362189
|
+
if (theirs != null && base4 != null && !useAccumulatorAsMergeBase) {
|
|
362190
|
+
if (accumulatorEntry && accumulatorEntry.baseGeneration === patch5.base_generation) {
|
|
362191
|
+
try {
|
|
362192
|
+
const preMerged = threeWayMerge(base4, accumulatorEntry.content, theirs);
|
|
362193
|
+
if (!preMerged.hasConflicts) {
|
|
362194
|
+
effective_theirs = preMerged.content;
|
|
362195
|
+
} else {
|
|
362196
|
+
effective_theirs = theirs;
|
|
362197
|
+
}
|
|
362198
|
+
} catch {
|
|
362199
|
+
effective_theirs = theirs;
|
|
362200
|
+
}
|
|
362201
|
+
} else if (accumulatorEntry) {
|
|
362202
|
+
conflictReasonHint = "base-generation-mismatch";
|
|
362203
|
+
}
|
|
362204
|
+
}
|
|
362205
|
+
if (base4 == null && ours == null && effective_theirs != null) {
|
|
362206
|
+
return { kind: "new-file-only-user", theirs: effective_theirs };
|
|
362207
|
+
}
|
|
362208
|
+
if (base4 == null && ours != null && effective_theirs != null && !useAccumulatorAsMergeBase) {
|
|
362209
|
+
return { kind: "new-file-both", theirs: effective_theirs };
|
|
362210
|
+
}
|
|
362211
|
+
if (effective_theirs == null) {
|
|
362212
|
+
return { kind: "skipped", reason: "missing-content" };
|
|
362213
|
+
}
|
|
362214
|
+
if (base4 == null && !useAccumulatorAsMergeBase || ours == null) {
|
|
362215
|
+
return { kind: "skipped", reason: "missing-content" };
|
|
362216
|
+
}
|
|
362217
|
+
if (useAccumulatorAsMergeBase) {
|
|
362218
|
+
const mergeBase = accumulatorEntry?.content ?? base4;
|
|
362219
|
+
if (mergeBase == null) {
|
|
362220
|
+
return { kind: "skipped", reason: "missing-content" };
|
|
362221
|
+
}
|
|
362222
|
+
return {
|
|
362223
|
+
kind: "accumulator-base",
|
|
362224
|
+
theirs: effective_theirs,
|
|
362225
|
+
mergeBase,
|
|
362226
|
+
conflictReasonHint
|
|
362227
|
+
};
|
|
362228
|
+
}
|
|
362229
|
+
return {
|
|
362230
|
+
kind: primarySource,
|
|
362231
|
+
base: base4,
|
|
362232
|
+
theirs: effective_theirs,
|
|
362233
|
+
mergeBase: base4,
|
|
362234
|
+
conflictReasonHint
|
|
362235
|
+
};
|
|
362236
|
+
}
|
|
362237
|
+
var import_promises210 = require("fs/promises");
|
|
362238
|
+
var import_node_path43 = require("path");
|
|
362239
|
+
async function runMergeAndRecord(args) {
|
|
362240
|
+
const { plan, inputs, patch: patch5, accumulator } = args;
|
|
362241
|
+
const { resolvedPath, metadata, oursPath, ours } = inputs;
|
|
362242
|
+
if (plan.kind === "skipped") {
|
|
362243
|
+
return { file: resolvedPath, status: "skipped", reason: plan.reason };
|
|
362244
|
+
}
|
|
362245
|
+
if (plan.kind === "new-file-only-user") {
|
|
362246
|
+
accumulator.recordMerge(resolvedPath, plan.theirs, patch5.base_generation);
|
|
362247
|
+
await (0, import_promises210.mkdir)((0, import_node_path43.dirname)(oursPath), { recursive: true });
|
|
362248
|
+
await (0, import_promises210.writeFile)(oursPath, plan.theirs);
|
|
362249
|
+
return { file: resolvedPath, status: "merged", reason: "new-file" };
|
|
362250
|
+
}
|
|
362251
|
+
if (plan.kind === "new-file-both") {
|
|
362252
|
+
const merged2 = threeWayMerge("", ours, plan.theirs);
|
|
362253
|
+
await (0, import_promises210.mkdir)((0, import_node_path43.dirname)(oursPath), { recursive: true });
|
|
362254
|
+
await (0, import_promises210.writeFile)(oursPath, merged2.content);
|
|
362255
|
+
if (merged2.hasConflicts) {
|
|
362256
|
+
return {
|
|
362257
|
+
file: resolvedPath,
|
|
362258
|
+
status: "conflict",
|
|
362259
|
+
conflicts: merged2.conflicts,
|
|
362260
|
+
conflictReason: "new-file-both",
|
|
362261
|
+
conflictMetadata: metadata
|
|
362262
|
+
};
|
|
362263
|
+
}
|
|
362264
|
+
accumulator.recordMerge(resolvedPath, merged2.content, patch5.base_generation);
|
|
362265
|
+
return { file: resolvedPath, status: "merged" };
|
|
362266
|
+
}
|
|
362267
|
+
const merged = threeWayMerge(plan.mergeBase, ours, plan.theirs);
|
|
362268
|
+
await (0, import_promises210.mkdir)((0, import_node_path43.dirname)(oursPath), { recursive: true });
|
|
362269
|
+
await (0, import_promises210.writeFile)(oursPath, merged.content);
|
|
362270
|
+
if (merged.hasConflicts) {
|
|
362271
|
+
accumulator.recordConflict(resolvedPath, plan.theirs, patch5.base_generation);
|
|
362272
|
+
return {
|
|
362273
|
+
file: resolvedPath,
|
|
362274
|
+
status: "conflict",
|
|
362275
|
+
conflicts: merged.conflicts,
|
|
362276
|
+
conflictReason: plan.conflictReasonHint ?? "same-line-edit",
|
|
362277
|
+
conflictMetadata: metadata
|
|
362278
|
+
};
|
|
362279
|
+
}
|
|
362280
|
+
accumulator.recordMerge(resolvedPath, plan.theirs, patch5.base_generation);
|
|
362281
|
+
return {
|
|
362282
|
+
file: resolvedPath,
|
|
362283
|
+
status: "merged",
|
|
362284
|
+
...merged.droppedContextLines && merged.droppedContextLines.length > 0 ? { droppedContextLines: merged.droppedContextLines } : {}
|
|
362285
|
+
};
|
|
362286
|
+
}
|
|
362287
|
+
var ReplayApplicator = class {
|
|
362288
|
+
git;
|
|
362289
|
+
lockManager;
|
|
362290
|
+
outputDir;
|
|
362291
|
+
renameCache = /* @__PURE__ */ new Map();
|
|
362292
|
+
treeExistsCache = /* @__PURE__ */ new Map();
|
|
362293
|
+
/**
|
|
362294
|
+
* Inter-patch THEIRS coordination. Created fresh in each `applyPatches`
|
|
362295
|
+
* invocation with the run's apply mode + precomputed shared-file set.
|
|
362296
|
+
* See `src/accumulator/MergeAccumulator.ts` for the full contract.
|
|
362297
|
+
*
|
|
362298
|
+
* Initialized to an empty default in the constructor so type narrowing
|
|
362299
|
+
* works for callers that read it before any `applyPatches` invocation
|
|
362300
|
+
* (e.g., the `getAccumulatorSnapshot` test accessor).
|
|
362301
|
+
*/
|
|
362302
|
+
accumulator = new MergeAccumulator("replay", /* @__PURE__ */ new Set());
|
|
362303
|
+
constructor(git, lockManager, outputDir) {
|
|
362304
|
+
this.git = git;
|
|
362305
|
+
this.lockManager = lockManager;
|
|
362306
|
+
this.outputDir = outputDir;
|
|
362307
|
+
}
|
|
362308
|
+
/**
|
|
362309
|
+
* Resolve the GenerationRecord for a patch's base_generation.
|
|
362310
|
+
* Falls back to constructing an ad-hoc record from the commit's tree
|
|
362311
|
+
* when base_generation isn't a tracked generation (commit-scan patches).
|
|
362312
|
+
*/
|
|
362313
|
+
async resolveBaseGeneration(baseGeneration) {
|
|
362314
|
+
const gen = this.lockManager.getGeneration(baseGeneration);
|
|
362315
|
+
if (gen) return gen;
|
|
362316
|
+
try {
|
|
362317
|
+
const treeHash = await this.git.getTreeHash(baseGeneration);
|
|
362318
|
+
return {
|
|
362319
|
+
commit_sha: baseGeneration,
|
|
362320
|
+
tree_hash: treeHash,
|
|
362321
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
362322
|
+
cli_version: "unknown",
|
|
362323
|
+
generator_versions: {}
|
|
362324
|
+
};
|
|
362325
|
+
} catch {
|
|
362326
|
+
return void 0;
|
|
362327
|
+
}
|
|
362328
|
+
}
|
|
362329
|
+
/**
|
|
362330
|
+
* @internal Test-only.
|
|
362331
|
+
* Returns a defensive copy of the inter-patch accumulator. Used by
|
|
362332
|
+
* invariant I2 to assert that after every applied patch, the
|
|
362333
|
+
* accumulator has an entry for each file the patch touched, and the
|
|
362334
|
+
* entry's content matches on-disk.
|
|
362335
|
+
*/
|
|
362336
|
+
getAccumulatorSnapshot() {
|
|
362337
|
+
return this.accumulator.snapshot();
|
|
362338
|
+
}
|
|
362339
|
+
/**
|
|
362340
|
+
* Apply all patches, returning results for each.
|
|
362341
|
+
* Skips patches that match exclude patterns in replay.yml
|
|
362342
|
+
*
|
|
362343
|
+
* `applyMode` controls the post-conflict marker strategy:
|
|
362344
|
+
* - `"replay"` (default): strip conflict markers from disk between
|
|
362345
|
+
* iterations whenever a later patch touches the same file. Lets the
|
|
362346
|
+
* follow-up patch's `git apply --3way` see clean OURS content,
|
|
362347
|
+
* which is what the silent-loss fix (PR #73) relies on. The replay
|
|
362348
|
+
* pipeline calls `revertConflictingFiles` after the loop to clean
|
|
362349
|
+
* any markers that survive.
|
|
362350
|
+
* - `"resolve"`: keep markers on disk. The resolve command needs the
|
|
362351
|
+
* customer to see them. Slow-path 3-way merge naturally preserves
|
|
362352
|
+
* A's markers and B's clean writes when their regions don't overlap;
|
|
362353
|
+
* when they do overlap, the customer gets nested markers and
|
|
362354
|
+
* resolves manually. Either way they aren't silently dropped.
|
|
362355
|
+
*/
|
|
362356
|
+
async applyPatches(patches, opts) {
|
|
362357
|
+
const applyMode = opts?.applyMode ?? "replay";
|
|
362358
|
+
const fileFreq = /* @__PURE__ */ new Map();
|
|
362359
|
+
for (const p14 of patches) {
|
|
362360
|
+
for (const f4 of p14.files) {
|
|
362361
|
+
fileFreq.set(f4, (fileFreq.get(f4) ?? 0) + 1);
|
|
362362
|
+
}
|
|
362363
|
+
}
|
|
362364
|
+
const sharedFiles = new Set(
|
|
362365
|
+
Array.from(fileFreq.entries()).filter(([, n19]) => n19 > 1).map(([f4]) => f4)
|
|
362366
|
+
);
|
|
362367
|
+
this.accumulator = new MergeAccumulator(applyMode, sharedFiles);
|
|
362368
|
+
const results = [];
|
|
362369
|
+
for (let i9 = 0; i9 < patches.length; i9++) {
|
|
362370
|
+
const patch5 = patches[i9];
|
|
362371
|
+
if (this.isExcluded(patch5)) {
|
|
362372
|
+
results.push({
|
|
362373
|
+
patch: patch5,
|
|
362374
|
+
status: "skipped",
|
|
362375
|
+
method: "git-am"
|
|
362376
|
+
});
|
|
362377
|
+
continue;
|
|
362378
|
+
}
|
|
362379
|
+
const result = await this.applyPatchWithFallback(patch5);
|
|
362380
|
+
results.push(result);
|
|
362381
|
+
if (applyMode !== "resolve" && result.status === "conflict" && result.fileResults) {
|
|
362382
|
+
const laterFiles = /* @__PURE__ */ new Set();
|
|
362383
|
+
for (let j7 = i9 + 1; j7 < patches.length; j7++) {
|
|
362384
|
+
for (const f4 of patches[j7].files) {
|
|
362385
|
+
laterFiles.add(f4);
|
|
362386
|
+
}
|
|
362387
|
+
}
|
|
362388
|
+
const resolvedToOriginal = /* @__PURE__ */ new Map();
|
|
362389
|
+
if (result.resolvedFiles) {
|
|
362390
|
+
for (const [orig, resolved] of Object.entries(result.resolvedFiles)) {
|
|
362391
|
+
resolvedToOriginal.set(resolved, orig);
|
|
362392
|
+
}
|
|
362393
|
+
}
|
|
362394
|
+
for (const fileResult of result.fileResults) {
|
|
362395
|
+
if (fileResult.status !== "conflict") continue;
|
|
362396
|
+
const originalPath = resolvedToOriginal.get(fileResult.file) ?? fileResult.file;
|
|
362397
|
+
if (laterFiles.has(fileResult.file) || laterFiles.has(originalPath)) {
|
|
362398
|
+
const filePath = (0, import_node_path52.join)(this.outputDir, fileResult.file);
|
|
362399
|
+
try {
|
|
362400
|
+
const content5 = await (0, import_promises310.readFile)(filePath, "utf-8");
|
|
362401
|
+
const stripped = stripConflictMarkers(content5);
|
|
362402
|
+
await (0, import_promises310.writeFile)(filePath, stripped);
|
|
362403
|
+
} catch {
|
|
362404
|
+
}
|
|
362405
|
+
}
|
|
362406
|
+
}
|
|
362407
|
+
}
|
|
362408
|
+
}
|
|
362409
|
+
return results;
|
|
362410
|
+
}
|
|
362411
|
+
/**
|
|
362412
|
+
* Populate accumulator after git apply succeeds, AND collect per-file
|
|
362413
|
+
* results including any "dropped context lines" — lines that were
|
|
362414
|
+
* present in BASE and THEIRS (unchanged context) but absent from the
|
|
362415
|
+
* final on-disk state because OURS deleted them. This is the fast-path
|
|
362416
|
+
* counterpart to ThreeWayMerge.computeDroppedContextLines used by the
|
|
362417
|
+
* 3-way slow path; both must surface the same warning.
|
|
362418
|
+
*/
|
|
362419
|
+
async populateAccumulatorForPatch(patch5, baseGen, currentTreeHash) {
|
|
362420
|
+
const fileResults = [];
|
|
362421
|
+
if (!baseGen) return fileResults;
|
|
362422
|
+
const tempDir2 = await (0, import_promises310.mkdtemp)((0, import_node_path52.join)((0, import_node_os15.tmpdir)(), "replay-acc-"));
|
|
362423
|
+
const { GitClient: GitClient2 } = await Promise.resolve().then(() => (init_GitClient(), GitClient_exports));
|
|
362424
|
+
const tempGit = new GitClient2(tempDir2);
|
|
362425
|
+
await tempGit.exec(["init"]);
|
|
362426
|
+
await tempGit.exec(["config", "user.email", "replay@fern.com"]);
|
|
362427
|
+
await tempGit.exec(["config", "user.name", "Fern Replay"]);
|
|
362428
|
+
try {
|
|
362429
|
+
for (const filePath of patch5.files) {
|
|
362430
|
+
if (isBinaryFile(filePath)) continue;
|
|
362431
|
+
const resolvedPath = await this.resolveFilePath(filePath, baseGen.tree_hash, currentTreeHash);
|
|
362432
|
+
const base4 = await this.git.showFile(baseGen.tree_hash, filePath);
|
|
362433
|
+
const snapshotForFile = patch5.theirs_snapshot?.[resolvedPath] ?? patch5.theirs_snapshot?.[filePath];
|
|
362434
|
+
const fileIsShared = this.accumulator.isShared(resolvedPath) || this.accumulator.isShared(filePath);
|
|
362435
|
+
const theirs = !fileIsShared && snapshotForFile != null ? snapshotForFile : await this.applyPatchToContent(base4, patch5.patch_content, filePath, tempGit, tempDir2);
|
|
362436
|
+
const finalOnDisk = await (0, import_promises310.readFile)((0, import_node_path52.join)(this.outputDir, resolvedPath), "utf-8").catch(() => null);
|
|
362437
|
+
const effectiveTheirs = theirs ?? finalOnDisk;
|
|
362438
|
+
if (effectiveTheirs != null) {
|
|
362439
|
+
this.accumulator.recordMerge(resolvedPath, effectiveTheirs, patch5.base_generation);
|
|
362440
|
+
}
|
|
362441
|
+
if (base4 != null && effectiveTheirs != null && finalOnDisk != null) {
|
|
362442
|
+
const dropped = computeDroppedContextLinesForFile(base4, effectiveTheirs, finalOnDisk);
|
|
362443
|
+
if (dropped.length > 0) {
|
|
362444
|
+
fileResults.push({
|
|
362445
|
+
file: resolvedPath,
|
|
362446
|
+
status: "merged",
|
|
362447
|
+
droppedContextLines: dropped
|
|
362448
|
+
});
|
|
362449
|
+
}
|
|
362450
|
+
}
|
|
362451
|
+
}
|
|
362452
|
+
} finally {
|
|
362453
|
+
await (0, import_promises310.rm)(tempDir2, { recursive: true }).catch(() => {
|
|
362454
|
+
});
|
|
362455
|
+
}
|
|
362456
|
+
return fileResults;
|
|
362457
|
+
}
|
|
362458
|
+
async applyPatchWithFallback(patch5) {
|
|
362459
|
+
const baseGen = await this.resolveBaseGeneration(patch5.base_generation);
|
|
362460
|
+
const lock = this.lockManager.read();
|
|
362461
|
+
const currentGen = lock.generations.find((g8) => g8.commit_sha === lock.current_generation);
|
|
362462
|
+
const currentTreeHash = currentGen?.tree_hash ?? baseGen?.tree_hash ?? "";
|
|
362463
|
+
const needsAccumulation = await Promise.all(
|
|
362464
|
+
patch5.files.map(async (f4) => {
|
|
362465
|
+
if (!baseGen) return false;
|
|
362466
|
+
const resolved = await this.resolveFilePath(f4, baseGen.tree_hash, currentTreeHash);
|
|
362467
|
+
return this.accumulator.has(resolved);
|
|
362468
|
+
})
|
|
362469
|
+
).then((results) => results.some(Boolean));
|
|
362470
|
+
const resolvedFiles = {};
|
|
362471
|
+
for (const filePath of patch5.files) {
|
|
362472
|
+
const resolvedPath = baseGen ? await this.resolveFilePath(filePath, baseGen.tree_hash, currentTreeHash) : filePath;
|
|
362473
|
+
if (resolvedPath !== filePath) {
|
|
362474
|
+
resolvedFiles[filePath] = resolvedPath;
|
|
362475
|
+
}
|
|
362476
|
+
}
|
|
362477
|
+
const patchIsRenameAware = /^rename from /m.test(patch5.patch_content);
|
|
362478
|
+
const hasGeneratorRename = Object.keys(resolvedFiles).length > 0 && !patchIsRenameAware;
|
|
362479
|
+
if (!needsAccumulation && !hasGeneratorRename) {
|
|
362480
|
+
const snapshots = /* @__PURE__ */ new Map();
|
|
362481
|
+
for (const filePath of patch5.files) {
|
|
362482
|
+
const fullPath = (0, import_node_path52.join)(this.outputDir, filePath);
|
|
362483
|
+
snapshots.set(filePath, await (0, import_promises310.readFile)(fullPath, "utf-8").catch(() => null));
|
|
362484
|
+
}
|
|
362485
|
+
try {
|
|
362486
|
+
await this.git.execWithInput(["apply", "--3way"], patch5.patch_content);
|
|
362487
|
+
const fastPathFileResults = await this.populateAccumulatorForPatch(patch5, baseGen, currentTreeHash);
|
|
362488
|
+
return {
|
|
362489
|
+
patch: patch5,
|
|
362490
|
+
status: "applied",
|
|
362491
|
+
method: "git-am",
|
|
362492
|
+
...fastPathFileResults.length > 0 && { fileResults: fastPathFileResults },
|
|
362493
|
+
...Object.keys(resolvedFiles).length > 0 && { resolvedFiles }
|
|
362494
|
+
};
|
|
362495
|
+
} catch {
|
|
362496
|
+
for (const [filePath, content5] of snapshots) {
|
|
362497
|
+
const fullPath = (0, import_node_path52.join)(this.outputDir, filePath);
|
|
362498
|
+
if (content5 != null) {
|
|
362499
|
+
await (0, import_promises310.writeFile)(fullPath, content5);
|
|
362500
|
+
} else {
|
|
362501
|
+
await (0, import_promises310.unlink)(fullPath).catch(() => {
|
|
362502
|
+
});
|
|
362503
|
+
}
|
|
362504
|
+
}
|
|
362505
|
+
}
|
|
362506
|
+
}
|
|
362507
|
+
return this.applyWithThreeWayMerge(patch5);
|
|
362508
|
+
}
|
|
362509
|
+
async applyWithThreeWayMerge(patch5) {
|
|
362510
|
+
const fileResults = [];
|
|
362511
|
+
const resolvedFiles = {};
|
|
362512
|
+
const tempDir2 = await (0, import_promises310.mkdtemp)((0, import_node_path52.join)((0, import_node_os15.tmpdir)(), "replay-"));
|
|
362513
|
+
const { GitClient: GitClient2 } = await Promise.resolve().then(() => (init_GitClient(), GitClient_exports));
|
|
362514
|
+
const tempGit = new GitClient2(tempDir2);
|
|
362515
|
+
await tempGit.exec(["init"]);
|
|
362516
|
+
await tempGit.exec(["config", "user.email", "replay@fern.com"]);
|
|
362517
|
+
await tempGit.exec(["config", "user.name", "Fern Replay"]);
|
|
362518
|
+
try {
|
|
362519
|
+
for (const filePath of patch5.files) {
|
|
362520
|
+
if (isBinaryFile(filePath)) {
|
|
362521
|
+
fileResults.push({
|
|
362522
|
+
file: filePath,
|
|
362523
|
+
status: "skipped",
|
|
362524
|
+
reason: "binary-file"
|
|
362525
|
+
});
|
|
362526
|
+
continue;
|
|
362527
|
+
}
|
|
362528
|
+
const result = await this.mergeFile(patch5, filePath, tempGit, tempDir2);
|
|
362529
|
+
if (result.file !== filePath) {
|
|
362530
|
+
resolvedFiles[filePath] = result.file;
|
|
362531
|
+
}
|
|
362532
|
+
fileResults.push(result);
|
|
362533
|
+
}
|
|
362534
|
+
} finally {
|
|
362535
|
+
await (0, import_promises310.rm)(tempDir2, { recursive: true }).catch(() => {
|
|
362536
|
+
});
|
|
362537
|
+
}
|
|
362538
|
+
const conflictFiles = fileResults.filter((r5) => r5.status === "conflict");
|
|
362539
|
+
const hasConflicts = conflictFiles.length > 0;
|
|
362540
|
+
const conflictReason = hasConflicts ? conflictFiles.some((f4) => f4.conflictReason === "base-generation-mismatch") ? "base-generation-mismatch" : conflictFiles[0]?.conflictReason : void 0;
|
|
362541
|
+
return {
|
|
362542
|
+
patch: patch5,
|
|
362543
|
+
status: hasConflicts ? "conflict" : "applied",
|
|
362544
|
+
method: "3way-merge",
|
|
362545
|
+
fileResults,
|
|
362546
|
+
conflictReason,
|
|
362547
|
+
...Object.keys(resolvedFiles).length > 0 && { resolvedFiles }
|
|
362548
|
+
};
|
|
362549
|
+
}
|
|
362550
|
+
async mergeFile(patch5, filePath, tempGit, tempDir2) {
|
|
362551
|
+
try {
|
|
362552
|
+
const inputs = await resolveInputs({
|
|
362553
|
+
patch: patch5,
|
|
362554
|
+
filePath,
|
|
362555
|
+
git: this.git,
|
|
362556
|
+
lockManager: this.lockManager,
|
|
362557
|
+
outputDir: this.outputDir,
|
|
362558
|
+
isTreeReachable: (h5) => this.isTreeReachable(h5),
|
|
362559
|
+
resolveBaseGeneration: (s9) => this.resolveBaseGeneration(s9),
|
|
362560
|
+
resolveFilePath: (f4, b8, c6) => this.resolveFilePath(f4, b8, c6),
|
|
362561
|
+
extractRenameSource: (p14, f4) => this.extractRenameSource(p14, f4),
|
|
362562
|
+
extractFileDiff: (p14, f4) => this.extractFileDiff(p14, f4)
|
|
362563
|
+
});
|
|
362564
|
+
if (inputs.earlyExit) {
|
|
362565
|
+
return { file: filePath, ...inputs.earlyExit };
|
|
362566
|
+
}
|
|
362567
|
+
const plan = await buildMergePlan({
|
|
362568
|
+
inputs,
|
|
362569
|
+
patch: patch5,
|
|
362570
|
+
filePath,
|
|
362571
|
+
accumulator: this.accumulator,
|
|
362572
|
+
tempGit,
|
|
362573
|
+
tempDir: tempDir2,
|
|
362574
|
+
applyPatchToContent: (b8, p14, f4, g8, d5, s9) => this.applyPatchToContent(b8, p14, f4, g8, d5, s9),
|
|
362575
|
+
isPatchAlreadyApplied: (p14, f4, c6, g8, d5) => this.isPatchAlreadyApplied(p14, f4, c6, g8, d5)
|
|
362576
|
+
});
|
|
362577
|
+
return await runMergeAndRecord({
|
|
362578
|
+
plan,
|
|
362579
|
+
inputs,
|
|
362580
|
+
patch: patch5,
|
|
362581
|
+
accumulator: this.accumulator
|
|
362582
|
+
});
|
|
362583
|
+
} catch (error50) {
|
|
362584
|
+
return {
|
|
362585
|
+
file: filePath,
|
|
362586
|
+
status: "skipped",
|
|
362587
|
+
reason: `error: ${error50 instanceof Error ? error50.message : String(error50)}`
|
|
362588
|
+
};
|
|
362589
|
+
}
|
|
362590
|
+
}
|
|
362591
|
+
async isTreeReachable(treeHash) {
|
|
362592
|
+
let result = this.treeExistsCache.get(treeHash);
|
|
362593
|
+
if (result === void 0) {
|
|
362594
|
+
result = await this.git.treeExists(treeHash);
|
|
362595
|
+
this.treeExistsCache.set(treeHash, result);
|
|
362596
|
+
}
|
|
362597
|
+
return result;
|
|
362598
|
+
}
|
|
362599
|
+
isExcluded(patch5) {
|
|
362600
|
+
const config5 = this.lockManager.getCustomizationsConfig();
|
|
362601
|
+
if (!config5.exclude) return false;
|
|
362602
|
+
return patch5.files.some((file4) => config5.exclude.some((pattern) => (0, import_minimatch8.minimatch)(file4, pattern)));
|
|
362603
|
+
}
|
|
362604
|
+
async resolveFilePath(filePath, baseTreeHash, currentTreeHash) {
|
|
362605
|
+
const config5 = this.lockManager.getCustomizationsConfig();
|
|
362606
|
+
if (config5.moves) {
|
|
362607
|
+
for (const move of config5.moves) {
|
|
362608
|
+
if ((0, import_minimatch8.minimatch)(filePath, move.from) || filePath === move.from) {
|
|
362609
|
+
if (filePath === move.from) {
|
|
362610
|
+
return move.to;
|
|
362611
|
+
}
|
|
362612
|
+
const fromBase = move.from.replace(/\*\*.*$/, "");
|
|
362613
|
+
const toBase = move.to.replace(/\*\*.*$/, "");
|
|
362614
|
+
if (filePath.startsWith(fromBase)) {
|
|
362615
|
+
return toBase + filePath.slice(fromBase.length);
|
|
362616
|
+
}
|
|
362617
|
+
}
|
|
362618
|
+
}
|
|
362619
|
+
}
|
|
362620
|
+
const cacheKey = `${baseTreeHash}:${currentTreeHash}`;
|
|
362621
|
+
let renames = this.renameCache.get(cacheKey);
|
|
362622
|
+
if (!renames) {
|
|
362623
|
+
renames = await this.git.detectRenames(baseTreeHash, currentTreeHash);
|
|
362624
|
+
this.renameCache.set(cacheKey, renames);
|
|
362625
|
+
}
|
|
362626
|
+
const gitRename = renames.find((r5) => r5.from === filePath);
|
|
362627
|
+
if (gitRename) {
|
|
362628
|
+
return gitRename.to;
|
|
362629
|
+
}
|
|
362630
|
+
return filePath;
|
|
362631
|
+
}
|
|
362632
|
+
async applyPatchToContent(base4, patchContent, filePath, tempGit, tempDir2, sourceFilePath) {
|
|
362633
|
+
if (!base4) {
|
|
362634
|
+
return this.extractNewFileFromPatch(patchContent, filePath);
|
|
362635
|
+
}
|
|
362636
|
+
const fileDiff = this.extractFileDiff(patchContent, filePath);
|
|
362637
|
+
if (!fileDiff) return null;
|
|
362638
|
+
try {
|
|
362639
|
+
if (sourceFilePath) {
|
|
362640
|
+
const tempSourcePath = (0, import_node_path52.join)(tempDir2, sourceFilePath);
|
|
362641
|
+
await (0, import_promises310.mkdir)((0, import_node_path52.dirname)(tempSourcePath), { recursive: true });
|
|
362642
|
+
await (0, import_promises310.writeFile)(tempSourcePath, base4);
|
|
362643
|
+
await tempGit.exec(["add", sourceFilePath]);
|
|
362644
|
+
await tempGit.exec([
|
|
362645
|
+
"commit",
|
|
362646
|
+
"-m",
|
|
362647
|
+
`base for rename ${sourceFilePath} -> ${filePath}`,
|
|
362648
|
+
"--allow-empty"
|
|
362649
|
+
]);
|
|
362650
|
+
await tempGit.execWithInput(["apply", "--allow-empty"], fileDiff);
|
|
362651
|
+
const tempTargetPath = (0, import_node_path52.join)(tempDir2, filePath);
|
|
362652
|
+
return await (0, import_promises310.readFile)(tempTargetPath, "utf-8");
|
|
362653
|
+
}
|
|
362654
|
+
const tempFilePath = (0, import_node_path52.join)(tempDir2, filePath);
|
|
362655
|
+
await (0, import_promises310.mkdir)((0, import_node_path52.dirname)(tempFilePath), { recursive: true });
|
|
362656
|
+
await (0, import_promises310.writeFile)(tempFilePath, base4);
|
|
362657
|
+
await tempGit.exec(["add", filePath]);
|
|
362658
|
+
await tempGit.exec(["commit", "-m", `base for ${filePath}`, "--allow-empty"]);
|
|
362659
|
+
await tempGit.execWithInput(["apply", "--allow-empty"], fileDiff);
|
|
362660
|
+
return await (0, import_promises310.readFile)(tempFilePath, "utf-8");
|
|
362661
|
+
} catch {
|
|
362662
|
+
return null;
|
|
362663
|
+
}
|
|
362664
|
+
}
|
|
362665
|
+
/**
|
|
362666
|
+
* Detects whether a patch's additions are already present in `content`.
|
|
362667
|
+
* Uses `git apply --reverse --check` — if the reverse patch applies cleanly,
|
|
362668
|
+
* the forward patch is effectively a no-op (its +lines are already there and
|
|
362669
|
+
* its -lines are already gone). Used to distinguish "patch already applied"
|
|
362670
|
+
* from "patch base mismatch" after a forward-apply fails.
|
|
362671
|
+
*/
|
|
362672
|
+
async isPatchAlreadyApplied(patchContent, filePath, content5, tempGit, tempDir2) {
|
|
362673
|
+
const fileDiff = this.extractFileDiff(patchContent, filePath);
|
|
362674
|
+
if (!fileDiff) return false;
|
|
362675
|
+
try {
|
|
362676
|
+
const tempFilePath = (0, import_node_path52.join)(tempDir2, filePath);
|
|
362677
|
+
await (0, import_promises310.mkdir)((0, import_node_path52.dirname)(tempFilePath), { recursive: true });
|
|
362678
|
+
await (0, import_promises310.writeFile)(tempFilePath, content5);
|
|
362679
|
+
await tempGit.exec(["add", filePath]);
|
|
362680
|
+
await tempGit.exec(["commit", "-m", `check already-applied ${filePath}`, "--allow-empty"]);
|
|
362681
|
+
await tempGit.execWithInput(["apply", "--reverse", "--check", "--allow-empty"], fileDiff);
|
|
362682
|
+
return true;
|
|
362683
|
+
} catch {
|
|
362684
|
+
return false;
|
|
362685
|
+
}
|
|
362686
|
+
}
|
|
362687
|
+
extractFileDiff(patchContent, filePath) {
|
|
362688
|
+
const lines = patchContent.split("\n");
|
|
362689
|
+
const diffLines = [];
|
|
362690
|
+
let inTargetFile = false;
|
|
362691
|
+
for (const line of lines) {
|
|
362692
|
+
if (line.startsWith("diff --git")) {
|
|
362693
|
+
if (inTargetFile) {
|
|
362694
|
+
break;
|
|
362695
|
+
}
|
|
362696
|
+
if (isDiffLineForFile(line, filePath)) {
|
|
362697
|
+
inTargetFile = true;
|
|
362698
|
+
diffLines.push(line);
|
|
362699
|
+
}
|
|
362700
|
+
continue;
|
|
362701
|
+
}
|
|
362702
|
+
if (inTargetFile) {
|
|
362703
|
+
diffLines.push(line);
|
|
362704
|
+
}
|
|
362705
|
+
}
|
|
362706
|
+
return diffLines.length > 0 ? diffLines.join("\n") + "\n" : null;
|
|
362707
|
+
}
|
|
362708
|
+
extractRenameSource(patchContent, targetFilePath) {
|
|
362709
|
+
const lines = patchContent.split("\n");
|
|
362710
|
+
let inTargetFile = false;
|
|
362711
|
+
for (const line of lines) {
|
|
362712
|
+
if (line.startsWith("diff --git")) {
|
|
362713
|
+
if (inTargetFile) break;
|
|
362714
|
+
inTargetFile = isDiffLineForFile(line, targetFilePath);
|
|
362715
|
+
continue;
|
|
362716
|
+
}
|
|
362717
|
+
if (!inTargetFile) continue;
|
|
362718
|
+
if (line.startsWith("@@")) break;
|
|
362719
|
+
if (line.startsWith("rename from ")) {
|
|
362720
|
+
return line.slice("rename from ".length);
|
|
362721
|
+
}
|
|
362722
|
+
}
|
|
362723
|
+
return null;
|
|
362724
|
+
}
|
|
362725
|
+
extractNewFileFromPatch(patchContent, filePath) {
|
|
362726
|
+
const lines = patchContent.split("\n");
|
|
362727
|
+
const addedLines = [];
|
|
362728
|
+
let inTargetFile = false;
|
|
362729
|
+
let inHunk = false;
|
|
362730
|
+
let isNewFile = false;
|
|
362731
|
+
let noTrailingNewline = false;
|
|
362732
|
+
for (const line of lines) {
|
|
362733
|
+
if (line.startsWith("diff --git")) {
|
|
362734
|
+
if (inTargetFile) break;
|
|
362735
|
+
inTargetFile = isDiffLineForFile(line, filePath);
|
|
362736
|
+
inHunk = false;
|
|
362737
|
+
isNewFile = false;
|
|
362738
|
+
continue;
|
|
362739
|
+
}
|
|
362740
|
+
if (!inTargetFile) continue;
|
|
362741
|
+
if (!inHunk) {
|
|
362742
|
+
if (line === "--- /dev/null" || line.startsWith("new file mode")) {
|
|
362743
|
+
isNewFile = true;
|
|
362744
|
+
}
|
|
362745
|
+
}
|
|
362746
|
+
if (line.startsWith("@@")) {
|
|
362747
|
+
if (!isNewFile) return null;
|
|
362748
|
+
inHunk = true;
|
|
362749
|
+
continue;
|
|
362750
|
+
}
|
|
362751
|
+
if (!inHunk) continue;
|
|
362752
|
+
if (line === "\") {
|
|
362753
|
+
noTrailingNewline = true;
|
|
362754
|
+
continue;
|
|
362755
|
+
}
|
|
362756
|
+
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
362757
|
+
addedLines.push(line.slice(1));
|
|
362758
|
+
}
|
|
362759
|
+
}
|
|
362760
|
+
if (addedLines.length === 0) return null;
|
|
362761
|
+
return addedLines.join("\n") + (noTrailingNewline ? "" : "\n");
|
|
362762
|
+
}
|
|
362763
|
+
};
|
|
362764
|
+
function computeDroppedContextLinesForFile(base4, theirs, finalContent) {
|
|
362765
|
+
const baseLines = base4.split("\n");
|
|
362766
|
+
const theirsLines = theirs.split("\n");
|
|
362767
|
+
const finalLines = finalContent.split("\n");
|
|
362768
|
+
const baseCounts = /* @__PURE__ */ new Map();
|
|
362769
|
+
const theirsCounts = /* @__PURE__ */ new Map();
|
|
362770
|
+
const finalCounts = /* @__PURE__ */ new Map();
|
|
362771
|
+
for (const l9 of baseLines) baseCounts.set(l9, (baseCounts.get(l9) ?? 0) + 1);
|
|
362772
|
+
for (const l9 of theirsLines) theirsCounts.set(l9, (theirsCounts.get(l9) ?? 0) + 1);
|
|
362773
|
+
for (const l9 of finalLines) finalCounts.set(l9, (finalCounts.get(l9) ?? 0) + 1);
|
|
362774
|
+
const dropped = [];
|
|
362775
|
+
const seen = /* @__PURE__ */ new Set();
|
|
362776
|
+
for (const line of baseLines) {
|
|
362777
|
+
if (seen.has(line)) continue;
|
|
362778
|
+
const inBase = baseCounts.get(line) ?? 0;
|
|
362779
|
+
const inTheirs = theirsCounts.get(line) ?? 0;
|
|
362780
|
+
const inFinal = finalCounts.get(line) ?? 0;
|
|
362781
|
+
if (inBase > 0 && inTheirs > 0 && inFinal < Math.min(inBase, inTheirs)) {
|
|
362782
|
+
dropped.push(line);
|
|
362783
|
+
seen.add(line);
|
|
362784
|
+
}
|
|
362785
|
+
}
|
|
362786
|
+
return dropped;
|
|
362787
|
+
}
|
|
362788
|
+
function isDiffLineForFile(diffLine, filePath) {
|
|
362789
|
+
const match3 = diffLine.match(/^diff --git a\/.+ b\/(.+)$/);
|
|
362790
|
+
return match3 !== null && match3[1] === filePath;
|
|
362791
|
+
}
|
|
362671
362792
|
var ReplayCommitter = class {
|
|
362672
362793
|
git;
|
|
362673
362794
|
outputDir;
|
|
@@ -362770,13 +362891,13 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
362770
362891
|
}
|
|
362771
362892
|
};
|
|
362772
362893
|
var import_node_fs210 = require("fs");
|
|
362773
|
-
var
|
|
362774
|
-
var
|
|
362775
|
-
var
|
|
362894
|
+
var import_promises610 = require("fs/promises");
|
|
362895
|
+
var import_node_path82 = require("path");
|
|
362896
|
+
var import_minimatch32 = require_commonjs3();
|
|
362776
362897
|
init_GitClient();
|
|
362777
|
-
var
|
|
362898
|
+
var import_promises410 = require("fs/promises");
|
|
362778
362899
|
var import_node_os22 = require("os");
|
|
362779
|
-
var
|
|
362900
|
+
var import_node_path62 = require("path");
|
|
362780
362901
|
var import_node_child_process8 = require("child_process");
|
|
362781
362902
|
init_HybridReconstruction();
|
|
362782
362903
|
function normalizeHunkBody(hunk) {
|
|
@@ -362983,17 +363104,17 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
362983
363104
|
}
|
|
362984
363105
|
async function unifiedDiff(beforeContent, afterContent, filePath) {
|
|
362985
363106
|
if (beforeContent === afterContent) return "";
|
|
362986
|
-
const tmp18 = await (0,
|
|
363107
|
+
const tmp18 = await (0, import_promises410.mkdtemp)((0, import_node_path62.join)((0, import_node_os22.tmpdir)(), "fern-replay-pp-"));
|
|
362987
363108
|
try {
|
|
362988
|
-
const beforePath = (0,
|
|
362989
|
-
const afterPath = (0,
|
|
362990
|
-
await (0,
|
|
362991
|
-
await (0,
|
|
363109
|
+
const beforePath = (0, import_node_path62.join)(tmp18, "before");
|
|
363110
|
+
const afterPath = (0, import_node_path62.join)(tmp18, "after");
|
|
363111
|
+
await (0, import_promises410.writeFile)(beforePath, beforeContent, "utf-8");
|
|
363112
|
+
await (0, import_promises410.writeFile)(afterPath, afterContent, "utf-8");
|
|
362992
363113
|
const raw = await runGitDiffNoIndex(beforePath, afterPath);
|
|
362993
363114
|
if (!raw.trim()) return "";
|
|
362994
363115
|
return rewriteDiffHeaders(raw, filePath);
|
|
362995
363116
|
} finally {
|
|
362996
|
-
await (0,
|
|
363117
|
+
await (0, import_promises410.rm)(tmp18, { recursive: true, force: true });
|
|
362997
363118
|
}
|
|
362998
363119
|
}
|
|
362999
363120
|
function runGitDiffNoIndex(beforePath, afterPath) {
|
|
@@ -363064,27 +363185,513 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363064
363185
|
const trailer = hasTrailingNewline ? "" : "\n\";
|
|
363065
363186
|
return header + "\n" + body + trailer + "\n";
|
|
363066
363187
|
}
|
|
363188
|
+
var import_minimatch22 = require_commonjs3();
|
|
363189
|
+
var FileOwnership = class {
|
|
363190
|
+
constructor(git) {
|
|
363191
|
+
this.git = git;
|
|
363192
|
+
}
|
|
363193
|
+
/**
|
|
363194
|
+
* Canonical resolution. Returns true if `file` is user-owned per:
|
|
363195
|
+
*
|
|
363196
|
+
* 1. `patch.user_owned === true` — authoritative, persisted flag.
|
|
363197
|
+
* 2. `file === ".fernignore"` — always user-owned.
|
|
363198
|
+
* 3. File matches a `.fernignore` pattern (via minimatch).
|
|
363199
|
+
* 4. Patch content shows `--- /dev/null` for this file (legacy-safe
|
|
363200
|
+
* signal for patches predating `user_owned` or whose flag was lost).
|
|
363201
|
+
* 5. Tree-based check against `patch.base_generation` when reachable
|
|
363202
|
+
* AND distinct from `currentGenSha`. Unreachable base → conservative
|
|
363203
|
+
* user-owned (silent-absorption safety net), with a one-time warning
|
|
363204
|
+
* per base SHA.
|
|
363205
|
+
* 6. Final fallback: check `currentGenSha` when no usable base exists
|
|
363206
|
+
* (legacy one-gen lockfile).
|
|
363207
|
+
*/
|
|
363208
|
+
async resolveCanonical(ctx) {
|
|
363209
|
+
const { file: file4, originalFileName, patch: patch5, currentGenSha, fernignorePatterns, warnedGens, warnings } = ctx;
|
|
363210
|
+
if (patch5.user_owned) return true;
|
|
363211
|
+
if (file4 === ".fernignore") return true;
|
|
363212
|
+
if (fernignorePatterns.some((p14) => file4 === p14 || (0, import_minimatch22.minimatch)(file4, p14))) return true;
|
|
363213
|
+
if (fileIsCreationInPatchContent(patch5.patch_content, originalFileName ?? file4)) {
|
|
363214
|
+
return true;
|
|
363215
|
+
}
|
|
363216
|
+
const base4 = patch5.base_generation;
|
|
363217
|
+
if (base4 && base4 !== currentGenSha) {
|
|
363218
|
+
const reachable = await this.git.commitExists(base4) || await this.git.treeExists(base4);
|
|
363219
|
+
if (!reachable) {
|
|
363220
|
+
if (!warnedGens.has(base4)) {
|
|
363221
|
+
warnedGens.add(base4);
|
|
363222
|
+
warnings.push(
|
|
363223
|
+
`Base generation ${base4.slice(0, 7)} is unreachable (shallow clone or pruned history). Treating affected files as user-owned to avoid silent data loss. Run \`git fetch --unshallow\` or avoid \`git gc --prune\` to restore full history.`
|
|
363224
|
+
);
|
|
363225
|
+
}
|
|
363226
|
+
return true;
|
|
363227
|
+
}
|
|
363228
|
+
return await this.git.showFile(base4, originalFileName ?? file4) === null;
|
|
363229
|
+
}
|
|
363230
|
+
return await this.git.showFile(currentGenSha, originalFileName ?? file4) === null;
|
|
363231
|
+
}
|
|
363232
|
+
};
|
|
363233
|
+
function fileIsCreationInPatchContent(patchContent, file4) {
|
|
363234
|
+
const lines = patchContent.split("\n");
|
|
363235
|
+
let inFileSection = false;
|
|
363236
|
+
for (const line of lines) {
|
|
363237
|
+
if (line.startsWith("diff --git ")) {
|
|
363238
|
+
inFileSection = line.includes(` b/${file4}`) || line.endsWith(` b/${file4}`);
|
|
363239
|
+
continue;
|
|
363240
|
+
}
|
|
363241
|
+
if (inFileSection && line.startsWith("--- ")) {
|
|
363242
|
+
return line === "--- /dev/null";
|
|
363243
|
+
}
|
|
363244
|
+
}
|
|
363245
|
+
return false;
|
|
363246
|
+
}
|
|
363247
|
+
var FirstGenerationFlow = class {
|
|
363248
|
+
constructor(service) {
|
|
363249
|
+
this.service = service;
|
|
363250
|
+
}
|
|
363251
|
+
async prepare(options2) {
|
|
363252
|
+
if (options2?.dryRun) {
|
|
363253
|
+
const headSha = await this.service.git.exec(["rev-parse", "HEAD"]).then((s9) => s9.trim()).catch(() => "");
|
|
363254
|
+
return {
|
|
363255
|
+
flow: "first-generation",
|
|
363256
|
+
previousGenerationSha: null,
|
|
363257
|
+
currentGenerationSha: headSha,
|
|
363258
|
+
baseBranchHead: options2.baseBranchHead ?? null,
|
|
363259
|
+
_prepared: {
|
|
363260
|
+
kind: "terminal",
|
|
363261
|
+
flow: "first-generation",
|
|
363262
|
+
preparedReport: firstGenerationReport()
|
|
363263
|
+
}
|
|
363264
|
+
};
|
|
363265
|
+
}
|
|
363266
|
+
const commitOpts = options2 ? {
|
|
363267
|
+
cliVersion: options2.cliVersion ?? "unknown",
|
|
363268
|
+
generatorVersions: options2.generatorVersions ?? {},
|
|
363269
|
+
baseBranchHead: options2.baseBranchHead
|
|
363270
|
+
} : void 0;
|
|
363271
|
+
await this.service.committer.commitGeneration("Initial SDK generation", commitOpts);
|
|
363272
|
+
const genRecord = await this.service.committer.createGenerationRecord(commitOpts);
|
|
363273
|
+
this.service.lockManager.initializeInMemory(genRecord);
|
|
363274
|
+
return {
|
|
363275
|
+
flow: "first-generation",
|
|
363276
|
+
previousGenerationSha: null,
|
|
363277
|
+
currentGenerationSha: genRecord.commit_sha,
|
|
363278
|
+
baseBranchHead: options2?.baseBranchHead ?? null,
|
|
363279
|
+
_prepared: {
|
|
363280
|
+
kind: "active",
|
|
363281
|
+
flow: "first-generation",
|
|
363282
|
+
patchesToApply: [],
|
|
363283
|
+
newPatchIds: /* @__PURE__ */ new Set(),
|
|
363284
|
+
detectorWarnings: [],
|
|
363285
|
+
revertedCount: 0,
|
|
363286
|
+
genSha: genRecord.commit_sha,
|
|
363287
|
+
optionsSnapshot: options2
|
|
363288
|
+
}
|
|
363289
|
+
};
|
|
363290
|
+
}
|
|
363291
|
+
async apply(_prep) {
|
|
363292
|
+
this.service.lockManager.save();
|
|
363293
|
+
return firstGenerationReport();
|
|
363294
|
+
}
|
|
363295
|
+
};
|
|
363296
|
+
function firstGenerationReport() {
|
|
363297
|
+
return {
|
|
363298
|
+
flow: "first-generation",
|
|
363299
|
+
patchesDetected: 0,
|
|
363300
|
+
patchesApplied: 0,
|
|
363301
|
+
patchesWithConflicts: 0,
|
|
363302
|
+
patchesSkipped: 0,
|
|
363303
|
+
conflicts: []
|
|
363304
|
+
};
|
|
363305
|
+
}
|
|
363306
|
+
var SkipApplicationFlow = class {
|
|
363307
|
+
constructor(service) {
|
|
363308
|
+
this.service = service;
|
|
363309
|
+
}
|
|
363310
|
+
async prepare(options2) {
|
|
363311
|
+
const commitOpts = options2 ? {
|
|
363312
|
+
cliVersion: options2.cliVersion ?? "unknown",
|
|
363313
|
+
generatorVersions: options2.generatorVersions ?? {},
|
|
363314
|
+
baseBranchHead: options2.baseBranchHead
|
|
363315
|
+
} : void 0;
|
|
363316
|
+
await this.service.committer.commitGeneration("Update SDK (replay skipped)", commitOpts);
|
|
363317
|
+
await this.service.cleanupStaleConflictMarkers();
|
|
363318
|
+
const genRecord = await this.service.committer.createGenerationRecord(commitOpts);
|
|
363319
|
+
let previousGenerationSha = null;
|
|
363320
|
+
try {
|
|
363321
|
+
const lock = this.service.lockManager.read();
|
|
363322
|
+
previousGenerationSha = lock.current_generation ?? null;
|
|
363323
|
+
this.service.lockManager.addGeneration(genRecord);
|
|
363324
|
+
} catch (error50) {
|
|
363325
|
+
if (error50 instanceof LockfileNotFoundError) {
|
|
363326
|
+
this.service.lockManager.initializeInMemory(genRecord);
|
|
363327
|
+
} else {
|
|
363328
|
+
throw error50;
|
|
363329
|
+
}
|
|
363330
|
+
}
|
|
363331
|
+
this.service.lockManager.setReplaySkippedAt((/* @__PURE__ */ new Date()).toISOString());
|
|
363332
|
+
return {
|
|
363333
|
+
flow: "skip-application",
|
|
363334
|
+
previousGenerationSha,
|
|
363335
|
+
currentGenerationSha: genRecord.commit_sha,
|
|
363336
|
+
baseBranchHead: options2?.baseBranchHead ?? null,
|
|
363337
|
+
_prepared: {
|
|
363338
|
+
kind: "active",
|
|
363339
|
+
flow: "skip-application",
|
|
363340
|
+
patchesToApply: [],
|
|
363341
|
+
newPatchIds: /* @__PURE__ */ new Set(),
|
|
363342
|
+
detectorWarnings: [],
|
|
363343
|
+
revertedCount: 0,
|
|
363344
|
+
genSha: genRecord.commit_sha,
|
|
363345
|
+
optionsSnapshot: options2
|
|
363346
|
+
}
|
|
363347
|
+
};
|
|
363348
|
+
}
|
|
363349
|
+
async apply(_prep, options2) {
|
|
363350
|
+
this.service.lockManager.save();
|
|
363351
|
+
const credentialWarnings = [...this.service.lockManager.warnings];
|
|
363352
|
+
if (!options2?.stageOnly) {
|
|
363353
|
+
await this.service.committer.commitReplay(0);
|
|
363354
|
+
} else {
|
|
363355
|
+
await this.service.committer.stageAll();
|
|
363356
|
+
}
|
|
363357
|
+
return {
|
|
363358
|
+
flow: "skip-application",
|
|
363359
|
+
patchesDetected: 0,
|
|
363360
|
+
patchesApplied: 0,
|
|
363361
|
+
patchesWithConflicts: 0,
|
|
363362
|
+
patchesSkipped: 0,
|
|
363363
|
+
conflicts: [],
|
|
363364
|
+
warnings: credentialWarnings.length > 0 ? credentialWarnings : void 0
|
|
363365
|
+
};
|
|
363366
|
+
}
|
|
363367
|
+
};
|
|
363368
|
+
var NoPatchesFlow = class {
|
|
363369
|
+
constructor(service) {
|
|
363370
|
+
this.service = service;
|
|
363371
|
+
}
|
|
363372
|
+
async prepare(options2) {
|
|
363373
|
+
const preLock = this.service.lockManager.read();
|
|
363374
|
+
const previousGenerationSha = preLock.current_generation ?? null;
|
|
363375
|
+
let { patches: newPatches, revertedPatchIds } = await this.service.detector.detectNewPatches();
|
|
363376
|
+
const detectorWarnings = [...this.service.detector.warnings];
|
|
363377
|
+
if (options2?.dryRun) {
|
|
363378
|
+
const dryRunWarnings = [...detectorWarnings, ...this.service.warnings];
|
|
363379
|
+
const preparedReport = {
|
|
363380
|
+
flow: "no-patches",
|
|
363381
|
+
patchesDetected: newPatches.length,
|
|
363382
|
+
patchesApplied: 0,
|
|
363383
|
+
patchesWithConflicts: 0,
|
|
363384
|
+
patchesSkipped: 0,
|
|
363385
|
+
patchesReverted: revertedPatchIds.length,
|
|
363386
|
+
conflicts: [],
|
|
363387
|
+
wouldApply: newPatches,
|
|
363388
|
+
warnings: dryRunWarnings.length > 0 ? dryRunWarnings : void 0
|
|
363389
|
+
};
|
|
363390
|
+
const headSha = await this.service.git.exec(["rev-parse", "HEAD"]).then((s9) => s9.trim()).catch(() => "");
|
|
363391
|
+
return {
|
|
363392
|
+
flow: "no-patches",
|
|
363393
|
+
previousGenerationSha,
|
|
363394
|
+
currentGenerationSha: headSha,
|
|
363395
|
+
baseBranchHead: options2.baseBranchHead ?? null,
|
|
363396
|
+
_prepared: {
|
|
363397
|
+
kind: "terminal",
|
|
363398
|
+
flow: "no-patches",
|
|
363399
|
+
preparedReport
|
|
363400
|
+
}
|
|
363401
|
+
};
|
|
363402
|
+
}
|
|
363403
|
+
{
|
|
363404
|
+
const preCurrentGen = preLock.current_generation;
|
|
363405
|
+
const uniqueFiles = [...new Set(newPatches.flatMap((p14) => p14.files))];
|
|
363406
|
+
const absorbedFiles = /* @__PURE__ */ new Set();
|
|
363407
|
+
for (const file4 of uniqueFiles) {
|
|
363408
|
+
const diff2 = await this.service.git.exec(["diff", preCurrentGen, "HEAD", "--", file4]).catch(() => null);
|
|
363409
|
+
if (diff2 !== null && !diff2.trim()) {
|
|
363410
|
+
absorbedFiles.add(file4);
|
|
363411
|
+
}
|
|
363412
|
+
}
|
|
363413
|
+
if (absorbedFiles.size > 0) {
|
|
363414
|
+
newPatches = newPatches.filter(
|
|
363415
|
+
(p14) => p14.files.length === 0 || !p14.files.every((f4) => absorbedFiles.has(f4))
|
|
363416
|
+
);
|
|
363417
|
+
}
|
|
363418
|
+
}
|
|
363419
|
+
for (const id2 of revertedPatchIds) {
|
|
363420
|
+
try {
|
|
363421
|
+
this.service.lockManager.removePatch(id2);
|
|
363422
|
+
} catch {
|
|
363423
|
+
}
|
|
363424
|
+
}
|
|
363425
|
+
const commitOpts = options2 ? {
|
|
363426
|
+
cliVersion: options2.cliVersion ?? "unknown",
|
|
363427
|
+
generatorVersions: options2.generatorVersions ?? {},
|
|
363428
|
+
baseBranchHead: options2.baseBranchHead
|
|
363429
|
+
} : void 0;
|
|
363430
|
+
await this.service.committer.commitGeneration("Update SDK", commitOpts);
|
|
363431
|
+
await this.service.cleanupStaleConflictMarkers();
|
|
363432
|
+
const genRecord = await this.service.committer.createGenerationRecord(commitOpts);
|
|
363433
|
+
this.service.lockManager.addGeneration(genRecord);
|
|
363434
|
+
return {
|
|
363435
|
+
flow: "no-patches",
|
|
363436
|
+
previousGenerationSha,
|
|
363437
|
+
currentGenerationSha: genRecord.commit_sha,
|
|
363438
|
+
baseBranchHead: options2?.baseBranchHead ?? null,
|
|
363439
|
+
_prepared: {
|
|
363440
|
+
kind: "active",
|
|
363441
|
+
flow: "no-patches",
|
|
363442
|
+
patchesToApply: newPatches,
|
|
363443
|
+
newPatchIds: new Set(newPatches.map((p14) => p14.id)),
|
|
363444
|
+
detectorWarnings,
|
|
363445
|
+
revertedCount: revertedPatchIds.length,
|
|
363446
|
+
genSha: genRecord.commit_sha,
|
|
363447
|
+
optionsSnapshot: options2
|
|
363448
|
+
}
|
|
363449
|
+
};
|
|
363450
|
+
}
|
|
363451
|
+
async apply(prep, options2) {
|
|
363452
|
+
assertActive(prep);
|
|
363453
|
+
const newPatches = prep._prepared.patchesToApply;
|
|
363454
|
+
const detectorWarnings = prep._prepared.detectorWarnings;
|
|
363455
|
+
const genSha = prep._prepared.genSha;
|
|
363456
|
+
let results = [];
|
|
363457
|
+
if (newPatches.length > 0) {
|
|
363458
|
+
results = await this.service.applicator.applyPatches(newPatches);
|
|
363459
|
+
this.service._lastApplyResults = results;
|
|
363460
|
+
this.service.recordDroppedContextLineWarnings(results);
|
|
363461
|
+
this.service.revertConflictingFiles(results);
|
|
363462
|
+
for (const result of results) {
|
|
363463
|
+
if (result.status === "conflict") {
|
|
363464
|
+
result.patch.status = "unresolved";
|
|
363465
|
+
}
|
|
363466
|
+
}
|
|
363467
|
+
}
|
|
363468
|
+
const rebaseCounts = await this.service.rebasePatches(results, genSha);
|
|
363469
|
+
for (const patch5 of newPatches) {
|
|
363470
|
+
if (!rebaseCounts.absorbedPatchIds.has(patch5.id)) {
|
|
363471
|
+
this.service.lockManager.addPatch(patch5);
|
|
363472
|
+
}
|
|
363473
|
+
}
|
|
363474
|
+
this.service.lockManager.save();
|
|
363475
|
+
this.service.warnings.push(...this.service.lockManager.warnings);
|
|
363476
|
+
if (newPatches.length > 0) {
|
|
363477
|
+
if (!options2?.stageOnly) {
|
|
363478
|
+
const appliedCount = results.filter((r5) => r5.status === "applied").length;
|
|
363479
|
+
const buckets = computeCommitBuckets(results, rebaseCounts.absorbedPatchIds, newPatches);
|
|
363480
|
+
await this.service.committer.commitReplay(appliedCount, newPatches, void 0, { buckets });
|
|
363481
|
+
} else {
|
|
363482
|
+
await this.service.committer.stageAll();
|
|
363483
|
+
}
|
|
363484
|
+
}
|
|
363485
|
+
const warnings = [...detectorWarnings, ...this.service.warnings];
|
|
363486
|
+
return this.service.buildReport(
|
|
363487
|
+
"no-patches",
|
|
363488
|
+
newPatches,
|
|
363489
|
+
results,
|
|
363490
|
+
prep._prepared.optionsSnapshot,
|
|
363491
|
+
warnings,
|
|
363492
|
+
rebaseCounts,
|
|
363493
|
+
void 0,
|
|
363494
|
+
prep._prepared.revertedCount
|
|
363495
|
+
);
|
|
363496
|
+
}
|
|
363497
|
+
};
|
|
363498
|
+
var NormalRegenerationFlow = class {
|
|
363499
|
+
constructor(service) {
|
|
363500
|
+
this.service = service;
|
|
363501
|
+
}
|
|
363502
|
+
async prepare(options2) {
|
|
363503
|
+
const preLock = this.service.lockManager.read();
|
|
363504
|
+
const previousGenerationSha = preLock.current_generation ?? null;
|
|
363505
|
+
if (options2?.dryRun) {
|
|
363506
|
+
const existingPatches2 = this.service.lockManager.getPatches();
|
|
363507
|
+
const { patches: newPatches2, revertedPatchIds: dryRunReverted } = await this.service.detector.detectNewPatches();
|
|
363508
|
+
const warnings = [...this.service.detector.warnings, ...this.service.warnings];
|
|
363509
|
+
const allPatches2 = [...existingPatches2, ...newPatches2];
|
|
363510
|
+
const preparedReport = {
|
|
363511
|
+
flow: "normal-regeneration",
|
|
363512
|
+
patchesDetected: allPatches2.length,
|
|
363513
|
+
patchesApplied: 0,
|
|
363514
|
+
patchesWithConflicts: 0,
|
|
363515
|
+
patchesSkipped: 0,
|
|
363516
|
+
patchesReverted: dryRunReverted.length,
|
|
363517
|
+
conflicts: [],
|
|
363518
|
+
wouldApply: allPatches2,
|
|
363519
|
+
warnings: warnings.length > 0 ? warnings : void 0
|
|
363520
|
+
};
|
|
363521
|
+
const headSha = await this.service.git.exec(["rev-parse", "HEAD"]).then((s9) => s9.trim()).catch(() => "");
|
|
363522
|
+
return {
|
|
363523
|
+
flow: "normal-regeneration",
|
|
363524
|
+
previousGenerationSha,
|
|
363525
|
+
currentGenerationSha: headSha,
|
|
363526
|
+
baseBranchHead: options2.baseBranchHead ?? null,
|
|
363527
|
+
_prepared: {
|
|
363528
|
+
kind: "terminal",
|
|
363529
|
+
flow: "normal-regeneration",
|
|
363530
|
+
preparedReport
|
|
363531
|
+
}
|
|
363532
|
+
};
|
|
363533
|
+
}
|
|
363534
|
+
let existingPatches = this.service.lockManager.getPatches();
|
|
363535
|
+
const preRebasePatchIds = new Set(existingPatches.map((p14) => p14.id));
|
|
363536
|
+
const preRebaseCounts = await this.service.preGenerationRebase(existingPatches);
|
|
363537
|
+
const postRebasePatchIds = new Set(this.service.lockManager.getPatches().map((p14) => p14.id));
|
|
363538
|
+
const removedByPreRebase = existingPatches.filter(
|
|
363539
|
+
(p14) => preRebasePatchIds.has(p14.id) && !postRebasePatchIds.has(p14.id)
|
|
363540
|
+
);
|
|
363541
|
+
existingPatches = this.service.lockManager.getPatches();
|
|
363542
|
+
const seenHashes = /* @__PURE__ */ new Map();
|
|
363543
|
+
for (const p14 of existingPatches) {
|
|
363544
|
+
const priorPatchId = seenHashes.get(p14.content_hash);
|
|
363545
|
+
if (priorPatchId !== void 0) {
|
|
363546
|
+
this.service.lockManager.removePatch(p14.id);
|
|
363547
|
+
this.service.warnings.push(
|
|
363548
|
+
`Consolidated patch ${p14.id} (commit ${(p14.original_commit || "<missing>").slice(0, 7)}) into prior patch ${priorPatchId}: byte-identical patch_content. Per-commit attribution preserved in git log; lockfile collapsed to avoid duplicate-apply corruption on next regen.`
|
|
363549
|
+
);
|
|
363550
|
+
} else {
|
|
363551
|
+
seenHashes.set(p14.content_hash, p14.id);
|
|
363552
|
+
}
|
|
363553
|
+
}
|
|
363554
|
+
existingPatches = this.service.lockManager.getPatches();
|
|
363555
|
+
let { patches: newPatches, revertedPatchIds } = await this.service.detector.detectNewPatches();
|
|
363556
|
+
const detectorWarnings = [...this.service.detector.warnings];
|
|
363557
|
+
if (removedByPreRebase.length > 0) {
|
|
363558
|
+
const removedOriginalCommits = new Set(removedByPreRebase.map((p14) => p14.original_commit));
|
|
363559
|
+
const removedOriginalMessages = new Set(removedByPreRebase.map((p14) => p14.original_message));
|
|
363560
|
+
newPatches = newPatches.filter((p14) => {
|
|
363561
|
+
if (removedOriginalCommits.has(p14.original_commit)) return false;
|
|
363562
|
+
if (isRevertCommit(p14.original_message)) {
|
|
363563
|
+
const revertedMsg = parseRevertedMessage(p14.original_message);
|
|
363564
|
+
if (revertedMsg && removedOriginalMessages.has(revertedMsg)) return false;
|
|
363565
|
+
}
|
|
363566
|
+
return true;
|
|
363567
|
+
});
|
|
363568
|
+
}
|
|
363569
|
+
for (const id2 of revertedPatchIds) {
|
|
363570
|
+
try {
|
|
363571
|
+
this.service.lockManager.removePatch(id2);
|
|
363572
|
+
} catch {
|
|
363573
|
+
}
|
|
363574
|
+
}
|
|
363575
|
+
const revertedSet = new Set(revertedPatchIds);
|
|
363576
|
+
existingPatches = existingPatches.filter((p14) => !revertedSet.has(p14.id));
|
|
363577
|
+
const allPatches = [...existingPatches, ...newPatches];
|
|
363578
|
+
const commitOpts = options2 ? {
|
|
363579
|
+
cliVersion: options2.cliVersion ?? "unknown",
|
|
363580
|
+
generatorVersions: options2.generatorVersions ?? {},
|
|
363581
|
+
baseBranchHead: options2.baseBranchHead
|
|
363582
|
+
} : void 0;
|
|
363583
|
+
await this.service.committer.commitGeneration("Update SDK", commitOpts);
|
|
363584
|
+
await this.service.cleanupStaleConflictMarkers();
|
|
363585
|
+
const genRecord = await this.service.committer.createGenerationRecord(commitOpts);
|
|
363586
|
+
this.service.lockManager.addGeneration(genRecord);
|
|
363587
|
+
return {
|
|
363588
|
+
flow: "normal-regeneration",
|
|
363589
|
+
previousGenerationSha,
|
|
363590
|
+
currentGenerationSha: genRecord.commit_sha,
|
|
363591
|
+
baseBranchHead: options2?.baseBranchHead ?? null,
|
|
363592
|
+
_prepared: {
|
|
363593
|
+
kind: "active",
|
|
363594
|
+
flow: "normal-regeneration",
|
|
363595
|
+
patchesToApply: allPatches,
|
|
363596
|
+
newPatchIds: new Set(newPatches.map((p14) => p14.id)),
|
|
363597
|
+
preRebaseCounts,
|
|
363598
|
+
detectorWarnings,
|
|
363599
|
+
revertedCount: revertedPatchIds.length,
|
|
363600
|
+
genSha: genRecord.commit_sha,
|
|
363601
|
+
optionsSnapshot: options2
|
|
363602
|
+
}
|
|
363603
|
+
};
|
|
363604
|
+
}
|
|
363605
|
+
async apply(prep, options2) {
|
|
363606
|
+
assertActive(prep);
|
|
363607
|
+
const allPatches = prep._prepared.patchesToApply;
|
|
363608
|
+
const newPatchIds = prep._prepared.newPatchIds;
|
|
363609
|
+
const detectorWarnings = prep._prepared.detectorWarnings;
|
|
363610
|
+
const genSha = prep._prepared.genSha;
|
|
363611
|
+
const results = await this.service.applicator.applyPatches(allPatches);
|
|
363612
|
+
this.service._lastApplyResults = results;
|
|
363613
|
+
this.service.recordDroppedContextLineWarnings(results);
|
|
363614
|
+
this.service.revertConflictingFiles(results);
|
|
363615
|
+
for (const result of results) {
|
|
363616
|
+
if (result.status === "conflict") {
|
|
363617
|
+
result.patch.status = "unresolved";
|
|
363618
|
+
}
|
|
363619
|
+
}
|
|
363620
|
+
const rebaseCounts = await this.service.rebasePatches(results, genSha);
|
|
363621
|
+
const newPatches = allPatches.filter((p14) => newPatchIds.has(p14.id));
|
|
363622
|
+
for (const patch5 of newPatches) {
|
|
363623
|
+
if (!rebaseCounts.absorbedPatchIds.has(patch5.id)) {
|
|
363624
|
+
this.service.lockManager.addPatch(patch5);
|
|
363625
|
+
}
|
|
363626
|
+
}
|
|
363627
|
+
for (const result of results) {
|
|
363628
|
+
if (result.status === "conflict") {
|
|
363629
|
+
try {
|
|
363630
|
+
this.service.lockManager.markPatchUnresolved(result.patch.id);
|
|
363631
|
+
} catch {
|
|
363632
|
+
}
|
|
363633
|
+
}
|
|
363634
|
+
}
|
|
363635
|
+
this.service.lockManager.save();
|
|
363636
|
+
this.service.warnings.push(...this.service.lockManager.warnings);
|
|
363637
|
+
if (options2?.stageOnly) {
|
|
363638
|
+
await this.service.committer.stageAll();
|
|
363639
|
+
} else {
|
|
363640
|
+
const appliedCount = results.filter((r5) => r5.status === "applied").length;
|
|
363641
|
+
const buckets = computeCommitBuckets(results, rebaseCounts.absorbedPatchIds, allPatches);
|
|
363642
|
+
await this.service.committer.commitReplay(appliedCount, allPatches, void 0, { buckets });
|
|
363643
|
+
}
|
|
363644
|
+
const warnings = [...detectorWarnings, ...this.service.warnings];
|
|
363645
|
+
return this.service.buildReport(
|
|
363646
|
+
"normal-regeneration",
|
|
363647
|
+
allPatches,
|
|
363648
|
+
results,
|
|
363649
|
+
prep._prepared.optionsSnapshot,
|
|
363650
|
+
warnings,
|
|
363651
|
+
rebaseCounts,
|
|
363652
|
+
prep._prepared.preRebaseCounts,
|
|
363653
|
+
prep._prepared.revertedCount
|
|
363654
|
+
);
|
|
363655
|
+
}
|
|
363656
|
+
};
|
|
363657
|
+
function assertActive(prep) {
|
|
363658
|
+
if (prep._prepared.kind !== "active") {
|
|
363659
|
+
throw new Error(
|
|
363660
|
+
`Flow.apply() called with terminal preparation (kind=${prep._prepared.kind}); terminal cases must be handled by applyPreparedReplay's dispatcher`
|
|
363661
|
+
);
|
|
363662
|
+
}
|
|
363663
|
+
}
|
|
363067
363664
|
var ReplayService = class {
|
|
363665
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
363068
363666
|
git;
|
|
363667
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
363069
363668
|
detector;
|
|
363669
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
363070
363670
|
applicator;
|
|
363671
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
363071
363672
|
committer;
|
|
363673
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
363072
363674
|
lockManager;
|
|
363675
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
363676
|
+
fileOwnership;
|
|
363677
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
363073
363678
|
outputDir;
|
|
363074
363679
|
/** @internal Test-only. Captures the last ReplayResult[] returned from applicator.applyPatches(). */
|
|
363075
363680
|
_lastApplyResults = [];
|
|
363076
363681
|
/**
|
|
363077
363682
|
* Service-level warnings accumulated during a single runReplay() call.
|
|
363078
363683
|
* Merged with `detector.warnings` into `ReplayReport.warnings` by each flow handler.
|
|
363079
|
-
* Populated by `
|
|
363080
|
-
* (shallow clone / aggressive `git gc --prune`) and we fall back to a
|
|
363081
|
-
* heuristic. Cleared at the top of `runReplay()`.
|
|
363684
|
+
* Populated by `FileOwnership.resolveCanonical` when a patch's base generation tree
|
|
363685
|
+
* is unreachable (shallow clone / aggressive `git gc --prune`) and we fall back to a
|
|
363686
|
+
* conservative heuristic. Cleared at the top of `runReplay()`.
|
|
363687
|
+
*
|
|
363688
|
+
* @internal Used by Flow implementations in `src/flows/`.
|
|
363082
363689
|
*/
|
|
363083
363690
|
warnings = [];
|
|
363084
363691
|
/**
|
|
363085
363692
|
* @internal Test-only accessor.
|
|
363086
363693
|
* Returns the ReplayApplicator instance used for the last run. Tests use this
|
|
363087
|
-
* to inspect the inter-patch accumulator after runReplay() completes (
|
|
363694
|
+
* to inspect the inter-patch accumulator after runReplay() completes (invariant I2).
|
|
363088
363695
|
*/
|
|
363089
363696
|
get applicatorRef() {
|
|
363090
363697
|
return this.applicator;
|
|
@@ -363092,8 +363699,8 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363092
363699
|
/**
|
|
363093
363700
|
* @internal Test-only accessor.
|
|
363094
363701
|
* Returns the ReplayResult[] from the most recent applyPatches() call. Tests use
|
|
363095
|
-
* this to filter applied-vs-conflicted-vs-absorbed patches for invariant
|
|
363096
|
-
*
|
|
363702
|
+
* this to filter applied-vs-conflicted-vs-absorbed patches for invariant
|
|
363703
|
+
* assertions. Returns an empty array if no patches have been applied yet.
|
|
363097
363704
|
*/
|
|
363098
363705
|
getLastResults() {
|
|
363099
363706
|
return this._lastApplyResults;
|
|
@@ -363106,6 +363713,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363106
363713
|
this.detector = new ReplayDetector(git, this.lockManager, outputDir);
|
|
363107
363714
|
this.applicator = new ReplayApplicator(git, this.lockManager, outputDir);
|
|
363108
363715
|
this.committer = new ReplayCommitter(git, outputDir);
|
|
363716
|
+
this.fileOwnership = new FileOwnership(git);
|
|
363109
363717
|
}
|
|
363110
363718
|
/**
|
|
363111
363719
|
* Phase 1 of the two-phase replay flow. Runs preGenerationRebase (when applicable),
|
|
@@ -363122,18 +363730,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363122
363730
|
async prepareReplay(options2) {
|
|
363123
363731
|
this.warnings = [];
|
|
363124
363732
|
this.detector.warnings.length = 0;
|
|
363125
|
-
|
|
363126
|
-
return this._prepareSkipApplication(options2);
|
|
363127
|
-
}
|
|
363128
|
-
const flow4 = this.determineFlow();
|
|
363129
|
-
switch (flow4) {
|
|
363130
|
-
case "first-generation":
|
|
363131
|
-
return this._prepareFirstGeneration(options2);
|
|
363132
|
-
case "no-patches":
|
|
363133
|
-
return this._prepareNoPatchesRegeneration(options2);
|
|
363134
|
-
case "normal-regeneration":
|
|
363135
|
-
return this._prepareNormalRegeneration(options2);
|
|
363136
|
-
}
|
|
363733
|
+
return this.selectFlow(options2).prepare(options2);
|
|
363137
363734
|
}
|
|
363138
363735
|
/**
|
|
363139
363736
|
* Phase 2 of the two-phase replay flow. Applies patches, rebases them against
|
|
@@ -363145,18 +363742,33 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363145
363742
|
* its own post-commit logic), this returns the precomputed report.
|
|
363146
363743
|
*/
|
|
363147
363744
|
async applyPreparedReplay(prep, options2) {
|
|
363148
|
-
if (prep._prepared.terminal) {
|
|
363745
|
+
if (prep._prepared.kind === "terminal") {
|
|
363149
363746
|
return prep._prepared.preparedReport;
|
|
363150
363747
|
}
|
|
363151
|
-
|
|
363748
|
+
return this.flowForKind(prep._prepared.flow).apply(prep, options2);
|
|
363749
|
+
}
|
|
363750
|
+
/**
|
|
363751
|
+
* Construct the flow for the current run. `selectFlow` is called from
|
|
363752
|
+
* `prepareReplay` and decides based on options + lockfile state.
|
|
363753
|
+
*/
|
|
363754
|
+
selectFlow(options2) {
|
|
363755
|
+
if (options2?.skipApplication) return new SkipApplicationFlow(this);
|
|
363756
|
+
return this.flowForKind(this.determineFlow());
|
|
363757
|
+
}
|
|
363758
|
+
/**
|
|
363759
|
+
* Construct a flow given its kind. Used by `applyPreparedReplay` to
|
|
363760
|
+
* route to the same kind that was selected in phase 1.
|
|
363761
|
+
*/
|
|
363762
|
+
flowForKind(kind) {
|
|
363763
|
+
switch (kind) {
|
|
363152
363764
|
case "first-generation":
|
|
363153
|
-
return this
|
|
363154
|
-
case "skip-application":
|
|
363155
|
-
return this._applySkipApplication(prep, options2);
|
|
363765
|
+
return new FirstGenerationFlow(this);
|
|
363156
363766
|
case "no-patches":
|
|
363157
|
-
return this
|
|
363767
|
+
return new NoPatchesFlow(this);
|
|
363158
363768
|
case "normal-regeneration":
|
|
363159
|
-
return this
|
|
363769
|
+
return new NormalRegenerationFlow(this);
|
|
363770
|
+
case "skip-application":
|
|
363771
|
+
return new SkipApplicationFlow(this);
|
|
363160
363772
|
}
|
|
363161
363773
|
}
|
|
363162
363774
|
/**
|
|
@@ -363240,423 +363852,12 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363240
363852
|
throw error50;
|
|
363241
363853
|
}
|
|
363242
363854
|
}
|
|
363243
|
-
firstGenerationReport() {
|
|
363244
|
-
return {
|
|
363245
|
-
flow: "first-generation",
|
|
363246
|
-
patchesDetected: 0,
|
|
363247
|
-
patchesApplied: 0,
|
|
363248
|
-
patchesWithConflicts: 0,
|
|
363249
|
-
patchesSkipped: 0,
|
|
363250
|
-
conflicts: []
|
|
363251
|
-
};
|
|
363252
|
-
}
|
|
363253
|
-
async _prepareFirstGeneration(options2) {
|
|
363254
|
-
if (options2?.dryRun) {
|
|
363255
|
-
const headSha = await this.git.exec(["rev-parse", "HEAD"]).then((s9) => s9.trim()).catch(() => "");
|
|
363256
|
-
return {
|
|
363257
|
-
flow: "first-generation",
|
|
363258
|
-
previousGenerationSha: null,
|
|
363259
|
-
currentGenerationSha: headSha,
|
|
363260
|
-
baseBranchHead: options2.baseBranchHead ?? null,
|
|
363261
|
-
_prepared: {
|
|
363262
|
-
flow: "first-generation",
|
|
363263
|
-
terminal: true,
|
|
363264
|
-
preparedReport: this.firstGenerationReport(),
|
|
363265
|
-
patchesToApply: [],
|
|
363266
|
-
newPatchIds: /* @__PURE__ */ new Set(),
|
|
363267
|
-
detectorWarnings: [],
|
|
363268
|
-
revertedCount: 0,
|
|
363269
|
-
genSha: headSha,
|
|
363270
|
-
optionsSnapshot: options2
|
|
363271
|
-
}
|
|
363272
|
-
};
|
|
363273
|
-
}
|
|
363274
|
-
const commitOpts = options2 ? {
|
|
363275
|
-
cliVersion: options2.cliVersion ?? "unknown",
|
|
363276
|
-
generatorVersions: options2.generatorVersions ?? {},
|
|
363277
|
-
baseBranchHead: options2.baseBranchHead
|
|
363278
|
-
} : void 0;
|
|
363279
|
-
await this.committer.commitGeneration("Initial SDK generation", commitOpts);
|
|
363280
|
-
const genRecord = await this.committer.createGenerationRecord(commitOpts);
|
|
363281
|
-
this.lockManager.initializeInMemory(genRecord);
|
|
363282
|
-
return {
|
|
363283
|
-
flow: "first-generation",
|
|
363284
|
-
previousGenerationSha: null,
|
|
363285
|
-
currentGenerationSha: genRecord.commit_sha,
|
|
363286
|
-
baseBranchHead: options2?.baseBranchHead ?? null,
|
|
363287
|
-
_prepared: {
|
|
363288
|
-
flow: "first-generation",
|
|
363289
|
-
terminal: false,
|
|
363290
|
-
patchesToApply: [],
|
|
363291
|
-
newPatchIds: /* @__PURE__ */ new Set(),
|
|
363292
|
-
detectorWarnings: [],
|
|
363293
|
-
revertedCount: 0,
|
|
363294
|
-
genSha: genRecord.commit_sha,
|
|
363295
|
-
optionsSnapshot: options2
|
|
363296
|
-
}
|
|
363297
|
-
};
|
|
363298
|
-
}
|
|
363299
|
-
async _applyFirstGeneration(_prep) {
|
|
363300
|
-
this.lockManager.save();
|
|
363301
|
-
return this.firstGenerationReport();
|
|
363302
|
-
}
|
|
363303
|
-
/**
|
|
363304
|
-
* Skip-application mode phase 1: commit the generation and update the
|
|
363305
|
-
* in-memory lockfile, but don't detect or apply patches. Sets a marker
|
|
363306
|
-
* so the next normal run skips revert detection in preGenerationRebase().
|
|
363307
|
-
*
|
|
363308
|
-
* Disk save is deferred to `_applySkipApplication`.
|
|
363309
|
-
*/
|
|
363310
|
-
async _prepareSkipApplication(options2) {
|
|
363311
|
-
const commitOpts = options2 ? {
|
|
363312
|
-
cliVersion: options2.cliVersion ?? "unknown",
|
|
363313
|
-
generatorVersions: options2.generatorVersions ?? {},
|
|
363314
|
-
baseBranchHead: options2.baseBranchHead
|
|
363315
|
-
} : void 0;
|
|
363316
|
-
await this.committer.commitGeneration("Update SDK (replay skipped)", commitOpts);
|
|
363317
|
-
await this.cleanupStaleConflictMarkers();
|
|
363318
|
-
const genRecord = await this.committer.createGenerationRecord(commitOpts);
|
|
363319
|
-
let previousGenerationSha = null;
|
|
363320
|
-
try {
|
|
363321
|
-
const lock = this.lockManager.read();
|
|
363322
|
-
previousGenerationSha = lock.current_generation ?? null;
|
|
363323
|
-
this.lockManager.addGeneration(genRecord);
|
|
363324
|
-
} catch (error50) {
|
|
363325
|
-
if (error50 instanceof LockfileNotFoundError) {
|
|
363326
|
-
this.lockManager.initializeInMemory(genRecord);
|
|
363327
|
-
} else {
|
|
363328
|
-
throw error50;
|
|
363329
|
-
}
|
|
363330
|
-
}
|
|
363331
|
-
this.lockManager.setReplaySkippedAt((/* @__PURE__ */ new Date()).toISOString());
|
|
363332
|
-
return {
|
|
363333
|
-
flow: "skip-application",
|
|
363334
|
-
previousGenerationSha,
|
|
363335
|
-
currentGenerationSha: genRecord.commit_sha,
|
|
363336
|
-
baseBranchHead: options2?.baseBranchHead ?? null,
|
|
363337
|
-
_prepared: {
|
|
363338
|
-
flow: "skip-application",
|
|
363339
|
-
terminal: false,
|
|
363340
|
-
patchesToApply: [],
|
|
363341
|
-
newPatchIds: /* @__PURE__ */ new Set(),
|
|
363342
|
-
detectorWarnings: [],
|
|
363343
|
-
revertedCount: 0,
|
|
363344
|
-
genSha: genRecord.commit_sha,
|
|
363345
|
-
optionsSnapshot: options2
|
|
363346
|
-
}
|
|
363347
|
-
};
|
|
363348
|
-
}
|
|
363349
|
-
async _applySkipApplication(_prep, options2) {
|
|
363350
|
-
this.lockManager.save();
|
|
363351
|
-
const credentialWarnings = [...this.lockManager.warnings];
|
|
363352
|
-
if (!options2?.stageOnly) {
|
|
363353
|
-
await this.committer.commitReplay(0);
|
|
363354
|
-
} else {
|
|
363355
|
-
await this.committer.stageAll();
|
|
363356
|
-
}
|
|
363357
|
-
return {
|
|
363358
|
-
flow: "skip-application",
|
|
363359
|
-
patchesDetected: 0,
|
|
363360
|
-
patchesApplied: 0,
|
|
363361
|
-
patchesWithConflicts: 0,
|
|
363362
|
-
patchesSkipped: 0,
|
|
363363
|
-
conflicts: [],
|
|
363364
|
-
warnings: credentialWarnings.length > 0 ? credentialWarnings : void 0
|
|
363365
|
-
};
|
|
363366
|
-
}
|
|
363367
|
-
async _prepareNoPatchesRegeneration(options2) {
|
|
363368
|
-
const preLock = this.lockManager.read();
|
|
363369
|
-
const previousGenerationSha = preLock.current_generation ?? null;
|
|
363370
|
-
let { patches: newPatches, revertedPatchIds } = await this.detector.detectNewPatches();
|
|
363371
|
-
const detectorWarnings = [...this.detector.warnings];
|
|
363372
|
-
if (options2?.dryRun) {
|
|
363373
|
-
const dryRunWarnings = [...detectorWarnings, ...this.warnings];
|
|
363374
|
-
const preparedReport = {
|
|
363375
|
-
flow: "no-patches",
|
|
363376
|
-
patchesDetected: newPatches.length,
|
|
363377
|
-
patchesApplied: 0,
|
|
363378
|
-
patchesWithConflicts: 0,
|
|
363379
|
-
patchesSkipped: 0,
|
|
363380
|
-
patchesReverted: revertedPatchIds.length,
|
|
363381
|
-
conflicts: [],
|
|
363382
|
-
wouldApply: newPatches,
|
|
363383
|
-
warnings: dryRunWarnings.length > 0 ? dryRunWarnings : void 0
|
|
363384
|
-
};
|
|
363385
|
-
const headSha = await this.git.exec(["rev-parse", "HEAD"]).then((s9) => s9.trim()).catch(() => "");
|
|
363386
|
-
return {
|
|
363387
|
-
flow: "no-patches",
|
|
363388
|
-
previousGenerationSha,
|
|
363389
|
-
currentGenerationSha: headSha,
|
|
363390
|
-
baseBranchHead: options2.baseBranchHead ?? null,
|
|
363391
|
-
_prepared: {
|
|
363392
|
-
flow: "no-patches",
|
|
363393
|
-
terminal: true,
|
|
363394
|
-
preparedReport,
|
|
363395
|
-
patchesToApply: [],
|
|
363396
|
-
newPatchIds: /* @__PURE__ */ new Set(),
|
|
363397
|
-
detectorWarnings,
|
|
363398
|
-
revertedCount: revertedPatchIds.length,
|
|
363399
|
-
genSha: headSha,
|
|
363400
|
-
optionsSnapshot: options2
|
|
363401
|
-
}
|
|
363402
|
-
};
|
|
363403
|
-
}
|
|
363404
|
-
{
|
|
363405
|
-
const preCurrentGen = preLock.current_generation;
|
|
363406
|
-
const uniqueFiles = [...new Set(newPatches.flatMap((p14) => p14.files))];
|
|
363407
|
-
const absorbedFiles = /* @__PURE__ */ new Set();
|
|
363408
|
-
for (const file4 of uniqueFiles) {
|
|
363409
|
-
const diff2 = await this.git.exec(["diff", preCurrentGen, "HEAD", "--", file4]).catch(() => null);
|
|
363410
|
-
if (diff2 !== null && !diff2.trim()) {
|
|
363411
|
-
absorbedFiles.add(file4);
|
|
363412
|
-
}
|
|
363413
|
-
}
|
|
363414
|
-
if (absorbedFiles.size > 0) {
|
|
363415
|
-
newPatches = newPatches.filter(
|
|
363416
|
-
(p14) => p14.files.length === 0 || !p14.files.every((f4) => absorbedFiles.has(f4))
|
|
363417
|
-
);
|
|
363418
|
-
}
|
|
363419
|
-
}
|
|
363420
|
-
for (const id2 of revertedPatchIds) {
|
|
363421
|
-
try {
|
|
363422
|
-
this.lockManager.removePatch(id2);
|
|
363423
|
-
} catch {
|
|
363424
|
-
}
|
|
363425
|
-
}
|
|
363426
|
-
const commitOpts = options2 ? {
|
|
363427
|
-
cliVersion: options2.cliVersion ?? "unknown",
|
|
363428
|
-
generatorVersions: options2.generatorVersions ?? {},
|
|
363429
|
-
baseBranchHead: options2.baseBranchHead
|
|
363430
|
-
} : void 0;
|
|
363431
|
-
await this.committer.commitGeneration("Update SDK", commitOpts);
|
|
363432
|
-
await this.cleanupStaleConflictMarkers();
|
|
363433
|
-
const genRecord = await this.committer.createGenerationRecord(commitOpts);
|
|
363434
|
-
this.lockManager.addGeneration(genRecord);
|
|
363435
|
-
return {
|
|
363436
|
-
flow: "no-patches",
|
|
363437
|
-
previousGenerationSha,
|
|
363438
|
-
currentGenerationSha: genRecord.commit_sha,
|
|
363439
|
-
baseBranchHead: options2?.baseBranchHead ?? null,
|
|
363440
|
-
_prepared: {
|
|
363441
|
-
flow: "no-patches",
|
|
363442
|
-
terminal: false,
|
|
363443
|
-
patchesToApply: newPatches,
|
|
363444
|
-
newPatchIds: new Set(newPatches.map((p14) => p14.id)),
|
|
363445
|
-
detectorWarnings,
|
|
363446
|
-
revertedCount: revertedPatchIds.length,
|
|
363447
|
-
genSha: genRecord.commit_sha,
|
|
363448
|
-
optionsSnapshot: options2
|
|
363449
|
-
}
|
|
363450
|
-
};
|
|
363451
|
-
}
|
|
363452
|
-
async _applyNoPatchesRegeneration(prep, options2) {
|
|
363453
|
-
const newPatches = prep._prepared.patchesToApply;
|
|
363454
|
-
const detectorWarnings = prep._prepared.detectorWarnings;
|
|
363455
|
-
const genSha = prep._prepared.genSha;
|
|
363456
|
-
let results = [];
|
|
363457
|
-
if (newPatches.length > 0) {
|
|
363458
|
-
results = await this.applicator.applyPatches(newPatches);
|
|
363459
|
-
this._lastApplyResults = results;
|
|
363460
|
-
this.recordDroppedContextLineWarnings(results);
|
|
363461
|
-
this.revertConflictingFiles(results);
|
|
363462
|
-
for (const result of results) {
|
|
363463
|
-
if (result.status === "conflict") {
|
|
363464
|
-
result.patch.status = "unresolved";
|
|
363465
|
-
}
|
|
363466
|
-
}
|
|
363467
|
-
}
|
|
363468
|
-
const rebaseCounts = await this.rebasePatches(results, genSha);
|
|
363469
|
-
for (const patch5 of newPatches) {
|
|
363470
|
-
if (!rebaseCounts.absorbedPatchIds.has(patch5.id)) {
|
|
363471
|
-
this.lockManager.addPatch(patch5);
|
|
363472
|
-
}
|
|
363473
|
-
}
|
|
363474
|
-
this.lockManager.save();
|
|
363475
|
-
this.warnings.push(...this.lockManager.warnings);
|
|
363476
|
-
if (newPatches.length > 0) {
|
|
363477
|
-
if (!options2?.stageOnly) {
|
|
363478
|
-
const appliedCount = results.filter((r5) => r5.status === "applied").length;
|
|
363479
|
-
const buckets = computeCommitBuckets(results, rebaseCounts.absorbedPatchIds, newPatches);
|
|
363480
|
-
await this.committer.commitReplay(appliedCount, newPatches, void 0, { buckets });
|
|
363481
|
-
} else {
|
|
363482
|
-
await this.committer.stageAll();
|
|
363483
|
-
}
|
|
363484
|
-
}
|
|
363485
|
-
const warnings = [...detectorWarnings, ...this.warnings];
|
|
363486
|
-
return this.buildReport(
|
|
363487
|
-
"no-patches",
|
|
363488
|
-
newPatches,
|
|
363489
|
-
results,
|
|
363490
|
-
prep._prepared.optionsSnapshot,
|
|
363491
|
-
warnings,
|
|
363492
|
-
rebaseCounts,
|
|
363493
|
-
void 0,
|
|
363494
|
-
prep._prepared.revertedCount
|
|
363495
|
-
);
|
|
363496
|
-
}
|
|
363497
|
-
async _prepareNormalRegeneration(options2) {
|
|
363498
|
-
const preLock = this.lockManager.read();
|
|
363499
|
-
const previousGenerationSha = preLock.current_generation ?? null;
|
|
363500
|
-
if (options2?.dryRun) {
|
|
363501
|
-
const existingPatches2 = this.lockManager.getPatches();
|
|
363502
|
-
const { patches: newPatches2, revertedPatchIds: dryRunReverted } = await this.detector.detectNewPatches();
|
|
363503
|
-
const warnings = [...this.detector.warnings, ...this.warnings];
|
|
363504
|
-
const allPatches2 = [...existingPatches2, ...newPatches2];
|
|
363505
|
-
const preparedReport = {
|
|
363506
|
-
flow: "normal-regeneration",
|
|
363507
|
-
patchesDetected: allPatches2.length,
|
|
363508
|
-
patchesApplied: 0,
|
|
363509
|
-
patchesWithConflicts: 0,
|
|
363510
|
-
patchesSkipped: 0,
|
|
363511
|
-
patchesReverted: dryRunReverted.length,
|
|
363512
|
-
conflicts: [],
|
|
363513
|
-
wouldApply: allPatches2,
|
|
363514
|
-
warnings: warnings.length > 0 ? warnings : void 0
|
|
363515
|
-
};
|
|
363516
|
-
const headSha = await this.git.exec(["rev-parse", "HEAD"]).then((s9) => s9.trim()).catch(() => "");
|
|
363517
|
-
return {
|
|
363518
|
-
flow: "normal-regeneration",
|
|
363519
|
-
previousGenerationSha,
|
|
363520
|
-
currentGenerationSha: headSha,
|
|
363521
|
-
baseBranchHead: options2.baseBranchHead ?? null,
|
|
363522
|
-
_prepared: {
|
|
363523
|
-
flow: "normal-regeneration",
|
|
363524
|
-
terminal: true,
|
|
363525
|
-
preparedReport,
|
|
363526
|
-
patchesToApply: [],
|
|
363527
|
-
newPatchIds: /* @__PURE__ */ new Set(),
|
|
363528
|
-
detectorWarnings: [...this.detector.warnings],
|
|
363529
|
-
revertedCount: dryRunReverted.length,
|
|
363530
|
-
genSha: headSha,
|
|
363531
|
-
optionsSnapshot: options2
|
|
363532
|
-
}
|
|
363533
|
-
};
|
|
363534
|
-
}
|
|
363535
|
-
let existingPatches = this.lockManager.getPatches();
|
|
363536
|
-
const preRebasePatchIds = new Set(existingPatches.map((p14) => p14.id));
|
|
363537
|
-
const preRebaseCounts = await this.preGenerationRebase(existingPatches);
|
|
363538
|
-
const postRebasePatchIds = new Set(this.lockManager.getPatches().map((p14) => p14.id));
|
|
363539
|
-
const removedByPreRebase = existingPatches.filter(
|
|
363540
|
-
(p14) => preRebasePatchIds.has(p14.id) && !postRebasePatchIds.has(p14.id)
|
|
363541
|
-
);
|
|
363542
|
-
existingPatches = this.lockManager.getPatches();
|
|
363543
|
-
const seenHashes = /* @__PURE__ */ new Map();
|
|
363544
|
-
for (const p14 of existingPatches) {
|
|
363545
|
-
const priorPatchId = seenHashes.get(p14.content_hash);
|
|
363546
|
-
if (priorPatchId !== void 0) {
|
|
363547
|
-
this.lockManager.removePatch(p14.id);
|
|
363548
|
-
this.warnings.push(
|
|
363549
|
-
`Consolidated patch ${p14.id} (commit ${(p14.original_commit || "<missing>").slice(0, 7)}) into prior patch ${priorPatchId}: byte-identical patch_content. Per-commit attribution preserved in git log; lockfile collapsed to avoid duplicate-apply corruption on next regen.`
|
|
363550
|
-
);
|
|
363551
|
-
} else {
|
|
363552
|
-
seenHashes.set(p14.content_hash, p14.id);
|
|
363553
|
-
}
|
|
363554
|
-
}
|
|
363555
|
-
existingPatches = this.lockManager.getPatches();
|
|
363556
|
-
let { patches: newPatches, revertedPatchIds } = await this.detector.detectNewPatches();
|
|
363557
|
-
const detectorWarnings = [...this.detector.warnings];
|
|
363558
|
-
if (removedByPreRebase.length > 0) {
|
|
363559
|
-
const removedOriginalCommits = new Set(removedByPreRebase.map((p14) => p14.original_commit));
|
|
363560
|
-
const removedOriginalMessages = new Set(removedByPreRebase.map((p14) => p14.original_message));
|
|
363561
|
-
newPatches = newPatches.filter((p14) => {
|
|
363562
|
-
if (removedOriginalCommits.has(p14.original_commit)) return false;
|
|
363563
|
-
if (isRevertCommit(p14.original_message)) {
|
|
363564
|
-
const revertedMsg = parseRevertedMessage(p14.original_message);
|
|
363565
|
-
if (revertedMsg && removedOriginalMessages.has(revertedMsg)) return false;
|
|
363566
|
-
}
|
|
363567
|
-
return true;
|
|
363568
|
-
});
|
|
363569
|
-
}
|
|
363570
|
-
for (const id2 of revertedPatchIds) {
|
|
363571
|
-
try {
|
|
363572
|
-
this.lockManager.removePatch(id2);
|
|
363573
|
-
} catch {
|
|
363574
|
-
}
|
|
363575
|
-
}
|
|
363576
|
-
const revertedSet = new Set(revertedPatchIds);
|
|
363577
|
-
existingPatches = existingPatches.filter((p14) => !revertedSet.has(p14.id));
|
|
363578
|
-
const allPatches = [...existingPatches, ...newPatches];
|
|
363579
|
-
const commitOpts = options2 ? {
|
|
363580
|
-
cliVersion: options2.cliVersion ?? "unknown",
|
|
363581
|
-
generatorVersions: options2.generatorVersions ?? {},
|
|
363582
|
-
baseBranchHead: options2.baseBranchHead
|
|
363583
|
-
} : void 0;
|
|
363584
|
-
await this.committer.commitGeneration("Update SDK", commitOpts);
|
|
363585
|
-
await this.cleanupStaleConflictMarkers();
|
|
363586
|
-
const genRecord = await this.committer.createGenerationRecord(commitOpts);
|
|
363587
|
-
this.lockManager.addGeneration(genRecord);
|
|
363588
|
-
return {
|
|
363589
|
-
flow: "normal-regeneration",
|
|
363590
|
-
previousGenerationSha,
|
|
363591
|
-
currentGenerationSha: genRecord.commit_sha,
|
|
363592
|
-
baseBranchHead: options2?.baseBranchHead ?? null,
|
|
363593
|
-
_prepared: {
|
|
363594
|
-
flow: "normal-regeneration",
|
|
363595
|
-
terminal: false,
|
|
363596
|
-
patchesToApply: allPatches,
|
|
363597
|
-
newPatchIds: new Set(newPatches.map((p14) => p14.id)),
|
|
363598
|
-
preRebaseCounts,
|
|
363599
|
-
detectorWarnings,
|
|
363600
|
-
revertedCount: revertedPatchIds.length,
|
|
363601
|
-
genSha: genRecord.commit_sha,
|
|
363602
|
-
optionsSnapshot: options2
|
|
363603
|
-
}
|
|
363604
|
-
};
|
|
363605
|
-
}
|
|
363606
|
-
async _applyNormalRegeneration(prep, options2) {
|
|
363607
|
-
const allPatches = prep._prepared.patchesToApply;
|
|
363608
|
-
const newPatchIds = prep._prepared.newPatchIds;
|
|
363609
|
-
const detectorWarnings = prep._prepared.detectorWarnings;
|
|
363610
|
-
const genSha = prep._prepared.genSha;
|
|
363611
|
-
const results = await this.applicator.applyPatches(allPatches);
|
|
363612
|
-
this._lastApplyResults = results;
|
|
363613
|
-
this.recordDroppedContextLineWarnings(results);
|
|
363614
|
-
this.revertConflictingFiles(results);
|
|
363615
|
-
for (const result of results) {
|
|
363616
|
-
if (result.status === "conflict") {
|
|
363617
|
-
result.patch.status = "unresolved";
|
|
363618
|
-
}
|
|
363619
|
-
}
|
|
363620
|
-
const rebaseCounts = await this.rebasePatches(results, genSha);
|
|
363621
|
-
const newPatches = allPatches.filter((p14) => newPatchIds.has(p14.id));
|
|
363622
|
-
for (const patch5 of newPatches) {
|
|
363623
|
-
if (!rebaseCounts.absorbedPatchIds.has(patch5.id)) {
|
|
363624
|
-
this.lockManager.addPatch(patch5);
|
|
363625
|
-
}
|
|
363626
|
-
}
|
|
363627
|
-
for (const result of results) {
|
|
363628
|
-
if (result.status === "conflict") {
|
|
363629
|
-
try {
|
|
363630
|
-
this.lockManager.markPatchUnresolved(result.patch.id);
|
|
363631
|
-
} catch {
|
|
363632
|
-
}
|
|
363633
|
-
}
|
|
363634
|
-
}
|
|
363635
|
-
this.lockManager.save();
|
|
363636
|
-
this.warnings.push(...this.lockManager.warnings);
|
|
363637
|
-
if (options2?.stageOnly) {
|
|
363638
|
-
await this.committer.stageAll();
|
|
363639
|
-
} else {
|
|
363640
|
-
const appliedCount = results.filter((r5) => r5.status === "applied").length;
|
|
363641
|
-
const buckets = computeCommitBuckets(results, rebaseCounts.absorbedPatchIds, allPatches);
|
|
363642
|
-
await this.committer.commitReplay(appliedCount, allPatches, void 0, { buckets });
|
|
363643
|
-
}
|
|
363644
|
-
const warnings = [...detectorWarnings, ...this.warnings];
|
|
363645
|
-
return this.buildReport(
|
|
363646
|
-
"normal-regeneration",
|
|
363647
|
-
allPatches,
|
|
363648
|
-
results,
|
|
363649
|
-
prep._prepared.optionsSnapshot,
|
|
363650
|
-
warnings,
|
|
363651
|
-
rebaseCounts,
|
|
363652
|
-
prep._prepared.preRebaseCounts,
|
|
363653
|
-
prep._prepared.revertedCount
|
|
363654
|
-
);
|
|
363655
|
-
}
|
|
363656
363855
|
/**
|
|
363657
363856
|
* Rebase cleanly applied patches so they are relative to the current generation.
|
|
363658
363857
|
* This prevents recurring conflicts on subsequent regenerations.
|
|
363659
363858
|
* Returns the number of patches rebased.
|
|
363859
|
+
*
|
|
363860
|
+
* @internal Used by Flow implementations in `src/flows/`.
|
|
363660
363861
|
*/
|
|
363661
363862
|
async rebasePatches(results, currentGenSha) {
|
|
363662
363863
|
let absorbed = 0;
|
|
@@ -363666,6 +363867,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363666
363867
|
const seenContentHashes = /* @__PURE__ */ new Map();
|
|
363667
363868
|
const absorbedPatchIds = /* @__PURE__ */ new Set();
|
|
363668
363869
|
const fernignorePatterns = this.readFernignorePatterns();
|
|
363870
|
+
const warnedGens = /* @__PURE__ */ new Set();
|
|
363669
363871
|
for (const result of results) {
|
|
363670
363872
|
if (result.resolvedFiles && Object.keys(result.resolvedFiles).length > 0) {
|
|
363671
363873
|
const patch5 = result.patch;
|
|
@@ -363710,21 +363912,20 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363710
363912
|
}
|
|
363711
363913
|
}
|
|
363712
363914
|
const isUserOwnedPerFile = await Promise.all(
|
|
363713
|
-
patch5.files.map(
|
|
363714
|
-
|
|
363715
|
-
|
|
363716
|
-
|
|
363717
|
-
|
|
363718
|
-
|
|
363719
|
-
|
|
363720
|
-
|
|
363721
|
-
|
|
363722
|
-
|
|
363723
|
-
|
|
363724
|
-
})
|
|
363915
|
+
patch5.files.map(
|
|
363916
|
+
(file4) => this.fileOwnership.resolveCanonical({
|
|
363917
|
+
file: file4,
|
|
363918
|
+
originalFileName: originalByResolved[file4],
|
|
363919
|
+
patch: patch5,
|
|
363920
|
+
currentGenSha,
|
|
363921
|
+
fernignorePatterns,
|
|
363922
|
+
warnedGens,
|
|
363923
|
+
warnings: this.warnings
|
|
363924
|
+
})
|
|
363925
|
+
)
|
|
363725
363926
|
);
|
|
363726
363927
|
const hasProtectedFiles = patch5.files.some(
|
|
363727
|
-
(file4) => file4 === ".fernignore" || fernignorePatterns.some((p14) => file4 === p14 || (0,
|
|
363928
|
+
(file4) => file4 === ".fernignore" || fernignorePatterns.some((p14) => file4 === p14 || (0, import_minimatch32.minimatch)(file4, p14))
|
|
363728
363929
|
);
|
|
363729
363930
|
const allUserOwned = isUserOwnedPerFile.every(Boolean);
|
|
363730
363931
|
if (allUserOwned && patch5.files.length > 0) {
|
|
@@ -363848,7 +364049,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363848
364049
|
for (const file4 of files) {
|
|
363849
364050
|
if (isBinaryFile(file4)) continue;
|
|
363850
364051
|
try {
|
|
363851
|
-
const content5 = await (0,
|
|
364052
|
+
const content5 = await (0, import_promises610.readFile)((0, import_node_path82.join)(this.outputDir, file4), "utf-8");
|
|
363852
364053
|
snapshot[file4] = content5;
|
|
363853
364054
|
} catch {
|
|
363854
364055
|
}
|
|
@@ -363856,10 +364057,16 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363856
364057
|
return snapshot;
|
|
363857
364058
|
}
|
|
363858
364059
|
/**
|
|
363859
|
-
*
|
|
363860
|
-
* `fromSha` and `toSha` touched any
|
|
363861
|
-
*
|
|
363862
|
-
*
|
|
364060
|
+
* Detects autoversion contamination — returns true when a
|
|
364061
|
+
* `[fern-autoversion]` commit between `fromSha` and `toSha` touched any
|
|
364062
|
+
* of the patch's `files`. Re-classifies each grep match via
|
|
364063
|
+
* `isGenerationCommit` so a customer commit that merely quotes the
|
|
364064
|
+
* marker in its body doesn't false-positive.
|
|
364065
|
+
*
|
|
364066
|
+
* Used by preGenerationRebase to skip the customer-content rebuild for
|
|
364067
|
+
* patches whose files were last touched by an autoversion step — a
|
|
364068
|
+
* naive `git diff currentGen HEAD` there would capture the
|
|
364069
|
+
* placeholder→semver swap as customer customization.
|
|
363863
364070
|
*/
|
|
363864
364071
|
async hasAutoversionContamination(fromSha, toSha, files) {
|
|
363865
364072
|
if (files.length === 0) return false;
|
|
@@ -363888,10 +364095,15 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363888
364095
|
return false;
|
|
363889
364096
|
}
|
|
363890
364097
|
/**
|
|
363891
|
-
*
|
|
363892
|
-
*
|
|
363893
|
-
*
|
|
363894
|
-
*
|
|
364098
|
+
* Rebuild patch_content from `theirs_snapshot` (captured at detection
|
|
364099
|
+
* time, predates pipeline content) rather than a HEAD-relative diff.
|
|
364100
|
+
* Returns `null` when snapshot is missing or doesn't cover every file
|
|
364101
|
+
* in `patch.files` — caller leaves the patch unchanged.
|
|
364102
|
+
*
|
|
364103
|
+
* Used as the autoversion-contamination escape hatch: when the
|
|
364104
|
+
* HEAD-relative diff would capture pipeline content (autoversion's
|
|
364105
|
+
* placeholder swap) as customer customization, the snapshot path
|
|
364106
|
+
* preserves the customer's actual intent.
|
|
363895
364107
|
*/
|
|
363896
364108
|
async computeSnapshotBasedPatchDiff(patch5, currentGen) {
|
|
363897
364109
|
if (!patch5.theirs_snapshot) return null;
|
|
@@ -363939,7 +364151,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363939
364151
|
* **Skips patches sharing files with other patches.** HEAD's content
|
|
363940
364152
|
* for a file shared by N patches is cumulative across all of them, so
|
|
363941
364153
|
* a HEAD-fallback would falsely encode other patches' contributions
|
|
363942
|
-
* into one patch's snapshot — breaking
|
|
364154
|
+
* into one patch's snapshot — breaking cross-patch isolation
|
|
363943
364155
|
* if the snapshot fallback later fires. Per-patch snapshots only
|
|
363944
364156
|
* make sense when the patch owns its file. Multi-patch-same-file
|
|
363945
364157
|
* legacy lockfiles keep using line-anchored reconstruction (the
|
|
@@ -364001,72 +364213,6 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
364001
364213
|
);
|
|
364002
364214
|
}
|
|
364003
364215
|
}
|
|
364004
|
-
/**
|
|
364005
|
-
* Determine whether `file` is user-owned (never produced by the generator).
|
|
364006
|
-
*
|
|
364007
|
-
* Resolution order (first match wins):
|
|
364008
|
-
* 1. `patch.user_owned === true` — authoritative, persisted across cycles.
|
|
364009
|
-
* 2. `.fernignore` itself, or file matches a .fernignore pattern.
|
|
364010
|
-
* 3. Legacy-safe signal: `patch_content` shows this file as a `--- /dev/null`
|
|
364011
|
-
* creation. Works for patches that predate the `user_owned` field or
|
|
364012
|
-
* whose flag was lost. Immune to rename tricks — relies on raw patch text.
|
|
364013
|
-
* 4. Tree-based check against `patch.base_generation` when it is reachable
|
|
364014
|
-
* AND distinct from `currentGenSha`. If the base tree is unreachable
|
|
364015
|
-
* (shallow clone / aggressive `git gc --prune`), emit a one-time
|
|
364016
|
-
* customer-actionable warning (deduplicated via warnedGens) and
|
|
364017
|
-
* conservatively treat as user-owned — strictly safer than silent
|
|
364018
|
-
* absorption (FER-9809).
|
|
364019
|
-
* 5. Final fallback — check `currentGenSha` when there is no usable base
|
|
364020
|
-
* (legacy one-gen lockfile). Preserves pre-fix behavior for that edge.
|
|
364021
|
-
*
|
|
364022
|
-
* `originalFileName` is the pre-rename path when the generator moved the
|
|
364023
|
-
* file between the patch's base and the current generation. Tree lookups
|
|
364024
|
-
* at ancestor generations use the original path — that's where the
|
|
364025
|
-
* generator produced the content.
|
|
364026
|
-
*/
|
|
364027
|
-
async isFileUserOwned(file4, patch5, currentGenSha, fernignorePatterns, warnedGens, originalFileName) {
|
|
364028
|
-
if (patch5.user_owned) return true;
|
|
364029
|
-
if (file4 === ".fernignore") return true;
|
|
364030
|
-
if (fernignorePatterns.some((p14) => file4 === p14 || (0, import_minimatch22.minimatch)(file4, p14))) return true;
|
|
364031
|
-
if (this.fileIsCreationInPatchContent(patch5.patch_content, originalFileName ?? file4)) {
|
|
364032
|
-
return true;
|
|
364033
|
-
}
|
|
364034
|
-
const base4 = patch5.base_generation;
|
|
364035
|
-
if (base4 && base4 !== currentGenSha) {
|
|
364036
|
-
const reachable = await this.git.commitExists(base4) || await this.git.treeExists(base4);
|
|
364037
|
-
if (!reachable) {
|
|
364038
|
-
if (!warnedGens.has(base4)) {
|
|
364039
|
-
warnedGens.add(base4);
|
|
364040
|
-
this.warnings.push(
|
|
364041
|
-
`Base generation ${base4.slice(0, 7)} is unreachable (shallow clone or pruned history). Treating affected files as user-owned to avoid silent data loss. Run \`git fetch --unshallow\` or avoid \`git gc --prune\` to restore full history.`
|
|
364042
|
-
);
|
|
364043
|
-
}
|
|
364044
|
-
return true;
|
|
364045
|
-
}
|
|
364046
|
-
return await this.git.showFile(base4, originalFileName ?? file4) === null;
|
|
364047
|
-
}
|
|
364048
|
-
return await this.git.showFile(currentGenSha, originalFileName ?? file4) === null;
|
|
364049
|
-
}
|
|
364050
|
-
/**
|
|
364051
|
-
* Returns true if `file`'s diff section in `patchContent` starts with
|
|
364052
|
-
* `--- /dev/null`, meaning this file was introduced by the patch (user
|
|
364053
|
-
* creation). Used as a legacy-safe fallback when `patch.user_owned` is
|
|
364054
|
-
* absent or cleared.
|
|
364055
|
-
*/
|
|
364056
|
-
fileIsCreationInPatchContent(patchContent, file4) {
|
|
364057
|
-
const lines = patchContent.split("\n");
|
|
364058
|
-
let inFileSection = false;
|
|
364059
|
-
for (const line of lines) {
|
|
364060
|
-
if (line.startsWith("diff --git ")) {
|
|
364061
|
-
inFileSection = line.includes(` b/${file4}`) || line.endsWith(` b/${file4}`);
|
|
364062
|
-
continue;
|
|
364063
|
-
}
|
|
364064
|
-
if (inFileSection && line.startsWith("--- ")) {
|
|
364065
|
-
return line === "--- /dev/null";
|
|
364066
|
-
}
|
|
364067
|
-
}
|
|
364068
|
-
return false;
|
|
364069
|
-
}
|
|
364070
364216
|
/**
|
|
364071
364217
|
* For conflict patches with mixed results (some files merged, some conflicted),
|
|
364072
364218
|
* check if the cleanly merged files were absorbed by the generator (empty diff).
|
|
@@ -364085,7 +364231,15 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
364085
364231
|
const warnedGens = /* @__PURE__ */ new Set();
|
|
364086
364232
|
const generatorCleanFiles = [];
|
|
364087
364233
|
for (const file4 of cleanFiles) {
|
|
364088
|
-
|
|
364234
|
+
const userOwned = await this.fileOwnership.resolveCanonical({
|
|
364235
|
+
file: file4,
|
|
364236
|
+
patch: patch5,
|
|
364237
|
+
currentGenSha,
|
|
364238
|
+
fernignorePatterns,
|
|
364239
|
+
warnedGens,
|
|
364240
|
+
warnings: this.warnings
|
|
364241
|
+
});
|
|
364242
|
+
if (userOwned) {
|
|
364089
364243
|
continue;
|
|
364090
364244
|
}
|
|
364091
364245
|
generatorCleanFiles.push(file4);
|
|
@@ -364114,6 +364268,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
364114
364268
|
* Pre-generation rebase: update patches using the customer's current state.
|
|
364115
364269
|
* Called BEFORE commitGeneration() while HEAD has customer code.
|
|
364116
364270
|
*/
|
|
364271
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
364117
364272
|
async preGenerationRebase(patches) {
|
|
364118
364273
|
const lock = this.lockManager.read();
|
|
364119
364274
|
const currentGen = lock.current_generation;
|
|
@@ -364152,7 +364307,15 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
364152
364307
|
if (patch5.user_owned) return true;
|
|
364153
364308
|
if (patch5.files.length === 0) return false;
|
|
364154
364309
|
for (const file4 of patch5.files) {
|
|
364155
|
-
|
|
364310
|
+
const userOwned = await this.fileOwnership.resolveCanonical({
|
|
364311
|
+
file: file4,
|
|
364312
|
+
patch: patch5,
|
|
364313
|
+
currentGenSha: currentGen,
|
|
364314
|
+
fernignorePatterns,
|
|
364315
|
+
warnedGens,
|
|
364316
|
+
warnings: this.warnings
|
|
364317
|
+
});
|
|
364318
|
+
if (!userOwned) {
|
|
364156
364319
|
return false;
|
|
364157
364320
|
}
|
|
364158
364321
|
}
|
|
@@ -364202,7 +364365,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
364202
364365
|
);
|
|
364203
364366
|
if (snapHasStaleMarkers) continue;
|
|
364204
364367
|
const isProtectedSnap = patch5.files.some(
|
|
364205
|
-
(file4) => file4 === ".fernignore" || fernignorePatterns.some((p14) => file4 === p14 || (0,
|
|
364368
|
+
(file4) => file4 === ".fernignore" || fernignorePatterns.some((p14) => file4 === p14 || (0, import_minimatch32.minimatch)(file4, p14))
|
|
364206
364369
|
);
|
|
364207
364370
|
if (isProtectedSnap) continue;
|
|
364208
364371
|
const snapHash = this.detector.computeContentHash(snapshotDiff);
|
|
@@ -364237,7 +364400,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
364237
364400
|
continue;
|
|
364238
364401
|
}
|
|
364239
364402
|
const isProtected = patch5.files.some(
|
|
364240
|
-
(file4) => file4 === ".fernignore" || fernignorePatterns.some((p14) => file4 === p14 || (0,
|
|
364403
|
+
(file4) => file4 === ".fernignore" || fernignorePatterns.some((p14) => file4 === p14 || (0, import_minimatch32.minimatch)(file4, p14))
|
|
364241
364404
|
);
|
|
364242
364405
|
if (isProtected) {
|
|
364243
364406
|
continue;
|
|
@@ -364344,6 +364507,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
364344
364507
|
* relied on them. This is the "surface or preserve, never silently drop"
|
|
364345
364508
|
* contract for legitimate-deletion cases.
|
|
364346
364509
|
*/
|
|
364510
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
364347
364511
|
recordDroppedContextLineWarnings(results) {
|
|
364348
364512
|
const PREVIEW_COUNT = 5;
|
|
364349
364513
|
for (const result of results) {
|
|
@@ -364366,12 +364530,13 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
364366
364530
|
* After applyPatches(), strip conflict markers from conflicting files
|
|
364367
364531
|
* so only clean content is committed. Keeps the Generated (OURS) side.
|
|
364368
364532
|
*/
|
|
364533
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
364369
364534
|
revertConflictingFiles(results) {
|
|
364370
364535
|
for (const result of results) {
|
|
364371
364536
|
if (result.status !== "conflict" || !result.fileResults) continue;
|
|
364372
364537
|
for (const fileResult of result.fileResults) {
|
|
364373
364538
|
if (fileResult.status !== "conflict") continue;
|
|
364374
|
-
const filePath = (0,
|
|
364539
|
+
const filePath = (0, import_node_path82.join)(this.outputDir, fileResult.file);
|
|
364375
364540
|
try {
|
|
364376
364541
|
const content5 = (0, import_node_fs210.readFileSync)(filePath, "utf-8");
|
|
364377
364542
|
const stripped = stripConflictMarkers(content5);
|
|
@@ -364387,13 +364552,14 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
364387
364552
|
* Restores files to their clean generated state from HEAD.
|
|
364388
364553
|
* Skips .fernignore-protected files to prevent overwriting user content.
|
|
364389
364554
|
*/
|
|
364555
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
364390
364556
|
async cleanupStaleConflictMarkers() {
|
|
364391
364557
|
const markerFiles = await this.git.exec(["grep", "-l", "<<<<<<< Generated", "--", "."]).catch(() => "");
|
|
364392
364558
|
const files = markerFiles.trim().split("\n").filter(Boolean);
|
|
364393
364559
|
if (files.length === 0) return;
|
|
364394
364560
|
const fernignorePatterns = this.readFernignorePatterns();
|
|
364395
364561
|
for (const file4 of files) {
|
|
364396
|
-
if (fernignorePatterns.some((pattern) => (0,
|
|
364562
|
+
if (fernignorePatterns.some((pattern) => (0, import_minimatch32.minimatch)(file4, pattern))) continue;
|
|
364397
364563
|
try {
|
|
364398
364564
|
await this.git.exec(["checkout", "HEAD", "--", file4]);
|
|
364399
364565
|
} catch {
|
|
@@ -364401,10 +364567,11 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
364401
364567
|
}
|
|
364402
364568
|
}
|
|
364403
364569
|
readFernignorePatterns() {
|
|
364404
|
-
const fernignorePath = (0,
|
|
364570
|
+
const fernignorePath = (0, import_node_path82.join)(this.outputDir, ".fernignore");
|
|
364405
364571
|
if (!(0, import_node_fs210.existsSync)(fernignorePath)) return [];
|
|
364406
364572
|
return (0, import_node_fs210.readFileSync)(fernignorePath, "utf-8").split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
364407
364573
|
}
|
|
364574
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
364408
364575
|
buildReport(flow4, patches, results, options2, warnings, rebaseCounts, preRebaseCounts, patchesReverted) {
|
|
364409
364576
|
const conflictResults = results.filter((r5) => r5.status === "conflict");
|
|
364410
364577
|
const conflictDetails = conflictResults.map((r5) => {
|
|
@@ -364478,8 +364645,8 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
364478
364645
|
}
|
|
364479
364646
|
var import_node_crypto22 = require("crypto");
|
|
364480
364647
|
var import_node_fs33 = require("fs");
|
|
364481
|
-
var
|
|
364482
|
-
var
|
|
364648
|
+
var import_node_path92 = require("path");
|
|
364649
|
+
var import_minimatch42 = require_commonjs3();
|
|
364483
364650
|
var import_yaml22 = require_dist11();
|
|
364484
364651
|
var FernignoreMigrator = class {
|
|
364485
364652
|
git;
|
|
@@ -364491,10 +364658,10 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
364491
364658
|
this.outputDir = outputDir;
|
|
364492
364659
|
}
|
|
364493
364660
|
fernignoreExists() {
|
|
364494
|
-
return (0, import_node_fs33.existsSync)((0,
|
|
364661
|
+
return (0, import_node_fs33.existsSync)((0, import_node_path92.join)(this.outputDir, ".fernignore"));
|
|
364495
364662
|
}
|
|
364496
364663
|
readFernignorePatterns() {
|
|
364497
|
-
const fernignorePath = (0,
|
|
364664
|
+
const fernignorePath = (0, import_node_path92.join)(this.outputDir, ".fernignore");
|
|
364498
364665
|
if (!(0, import_node_fs33.existsSync)(fernignorePath)) {
|
|
364499
364666
|
return [];
|
|
364500
364667
|
}
|
|
@@ -364511,7 +364678,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
364511
364678
|
const commitsOnly = [];
|
|
364512
364679
|
const syntheticPatches = [];
|
|
364513
364680
|
for (const pattern of patterns) {
|
|
364514
|
-
const matchingPatch = patches.find((p14) => p14.files.some((f4) => (0,
|
|
364681
|
+
const matchingPatch = patches.find((p14) => p14.files.some((f4) => (0, import_minimatch42.minimatch)(f4, pattern) || f4 === pattern));
|
|
364515
364682
|
if (matchingPatch) {
|
|
364516
364683
|
trackedByBoth.push({
|
|
364517
364684
|
file: pattern,
|
|
@@ -364584,7 +364751,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
364584
364751
|
fernignoreOnly.push(...remainingFernignoreOnly);
|
|
364585
364752
|
}
|
|
364586
364753
|
for (const patch5 of patches) {
|
|
364587
|
-
const hasUnprotectedFiles = patch5.files.some((f4) => !patterns.some((p14) => (0,
|
|
364754
|
+
const hasUnprotectedFiles = patch5.files.some((f4) => !patterns.some((p14) => (0, import_minimatch42.minimatch)(f4, p14) || f4 === p14));
|
|
364588
364755
|
if (hasUnprotectedFiles) {
|
|
364589
364756
|
commitsOnly.push(patch5);
|
|
364590
364757
|
}
|
|
@@ -364596,14 +364763,14 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
364596
364763
|
const results = [];
|
|
364597
364764
|
for (const pattern of patterns) {
|
|
364598
364765
|
const matching = allFiles.filter(
|
|
364599
|
-
(f4) => (0,
|
|
364766
|
+
(f4) => (0, import_minimatch42.minimatch)(f4, pattern) || f4 === pattern || f4.startsWith(pattern + "/")
|
|
364600
364767
|
);
|
|
364601
364768
|
results.push({ pattern, files: matching.length > 0 ? matching : [pattern] });
|
|
364602
364769
|
}
|
|
364603
364770
|
return results;
|
|
364604
364771
|
}
|
|
364605
364772
|
readFileContent(filePath) {
|
|
364606
|
-
const fullPath = (0,
|
|
364773
|
+
const fullPath = (0, import_node_path92.join)(this.outputDir, filePath);
|
|
364607
364774
|
if (!(0, import_node_fs33.existsSync)(fullPath)) {
|
|
364608
364775
|
return null;
|
|
364609
364776
|
}
|
|
@@ -364668,7 +364835,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
364668
364835
|
};
|
|
364669
364836
|
}
|
|
364670
364837
|
movePatternsToReplayYml(patterns) {
|
|
364671
|
-
const replayYmlPath = (0,
|
|
364838
|
+
const replayYmlPath = (0, import_node_path92.join)(this.outputDir, ".fern", "replay.yml");
|
|
364672
364839
|
let config5 = {};
|
|
364673
364840
|
if ((0, import_node_fs33.existsSync)(replayYmlPath)) {
|
|
364674
364841
|
const content5 = (0, import_node_fs33.readFileSync)(replayYmlPath, "utf-8");
|
|
@@ -364677,7 +364844,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
364677
364844
|
const existing = config5.exclude ?? [];
|
|
364678
364845
|
const merged = [.../* @__PURE__ */ new Set([...existing, ...patterns])];
|
|
364679
364846
|
config5.exclude = merged;
|
|
364680
|
-
const dir = (0,
|
|
364847
|
+
const dir = (0, import_node_path92.dirname)(replayYmlPath);
|
|
364681
364848
|
if (!(0, import_node_fs33.existsSync)(dir)) {
|
|
364682
364849
|
(0, import_node_fs33.mkdirSync)(dir, { recursive: true });
|
|
364683
364850
|
}
|
|
@@ -364686,7 +364853,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
364686
364853
|
};
|
|
364687
364854
|
var import_node_crypto32 = require("crypto");
|
|
364688
364855
|
var import_node_fs42 = require("fs");
|
|
364689
|
-
var
|
|
364856
|
+
var import_node_path102 = require("path");
|
|
364690
364857
|
init_GitClient();
|
|
364691
364858
|
async function bootstrap(outputDir, options2) {
|
|
364692
364859
|
const git = new GitClient(outputDir);
|
|
@@ -364853,7 +365020,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
364853
365020
|
}
|
|
364854
365021
|
var REPLAY_FERNIGNORE_ENTRIES = [".fern/replay.lock", ".fern/replay.yml", ".gitattributes"];
|
|
364855
365022
|
function ensureFernignoreEntries(outputDir) {
|
|
364856
|
-
const fernignorePath = (0,
|
|
365023
|
+
const fernignorePath = (0, import_node_path102.join)(outputDir, ".fernignore");
|
|
364857
365024
|
let content5 = "";
|
|
364858
365025
|
if ((0, import_node_fs42.existsSync)(fernignorePath)) {
|
|
364859
365026
|
content5 = (0, import_node_fs42.readFileSync)(fernignorePath, "utf-8");
|
|
@@ -364877,7 +365044,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
364877
365044
|
}
|
|
364878
365045
|
var GITATTRIBUTES_ENTRIES = [".fern/replay.lock linguist-generated=true"];
|
|
364879
365046
|
function ensureGitattributesEntries(outputDir) {
|
|
364880
|
-
const gitattributesPath = (0,
|
|
365047
|
+
const gitattributesPath = (0, import_node_path102.join)(outputDir, ".gitattributes");
|
|
364881
365048
|
let content5 = "";
|
|
364882
365049
|
if ((0, import_node_fs42.existsSync)(gitattributesPath)) {
|
|
364883
365050
|
content5 = (0, import_node_fs42.readFileSync)(gitattributesPath, "utf-8");
|
|
@@ -364905,7 +365072,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
364905
365072
|
const normalized = relevantLines.filter((line) => !line.startsWith("index ")).join("\n").replace(/\n-- \n[\d]+\.[\d]+[^\n]*\s*$/, "");
|
|
364906
365073
|
return `sha256:${(0, import_node_crypto32.createHash)("sha256").update(normalized).digest("hex")}`;
|
|
364907
365074
|
}
|
|
364908
|
-
var
|
|
365075
|
+
var import_minimatch52 = require_commonjs3();
|
|
364909
365076
|
function parseDiffStat(patchContent) {
|
|
364910
365077
|
let additions = 0;
|
|
364911
365078
|
let deletions = 0;
|
|
@@ -364935,7 +365102,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
364935
365102
|
}
|
|
364936
365103
|
function matchesPatch(patch5, pattern) {
|
|
364937
365104
|
const fileMatch = patch5.files.some(
|
|
364938
|
-
(file4) => file4 === pattern || (0,
|
|
365105
|
+
(file4) => file4 === pattern || (0, import_minimatch52.minimatch)(file4, pattern)
|
|
364939
365106
|
);
|
|
364940
365107
|
if (fileMatch) return true;
|
|
364941
365108
|
return patch5.original_message.toLowerCase().includes(pattern.toLowerCase());
|
|
@@ -365069,8 +365236,8 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
365069
365236
|
nothingToReset: false
|
|
365070
365237
|
};
|
|
365071
365238
|
}
|
|
365072
|
-
var
|
|
365073
|
-
var
|
|
365239
|
+
var import_promises710 = require("fs/promises");
|
|
365240
|
+
var import_node_path112 = require("path");
|
|
365074
365241
|
init_GitClient();
|
|
365075
365242
|
async function resolve15(outputDir, options2) {
|
|
365076
365243
|
const lockManager = new LockfileManager(outputDir);
|
|
@@ -365119,7 +365286,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
365119
365286
|
const result = await computePerPatchDiffWithFallback(
|
|
365120
365287
|
patch5,
|
|
365121
365288
|
(f4) => git.showFile(currentGen, f4),
|
|
365122
|
-
(f4) => (0,
|
|
365289
|
+
(f4) => (0, import_promises710.readFile)((0, import_node_path112.join)(outputDir, f4), "utf-8").catch(() => null),
|
|
365123
365290
|
(files) => git.exec(["diff", currentGen, "--", ...files]).catch(() => null)
|
|
365124
365291
|
);
|
|
365125
365292
|
if (result === null) {
|
|
@@ -365175,7 +365342,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
365175
365342
|
const theirsSnapshot = {};
|
|
365176
365343
|
for (const file4 of filesForSnapshot) {
|
|
365177
365344
|
if (isBinaryFile(file4)) continue;
|
|
365178
|
-
const content5 = await (0,
|
|
365345
|
+
const content5 = await (0, import_promises710.readFile)((0, import_node_path112.join)(outputDir, file4), "utf-8").catch(() => null);
|
|
365179
365346
|
if (content5 != null) {
|
|
365180
365347
|
theirsSnapshot[file4] = content5;
|
|
365181
365348
|
}
|
|
@@ -667965,7 +668132,7 @@ var AccessTokenPosthogManager = class {
|
|
|
667965
668132
|
properties: {
|
|
667966
668133
|
...event,
|
|
667967
668134
|
...event.properties,
|
|
667968
|
-
version: "5.27.
|
|
668135
|
+
version: "5.27.6",
|
|
667969
668136
|
usingAccessToken: true,
|
|
667970
668137
|
...getRunIdProperties()
|
|
667971
668138
|
}
|
|
@@ -668029,7 +668196,7 @@ var UserPosthogManager = class {
|
|
|
668029
668196
|
distinctId: this.userId ?? await this.getPersistedDistinctId(),
|
|
668030
668197
|
event: "CLI",
|
|
668031
668198
|
properties: {
|
|
668032
|
-
version: "5.27.
|
|
668199
|
+
version: "5.27.6",
|
|
668033
668200
|
...event,
|
|
668034
668201
|
...event.properties,
|
|
668035
668202
|
usingAccessToken: false,
|
|
@@ -859608,7 +859775,7 @@ var LOCAL_STORAGE_FOLDER4 = ".fern-dev";
|
|
|
859608
859775
|
var LOGS_FOLDER_NAME = "logs";
|
|
859609
859776
|
var MAX_LOGS_DIR_SIZE_BYTES = 100 * 1024 * 1024;
|
|
859610
859777
|
function getCliSource() {
|
|
859611
|
-
const version7 = "5.27.
|
|
859778
|
+
const version7 = "5.27.6";
|
|
859612
859779
|
return `cli@${version7}`;
|
|
859613
859780
|
}
|
|
859614
859781
|
var DebugLogger = class {
|
|
@@ -873608,7 +873775,7 @@ var LegacyDocsPublisher = class {
|
|
|
873608
873775
|
previewId,
|
|
873609
873776
|
disableTemplates: void 0,
|
|
873610
873777
|
skipUpload,
|
|
873611
|
-
cliVersion: "5.27.
|
|
873778
|
+
cliVersion: "5.27.6",
|
|
873612
873779
|
loginCommand: "fern auth login"
|
|
873613
873780
|
});
|
|
873614
873781
|
if (taskContext.getResult() === TaskResult.Failure) {
|
|
@@ -948013,7 +948180,7 @@ function getAutomationContextFromEnv() {
|
|
|
948013
948180
|
config_branch: process.env.FERN_CONFIG_BRANCH,
|
|
948014
948181
|
config_pr_number: process.env.FERN_CONFIG_PR_NUMBER,
|
|
948015
948182
|
trigger: process.env.GITHUB_EVENT_NAME,
|
|
948016
|
-
cli_version: "5.27.
|
|
948183
|
+
cli_version: "5.27.6"
|
|
948017
948184
|
};
|
|
948018
948185
|
}
|
|
948019
948186
|
function isAutomationMode() {
|
|
@@ -948839,7 +949006,7 @@ var CliContext = class _CliContext {
|
|
|
948839
949006
|
if (false) {
|
|
948840
949007
|
this.logger.error("CLI_VERSION is not defined");
|
|
948841
949008
|
}
|
|
948842
|
-
return "5.27.
|
|
949009
|
+
return "5.27.6";
|
|
948843
949010
|
}
|
|
948844
949011
|
getCliName() {
|
|
948845
949012
|
if (false) {
|