@fern-api/fern-api-dev 5.27.4 → 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 +1622 -1440
- 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,210 +363185,81 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363064
363185
|
const trailer = hasTrailingNewline ? "" : "\n\";
|
|
363065
363186
|
return header + "\n" + body + trailer + "\n";
|
|
363066
363187
|
}
|
|
363067
|
-
var
|
|
363068
|
-
|
|
363069
|
-
|
|
363070
|
-
applicator;
|
|
363071
|
-
committer;
|
|
363072
|
-
lockManager;
|
|
363073
|
-
outputDir;
|
|
363074
|
-
/** @internal Test-only. Captures the last ReplayResult[] returned from applicator.applyPatches(). */
|
|
363075
|
-
_lastApplyResults = [];
|
|
363076
|
-
/**
|
|
363077
|
-
* Service-level warnings accumulated during a single runReplay() call.
|
|
363078
|
-
* Merged with `detector.warnings` into `ReplayReport.warnings` by each flow handler.
|
|
363079
|
-
* Populated by `isFileUserOwned` when a patch's base generation tree is unreachable
|
|
363080
|
-
* (shallow clone / aggressive `git gc --prune`) and we fall back to a conservative
|
|
363081
|
-
* heuristic. Cleared at the top of `runReplay()`.
|
|
363082
|
-
*/
|
|
363083
|
-
warnings = [];
|
|
363084
|
-
/**
|
|
363085
|
-
* @internal Test-only accessor.
|
|
363086
|
-
* Returns the ReplayApplicator instance used for the last run. Tests use this
|
|
363087
|
-
* to inspect the inter-patch accumulator after runReplay() completes (FER-9791, I2).
|
|
363088
|
-
*/
|
|
363089
|
-
get applicatorRef() {
|
|
363090
|
-
return this.applicator;
|
|
363091
|
-
}
|
|
363092
|
-
/**
|
|
363093
|
-
* @internal Test-only accessor.
|
|
363094
|
-
* Returns the ReplayResult[] from the most recent applyPatches() call. Tests use
|
|
363095
|
-
* this to filter applied-vs-conflicted-vs-absorbed patches for invariant assertions
|
|
363096
|
-
* (FER-9791). Returns an empty array if no patches have been applied yet.
|
|
363097
|
-
*/
|
|
363098
|
-
getLastResults() {
|
|
363099
|
-
return this._lastApplyResults;
|
|
363100
|
-
}
|
|
363101
|
-
constructor(outputDir, _config) {
|
|
363102
|
-
const git = new GitClient(outputDir);
|
|
363188
|
+
var import_minimatch22 = require_commonjs3();
|
|
363189
|
+
var FileOwnership = class {
|
|
363190
|
+
constructor(git) {
|
|
363103
363191
|
this.git = git;
|
|
363104
|
-
this.outputDir = outputDir;
|
|
363105
|
-
this.lockManager = new LockfileManager(outputDir);
|
|
363106
|
-
this.detector = new ReplayDetector(git, this.lockManager, outputDir);
|
|
363107
|
-
this.applicator = new ReplayApplicator(git, this.lockManager, outputDir);
|
|
363108
|
-
this.committer = new ReplayCommitter(git, outputDir);
|
|
363109
363192
|
}
|
|
363110
363193
|
/**
|
|
363111
|
-
*
|
|
363112
|
-
* detects new patches, and creates the `[fern-generated]` commit. Leaves HEAD at
|
|
363113
|
-
* the generation commit (or unchanged for dry-run).
|
|
363114
|
-
*
|
|
363115
|
-
* Does NOT apply patches — the returned handle must be passed to
|
|
363116
|
-
* `applyPreparedReplay()` to complete the run. No disk writes to the lockfile
|
|
363117
|
-
* happen here; lockfile persistence is deferred to phase 2.
|
|
363118
|
-
*
|
|
363119
|
-
* Callers may land additional commits on HEAD between `prepareReplay` and
|
|
363120
|
-
* `applyPreparedReplay` (e.g., `[fern-autoversion]`).
|
|
363121
|
-
*/
|
|
363122
|
-
async prepareReplay(options2) {
|
|
363123
|
-
this.warnings = [];
|
|
363124
|
-
this.detector.warnings.length = 0;
|
|
363125
|
-
if (options2?.skipApplication) {
|
|
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
|
-
}
|
|
363137
|
-
}
|
|
363138
|
-
/**
|
|
363139
|
-
* Phase 2 of the two-phase replay flow. Applies patches, rebases them against
|
|
363140
|
-
* the generation, saves the lockfile, and commits `[fern-replay]` (or stages
|
|
363141
|
-
* under `stageOnly`). Operates on HEAD at call time — mid-phase commits are
|
|
363142
|
-
* respected.
|
|
363143
|
-
*
|
|
363144
|
-
* For terminal handles (dry-run, first-gen has no patch work, skip-app does
|
|
363145
|
-
* its own post-commit logic), this returns the precomputed report.
|
|
363146
|
-
*/
|
|
363147
|
-
async applyPreparedReplay(prep, options2) {
|
|
363148
|
-
if (prep._prepared.terminal) {
|
|
363149
|
-
return prep._prepared.preparedReport;
|
|
363150
|
-
}
|
|
363151
|
-
switch (prep._prepared.flow) {
|
|
363152
|
-
case "first-generation":
|
|
363153
|
-
return this._applyFirstGeneration(prep);
|
|
363154
|
-
case "skip-application":
|
|
363155
|
-
return this._applySkipApplication(prep, options2);
|
|
363156
|
-
case "no-patches":
|
|
363157
|
-
return this._applyNoPatchesRegeneration(prep, options2);
|
|
363158
|
-
case "normal-regeneration":
|
|
363159
|
-
return this._applyNormalRegeneration(prep, options2);
|
|
363160
|
-
}
|
|
363161
|
-
}
|
|
363162
|
-
/**
|
|
363163
|
-
* Backwards-compatible atomic entry point. Composes `prepareReplay` + `applyPreparedReplay`.
|
|
363164
|
-
* Behavior is byte-identical to the pre-split implementation for all existing callers.
|
|
363165
|
-
*/
|
|
363166
|
-
async runReplay(options2) {
|
|
363167
|
-
const prep = await this.prepareReplay(options2);
|
|
363168
|
-
return this.applyPreparedReplay(prep, options2);
|
|
363169
|
-
}
|
|
363170
|
-
/**
|
|
363171
|
-
* Sync the lockfile after a divergent PR was squash-merged.
|
|
363172
|
-
* Call this BEFORE runReplay() when the CLI detects a merged divergent PR.
|
|
363194
|
+
* Canonical resolution. Returns true if `file` is user-owned per:
|
|
363173
363195
|
*
|
|
363174
|
-
*
|
|
363175
|
-
*
|
|
363176
|
-
*
|
|
363177
|
-
*
|
|
363178
|
-
*
|
|
363179
|
-
|
|
363180
|
-
|
|
363181
|
-
|
|
363182
|
-
|
|
363183
|
-
|
|
363184
|
-
|
|
363185
|
-
|
|
363186
|
-
|
|
363187
|
-
|
|
363188
|
-
|
|
363189
|
-
|
|
363190
|
-
|
|
363191
|
-
|
|
363192
|
-
|
|
363193
|
-
this.lockManager.initializeInMemory(record23);
|
|
363194
|
-
} else {
|
|
363195
|
-
this.lockManager.read();
|
|
363196
|
-
const unresolvedPatches = [
|
|
363197
|
-
...this.lockManager.getUnresolvedPatches(),
|
|
363198
|
-
...this.lockManager.getResolvingPatches()
|
|
363199
|
-
];
|
|
363200
|
-
resolvedPatches = this.lockManager.getPatches().filter((p14) => p14.status == null);
|
|
363201
|
-
this.lockManager.addGeneration(record23);
|
|
363202
|
-
this.lockManager.clearPatches();
|
|
363203
|
-
for (const patch5 of unresolvedPatches) {
|
|
363204
|
-
this.lockManager.addPatch(patch5);
|
|
363205
|
-
}
|
|
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;
|
|
363206
363215
|
}
|
|
363207
|
-
|
|
363208
|
-
|
|
363209
|
-
|
|
363210
|
-
|
|
363211
|
-
|
|
363212
|
-
|
|
363213
|
-
|
|
363214
|
-
|
|
363215
|
-
|
|
363216
|
-
}
|
|
363217
|
-
for (const patch5 of redetectedPatches) {
|
|
363218
|
-
this.lockManager.addPatch(patch5);
|
|
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
|
+
);
|
|
363219
363225
|
}
|
|
363226
|
+
return true;
|
|
363220
363227
|
}
|
|
363221
|
-
|
|
363222
|
-
for (const patch5 of resolvedPatches ?? []) {
|
|
363223
|
-
this.lockManager.addPatch(patch5);
|
|
363224
|
-
}
|
|
363225
|
-
this.detector.warnings.push(
|
|
363226
|
-
`Patch re-detection failed after divergent merge sync. ${(resolvedPatches ?? []).length} previously resolved patch(es) preserved. Error: ${error50 instanceof Error ? error50.message : String(error50)}`
|
|
363227
|
-
);
|
|
363228
|
+
return await this.git.showFile(base4, originalFileName ?? file4) === null;
|
|
363228
363229
|
}
|
|
363229
|
-
this.
|
|
363230
|
-
this.detector.warnings.push(...this.lockManager.warnings);
|
|
363230
|
+
return await this.git.showFile(currentGenSha, originalFileName ?? file4) === null;
|
|
363231
363231
|
}
|
|
363232
|
-
|
|
363233
|
-
|
|
363234
|
-
|
|
363235
|
-
|
|
363236
|
-
|
|
363237
|
-
|
|
363238
|
-
|
|
363239
|
-
|
|
363240
|
-
|
|
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";
|
|
363241
363243
|
}
|
|
363242
363244
|
}
|
|
363243
|
-
|
|
363244
|
-
|
|
363245
|
-
|
|
363246
|
-
|
|
363247
|
-
|
|
363248
|
-
patchesWithConflicts: 0,
|
|
363249
|
-
patchesSkipped: 0,
|
|
363250
|
-
conflicts: []
|
|
363251
|
-
};
|
|
363245
|
+
return false;
|
|
363246
|
+
}
|
|
363247
|
+
var FirstGenerationFlow = class {
|
|
363248
|
+
constructor(service) {
|
|
363249
|
+
this.service = service;
|
|
363252
363250
|
}
|
|
363253
|
-
async
|
|
363251
|
+
async prepare(options2) {
|
|
363254
363252
|
if (options2?.dryRun) {
|
|
363255
|
-
const headSha = await this.git.exec(["rev-parse", "HEAD"]).then((s9) => s9.trim()).catch(() => "");
|
|
363253
|
+
const headSha = await this.service.git.exec(["rev-parse", "HEAD"]).then((s9) => s9.trim()).catch(() => "");
|
|
363256
363254
|
return {
|
|
363257
363255
|
flow: "first-generation",
|
|
363258
363256
|
previousGenerationSha: null,
|
|
363259
363257
|
currentGenerationSha: headSha,
|
|
363260
363258
|
baseBranchHead: options2.baseBranchHead ?? null,
|
|
363261
363259
|
_prepared: {
|
|
363260
|
+
kind: "terminal",
|
|
363262
363261
|
flow: "first-generation",
|
|
363263
|
-
|
|
363264
|
-
preparedReport: this.firstGenerationReport(),
|
|
363265
|
-
patchesToApply: [],
|
|
363266
|
-
newPatchIds: /* @__PURE__ */ new Set(),
|
|
363267
|
-
detectorWarnings: [],
|
|
363268
|
-
revertedCount: 0,
|
|
363269
|
-
genSha: headSha,
|
|
363270
|
-
optionsSnapshot: options2
|
|
363262
|
+
preparedReport: firstGenerationReport()
|
|
363271
363263
|
}
|
|
363272
363264
|
};
|
|
363273
363265
|
}
|
|
@@ -363276,17 +363268,17 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363276
363268
|
generatorVersions: options2.generatorVersions ?? {},
|
|
363277
363269
|
baseBranchHead: options2.baseBranchHead
|
|
363278
363270
|
} : void 0;
|
|
363279
|
-
await this.committer.commitGeneration("Initial SDK generation", commitOpts);
|
|
363280
|
-
const genRecord = await this.committer.createGenerationRecord(commitOpts);
|
|
363281
|
-
this.lockManager.initializeInMemory(genRecord);
|
|
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);
|
|
363282
363274
|
return {
|
|
363283
363275
|
flow: "first-generation",
|
|
363284
363276
|
previousGenerationSha: null,
|
|
363285
363277
|
currentGenerationSha: genRecord.commit_sha,
|
|
363286
363278
|
baseBranchHead: options2?.baseBranchHead ?? null,
|
|
363287
363279
|
_prepared: {
|
|
363280
|
+
kind: "active",
|
|
363288
363281
|
flow: "first-generation",
|
|
363289
|
-
terminal: false,
|
|
363290
363282
|
patchesToApply: [],
|
|
363291
363283
|
newPatchIds: /* @__PURE__ */ new Set(),
|
|
363292
363284
|
detectorWarnings: [],
|
|
@@ -363296,47 +363288,55 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363296
363288
|
}
|
|
363297
363289
|
};
|
|
363298
363290
|
}
|
|
363299
|
-
async
|
|
363300
|
-
this.lockManager.save();
|
|
363301
|
-
return
|
|
363291
|
+
async apply(_prep) {
|
|
363292
|
+
this.service.lockManager.save();
|
|
363293
|
+
return firstGenerationReport();
|
|
363302
363294
|
}
|
|
363303
|
-
|
|
363304
|
-
|
|
363305
|
-
|
|
363306
|
-
|
|
363307
|
-
|
|
363308
|
-
|
|
363309
|
-
|
|
363310
|
-
|
|
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
363311
|
const commitOpts = options2 ? {
|
|
363312
363312
|
cliVersion: options2.cliVersion ?? "unknown",
|
|
363313
363313
|
generatorVersions: options2.generatorVersions ?? {},
|
|
363314
363314
|
baseBranchHead: options2.baseBranchHead
|
|
363315
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);
|
|
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
363319
|
let previousGenerationSha = null;
|
|
363320
363320
|
try {
|
|
363321
|
-
const lock = this.lockManager.read();
|
|
363321
|
+
const lock = this.service.lockManager.read();
|
|
363322
363322
|
previousGenerationSha = lock.current_generation ?? null;
|
|
363323
|
-
this.lockManager.addGeneration(genRecord);
|
|
363323
|
+
this.service.lockManager.addGeneration(genRecord);
|
|
363324
363324
|
} catch (error50) {
|
|
363325
363325
|
if (error50 instanceof LockfileNotFoundError) {
|
|
363326
|
-
this.lockManager.initializeInMemory(genRecord);
|
|
363326
|
+
this.service.lockManager.initializeInMemory(genRecord);
|
|
363327
363327
|
} else {
|
|
363328
363328
|
throw error50;
|
|
363329
363329
|
}
|
|
363330
363330
|
}
|
|
363331
|
-
this.lockManager.setReplaySkippedAt((/* @__PURE__ */ new Date()).toISOString());
|
|
363331
|
+
this.service.lockManager.setReplaySkippedAt((/* @__PURE__ */ new Date()).toISOString());
|
|
363332
363332
|
return {
|
|
363333
363333
|
flow: "skip-application",
|
|
363334
363334
|
previousGenerationSha,
|
|
363335
363335
|
currentGenerationSha: genRecord.commit_sha,
|
|
363336
363336
|
baseBranchHead: options2?.baseBranchHead ?? null,
|
|
363337
363337
|
_prepared: {
|
|
363338
|
+
kind: "active",
|
|
363338
363339
|
flow: "skip-application",
|
|
363339
|
-
terminal: false,
|
|
363340
363340
|
patchesToApply: [],
|
|
363341
363341
|
newPatchIds: /* @__PURE__ */ new Set(),
|
|
363342
363342
|
detectorWarnings: [],
|
|
@@ -363346,13 +363346,13 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363346
363346
|
}
|
|
363347
363347
|
};
|
|
363348
363348
|
}
|
|
363349
|
-
async
|
|
363350
|
-
this.lockManager.save();
|
|
363351
|
-
const credentialWarnings = [...this.lockManager.warnings];
|
|
363349
|
+
async apply(_prep, options2) {
|
|
363350
|
+
this.service.lockManager.save();
|
|
363351
|
+
const credentialWarnings = [...this.service.lockManager.warnings];
|
|
363352
363352
|
if (!options2?.stageOnly) {
|
|
363353
|
-
await this.committer.commitReplay(0);
|
|
363353
|
+
await this.service.committer.commitReplay(0);
|
|
363354
363354
|
} else {
|
|
363355
|
-
await this.committer.stageAll();
|
|
363355
|
+
await this.service.committer.stageAll();
|
|
363356
363356
|
}
|
|
363357
363357
|
return {
|
|
363358
363358
|
flow: "skip-application",
|
|
@@ -363364,13 +363364,18 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363364
363364
|
warnings: credentialWarnings.length > 0 ? credentialWarnings : void 0
|
|
363365
363365
|
};
|
|
363366
363366
|
}
|
|
363367
|
-
|
|
363368
|
-
|
|
363367
|
+
};
|
|
363368
|
+
var NoPatchesFlow = class {
|
|
363369
|
+
constructor(service) {
|
|
363370
|
+
this.service = service;
|
|
363371
|
+
}
|
|
363372
|
+
async prepare(options2) {
|
|
363373
|
+
const preLock = this.service.lockManager.read();
|
|
363369
363374
|
const previousGenerationSha = preLock.current_generation ?? null;
|
|
363370
|
-
let { patches: newPatches, revertedPatchIds } = await this.detector.detectNewPatches();
|
|
363371
|
-
const detectorWarnings = [...this.detector.warnings];
|
|
363375
|
+
let { patches: newPatches, revertedPatchIds } = await this.service.detector.detectNewPatches();
|
|
363376
|
+
const detectorWarnings = [...this.service.detector.warnings];
|
|
363372
363377
|
if (options2?.dryRun) {
|
|
363373
|
-
const dryRunWarnings = [...detectorWarnings, ...this.warnings];
|
|
363378
|
+
const dryRunWarnings = [...detectorWarnings, ...this.service.warnings];
|
|
363374
363379
|
const preparedReport = {
|
|
363375
363380
|
flow: "no-patches",
|
|
363376
363381
|
patchesDetected: newPatches.length,
|
|
@@ -363382,22 +363387,16 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363382
363387
|
wouldApply: newPatches,
|
|
363383
363388
|
warnings: dryRunWarnings.length > 0 ? dryRunWarnings : void 0
|
|
363384
363389
|
};
|
|
363385
|
-
const headSha = await this.git.exec(["rev-parse", "HEAD"]).then((s9) => s9.trim()).catch(() => "");
|
|
363390
|
+
const headSha = await this.service.git.exec(["rev-parse", "HEAD"]).then((s9) => s9.trim()).catch(() => "");
|
|
363386
363391
|
return {
|
|
363387
363392
|
flow: "no-patches",
|
|
363388
363393
|
previousGenerationSha,
|
|
363389
363394
|
currentGenerationSha: headSha,
|
|
363390
363395
|
baseBranchHead: options2.baseBranchHead ?? null,
|
|
363391
363396
|
_prepared: {
|
|
363397
|
+
kind: "terminal",
|
|
363392
363398
|
flow: "no-patches",
|
|
363393
|
-
|
|
363394
|
-
preparedReport,
|
|
363395
|
-
patchesToApply: [],
|
|
363396
|
-
newPatchIds: /* @__PURE__ */ new Set(),
|
|
363397
|
-
detectorWarnings,
|
|
363398
|
-
revertedCount: revertedPatchIds.length,
|
|
363399
|
-
genSha: headSha,
|
|
363400
|
-
optionsSnapshot: options2
|
|
363399
|
+
preparedReport
|
|
363401
363400
|
}
|
|
363402
363401
|
};
|
|
363403
363402
|
}
|
|
@@ -363406,7 +363405,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363406
363405
|
const uniqueFiles = [...new Set(newPatches.flatMap((p14) => p14.files))];
|
|
363407
363406
|
const absorbedFiles = /* @__PURE__ */ new Set();
|
|
363408
363407
|
for (const file4 of uniqueFiles) {
|
|
363409
|
-
const diff2 = await this.git.exec(["diff", preCurrentGen, "HEAD", "--", file4]).catch(() => null);
|
|
363408
|
+
const diff2 = await this.service.git.exec(["diff", preCurrentGen, "HEAD", "--", file4]).catch(() => null);
|
|
363410
363409
|
if (diff2 !== null && !diff2.trim()) {
|
|
363411
363410
|
absorbedFiles.add(file4);
|
|
363412
363411
|
}
|
|
@@ -363419,7 +363418,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363419
363418
|
}
|
|
363420
363419
|
for (const id2 of revertedPatchIds) {
|
|
363421
363420
|
try {
|
|
363422
|
-
this.lockManager.removePatch(id2);
|
|
363421
|
+
this.service.lockManager.removePatch(id2);
|
|
363423
363422
|
} catch {
|
|
363424
363423
|
}
|
|
363425
363424
|
}
|
|
@@ -363428,18 +363427,18 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363428
363427
|
generatorVersions: options2.generatorVersions ?? {},
|
|
363429
363428
|
baseBranchHead: options2.baseBranchHead
|
|
363430
363429
|
} : 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);
|
|
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);
|
|
363435
363434
|
return {
|
|
363436
363435
|
flow: "no-patches",
|
|
363437
363436
|
previousGenerationSha,
|
|
363438
363437
|
currentGenerationSha: genRecord.commit_sha,
|
|
363439
363438
|
baseBranchHead: options2?.baseBranchHead ?? null,
|
|
363440
363439
|
_prepared: {
|
|
363440
|
+
kind: "active",
|
|
363441
363441
|
flow: "no-patches",
|
|
363442
|
-
terminal: false,
|
|
363443
363442
|
patchesToApply: newPatches,
|
|
363444
363443
|
newPatchIds: new Set(newPatches.map((p14) => p14.id)),
|
|
363445
363444
|
detectorWarnings,
|
|
@@ -363449,41 +363448,42 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363449
363448
|
}
|
|
363450
363449
|
};
|
|
363451
363450
|
}
|
|
363452
|
-
async
|
|
363451
|
+
async apply(prep, options2) {
|
|
363452
|
+
assertActive(prep);
|
|
363453
363453
|
const newPatches = prep._prepared.patchesToApply;
|
|
363454
363454
|
const detectorWarnings = prep._prepared.detectorWarnings;
|
|
363455
363455
|
const genSha = prep._prepared.genSha;
|
|
363456
363456
|
let results = [];
|
|
363457
363457
|
if (newPatches.length > 0) {
|
|
363458
|
-
results = await this.applicator.applyPatches(newPatches);
|
|
363459
|
-
this._lastApplyResults = results;
|
|
363460
|
-
this.recordDroppedContextLineWarnings(results);
|
|
363461
|
-
this.revertConflictingFiles(results);
|
|
363458
|
+
results = await this.service.applicator.applyPatches(newPatches);
|
|
363459
|
+
this.service._lastApplyResults = results;
|
|
363460
|
+
this.service.recordDroppedContextLineWarnings(results);
|
|
363461
|
+
this.service.revertConflictingFiles(results);
|
|
363462
363462
|
for (const result of results) {
|
|
363463
363463
|
if (result.status === "conflict") {
|
|
363464
363464
|
result.patch.status = "unresolved";
|
|
363465
363465
|
}
|
|
363466
363466
|
}
|
|
363467
363467
|
}
|
|
363468
|
-
const rebaseCounts = await this.rebasePatches(results, genSha);
|
|
363468
|
+
const rebaseCounts = await this.service.rebasePatches(results, genSha);
|
|
363469
363469
|
for (const patch5 of newPatches) {
|
|
363470
363470
|
if (!rebaseCounts.absorbedPatchIds.has(patch5.id)) {
|
|
363471
|
-
this.lockManager.addPatch(patch5);
|
|
363471
|
+
this.service.lockManager.addPatch(patch5);
|
|
363472
363472
|
}
|
|
363473
363473
|
}
|
|
363474
|
-
this.lockManager.save();
|
|
363475
|
-
this.warnings.push(...this.lockManager.warnings);
|
|
363474
|
+
this.service.lockManager.save();
|
|
363475
|
+
this.service.warnings.push(...this.service.lockManager.warnings);
|
|
363476
363476
|
if (newPatches.length > 0) {
|
|
363477
363477
|
if (!options2?.stageOnly) {
|
|
363478
363478
|
const appliedCount = results.filter((r5) => r5.status === "applied").length;
|
|
363479
363479
|
const buckets = computeCommitBuckets(results, rebaseCounts.absorbedPatchIds, newPatches);
|
|
363480
|
-
await this.committer.commitReplay(appliedCount, newPatches, void 0, { buckets });
|
|
363480
|
+
await this.service.committer.commitReplay(appliedCount, newPatches, void 0, { buckets });
|
|
363481
363481
|
} else {
|
|
363482
|
-
await this.committer.stageAll();
|
|
363482
|
+
await this.service.committer.stageAll();
|
|
363483
363483
|
}
|
|
363484
363484
|
}
|
|
363485
|
-
const warnings = [...detectorWarnings, ...this.warnings];
|
|
363486
|
-
return this.buildReport(
|
|
363485
|
+
const warnings = [...detectorWarnings, ...this.service.warnings];
|
|
363486
|
+
return this.service.buildReport(
|
|
363487
363487
|
"no-patches",
|
|
363488
363488
|
newPatches,
|
|
363489
363489
|
results,
|
|
@@ -363494,13 +363494,18 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363494
363494
|
prep._prepared.revertedCount
|
|
363495
363495
|
);
|
|
363496
363496
|
}
|
|
363497
|
-
|
|
363498
|
-
|
|
363497
|
+
};
|
|
363498
|
+
var NormalRegenerationFlow = class {
|
|
363499
|
+
constructor(service) {
|
|
363500
|
+
this.service = service;
|
|
363501
|
+
}
|
|
363502
|
+
async prepare(options2) {
|
|
363503
|
+
const preLock = this.service.lockManager.read();
|
|
363499
363504
|
const previousGenerationSha = preLock.current_generation ?? null;
|
|
363500
363505
|
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];
|
|
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];
|
|
363504
363509
|
const allPatches2 = [...existingPatches2, ...newPatches2];
|
|
363505
363510
|
const preparedReport = {
|
|
363506
363511
|
flow: "normal-regeneration",
|
|
@@ -363513,48 +363518,42 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363513
363518
|
wouldApply: allPatches2,
|
|
363514
363519
|
warnings: warnings.length > 0 ? warnings : void 0
|
|
363515
363520
|
};
|
|
363516
|
-
const headSha = await this.git.exec(["rev-parse", "HEAD"]).then((s9) => s9.trim()).catch(() => "");
|
|
363521
|
+
const headSha = await this.service.git.exec(["rev-parse", "HEAD"]).then((s9) => s9.trim()).catch(() => "");
|
|
363517
363522
|
return {
|
|
363518
363523
|
flow: "normal-regeneration",
|
|
363519
363524
|
previousGenerationSha,
|
|
363520
363525
|
currentGenerationSha: headSha,
|
|
363521
363526
|
baseBranchHead: options2.baseBranchHead ?? null,
|
|
363522
363527
|
_prepared: {
|
|
363528
|
+
kind: "terminal",
|
|
363523
363529
|
flow: "normal-regeneration",
|
|
363524
|
-
|
|
363525
|
-
preparedReport,
|
|
363526
|
-
patchesToApply: [],
|
|
363527
|
-
newPatchIds: /* @__PURE__ */ new Set(),
|
|
363528
|
-
detectorWarnings: [...this.detector.warnings],
|
|
363529
|
-
revertedCount: dryRunReverted.length,
|
|
363530
|
-
genSha: headSha,
|
|
363531
|
-
optionsSnapshot: options2
|
|
363530
|
+
preparedReport
|
|
363532
363531
|
}
|
|
363533
363532
|
};
|
|
363534
363533
|
}
|
|
363535
|
-
let existingPatches = this.lockManager.getPatches();
|
|
363534
|
+
let existingPatches = this.service.lockManager.getPatches();
|
|
363536
363535
|
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));
|
|
363536
|
+
const preRebaseCounts = await this.service.preGenerationRebase(existingPatches);
|
|
363537
|
+
const postRebasePatchIds = new Set(this.service.lockManager.getPatches().map((p14) => p14.id));
|
|
363539
363538
|
const removedByPreRebase = existingPatches.filter(
|
|
363540
363539
|
(p14) => preRebasePatchIds.has(p14.id) && !postRebasePatchIds.has(p14.id)
|
|
363541
363540
|
);
|
|
363542
|
-
existingPatches = this.lockManager.getPatches();
|
|
363541
|
+
existingPatches = this.service.lockManager.getPatches();
|
|
363543
363542
|
const seenHashes = /* @__PURE__ */ new Map();
|
|
363544
363543
|
for (const p14 of existingPatches) {
|
|
363545
363544
|
const priorPatchId = seenHashes.get(p14.content_hash);
|
|
363546
363545
|
if (priorPatchId !== void 0) {
|
|
363547
|
-
this.lockManager.removePatch(p14.id);
|
|
363548
|
-
this.warnings.push(
|
|
363546
|
+
this.service.lockManager.removePatch(p14.id);
|
|
363547
|
+
this.service.warnings.push(
|
|
363549
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.`
|
|
363550
363549
|
);
|
|
363551
363550
|
} else {
|
|
363552
363551
|
seenHashes.set(p14.content_hash, p14.id);
|
|
363553
363552
|
}
|
|
363554
363553
|
}
|
|
363555
|
-
existingPatches = this.lockManager.getPatches();
|
|
363556
|
-
let { patches: newPatches, revertedPatchIds } = await this.detector.detectNewPatches();
|
|
363557
|
-
const detectorWarnings = [...this.detector.warnings];
|
|
363554
|
+
existingPatches = this.service.lockManager.getPatches();
|
|
363555
|
+
let { patches: newPatches, revertedPatchIds } = await this.service.detector.detectNewPatches();
|
|
363556
|
+
const detectorWarnings = [...this.service.detector.warnings];
|
|
363558
363557
|
if (removedByPreRebase.length > 0) {
|
|
363559
363558
|
const removedOriginalCommits = new Set(removedByPreRebase.map((p14) => p14.original_commit));
|
|
363560
363559
|
const removedOriginalMessages = new Set(removedByPreRebase.map((p14) => p14.original_message));
|
|
@@ -363569,7 +363568,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363569
363568
|
}
|
|
363570
363569
|
for (const id2 of revertedPatchIds) {
|
|
363571
363570
|
try {
|
|
363572
|
-
this.lockManager.removePatch(id2);
|
|
363571
|
+
this.service.lockManager.removePatch(id2);
|
|
363573
363572
|
} catch {
|
|
363574
363573
|
}
|
|
363575
363574
|
}
|
|
@@ -363581,18 +363580,18 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363581
363580
|
generatorVersions: options2.generatorVersions ?? {},
|
|
363582
363581
|
baseBranchHead: options2.baseBranchHead
|
|
363583
363582
|
} : 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);
|
|
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);
|
|
363588
363587
|
return {
|
|
363589
363588
|
flow: "normal-regeneration",
|
|
363590
363589
|
previousGenerationSha,
|
|
363591
363590
|
currentGenerationSha: genRecord.commit_sha,
|
|
363592
363591
|
baseBranchHead: options2?.baseBranchHead ?? null,
|
|
363593
363592
|
_prepared: {
|
|
363593
|
+
kind: "active",
|
|
363594
363594
|
flow: "normal-regeneration",
|
|
363595
|
-
terminal: false,
|
|
363596
363595
|
patchesToApply: allPatches,
|
|
363597
363596
|
newPatchIds: new Set(newPatches.map((p14) => p14.id)),
|
|
363598
363597
|
preRebaseCounts,
|
|
@@ -363603,46 +363602,47 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363603
363602
|
}
|
|
363604
363603
|
};
|
|
363605
363604
|
}
|
|
363606
|
-
async
|
|
363605
|
+
async apply(prep, options2) {
|
|
363606
|
+
assertActive(prep);
|
|
363607
363607
|
const allPatches = prep._prepared.patchesToApply;
|
|
363608
363608
|
const newPatchIds = prep._prepared.newPatchIds;
|
|
363609
363609
|
const detectorWarnings = prep._prepared.detectorWarnings;
|
|
363610
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);
|
|
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
363615
|
for (const result of results) {
|
|
363616
363616
|
if (result.status === "conflict") {
|
|
363617
363617
|
result.patch.status = "unresolved";
|
|
363618
363618
|
}
|
|
363619
363619
|
}
|
|
363620
|
-
const rebaseCounts = await this.rebasePatches(results, genSha);
|
|
363620
|
+
const rebaseCounts = await this.service.rebasePatches(results, genSha);
|
|
363621
363621
|
const newPatches = allPatches.filter((p14) => newPatchIds.has(p14.id));
|
|
363622
363622
|
for (const patch5 of newPatches) {
|
|
363623
363623
|
if (!rebaseCounts.absorbedPatchIds.has(patch5.id)) {
|
|
363624
|
-
this.lockManager.addPatch(patch5);
|
|
363624
|
+
this.service.lockManager.addPatch(patch5);
|
|
363625
363625
|
}
|
|
363626
363626
|
}
|
|
363627
363627
|
for (const result of results) {
|
|
363628
363628
|
if (result.status === "conflict") {
|
|
363629
363629
|
try {
|
|
363630
|
-
this.lockManager.markPatchUnresolved(result.patch.id);
|
|
363630
|
+
this.service.lockManager.markPatchUnresolved(result.patch.id);
|
|
363631
363631
|
} catch {
|
|
363632
363632
|
}
|
|
363633
363633
|
}
|
|
363634
363634
|
}
|
|
363635
|
-
this.lockManager.save();
|
|
363636
|
-
this.warnings.push(...this.lockManager.warnings);
|
|
363635
|
+
this.service.lockManager.save();
|
|
363636
|
+
this.service.warnings.push(...this.service.lockManager.warnings);
|
|
363637
363637
|
if (options2?.stageOnly) {
|
|
363638
|
-
await this.committer.stageAll();
|
|
363638
|
+
await this.service.committer.stageAll();
|
|
363639
363639
|
} else {
|
|
363640
363640
|
const appliedCount = results.filter((r5) => r5.status === "applied").length;
|
|
363641
363641
|
const buckets = computeCommitBuckets(results, rebaseCounts.absorbedPatchIds, allPatches);
|
|
363642
|
-
await this.committer.commitReplay(appliedCount, allPatches, void 0, { buckets });
|
|
363642
|
+
await this.service.committer.commitReplay(appliedCount, allPatches, void 0, { buckets });
|
|
363643
363643
|
}
|
|
363644
|
-
const warnings = [...detectorWarnings, ...this.warnings];
|
|
363645
|
-
return this.buildReport(
|
|
363644
|
+
const warnings = [...detectorWarnings, ...this.service.warnings];
|
|
363645
|
+
return this.service.buildReport(
|
|
363646
363646
|
"normal-regeneration",
|
|
363647
363647
|
allPatches,
|
|
363648
363648
|
results,
|
|
@@ -363653,10 +363653,211 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363653
363653
|
prep._prepared.revertedCount
|
|
363654
363654
|
);
|
|
363655
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
|
+
}
|
|
363664
|
+
var ReplayService = class {
|
|
363665
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
363666
|
+
git;
|
|
363667
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
363668
|
+
detector;
|
|
363669
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
363670
|
+
applicator;
|
|
363671
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
363672
|
+
committer;
|
|
363673
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
363674
|
+
lockManager;
|
|
363675
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
363676
|
+
fileOwnership;
|
|
363677
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
363678
|
+
outputDir;
|
|
363679
|
+
/** @internal Test-only. Captures the last ReplayResult[] returned from applicator.applyPatches(). */
|
|
363680
|
+
_lastApplyResults = [];
|
|
363681
|
+
/**
|
|
363682
|
+
* Service-level warnings accumulated during a single runReplay() call.
|
|
363683
|
+
* Merged with `detector.warnings` into `ReplayReport.warnings` by each flow handler.
|
|
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/`.
|
|
363689
|
+
*/
|
|
363690
|
+
warnings = [];
|
|
363691
|
+
/**
|
|
363692
|
+
* @internal Test-only accessor.
|
|
363693
|
+
* Returns the ReplayApplicator instance used for the last run. Tests use this
|
|
363694
|
+
* to inspect the inter-patch accumulator after runReplay() completes (invariant I2).
|
|
363695
|
+
*/
|
|
363696
|
+
get applicatorRef() {
|
|
363697
|
+
return this.applicator;
|
|
363698
|
+
}
|
|
363699
|
+
/**
|
|
363700
|
+
* @internal Test-only accessor.
|
|
363701
|
+
* Returns the ReplayResult[] from the most recent applyPatches() call. Tests use
|
|
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.
|
|
363704
|
+
*/
|
|
363705
|
+
getLastResults() {
|
|
363706
|
+
return this._lastApplyResults;
|
|
363707
|
+
}
|
|
363708
|
+
constructor(outputDir, _config) {
|
|
363709
|
+
const git = new GitClient(outputDir);
|
|
363710
|
+
this.git = git;
|
|
363711
|
+
this.outputDir = outputDir;
|
|
363712
|
+
this.lockManager = new LockfileManager(outputDir);
|
|
363713
|
+
this.detector = new ReplayDetector(git, this.lockManager, outputDir);
|
|
363714
|
+
this.applicator = new ReplayApplicator(git, this.lockManager, outputDir);
|
|
363715
|
+
this.committer = new ReplayCommitter(git, outputDir);
|
|
363716
|
+
this.fileOwnership = new FileOwnership(git);
|
|
363717
|
+
}
|
|
363718
|
+
/**
|
|
363719
|
+
* Phase 1 of the two-phase replay flow. Runs preGenerationRebase (when applicable),
|
|
363720
|
+
* detects new patches, and creates the `[fern-generated]` commit. Leaves HEAD at
|
|
363721
|
+
* the generation commit (or unchanged for dry-run).
|
|
363722
|
+
*
|
|
363723
|
+
* Does NOT apply patches — the returned handle must be passed to
|
|
363724
|
+
* `applyPreparedReplay()` to complete the run. No disk writes to the lockfile
|
|
363725
|
+
* happen here; lockfile persistence is deferred to phase 2.
|
|
363726
|
+
*
|
|
363727
|
+
* Callers may land additional commits on HEAD between `prepareReplay` and
|
|
363728
|
+
* `applyPreparedReplay` (e.g., `[fern-autoversion]`).
|
|
363729
|
+
*/
|
|
363730
|
+
async prepareReplay(options2) {
|
|
363731
|
+
this.warnings = [];
|
|
363732
|
+
this.detector.warnings.length = 0;
|
|
363733
|
+
return this.selectFlow(options2).prepare(options2);
|
|
363734
|
+
}
|
|
363735
|
+
/**
|
|
363736
|
+
* Phase 2 of the two-phase replay flow. Applies patches, rebases them against
|
|
363737
|
+
* the generation, saves the lockfile, and commits `[fern-replay]` (or stages
|
|
363738
|
+
* under `stageOnly`). Operates on HEAD at call time — mid-phase commits are
|
|
363739
|
+
* respected.
|
|
363740
|
+
*
|
|
363741
|
+
* For terminal handles (dry-run, first-gen has no patch work, skip-app does
|
|
363742
|
+
* its own post-commit logic), this returns the precomputed report.
|
|
363743
|
+
*/
|
|
363744
|
+
async applyPreparedReplay(prep, options2) {
|
|
363745
|
+
if (prep._prepared.kind === "terminal") {
|
|
363746
|
+
return prep._prepared.preparedReport;
|
|
363747
|
+
}
|
|
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) {
|
|
363764
|
+
case "first-generation":
|
|
363765
|
+
return new FirstGenerationFlow(this);
|
|
363766
|
+
case "no-patches":
|
|
363767
|
+
return new NoPatchesFlow(this);
|
|
363768
|
+
case "normal-regeneration":
|
|
363769
|
+
return new NormalRegenerationFlow(this);
|
|
363770
|
+
case "skip-application":
|
|
363771
|
+
return new SkipApplicationFlow(this);
|
|
363772
|
+
}
|
|
363773
|
+
}
|
|
363774
|
+
/**
|
|
363775
|
+
* Backwards-compatible atomic entry point. Composes `prepareReplay` + `applyPreparedReplay`.
|
|
363776
|
+
* Behavior is byte-identical to the pre-split implementation for all existing callers.
|
|
363777
|
+
*/
|
|
363778
|
+
async runReplay(options2) {
|
|
363779
|
+
const prep = await this.prepareReplay(options2);
|
|
363780
|
+
return this.applyPreparedReplay(prep, options2);
|
|
363781
|
+
}
|
|
363782
|
+
/**
|
|
363783
|
+
* Sync the lockfile after a divergent PR was squash-merged.
|
|
363784
|
+
* Call this BEFORE runReplay() when the CLI detects a merged divergent PR.
|
|
363785
|
+
*
|
|
363786
|
+
* After updating the generation record, re-detects customer patches via
|
|
363787
|
+
* tree diff between the generation tag (pure generation tree) and HEAD
|
|
363788
|
+
* (which includes customer customizations after squash merge). This ensures
|
|
363789
|
+
* patches survive the squash merge → regenerate cycle even when the lockfile
|
|
363790
|
+
* was restored from the base branch during conflict PR creation.
|
|
363791
|
+
*/
|
|
363792
|
+
async syncFromDivergentMerge(generationCommitSha, options2) {
|
|
363793
|
+
const treeHash = await this.git.getTreeHash(generationCommitSha);
|
|
363794
|
+
const timestamp2 = (await this.git.exec(["log", "-1", "--format=%aI", generationCommitSha])).trim();
|
|
363795
|
+
const record23 = {
|
|
363796
|
+
commit_sha: generationCommitSha,
|
|
363797
|
+
tree_hash: treeHash,
|
|
363798
|
+
timestamp: timestamp2,
|
|
363799
|
+
cli_version: options2?.cliVersion ?? "unknown",
|
|
363800
|
+
generator_versions: options2?.generatorVersions ?? {},
|
|
363801
|
+
base_branch_head: options2?.baseBranchHead
|
|
363802
|
+
};
|
|
363803
|
+
let resolvedPatches;
|
|
363804
|
+
if (!this.lockManager.exists()) {
|
|
363805
|
+
this.lockManager.initializeInMemory(record23);
|
|
363806
|
+
} else {
|
|
363807
|
+
this.lockManager.read();
|
|
363808
|
+
const unresolvedPatches = [
|
|
363809
|
+
...this.lockManager.getUnresolvedPatches(),
|
|
363810
|
+
...this.lockManager.getResolvingPatches()
|
|
363811
|
+
];
|
|
363812
|
+
resolvedPatches = this.lockManager.getPatches().filter((p14) => p14.status == null);
|
|
363813
|
+
this.lockManager.addGeneration(record23);
|
|
363814
|
+
this.lockManager.clearPatches();
|
|
363815
|
+
for (const patch5 of unresolvedPatches) {
|
|
363816
|
+
this.lockManager.addPatch(patch5);
|
|
363817
|
+
}
|
|
363818
|
+
}
|
|
363819
|
+
try {
|
|
363820
|
+
const { patches: redetectedPatches } = await this.detector.detectNewPatches();
|
|
363821
|
+
if (redetectedPatches.length > 0) {
|
|
363822
|
+
const redetectedFiles = new Set(redetectedPatches.flatMap((p14) => p14.files));
|
|
363823
|
+
const currentPatches = this.lockManager.getPatches();
|
|
363824
|
+
for (const patch5 of currentPatches) {
|
|
363825
|
+
if (patch5.status != null && patch5.files.some((f4) => redetectedFiles.has(f4))) {
|
|
363826
|
+
this.lockManager.removePatch(patch5.id);
|
|
363827
|
+
}
|
|
363828
|
+
}
|
|
363829
|
+
for (const patch5 of redetectedPatches) {
|
|
363830
|
+
this.lockManager.addPatch(patch5);
|
|
363831
|
+
}
|
|
363832
|
+
}
|
|
363833
|
+
} catch (error50) {
|
|
363834
|
+
for (const patch5 of resolvedPatches ?? []) {
|
|
363835
|
+
this.lockManager.addPatch(patch5);
|
|
363836
|
+
}
|
|
363837
|
+
this.detector.warnings.push(
|
|
363838
|
+
`Patch re-detection failed after divergent merge sync. ${(resolvedPatches ?? []).length} previously resolved patch(es) preserved. Error: ${error50 instanceof Error ? error50.message : String(error50)}`
|
|
363839
|
+
);
|
|
363840
|
+
}
|
|
363841
|
+
this.lockManager.save();
|
|
363842
|
+
this.detector.warnings.push(...this.lockManager.warnings);
|
|
363843
|
+
}
|
|
363844
|
+
determineFlow() {
|
|
363845
|
+
try {
|
|
363846
|
+
const lock = this.lockManager.read();
|
|
363847
|
+
return lock.patches.length === 0 ? "no-patches" : "normal-regeneration";
|
|
363848
|
+
} catch (error50) {
|
|
363849
|
+
if (error50 instanceof LockfileNotFoundError) {
|
|
363850
|
+
return "first-generation";
|
|
363851
|
+
}
|
|
363852
|
+
throw error50;
|
|
363853
|
+
}
|
|
363854
|
+
}
|
|
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,
|
|
@@ -823710,17 +823877,24 @@ var DocsDefinitionResolver = class {
|
|
|
823710
823877
|
this.taskContext.logger.debug("Replacing image paths and URLs in markdown...");
|
|
823711
823878
|
const replaceStart = performance.now();
|
|
823712
823879
|
for (const [relativePath, markdown] of Object.entries(this.parsedDocsConfig.pages)) {
|
|
823713
|
-
|
|
823714
|
-
|
|
823715
|
-
|
|
823716
|
-
|
|
823717
|
-
|
|
823718
|
-
|
|
823719
|
-
|
|
823720
|
-
|
|
823721
|
-
|
|
823722
|
-
|
|
823723
|
-
|
|
823880
|
+
try {
|
|
823881
|
+
this.parsedDocsConfig.pages[RelativeFilePath2.of(relativePath)] = replaceImagePathsAndUrls(
|
|
823882
|
+
markdown,
|
|
823883
|
+
this.collectedFileIds,
|
|
823884
|
+
// convert slugs to full URL pathnames
|
|
823885
|
+
markdownFilesToPathName,
|
|
823886
|
+
{
|
|
823887
|
+
absolutePathToMarkdownFile: this.resolveFilepath(relativePath),
|
|
823888
|
+
absolutePathToFernFolder: this.docsWorkspace.absoluteFilePath
|
|
823889
|
+
},
|
|
823890
|
+
this.taskContext
|
|
823891
|
+
);
|
|
823892
|
+
} catch (error50) {
|
|
823893
|
+
throw new CliError({
|
|
823894
|
+
message: `Failed to replace image paths in markdown file ${relativePath}: ${extractErrorMessage(error50)}`,
|
|
823895
|
+
code: CliError.Code.ParseError
|
|
823896
|
+
});
|
|
823897
|
+
}
|
|
823724
823898
|
}
|
|
823725
823899
|
const replaceTime = performance.now() - replaceStart;
|
|
823726
823900
|
this.taskContext.logger.debug(`Replaced image paths in ${replaceTime.toFixed(0)}ms`);
|
|
@@ -859601,7 +859775,7 @@ var LOCAL_STORAGE_FOLDER4 = ".fern-dev";
|
|
|
859601
859775
|
var LOGS_FOLDER_NAME = "logs";
|
|
859602
859776
|
var MAX_LOGS_DIR_SIZE_BYTES = 100 * 1024 * 1024;
|
|
859603
859777
|
function getCliSource() {
|
|
859604
|
-
const version7 = "5.27.
|
|
859778
|
+
const version7 = "5.27.6";
|
|
859605
859779
|
return `cli@${version7}`;
|
|
859606
859780
|
}
|
|
859607
859781
|
var DebugLogger = class {
|
|
@@ -860693,17 +860867,25 @@ async function getPreviewDocsDefinition({ domain: domain3, project, context: con
|
|
|
860693
860867
|
fileIdsMap.set(filepath, fileId);
|
|
860694
860868
|
}
|
|
860695
860869
|
}
|
|
860696
|
-
|
|
860697
|
-
|
|
860698
|
-
|
|
860699
|
-
|
|
860700
|
-
|
|
860701
|
-
|
|
860702
|
-
|
|
860703
|
-
|
|
860704
|
-
|
|
860705
|
-
|
|
860706
|
-
|
|
860870
|
+
let finalMarkdown;
|
|
860871
|
+
try {
|
|
860872
|
+
finalMarkdown = replaceImagePathsAndUrls(
|
|
860873
|
+
markdownWithAbsPaths,
|
|
860874
|
+
fileIdsMap,
|
|
860875
|
+
{},
|
|
860876
|
+
// markdownFilesToPathName - empty object since we don't need it for images
|
|
860877
|
+
{
|
|
860878
|
+
absolutePathToFernFolder: docsWorkspace.absoluteFilePath,
|
|
860879
|
+
absolutePathToMarkdownFile: absoluteFilePath
|
|
860880
|
+
},
|
|
860881
|
+
context3
|
|
860882
|
+
);
|
|
860883
|
+
} catch (error50) {
|
|
860884
|
+
throw new CliError({
|
|
860885
|
+
message: `Failed to replace image paths in ${absoluteFilePath}: ${extractErrorMessage(error50)}`,
|
|
860886
|
+
code: CliError.Code.ParseError
|
|
860887
|
+
});
|
|
860888
|
+
}
|
|
860707
860889
|
previousDocsDefinition.pages[pageId] = {
|
|
860708
860890
|
markdown: stripMdxComments(finalMarkdown),
|
|
860709
860891
|
editThisPageUrl: previousValue?.editThisPageUrl,
|
|
@@ -873593,7 +873775,7 @@ var LegacyDocsPublisher = class {
|
|
|
873593
873775
|
previewId,
|
|
873594
873776
|
disableTemplates: void 0,
|
|
873595
873777
|
skipUpload,
|
|
873596
|
-
cliVersion: "5.27.
|
|
873778
|
+
cliVersion: "5.27.6",
|
|
873597
873779
|
loginCommand: "fern auth login"
|
|
873598
873780
|
});
|
|
873599
873781
|
if (taskContext.getResult() === TaskResult.Failure) {
|
|
@@ -947998,7 +948180,7 @@ function getAutomationContextFromEnv() {
|
|
|
947998
948180
|
config_branch: process.env.FERN_CONFIG_BRANCH,
|
|
947999
948181
|
config_pr_number: process.env.FERN_CONFIG_PR_NUMBER,
|
|
948000
948182
|
trigger: process.env.GITHUB_EVENT_NAME,
|
|
948001
|
-
cli_version: "5.27.
|
|
948183
|
+
cli_version: "5.27.6"
|
|
948002
948184
|
};
|
|
948003
948185
|
}
|
|
948004
948186
|
function isAutomationMode() {
|
|
@@ -948824,7 +949006,7 @@ var CliContext = class _CliContext {
|
|
|
948824
949006
|
if (false) {
|
|
948825
949007
|
this.logger.error("CLI_VERSION is not defined");
|
|
948826
949008
|
}
|
|
948827
|
-
return "5.27.
|
|
949009
|
+
return "5.27.6";
|
|
948828
949010
|
}
|
|
948829
949011
|
getCliName() {
|
|
948830
949012
|
if (false) {
|