@fern-api/fern-api-dev 5.27.5 → 5.27.7
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 +1680 -1575
- 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.2/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.2/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,81 +361091,608 @@ 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
|
|
361098
|
-
|
|
361099
|
-
|
|
361100
|
-
|
|
361101
|
-
|
|
361102
|
-
|
|
361095
|
+
var BINARY_EXTENSIONS2 = /* @__PURE__ */ new Set([
|
|
361096
|
+
".png",
|
|
361097
|
+
".jpg",
|
|
361098
|
+
".jpeg",
|
|
361099
|
+
".gif",
|
|
361100
|
+
".bmp",
|
|
361101
|
+
".ico",
|
|
361102
|
+
".webp",
|
|
361103
|
+
".svg",
|
|
361104
|
+
".pdf",
|
|
361105
|
+
".doc",
|
|
361106
|
+
".docx",
|
|
361107
|
+
".xls",
|
|
361108
|
+
".xlsx",
|
|
361109
|
+
".ppt",
|
|
361110
|
+
".pptx",
|
|
361111
|
+
".zip",
|
|
361112
|
+
".gz",
|
|
361113
|
+
".tar",
|
|
361114
|
+
".bz2",
|
|
361115
|
+
".7z",
|
|
361116
|
+
".rar",
|
|
361117
|
+
".jar",
|
|
361118
|
+
".war",
|
|
361119
|
+
".ear",
|
|
361120
|
+
".class",
|
|
361121
|
+
".exe",
|
|
361122
|
+
".dll",
|
|
361123
|
+
".so",
|
|
361124
|
+
".dylib",
|
|
361125
|
+
".o",
|
|
361126
|
+
".a",
|
|
361127
|
+
".woff",
|
|
361128
|
+
".woff2",
|
|
361129
|
+
".ttf",
|
|
361130
|
+
".eot",
|
|
361131
|
+
".otf",
|
|
361132
|
+
".mp3",
|
|
361133
|
+
".mp4",
|
|
361134
|
+
".avi",
|
|
361135
|
+
".mov",
|
|
361136
|
+
".wav",
|
|
361137
|
+
".flac",
|
|
361138
|
+
".sqlite",
|
|
361139
|
+
".db",
|
|
361140
|
+
".pyc",
|
|
361141
|
+
".pyo",
|
|
361142
|
+
".DS_Store"
|
|
361143
|
+
]);
|
|
361144
|
+
function isBinaryFile(filePath) {
|
|
361145
|
+
const ext2 = (0, import_node_path210.extname)(filePath).toLowerCase();
|
|
361146
|
+
return BINARY_EXTENSIONS2.has(ext2);
|
|
361103
361147
|
}
|
|
361104
|
-
|
|
361105
|
-
|
|
361106
|
-
|
|
361107
|
-
|
|
361108
|
-
|
|
361109
|
-
|
|
361110
|
-
|
|
361111
|
-
|
|
361112
|
-
|
|
361113
|
-
|
|
361114
|
-
|
|
361115
|
-
|
|
361116
|
-
|
|
361117
|
-
|
|
361118
|
-
|
|
361119
|
-
|
|
361120
|
-
|
|
361121
|
-
|
|
361122
|
-
|
|
361123
|
-
|
|
361148
|
+
var INFRASTRUCTURE_FILES = /* @__PURE__ */ new Set([".fernignore"]);
|
|
361149
|
+
function parsePatchFileHeaders(patchContent) {
|
|
361150
|
+
const results = [];
|
|
361151
|
+
let currentPath = null;
|
|
361152
|
+
let currentIsCreate = false;
|
|
361153
|
+
let currentIsDelete = false;
|
|
361154
|
+
const flush3 = () => {
|
|
361155
|
+
if (currentPath !== null) {
|
|
361156
|
+
results.push({ path: currentPath, isCreate: currentIsCreate, isDelete: currentIsDelete });
|
|
361157
|
+
}
|
|
361158
|
+
};
|
|
361159
|
+
for (const line of patchContent.split("\n")) {
|
|
361160
|
+
if (line.startsWith("diff --git ")) {
|
|
361161
|
+
flush3();
|
|
361162
|
+
currentPath = null;
|
|
361163
|
+
currentIsCreate = false;
|
|
361164
|
+
currentIsDelete = false;
|
|
361165
|
+
const match3 = line.match(/^diff --git a\/(.+?) b\/(.+?)$/);
|
|
361166
|
+
if (match3) {
|
|
361167
|
+
currentPath = match3[2] ?? null;
|
|
361168
|
+
}
|
|
361169
|
+
} else if (currentPath !== null) {
|
|
361170
|
+
if (line.startsWith("new file mode ")) {
|
|
361171
|
+
currentIsCreate = true;
|
|
361172
|
+
} else if (line.startsWith("deleted file mode ")) {
|
|
361173
|
+
currentIsDelete = true;
|
|
361174
|
+
}
|
|
361175
|
+
}
|
|
361176
|
+
}
|
|
361177
|
+
flush3();
|
|
361178
|
+
return results;
|
|
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
|
+
}
|
|
361195
|
+
var ReplayDetector = class {
|
|
361196
|
+
git;
|
|
361197
|
+
lockManager;
|
|
361198
|
+
sdkOutputDir;
|
|
361199
|
+
warnings = [];
|
|
361200
|
+
constructor(git, lockManager, sdkOutputDir) {
|
|
361201
|
+
this.git = git;
|
|
361202
|
+
this.lockManager = lockManager;
|
|
361203
|
+
this.sdkOutputDir = sdkOutputDir;
|
|
361204
|
+
}
|
|
361205
|
+
async detectNewPatches() {
|
|
361206
|
+
const lock = this.lockManager.read();
|
|
361207
|
+
const lastGen = this.getLastGeneration(lock);
|
|
361208
|
+
if (!lastGen) {
|
|
361209
|
+
return { patches: [], revertedPatchIds: [] };
|
|
361210
|
+
}
|
|
361211
|
+
const exists2 = await this.git.commitExists(lastGen.commit_sha);
|
|
361212
|
+
if (!exists2) {
|
|
361213
|
+
this.warnings.push(
|
|
361214
|
+
`Generation commit ${lastGen.commit_sha.slice(0, 7)} not found in git history. Falling back to alternate detection.`
|
|
361215
|
+
);
|
|
361216
|
+
return this.detectPatchesViaTreeDiff(
|
|
361217
|
+
lastGen,
|
|
361218
|
+
/* commitKnownMissing */
|
|
361219
|
+
true
|
|
361220
|
+
);
|
|
361221
|
+
}
|
|
361222
|
+
const isAncestor = await this.git.isAncestor(lastGen.commit_sha, "HEAD");
|
|
361223
|
+
if (!isAncestor) {
|
|
361224
|
+
return this.detectPatchesViaMergeBase(lastGen, lock);
|
|
361225
|
+
}
|
|
361226
|
+
return this.detectPatchesInRange(lastGen.commit_sha, lastGen, lock);
|
|
361227
|
+
}
|
|
361228
|
+
/**
|
|
361229
|
+
* Non-linear-history detection via `merge-base(prevGen, HEAD)`.
|
|
361230
|
+
*
|
|
361231
|
+
* After a squash-merge of a Fern-bot PR, `lastGen.commit_sha` (the previous
|
|
361232
|
+
* `[fern-generated]`) is orphaned but still in git's object database. The
|
|
361233
|
+
* merge-base is the commit on main from which the bot's branch was created —
|
|
361234
|
+
* a reachable ancestor of HEAD. Walking that range and classifying each
|
|
361235
|
+
* commit via `isGenerationCommit` mirrors the linear path and eliminates
|
|
361236
|
+
* the bug class where pipeline-driven changes (autoversion, replay,
|
|
361237
|
+
* generation) get bundled as customer customizations.
|
|
361238
|
+
*
|
|
361239
|
+
* Falls through to the legacy composite path when no common ancestor exists
|
|
361240
|
+
* (truly disjoint history — orphan branches with no shared root).
|
|
361241
|
+
*/
|
|
361242
|
+
async detectPatchesViaMergeBase(lastGen, lock) {
|
|
361243
|
+
let mergeBase = "";
|
|
361244
|
+
try {
|
|
361245
|
+
mergeBase = (await this.git.exec(["merge-base", lastGen.commit_sha, "HEAD"])).trim();
|
|
361246
|
+
} catch {
|
|
361247
|
+
}
|
|
361248
|
+
if (!mergeBase) {
|
|
361249
|
+
this.warnings.push(
|
|
361250
|
+
`No common ancestor between previous generation ${lastGen.commit_sha.slice(0, 7)} and HEAD. Falling back to composite tree-diff detection.`
|
|
361251
|
+
);
|
|
361252
|
+
return this.detectPatchesViaTreeDiff(
|
|
361253
|
+
lastGen,
|
|
361254
|
+
/* commitKnownMissing */
|
|
361255
|
+
false
|
|
361256
|
+
);
|
|
361257
|
+
}
|
|
361258
|
+
return this.detectPatchesInRange(mergeBase, lastGen, lock);
|
|
361259
|
+
}
|
|
361260
|
+
/**
|
|
361261
|
+
* Walk `${rangeStart}..HEAD`, classify each commit, emit one
|
|
361262
|
+
* `StoredPatch` per customer commit. Body shared by the linear path
|
|
361263
|
+
* (rangeStart = lastGen.commit_sha) and the merge-base path.
|
|
361264
|
+
*
|
|
361265
|
+
* Patch `base_generation` is always `lastGen.commit_sha` — the
|
|
361266
|
+
* lockfile-tracked reference the applicator uses to fetch base file
|
|
361267
|
+
* content, regardless of where the walk actually started.
|
|
361268
|
+
*/
|
|
361269
|
+
async detectPatchesInRange(rangeStart, lastGen, lock) {
|
|
361270
|
+
const log4 = await this.git.exec([
|
|
361271
|
+
"log",
|
|
361272
|
+
"--no-merges",
|
|
361273
|
+
"--format=%H%x00%an%x00%ae%x00%s",
|
|
361274
|
+
`${rangeStart}..HEAD`,
|
|
361275
|
+
"--",
|
|
361276
|
+
this.sdkOutputDir
|
|
361277
|
+
]);
|
|
361278
|
+
if (!log4.trim()) {
|
|
361279
|
+
return { patches: [], revertedPatchIds: [] };
|
|
361280
|
+
}
|
|
361281
|
+
const commits = this.parseGitLog(log4);
|
|
361282
|
+
const newPatches = [];
|
|
361283
|
+
const forgottenHashes = new Set(lock.forgotten_hashes ?? []);
|
|
361284
|
+
for (const commit of commits) {
|
|
361285
|
+
if (isGenerationCommit(commit)) {
|
|
361286
|
+
continue;
|
|
361287
|
+
}
|
|
361288
|
+
if (lock.patches.find((p14) => p14.original_commit === commit.sha)) {
|
|
361289
|
+
continue;
|
|
361290
|
+
}
|
|
361291
|
+
const parents = await this.git.getCommitParents(commit.sha);
|
|
361292
|
+
let patchContent;
|
|
361293
|
+
try {
|
|
361294
|
+
patchContent = await this.git.formatPatch(commit.sha);
|
|
361295
|
+
} catch {
|
|
361296
|
+
this.warnings.push(
|
|
361297
|
+
`Could not generate patch for commit ${commit.sha.slice(0, 7)} \u2014 it may be unreachable in a shallow clone. Skipping.`
|
|
361298
|
+
);
|
|
361299
|
+
continue;
|
|
361300
|
+
}
|
|
361301
|
+
let contentHash = this.computeContentHash(patchContent);
|
|
361302
|
+
if (lock.patches.find((p14) => p14.content_hash === contentHash) || forgottenHashes.has(contentHash)) {
|
|
361303
|
+
continue;
|
|
361304
|
+
}
|
|
361305
|
+
const filesOutput = await this.git.exec(["diff-tree", "--no-commit-id", "--name-only", "-r", commit.sha]);
|
|
361306
|
+
const { files, sensitiveFiles } = filterInfrastructureAndSensitiveFiles(filesOutput);
|
|
361307
|
+
if (sensitiveFiles.length > 0) {
|
|
361308
|
+
this.warnings.push(
|
|
361309
|
+
`Sensitive file(s) excluded from patch for commit ${commit.sha.slice(0, 7)}: ${sensitiveFiles.join(", ")}. These files will not be tracked by replay.`
|
|
361310
|
+
);
|
|
361311
|
+
if (files.length > 0) {
|
|
361312
|
+
const parentSha = parents[0];
|
|
361313
|
+
if (parentSha) {
|
|
361314
|
+
try {
|
|
361315
|
+
patchContent = await this.git.exec(["diff", parentSha, commit.sha, "--", ...files]);
|
|
361316
|
+
} catch {
|
|
361317
|
+
continue;
|
|
361318
|
+
}
|
|
361319
|
+
if (!patchContent.trim()) continue;
|
|
361320
|
+
contentHash = this.computeContentHash(patchContent);
|
|
361321
|
+
}
|
|
361124
361322
|
}
|
|
361125
|
-
j7++;
|
|
361126
361323
|
}
|
|
361127
|
-
if (
|
|
361128
|
-
i9++;
|
|
361324
|
+
if (files.length === 0) {
|
|
361129
361325
|
continue;
|
|
361130
361326
|
}
|
|
361327
|
+
const theirsSnapshot = await capturePatchSnapshot(this.git, files, commit.sha);
|
|
361328
|
+
newPatches.push({
|
|
361329
|
+
id: `patch-${commit.sha.slice(0, 8)}`,
|
|
361330
|
+
content_hash: contentHash,
|
|
361331
|
+
original_commit: commit.sha,
|
|
361332
|
+
original_message: commit.message,
|
|
361333
|
+
original_author: `${commit.authorName} <${commit.authorEmail}>`,
|
|
361334
|
+
base_generation: lastGen.commit_sha,
|
|
361335
|
+
files,
|
|
361336
|
+
patch_content: patchContent,
|
|
361337
|
+
...Object.keys(theirsSnapshot).length > 0 ? { theirs_snapshot: theirsSnapshot } : {},
|
|
361338
|
+
...this.isCreationOnlyPatch(patchContent) ? { user_owned: true } : {}
|
|
361339
|
+
});
|
|
361131
361340
|
}
|
|
361132
|
-
|
|
361341
|
+
const survivingPatches = await this.collapseNetZeroFiles(newPatches);
|
|
361342
|
+
survivingPatches.reverse();
|
|
361343
|
+
const revertedPatchIdSet = /* @__PURE__ */ new Set();
|
|
361344
|
+
const revertIndicesToRemove = /* @__PURE__ */ new Set();
|
|
361345
|
+
for (let i9 = 0; i9 < survivingPatches.length; i9++) {
|
|
361346
|
+
const patch5 = survivingPatches[i9];
|
|
361347
|
+
if (!isRevertCommit(patch5.original_message)) continue;
|
|
361348
|
+
let body = "";
|
|
361349
|
+
try {
|
|
361350
|
+
body = await this.git.getCommitBody(patch5.original_commit);
|
|
361351
|
+
} catch {
|
|
361352
|
+
}
|
|
361353
|
+
const revertedSha = parseRevertedSha(body);
|
|
361354
|
+
const revertedMessage = parseRevertedMessage(patch5.original_message);
|
|
361355
|
+
let matchedExisting = false;
|
|
361356
|
+
if (revertedSha) {
|
|
361357
|
+
const existing = lock.patches.find((p14) => p14.original_commit === revertedSha);
|
|
361358
|
+
if (existing) {
|
|
361359
|
+
revertedPatchIdSet.add(existing.id);
|
|
361360
|
+
revertIndicesToRemove.add(i9);
|
|
361361
|
+
matchedExisting = true;
|
|
361362
|
+
}
|
|
361363
|
+
}
|
|
361364
|
+
if (!matchedExisting && revertedMessage) {
|
|
361365
|
+
const existing = lock.patches.find((p14) => p14.original_message === revertedMessage);
|
|
361366
|
+
if (existing) {
|
|
361367
|
+
revertedPatchIdSet.add(existing.id);
|
|
361368
|
+
revertIndicesToRemove.add(i9);
|
|
361369
|
+
matchedExisting = true;
|
|
361370
|
+
}
|
|
361371
|
+
}
|
|
361372
|
+
if (matchedExisting) continue;
|
|
361373
|
+
let matchedNew = false;
|
|
361374
|
+
if (revertedSha) {
|
|
361375
|
+
const idx = survivingPatches.findIndex(
|
|
361376
|
+
(p14, j7) => j7 !== i9 && !revertIndicesToRemove.has(j7) && p14.original_commit === revertedSha
|
|
361377
|
+
);
|
|
361378
|
+
if (idx !== -1) {
|
|
361379
|
+
revertIndicesToRemove.add(i9);
|
|
361380
|
+
revertIndicesToRemove.add(idx);
|
|
361381
|
+
matchedNew = true;
|
|
361382
|
+
}
|
|
361383
|
+
}
|
|
361384
|
+
if (!matchedNew && revertedMessage) {
|
|
361385
|
+
const idx = survivingPatches.findIndex(
|
|
361386
|
+
(p14, j7) => j7 !== i9 && !revertIndicesToRemove.has(j7) && p14.original_message === revertedMessage
|
|
361387
|
+
);
|
|
361388
|
+
if (idx !== -1) {
|
|
361389
|
+
revertIndicesToRemove.add(i9);
|
|
361390
|
+
revertIndicesToRemove.add(idx);
|
|
361391
|
+
}
|
|
361392
|
+
}
|
|
361393
|
+
if (!matchedExisting && !matchedNew) {
|
|
361394
|
+
revertIndicesToRemove.add(i9);
|
|
361395
|
+
}
|
|
361396
|
+
}
|
|
361397
|
+
const filteredPatches = survivingPatches.filter((_7, i9) => !revertIndicesToRemove.has(i9));
|
|
361398
|
+
return { patches: filteredPatches, revertedPatchIds: [...revertedPatchIdSet] };
|
|
361133
361399
|
}
|
|
361134
|
-
|
|
361135
|
-
|
|
361136
|
-
|
|
361137
|
-
|
|
361138
|
-
|
|
361139
|
-
|
|
361140
|
-
|
|
361400
|
+
/**
|
|
361401
|
+
* Compute content hash for deduplication.
|
|
361402
|
+
*
|
|
361403
|
+
* Produces a format-agnostic hash so both `git format-patch` output
|
|
361404
|
+
* (email-wrapped) and plain `git diff` output hash to the same value
|
|
361405
|
+
* when the underlying diff hunks are identical.
|
|
361406
|
+
*
|
|
361407
|
+
* Normalization:
|
|
361408
|
+
* 1. Strip email wrapper: everything before the first `diff --git` line
|
|
361409
|
+
* (From:, Subject:, Date:, diffstat, blank separators).
|
|
361410
|
+
* 2. Strip `index` lines (blob SHA pairs change across rebases).
|
|
361411
|
+
* 3. Strip trailing git version marker (`-- \n<version>`).
|
|
361412
|
+
*
|
|
361413
|
+
* This ensures content hashes match across the no-patches and normal-
|
|
361414
|
+
* regeneration flows, which store formatPatch and git-diff formats
|
|
361415
|
+
* respectively.
|
|
361416
|
+
*/
|
|
361417
|
+
computeContentHash(patchContent) {
|
|
361418
|
+
const lines = patchContent.split("\n");
|
|
361419
|
+
const diffStart = lines.findIndex((l9) => l9.startsWith("diff --git "));
|
|
361420
|
+
const relevantLines = diffStart > 0 ? lines.slice(diffStart) : lines;
|
|
361421
|
+
const normalized = relevantLines.filter((line) => !line.startsWith("index ")).join("\n").replace(/\n-- \n[\d]+\.[\d]+[^\n]*\s*$/, "");
|
|
361422
|
+
return `sha256:${(0, import_node_crypto7.createHash)("sha256").update(normalized).digest("hex")}`;
|
|
361141
361423
|
}
|
|
361142
|
-
|
|
361143
|
-
|
|
361144
|
-
|
|
361145
|
-
|
|
361146
|
-
|
|
361147
|
-
|
|
361424
|
+
/**
|
|
361425
|
+
* Drop patches whose files were transiently created and then deleted
|
|
361426
|
+
* within the current linear detection window, and are absent from
|
|
361427
|
+
* HEAD at the end of the window.
|
|
361428
|
+
*
|
|
361429
|
+
* Rationale: each commit becomes its own patch, so a file that was
|
|
361430
|
+
* briefly added and later removed survives as a create+delete pair whose
|
|
361431
|
+
* cumulative diff is empty. We drop such patches before they reach the
|
|
361432
|
+
* lockfile.
|
|
361433
|
+
*
|
|
361434
|
+
* A file is "transient" when:
|
|
361435
|
+
* 1. some patch in the window sets `new file mode` for it (a creation), AND
|
|
361436
|
+
* 2. some patch in the window sets `deleted file mode` for it (a deletion), AND
|
|
361437
|
+
* 3. it is absent from HEAD's tree.
|
|
361438
|
+
*
|
|
361439
|
+
* The HEAD guard (#3) prevents false positives when a file is deleted
|
|
361440
|
+
* and then recreated with different content — the cumulative diff of
|
|
361441
|
+
* such a pair is non-empty and must be preserved.
|
|
361442
|
+
*
|
|
361443
|
+
* This only drops patches whose `files` are a subset of the transient
|
|
361444
|
+
* set. A partial-transient patch (one that touches a transient file
|
|
361445
|
+
* alongside a persistent one) is left unmodified; surgical stripping of
|
|
361446
|
+
* single files from a multi-file patch would require a follow-up that
|
|
361447
|
+
* regenerates the patch content via `git format-patch -- <files>`. No
|
|
361448
|
+
* current failing test exercises this, and leaving the patch intact
|
|
361449
|
+
* preserves today's behaviour. Revisit if a future adversarial test
|
|
361450
|
+
* demands per-file stripping.
|
|
361451
|
+
*/
|
|
361452
|
+
async collapseNetZeroFiles(patches) {
|
|
361453
|
+
if (patches.length === 0) return patches;
|
|
361454
|
+
const stateByFile = /* @__PURE__ */ new Map();
|
|
361455
|
+
for (const patch5 of patches) {
|
|
361456
|
+
for (const header of parsePatchFileHeaders(patch5.patch_content)) {
|
|
361457
|
+
if (!header.isCreate && !header.isDelete) continue;
|
|
361458
|
+
const prior = stateByFile.get(header.path) ?? { created: false, deleted: false };
|
|
361459
|
+
if (header.isCreate) prior.created = true;
|
|
361460
|
+
if (header.isDelete) prior.deleted = true;
|
|
361461
|
+
stateByFile.set(header.path, prior);
|
|
361462
|
+
}
|
|
361463
|
+
}
|
|
361464
|
+
let anyCandidate = false;
|
|
361465
|
+
for (const state of stateByFile.values()) {
|
|
361466
|
+
if (state.created && state.deleted) {
|
|
361467
|
+
anyCandidate = true;
|
|
361468
|
+
break;
|
|
361469
|
+
}
|
|
361470
|
+
}
|
|
361471
|
+
if (!anyCandidate) return patches;
|
|
361472
|
+
const headFiles = await this.listHeadFiles();
|
|
361473
|
+
const transient = /* @__PURE__ */ new Set();
|
|
361474
|
+
for (const [file4, state] of stateByFile) {
|
|
361475
|
+
if (state.created && state.deleted && !headFiles.has(file4)) {
|
|
361476
|
+
transient.add(file4);
|
|
361477
|
+
}
|
|
361478
|
+
}
|
|
361479
|
+
if (transient.size === 0) return patches;
|
|
361480
|
+
return patches.filter((patch5) => !patch5.files.every((f4) => transient.has(f4)));
|
|
361481
|
+
}
|
|
361482
|
+
/**
|
|
361483
|
+
* List every tracked path at HEAD (one `git ls-tree -r` invocation).
|
|
361484
|
+
* Returns an empty Set if HEAD is unborn or the invocation fails — the
|
|
361485
|
+
* caller then treats every candidate as "not in HEAD" and may collapse
|
|
361486
|
+
* more aggressively. On typical SDK repos this is a single sub-10ms call.
|
|
361487
|
+
*/
|
|
361488
|
+
async listHeadFiles() {
|
|
361489
|
+
try {
|
|
361490
|
+
const out = await this.git.exec(["ls-tree", "-r", "--name-only", "HEAD"]);
|
|
361491
|
+
return new Set(out.split("\n").map((l9) => l9.trim()).filter(Boolean));
|
|
361492
|
+
} catch {
|
|
361493
|
+
return /* @__PURE__ */ new Set();
|
|
361494
|
+
}
|
|
361495
|
+
}
|
|
361496
|
+
/**
|
|
361497
|
+
* Detect patches via tree diff for non-linear history. Returns a composite patch.
|
|
361498
|
+
* Revert reconciliation is skipped here because tree-diff produces a single composite
|
|
361499
|
+
* patch from the aggregate diff — individual revert commits are not distinguishable.
|
|
361500
|
+
*/
|
|
361501
|
+
async detectPatchesViaTreeDiff(lastGen, commitKnownMissing) {
|
|
361502
|
+
const diffBase = await this.resolveDiffBase(lastGen, commitKnownMissing);
|
|
361503
|
+
if (!diffBase) {
|
|
361504
|
+
return this.detectPatchesViaCommitScan();
|
|
361505
|
+
}
|
|
361506
|
+
const lockForGuard = this.lockManager.read();
|
|
361507
|
+
const knownGenerations = new Set(lockForGuard.generations.map((g8) => g8.commit_sha));
|
|
361508
|
+
let resolvedBaseGeneration = commitKnownMissing ? diffBase : lastGen.commit_sha;
|
|
361509
|
+
if (commitKnownMissing && !knownGenerations.has(diffBase)) {
|
|
361510
|
+
const fallback2 = lockForGuard.current_generation ?? lockForGuard.generations[lockForGuard.generations.length - 1]?.commit_sha;
|
|
361511
|
+
if (!fallback2) {
|
|
361512
|
+
this.warnings.push(
|
|
361513
|
+
`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.`
|
|
361514
|
+
);
|
|
361515
|
+
return { patches: [], revertedPatchIds: [] };
|
|
361516
|
+
}
|
|
361517
|
+
resolvedBaseGeneration = fallback2;
|
|
361518
|
+
}
|
|
361519
|
+
const filesOutput = await this.git.exec(["diff", "--name-only", diffBase, "HEAD"]);
|
|
361520
|
+
const { files, sensitiveFiles } = filterInfrastructureAndSensitiveFiles(filesOutput);
|
|
361521
|
+
if (sensitiveFiles.length > 0) {
|
|
361522
|
+
this.warnings.push(
|
|
361523
|
+
`Sensitive file(s) excluded from composite patch: ${sensitiveFiles.join(", ")}. These files will not be tracked by replay.`
|
|
361524
|
+
);
|
|
361525
|
+
}
|
|
361526
|
+
if (files.length === 0) return { patches: [], revertedPatchIds: [] };
|
|
361527
|
+
const diff2 = await this.git.exec(["diff", diffBase, "HEAD", "--", ...files]);
|
|
361528
|
+
if (!diff2.trim()) return { patches: [], revertedPatchIds: [] };
|
|
361529
|
+
const contentHash = this.computeContentHash(diff2);
|
|
361530
|
+
const lock = this.lockManager.read();
|
|
361531
|
+
if (lock.patches.some((p14) => p14.content_hash === contentHash) || (lock.forgotten_hashes ?? []).includes(contentHash)) {
|
|
361532
|
+
return { patches: [], revertedPatchIds: [] };
|
|
361533
|
+
}
|
|
361534
|
+
const headSha = (await this.git.exec(["rev-parse", "HEAD"])).trim();
|
|
361535
|
+
const theirsSnapshot = await capturePatchSnapshot(this.git, files, "HEAD");
|
|
361536
|
+
const compositePatch = {
|
|
361537
|
+
id: `patch-composite-${headSha.slice(0, 8)}`,
|
|
361538
|
+
content_hash: contentHash,
|
|
361539
|
+
original_commit: headSha,
|
|
361540
|
+
original_message: "Customer customizations (composite)",
|
|
361541
|
+
original_author: "composite",
|
|
361542
|
+
// Anchor `base_generation` on a SHA the applicator's
|
|
361543
|
+
// `resolveBaseGeneration` can find — always a member of
|
|
361544
|
+
// `lock.generations`. When `diffBase` is a recorded
|
|
361545
|
+
// generation, use it directly; otherwise the guard above
|
|
361546
|
+
// has already mapped it onto `current_generation`.
|
|
361547
|
+
base_generation: resolvedBaseGeneration,
|
|
361548
|
+
files,
|
|
361549
|
+
patch_content: diff2,
|
|
361550
|
+
...Object.keys(theirsSnapshot).length > 0 ? { theirs_snapshot: theirsSnapshot } : {},
|
|
361551
|
+
...this.isCreationOnlyPatch(diff2) ? { user_owned: true } : {}
|
|
361552
|
+
};
|
|
361553
|
+
return { patches: [compositePatch], revertedPatchIds: [] };
|
|
361554
|
+
}
|
|
361555
|
+
/**
|
|
361556
|
+
* Last-resort detection when both generation commit and tree are unreachable.
|
|
361557
|
+
* Scans all commits from HEAD, filters against known lockfile patches, and
|
|
361558
|
+
* skips creation-only commits (squashed history after force push).
|
|
361559
|
+
*
|
|
361560
|
+
* Detected patches use the commit's parent as base_generation so the applicator
|
|
361561
|
+
* can find base file content from the parent's tree (which IS reachable).
|
|
361562
|
+
*/
|
|
361563
|
+
async detectPatchesViaCommitScan() {
|
|
361564
|
+
const lock = this.lockManager.read();
|
|
361565
|
+
const knownGenerations = new Set(lock.generations.map((g8) => g8.commit_sha));
|
|
361566
|
+
const fallbackAnchor = lock.current_generation ?? lock.generations[lock.generations.length - 1]?.commit_sha;
|
|
361567
|
+
const log4 = await this.git.exec([
|
|
361568
|
+
"log",
|
|
361569
|
+
"--max-count=200",
|
|
361570
|
+
"--format=%H%x00%an%x00%ae%x00%s",
|
|
361571
|
+
"HEAD",
|
|
361572
|
+
"--",
|
|
361573
|
+
this.sdkOutputDir
|
|
361574
|
+
]);
|
|
361575
|
+
if (!log4.trim()) {
|
|
361576
|
+
return { patches: [], revertedPatchIds: [] };
|
|
361577
|
+
}
|
|
361578
|
+
const commits = this.parseGitLog(log4);
|
|
361579
|
+
const newPatches = [];
|
|
361580
|
+
const forgottenHashes = new Set(lock.forgotten_hashes ?? []);
|
|
361581
|
+
const existingHashes = new Set(lock.patches.map((p14) => p14.content_hash));
|
|
361582
|
+
const existingCommits = new Set(lock.patches.map((p14) => p14.original_commit));
|
|
361583
|
+
for (const commit of commits) {
|
|
361584
|
+
if (isGenerationCommit(commit)) {
|
|
361148
361585
|
continue;
|
|
361149
361586
|
}
|
|
361150
|
-
|
|
361151
|
-
|
|
361587
|
+
const parents = await this.git.getCommitParents(commit.sha);
|
|
361588
|
+
if (parents.length > 1) {
|
|
361152
361589
|
continue;
|
|
361153
361590
|
}
|
|
361154
|
-
if (
|
|
361591
|
+
if (existingCommits.has(commit.sha)) {
|
|
361155
361592
|
continue;
|
|
361156
361593
|
}
|
|
361157
|
-
|
|
361594
|
+
let patchContent;
|
|
361595
|
+
try {
|
|
361596
|
+
patchContent = await this.git.formatPatch(commit.sha);
|
|
361597
|
+
} catch {
|
|
361158
361598
|
continue;
|
|
361159
361599
|
}
|
|
361160
|
-
|
|
361161
|
-
|
|
361600
|
+
let contentHash = this.computeContentHash(patchContent);
|
|
361601
|
+
if (existingHashes.has(contentHash) || forgottenHashes.has(contentHash)) {
|
|
361602
|
+
continue;
|
|
361603
|
+
}
|
|
361604
|
+
if (this.isCreationOnlyPatch(patchContent)) {
|
|
361605
|
+
continue;
|
|
361606
|
+
}
|
|
361607
|
+
const filesOutput = await this.git.exec(["diff-tree", "--no-commit-id", "--name-only", "-r", commit.sha]);
|
|
361608
|
+
const { files, sensitiveFiles } = filterInfrastructureAndSensitiveFiles(filesOutput);
|
|
361609
|
+
if (sensitiveFiles.length > 0) {
|
|
361610
|
+
this.warnings.push(
|
|
361611
|
+
`Sensitive file(s) excluded from patch for commit ${commit.sha.slice(0, 7)}: ${sensitiveFiles.join(", ")}. These files will not be tracked by replay.`
|
|
361612
|
+
);
|
|
361613
|
+
if (files.length > 0) {
|
|
361614
|
+
if (parents.length === 0) {
|
|
361615
|
+
continue;
|
|
361616
|
+
}
|
|
361617
|
+
try {
|
|
361618
|
+
patchContent = await this.git.exec(["diff", parents[0], commit.sha, "--", ...files]);
|
|
361619
|
+
} catch {
|
|
361620
|
+
continue;
|
|
361621
|
+
}
|
|
361622
|
+
if (!patchContent.trim()) continue;
|
|
361623
|
+
contentHash = this.computeContentHash(patchContent);
|
|
361624
|
+
}
|
|
361625
|
+
}
|
|
361626
|
+
if (files.length === 0) {
|
|
361162
361627
|
continue;
|
|
361163
361628
|
}
|
|
361629
|
+
if (parents.length === 0) {
|
|
361630
|
+
continue;
|
|
361631
|
+
}
|
|
361632
|
+
const parentSha = parents[0];
|
|
361633
|
+
let baseGeneration;
|
|
361634
|
+
if (knownGenerations.has(parentSha)) {
|
|
361635
|
+
baseGeneration = parentSha;
|
|
361636
|
+
} else if (fallbackAnchor) {
|
|
361637
|
+
baseGeneration = fallbackAnchor;
|
|
361638
|
+
} else {
|
|
361639
|
+
this.warnings.push(
|
|
361640
|
+
`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.`
|
|
361641
|
+
);
|
|
361642
|
+
continue;
|
|
361643
|
+
}
|
|
361644
|
+
const theirsSnapshot = await capturePatchSnapshot(this.git, files, commit.sha);
|
|
361645
|
+
newPatches.push({
|
|
361646
|
+
id: `patch-${commit.sha.slice(0, 8)}`,
|
|
361647
|
+
content_hash: contentHash,
|
|
361648
|
+
original_commit: commit.sha,
|
|
361649
|
+
original_message: commit.message,
|
|
361650
|
+
original_author: `${commit.authorName} <${commit.authorEmail}>`,
|
|
361651
|
+
base_generation: baseGeneration,
|
|
361652
|
+
files,
|
|
361653
|
+
patch_content: patchContent,
|
|
361654
|
+
...Object.keys(theirsSnapshot).length > 0 ? { theirs_snapshot: theirsSnapshot } : {}
|
|
361655
|
+
});
|
|
361164
361656
|
}
|
|
361165
|
-
|
|
361657
|
+
newPatches.reverse();
|
|
361658
|
+
return { patches: newPatches, revertedPatchIds: [] };
|
|
361166
361659
|
}
|
|
361167
|
-
|
|
361168
|
-
|
|
361660
|
+
/**
|
|
361661
|
+
* Check if a format-patch consists entirely of new-file creations.
|
|
361662
|
+
* Used to identify squashed commits after force push, which create all files
|
|
361663
|
+
* from scratch (--- /dev/null) rather than modifying existing files.
|
|
361664
|
+
*/
|
|
361665
|
+
isCreationOnlyPatch(patchContent) {
|
|
361666
|
+
const diffOldHeaders = patchContent.split("\n").filter((l9) => l9.startsWith("--- "));
|
|
361667
|
+
if (diffOldHeaders.length === 0) {
|
|
361668
|
+
return false;
|
|
361669
|
+
}
|
|
361670
|
+
return diffOldHeaders.every((l9) => l9 === "--- /dev/null");
|
|
361671
|
+
}
|
|
361672
|
+
/**
|
|
361673
|
+
* Resolve the best available diff base for a generation record.
|
|
361674
|
+
* Prefers commit_sha, falls back to tree_hash for unreachable commits.
|
|
361675
|
+
* When commitKnownMissing is true, skips the redundant commitExists check.
|
|
361676
|
+
*/
|
|
361677
|
+
async resolveDiffBase(gen, commitKnownMissing) {
|
|
361678
|
+
if (!commitKnownMissing && await this.git.commitExists(gen.commit_sha)) {
|
|
361679
|
+
return gen.commit_sha;
|
|
361680
|
+
}
|
|
361681
|
+
if (await this.git.treeExists(gen.tree_hash)) {
|
|
361682
|
+
return gen.tree_hash;
|
|
361683
|
+
}
|
|
361684
|
+
return null;
|
|
361685
|
+
}
|
|
361686
|
+
parseGitLog(log4) {
|
|
361687
|
+
return log4.trim().split("\n").map((line) => {
|
|
361688
|
+
const [sha, authorName, authorEmail, message] = line.split("\0");
|
|
361689
|
+
return { sha, authorName, authorEmail, message };
|
|
361690
|
+
});
|
|
361691
|
+
}
|
|
361692
|
+
getLastGeneration(lock) {
|
|
361693
|
+
return lock.generations.find((g8) => g8.commit_sha === lock.current_generation);
|
|
361694
|
+
}
|
|
361695
|
+
};
|
|
361169
361696
|
var import_node_diff3 = require_diff3();
|
|
361170
361697
|
function threeWayMerge(base4, ours, theirs) {
|
|
361171
361698
|
const baseLines = base4.split("\n");
|
|
@@ -361303,95 +361830,414 @@ var require_dist14 = __commonJS({
|
|
|
361303
361830
|
}
|
|
361304
361831
|
return result;
|
|
361305
361832
|
}
|
|
361306
|
-
var
|
|
361307
|
-
|
|
361308
|
-
|
|
361309
|
-
|
|
361310
|
-
|
|
361311
|
-
|
|
361312
|
-
|
|
361313
|
-
|
|
361314
|
-
|
|
361315
|
-
|
|
361316
|
-
|
|
361317
|
-
|
|
361318
|
-
|
|
361319
|
-
|
|
361320
|
-
|
|
361321
|
-
|
|
361322
|
-
|
|
361323
|
-
|
|
361324
|
-
|
|
361325
|
-
|
|
361326
|
-
|
|
361327
|
-
|
|
361328
|
-
|
|
361329
|
-
|
|
361330
|
-
|
|
361331
|
-
|
|
361332
|
-
|
|
361333
|
-
|
|
361334
|
-
|
|
361335
|
-
|
|
361336
|
-
|
|
361337
|
-
|
|
361338
|
-
|
|
361339
|
-
|
|
361340
|
-
|
|
361341
|
-
|
|
361342
|
-
|
|
361343
|
-
|
|
361344
|
-
|
|
361345
|
-
|
|
361346
|
-
|
|
361347
|
-
|
|
361348
|
-
|
|
361349
|
-
|
|
361350
|
-
|
|
361351
|
-
|
|
361352
|
-
|
|
361353
|
-
|
|
361354
|
-
|
|
361833
|
+
var import_promises310 = require("fs/promises");
|
|
361834
|
+
var import_node_os15 = require("os");
|
|
361835
|
+
var import_node_path52 = require("path");
|
|
361836
|
+
var import_minimatch8 = require_commonjs3();
|
|
361837
|
+
var MergeAccumulator = class {
|
|
361838
|
+
constructor(mode, sharedFiles) {
|
|
361839
|
+
this.mode = mode;
|
|
361840
|
+
this.sharedFiles = sharedFiles;
|
|
361841
|
+
}
|
|
361842
|
+
entries = /* @__PURE__ */ new Map();
|
|
361843
|
+
/** Was this file touched by 2+ patches in the current `applyPatches` invocation? */
|
|
361844
|
+
isShared(path111) {
|
|
361845
|
+
return this.sharedFiles.has(path111);
|
|
361846
|
+
}
|
|
361847
|
+
/** Has any prior patch already recorded a result for this path? */
|
|
361848
|
+
has(path111) {
|
|
361849
|
+
return this.entries.has(path111);
|
|
361850
|
+
}
|
|
361851
|
+
/** Look up the accumulated content + base generation for this path. */
|
|
361852
|
+
lookup(path111) {
|
|
361853
|
+
return this.entries.get(path111);
|
|
361854
|
+
}
|
|
361855
|
+
/**
|
|
361856
|
+
* True if any of the patch's files have a prior accumulator entry — in
|
|
361857
|
+
* which case the fast path (`git apply --3way`) cannot honor prior
|
|
361858
|
+
* patches' contributions and we must route through the slow path.
|
|
361859
|
+
*/
|
|
361860
|
+
shouldSkipFastPath(patch5) {
|
|
361861
|
+
return patch5.files.some((f4) => this.entries.has(f4));
|
|
361862
|
+
}
|
|
361863
|
+
/** Record a successful (clean) merge result. */
|
|
361864
|
+
recordMerge(path111, content5, baseGeneration) {
|
|
361865
|
+
this.entries.set(path111, { content: content5, baseGeneration });
|
|
361866
|
+
}
|
|
361867
|
+
/**
|
|
361868
|
+
* Record a conflicted merge result. Populates only in `"resolve"` mode
|
|
361869
|
+
* — replay mode keeps the accumulator clean of marker-laden content
|
|
361870
|
+
* since markers will be stripped from disk after the apply loop.
|
|
361871
|
+
*/
|
|
361872
|
+
recordConflict(path111, content5, baseGeneration) {
|
|
361873
|
+
if (this.mode === "resolve") {
|
|
361874
|
+
this.entries.set(path111, { content: content5, baseGeneration });
|
|
361875
|
+
}
|
|
361876
|
+
}
|
|
361877
|
+
/**
|
|
361878
|
+
* Defensive copy for test introspection. Used by invariant I2 to assert
|
|
361879
|
+
* that after every applied patch, the accumulator has an entry for each
|
|
361880
|
+
* file the patch touched (see `__tests__/invariants/i2-accumulator-correctness.ts`).
|
|
361881
|
+
*
|
|
361882
|
+
* @internal
|
|
361883
|
+
*/
|
|
361884
|
+
snapshot() {
|
|
361885
|
+
const copy = /* @__PURE__ */ new Map();
|
|
361886
|
+
for (const [path111, entry] of this.entries) {
|
|
361887
|
+
copy.set(path111, { content: entry.content, baseGeneration: entry.baseGeneration });
|
|
361888
|
+
}
|
|
361889
|
+
return copy;
|
|
361890
|
+
}
|
|
361891
|
+
};
|
|
361892
|
+
var import_promises195 = require("fs/promises");
|
|
361893
|
+
var import_node_path310 = require("path");
|
|
361894
|
+
async function resolveInputs(args) {
|
|
361895
|
+
const {
|
|
361896
|
+
patch: patch5,
|
|
361897
|
+
filePath,
|
|
361898
|
+
git,
|
|
361899
|
+
lockManager,
|
|
361900
|
+
outputDir,
|
|
361901
|
+
isTreeReachable,
|
|
361902
|
+
resolveBaseGeneration,
|
|
361903
|
+
resolveFilePath,
|
|
361904
|
+
extractRenameSource,
|
|
361905
|
+
extractFileDiff: extractFileDiff2
|
|
361906
|
+
} = args;
|
|
361907
|
+
const baseGen = await resolveBaseGeneration(patch5.base_generation);
|
|
361908
|
+
if (!baseGen) {
|
|
361909
|
+
return {
|
|
361910
|
+
resolvedPath: filePath,
|
|
361911
|
+
metadata: {
|
|
361912
|
+
patchId: patch5.id,
|
|
361913
|
+
patchMessage: patch5.original_message,
|
|
361914
|
+
baseGeneration: patch5.base_generation,
|
|
361915
|
+
currentGeneration: lockManager.read().current_generation
|
|
361916
|
+
},
|
|
361917
|
+
base: null,
|
|
361918
|
+
ours: null,
|
|
361919
|
+
oursPath: (0, import_node_path310.join)(outputDir, filePath),
|
|
361920
|
+
renameSourcePath: void 0,
|
|
361921
|
+
ghostTheirs: null,
|
|
361922
|
+
earlyExit: { status: "skipped", reason: "base-generation-not-found" }
|
|
361923
|
+
};
|
|
361924
|
+
}
|
|
361925
|
+
const lock = lockManager.read();
|
|
361926
|
+
const currentGen = lock.generations.find((g8) => g8.commit_sha === lock.current_generation);
|
|
361927
|
+
const currentTreeHash = currentGen?.tree_hash ?? baseGen.tree_hash;
|
|
361928
|
+
const resolvedPath = await resolveFilePath(filePath, baseGen.tree_hash, currentTreeHash);
|
|
361929
|
+
const metadata = {
|
|
361930
|
+
patchId: patch5.id,
|
|
361931
|
+
patchMessage: patch5.original_message,
|
|
361932
|
+
baseGeneration: patch5.base_generation,
|
|
361933
|
+
currentGeneration: lock.current_generation
|
|
361934
|
+
};
|
|
361935
|
+
let base4 = await git.showFile(baseGen.tree_hash, filePath);
|
|
361936
|
+
let renameSourcePath;
|
|
361937
|
+
if (!base4) {
|
|
361938
|
+
const renameSource = extractRenameSource(patch5.patch_content, filePath);
|
|
361939
|
+
if (renameSource) {
|
|
361940
|
+
base4 = await git.showFile(baseGen.tree_hash, renameSource);
|
|
361941
|
+
renameSourcePath = renameSource;
|
|
361942
|
+
}
|
|
361943
|
+
}
|
|
361944
|
+
const oursPath = (0, import_node_path310.join)(outputDir, resolvedPath);
|
|
361945
|
+
const ours = await (0, import_promises195.readFile)(oursPath, "utf-8").catch(() => null);
|
|
361946
|
+
let ghostTheirs = null;
|
|
361947
|
+
if (!base4 && ours && !renameSourcePath) {
|
|
361948
|
+
const treeReachable = await isTreeReachable(baseGen.tree_hash);
|
|
361949
|
+
if (!treeReachable) {
|
|
361950
|
+
const fileDiff = extractFileDiff2(patch5.patch_content, filePath);
|
|
361951
|
+
if (fileDiff) {
|
|
361952
|
+
const { reconstructFromGhostPatch: reconstructFromGhostPatch2 } = await Promise.resolve().then(() => (init_HybridReconstruction(), HybridReconstruction_exports));
|
|
361953
|
+
const result = reconstructFromGhostPatch2(fileDiff, ours);
|
|
361954
|
+
if (result) {
|
|
361955
|
+
base4 = result.base;
|
|
361956
|
+
ghostTheirs = result.theirs;
|
|
361957
|
+
}
|
|
361958
|
+
}
|
|
361959
|
+
}
|
|
361960
|
+
}
|
|
361961
|
+
return {
|
|
361962
|
+
resolvedPath,
|
|
361963
|
+
metadata,
|
|
361964
|
+
base: base4,
|
|
361965
|
+
ours,
|
|
361966
|
+
oursPath,
|
|
361967
|
+
renameSourcePath,
|
|
361968
|
+
ghostTheirs
|
|
361969
|
+
};
|
|
361970
|
+
}
|
|
361971
|
+
var CONFLICT_OPENER = "<<<<<<< Generated";
|
|
361972
|
+
var CONFLICT_SEPARATOR = "=======";
|
|
361973
|
+
var CONFLICT_CLOSER = ">>>>>>> Your customization";
|
|
361974
|
+
function trimCR(line) {
|
|
361975
|
+
return line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
361976
|
+
}
|
|
361977
|
+
function findConflictRanges(lines) {
|
|
361978
|
+
const ranges = [];
|
|
361979
|
+
let i9 = 0;
|
|
361980
|
+
while (i9 < lines.length) {
|
|
361981
|
+
if (trimCR(lines[i9]) === CONFLICT_OPENER) {
|
|
361982
|
+
let separatorIdx = -1;
|
|
361983
|
+
let j7 = i9 + 1;
|
|
361984
|
+
let found = false;
|
|
361985
|
+
while (j7 < lines.length) {
|
|
361986
|
+
const trimmed2 = trimCR(lines[j7]);
|
|
361987
|
+
if (trimmed2 === CONFLICT_OPENER) {
|
|
361988
|
+
break;
|
|
361989
|
+
}
|
|
361990
|
+
if (separatorIdx === -1 && trimmed2 === CONFLICT_SEPARATOR) {
|
|
361991
|
+
separatorIdx = j7;
|
|
361992
|
+
} else if (separatorIdx !== -1 && trimmed2 === CONFLICT_CLOSER) {
|
|
361993
|
+
ranges.push({ start: i9, separator: separatorIdx, end: j7 });
|
|
361994
|
+
i9 = j7;
|
|
361995
|
+
found = true;
|
|
361996
|
+
break;
|
|
361997
|
+
}
|
|
361998
|
+
j7++;
|
|
361999
|
+
}
|
|
362000
|
+
if (!found) {
|
|
362001
|
+
i9++;
|
|
362002
|
+
continue;
|
|
362003
|
+
}
|
|
362004
|
+
}
|
|
362005
|
+
i9++;
|
|
362006
|
+
}
|
|
362007
|
+
return ranges;
|
|
362008
|
+
}
|
|
362009
|
+
function stripConflictMarkers(content5) {
|
|
362010
|
+
const lines = content5.split(/\r?\n/);
|
|
362011
|
+
const ranges = findConflictRanges(lines);
|
|
362012
|
+
if (ranges.length === 0) {
|
|
362013
|
+
return content5;
|
|
362014
|
+
}
|
|
362015
|
+
const result = [];
|
|
362016
|
+
let rangeIdx = 0;
|
|
362017
|
+
for (let i9 = 0; i9 < lines.length; i9++) {
|
|
362018
|
+
if (rangeIdx < ranges.length) {
|
|
362019
|
+
const range2 = ranges[rangeIdx];
|
|
362020
|
+
if (i9 === range2.start) {
|
|
362021
|
+
continue;
|
|
362022
|
+
}
|
|
362023
|
+
if (i9 > range2.start && i9 < range2.separator) {
|
|
362024
|
+
result.push(lines[i9]);
|
|
362025
|
+
continue;
|
|
362026
|
+
}
|
|
362027
|
+
if (i9 === range2.separator) {
|
|
362028
|
+
continue;
|
|
362029
|
+
}
|
|
362030
|
+
if (i9 > range2.separator && i9 < range2.end) {
|
|
362031
|
+
continue;
|
|
362032
|
+
}
|
|
362033
|
+
if (i9 === range2.end) {
|
|
362034
|
+
rangeIdx++;
|
|
362035
|
+
continue;
|
|
362036
|
+
}
|
|
362037
|
+
}
|
|
362038
|
+
result.push(lines[i9]);
|
|
362039
|
+
}
|
|
362040
|
+
return result.join("\n");
|
|
362041
|
+
}
|
|
362042
|
+
function hasConflictMarkerStrings(content5) {
|
|
362043
|
+
if (content5 == null) return false;
|
|
362044
|
+
return content5.includes(CONFLICT_OPENER) || content5.includes(CONFLICT_CLOSER);
|
|
362045
|
+
}
|
|
362046
|
+
async function buildMergePlan(args) {
|
|
362047
|
+
const {
|
|
362048
|
+
inputs,
|
|
362049
|
+
patch: patch5,
|
|
362050
|
+
filePath,
|
|
362051
|
+
accumulator,
|
|
362052
|
+
tempGit,
|
|
362053
|
+
tempDir: tempDir2,
|
|
362054
|
+
applyPatchToContent: applyPatchToContent2,
|
|
362055
|
+
isPatchAlreadyApplied
|
|
362056
|
+
} = args;
|
|
362057
|
+
const { base: base4, ours, resolvedPath, renameSourcePath, ghostTheirs } = inputs;
|
|
362058
|
+
let theirs = ghostTheirs;
|
|
362059
|
+
const ghostReconstructed = ghostTheirs != null;
|
|
362060
|
+
const snapshotForFile = patch5.theirs_snapshot?.[resolvedPath] ?? patch5.theirs_snapshot?.[filePath];
|
|
362061
|
+
const fileIsShared = accumulator.isShared(resolvedPath) || accumulator.isShared(filePath);
|
|
362062
|
+
const useSnapshotAsPrimary = !fileIsShared && snapshotForFile != null;
|
|
362063
|
+
let primarySource = ghostReconstructed ? "ghost" : "reconstructed";
|
|
362064
|
+
if (!ghostReconstructed) {
|
|
362065
|
+
if (useSnapshotAsPrimary) {
|
|
362066
|
+
theirs = snapshotForFile;
|
|
362067
|
+
primarySource = "snapshot";
|
|
362068
|
+
} else {
|
|
362069
|
+
theirs = await applyPatchToContent2(
|
|
362070
|
+
base4,
|
|
362071
|
+
patch5.patch_content,
|
|
362072
|
+
filePath,
|
|
362073
|
+
tempGit,
|
|
362074
|
+
tempDir2,
|
|
362075
|
+
renameSourcePath
|
|
362076
|
+
);
|
|
362077
|
+
const reconstructionHasMarkers = hasConflictMarkerStrings(theirs);
|
|
362078
|
+
const baseHadMarkers = hasConflictMarkerStrings(base4);
|
|
362079
|
+
if (reconstructionHasMarkers && !baseHadMarkers && snapshotForFile != null) {
|
|
362080
|
+
theirs = snapshotForFile;
|
|
362081
|
+
}
|
|
362082
|
+
}
|
|
362083
|
+
}
|
|
362084
|
+
const accumulatorEntry = accumulator.lookup(resolvedPath);
|
|
362085
|
+
if (theirs) {
|
|
362086
|
+
const theirsHasMarkers = hasConflictMarkerStrings(theirs);
|
|
362087
|
+
const baseHasMarkers = hasConflictMarkerStrings(base4);
|
|
362088
|
+
if (theirsHasMarkers && !baseHasMarkers) {
|
|
362089
|
+
if (accumulatorEntry) {
|
|
362090
|
+
theirs = null;
|
|
362091
|
+
} else {
|
|
362092
|
+
return { kind: "skipped", reason: "stale-conflict-markers" };
|
|
362093
|
+
}
|
|
362094
|
+
}
|
|
362095
|
+
}
|
|
362096
|
+
let useAccumulatorAsMergeBase = false;
|
|
362097
|
+
if (accumulatorEntry && (theirs == null || base4 == null)) {
|
|
362098
|
+
theirs = await applyPatchToContent2(
|
|
362099
|
+
accumulatorEntry.content,
|
|
362100
|
+
patch5.patch_content,
|
|
362101
|
+
filePath,
|
|
362102
|
+
tempGit,
|
|
362103
|
+
tempDir2
|
|
362104
|
+
);
|
|
362105
|
+
if (theirs != null) {
|
|
362106
|
+
useAccumulatorAsMergeBase = true;
|
|
362107
|
+
} else if (await isPatchAlreadyApplied(
|
|
362108
|
+
patch5.patch_content,
|
|
362109
|
+
filePath,
|
|
362110
|
+
accumulatorEntry.content,
|
|
362111
|
+
tempGit,
|
|
362112
|
+
tempDir2
|
|
362113
|
+
)) {
|
|
362114
|
+
theirs = accumulatorEntry.content;
|
|
362115
|
+
useAccumulatorAsMergeBase = true;
|
|
362116
|
+
}
|
|
362117
|
+
}
|
|
362118
|
+
if (theirs) {
|
|
362119
|
+
const theirsHasMarkers = hasConflictMarkerStrings(theirs);
|
|
362120
|
+
const accBaseHasMarkers = accumulatorEntry != null && hasConflictMarkerStrings(accumulatorEntry.content);
|
|
362121
|
+
if (theirsHasMarkers && !accBaseHasMarkers && !hasConflictMarkerStrings(base4)) {
|
|
362122
|
+
return { kind: "skipped", reason: "stale-conflict-markers" };
|
|
362123
|
+
}
|
|
362124
|
+
}
|
|
362125
|
+
let effective_theirs = theirs;
|
|
362126
|
+
let conflictReasonHint;
|
|
362127
|
+
if (theirs != null && base4 != null && !useAccumulatorAsMergeBase) {
|
|
362128
|
+
if (accumulatorEntry && accumulatorEntry.baseGeneration === patch5.base_generation) {
|
|
362129
|
+
try {
|
|
362130
|
+
const preMerged = threeWayMerge(base4, accumulatorEntry.content, theirs);
|
|
362131
|
+
if (!preMerged.hasConflicts) {
|
|
362132
|
+
effective_theirs = preMerged.content;
|
|
362133
|
+
} else {
|
|
362134
|
+
effective_theirs = theirs;
|
|
362135
|
+
}
|
|
362136
|
+
} catch {
|
|
362137
|
+
effective_theirs = theirs;
|
|
362138
|
+
}
|
|
362139
|
+
} else if (accumulatorEntry) {
|
|
362140
|
+
conflictReasonHint = "base-generation-mismatch";
|
|
362141
|
+
}
|
|
362142
|
+
}
|
|
362143
|
+
if (base4 == null && ours == null && effective_theirs != null) {
|
|
362144
|
+
return { kind: "new-file-only-user", theirs: effective_theirs };
|
|
362145
|
+
}
|
|
362146
|
+
if (base4 == null && ours != null && effective_theirs != null && !useAccumulatorAsMergeBase) {
|
|
362147
|
+
return { kind: "new-file-both", theirs: effective_theirs };
|
|
362148
|
+
}
|
|
362149
|
+
if (effective_theirs == null) {
|
|
362150
|
+
return { kind: "skipped", reason: "missing-content" };
|
|
362151
|
+
}
|
|
362152
|
+
if (base4 == null && !useAccumulatorAsMergeBase || ours == null) {
|
|
362153
|
+
return { kind: "skipped", reason: "missing-content" };
|
|
362154
|
+
}
|
|
362155
|
+
if (useAccumulatorAsMergeBase) {
|
|
362156
|
+
const mergeBase = accumulatorEntry?.content ?? base4;
|
|
362157
|
+
if (mergeBase == null) {
|
|
362158
|
+
return { kind: "skipped", reason: "missing-content" };
|
|
362159
|
+
}
|
|
362160
|
+
return {
|
|
362161
|
+
kind: "accumulator-base",
|
|
362162
|
+
theirs: effective_theirs,
|
|
362163
|
+
mergeBase,
|
|
362164
|
+
conflictReasonHint
|
|
362165
|
+
};
|
|
362166
|
+
}
|
|
362167
|
+
return {
|
|
362168
|
+
kind: primarySource,
|
|
362169
|
+
base: base4,
|
|
362170
|
+
theirs: effective_theirs,
|
|
362171
|
+
mergeBase: base4,
|
|
362172
|
+
conflictReasonHint
|
|
362173
|
+
};
|
|
362174
|
+
}
|
|
362175
|
+
var import_promises210 = require("fs/promises");
|
|
362176
|
+
var import_node_path43 = require("path");
|
|
362177
|
+
async function runMergeAndRecord(args) {
|
|
362178
|
+
const { plan, inputs, patch: patch5, accumulator } = args;
|
|
362179
|
+
const { resolvedPath, metadata, oursPath, ours } = inputs;
|
|
362180
|
+
if (plan.kind === "skipped") {
|
|
362181
|
+
return { file: resolvedPath, status: "skipped", reason: plan.reason };
|
|
362182
|
+
}
|
|
362183
|
+
if (plan.kind === "new-file-only-user") {
|
|
362184
|
+
accumulator.recordMerge(resolvedPath, plan.theirs, patch5.base_generation);
|
|
362185
|
+
await (0, import_promises210.mkdir)((0, import_node_path43.dirname)(oursPath), { recursive: true });
|
|
362186
|
+
await (0, import_promises210.writeFile)(oursPath, plan.theirs);
|
|
362187
|
+
return { file: resolvedPath, status: "merged", reason: "new-file" };
|
|
362188
|
+
}
|
|
362189
|
+
if (plan.kind === "new-file-both") {
|
|
362190
|
+
const merged2 = threeWayMerge("", ours, plan.theirs);
|
|
362191
|
+
await (0, import_promises210.mkdir)((0, import_node_path43.dirname)(oursPath), { recursive: true });
|
|
362192
|
+
await (0, import_promises210.writeFile)(oursPath, merged2.content);
|
|
362193
|
+
if (merged2.hasConflicts) {
|
|
362194
|
+
return {
|
|
362195
|
+
file: resolvedPath,
|
|
362196
|
+
status: "conflict",
|
|
362197
|
+
conflicts: merged2.conflicts,
|
|
362198
|
+
conflictReason: "new-file-both",
|
|
362199
|
+
conflictMetadata: metadata
|
|
362200
|
+
};
|
|
362201
|
+
}
|
|
362202
|
+
accumulator.recordMerge(resolvedPath, merged2.content, patch5.base_generation);
|
|
362203
|
+
return { file: resolvedPath, status: "merged" };
|
|
362204
|
+
}
|
|
362205
|
+
const merged = threeWayMerge(plan.mergeBase, ours, plan.theirs);
|
|
362206
|
+
await (0, import_promises210.mkdir)((0, import_node_path43.dirname)(oursPath), { recursive: true });
|
|
362207
|
+
await (0, import_promises210.writeFile)(oursPath, merged.content);
|
|
362208
|
+
if (merged.hasConflicts) {
|
|
362209
|
+
accumulator.recordConflict(resolvedPath, plan.theirs, patch5.base_generation);
|
|
362210
|
+
return {
|
|
362211
|
+
file: resolvedPath,
|
|
362212
|
+
status: "conflict",
|
|
362213
|
+
conflicts: merged.conflicts,
|
|
362214
|
+
conflictReason: plan.conflictReasonHint ?? "same-line-edit",
|
|
362215
|
+
conflictMetadata: metadata
|
|
362216
|
+
};
|
|
362217
|
+
}
|
|
362218
|
+
accumulator.recordMerge(resolvedPath, plan.theirs, patch5.base_generation);
|
|
362219
|
+
return {
|
|
362220
|
+
file: resolvedPath,
|
|
362221
|
+
status: "merged",
|
|
362222
|
+
...merged.droppedContextLines && merged.droppedContextLines.length > 0 ? { droppedContextLines: merged.droppedContextLines } : {}
|
|
362223
|
+
};
|
|
362224
|
+
}
|
|
361355
362225
|
var ReplayApplicator = class {
|
|
361356
362226
|
git;
|
|
361357
362227
|
lockManager;
|
|
361358
362228
|
outputDir;
|
|
361359
362229
|
renameCache = /* @__PURE__ */ new Map();
|
|
361360
362230
|
treeExistsCache = /* @__PURE__ */ new Map();
|
|
361361
|
-
|
|
361362
|
-
|
|
361363
|
-
*
|
|
361364
|
-
*
|
|
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.
|
|
362231
|
+
/**
|
|
362232
|
+
* Inter-patch THEIRS coordination. Created fresh in each `applyPatches`
|
|
362233
|
+
* invocation with the run's apply mode + precomputed shared-file set.
|
|
362234
|
+
* See `src/accumulator/MergeAccumulator.ts` for the full contract.
|
|
361386
362235
|
*
|
|
361387
|
-
*
|
|
361388
|
-
*
|
|
361389
|
-
*
|
|
361390
|
-
|
|
361391
|
-
|
|
361392
|
-
* via `applyPatchToContent` is required there.
|
|
361393
|
-
*/
|
|
361394
|
-
sharedFiles = /* @__PURE__ */ new Set();
|
|
362236
|
+
* Initialized to an empty default in the constructor so type narrowing
|
|
362237
|
+
* works for callers that read it before any `applyPatches` invocation
|
|
362238
|
+
* (e.g., the `getAccumulatorSnapshot` test accessor).
|
|
362239
|
+
*/
|
|
362240
|
+
accumulator = new MergeAccumulator("replay", /* @__PURE__ */ new Set());
|
|
361395
362241
|
constructor(git, lockManager, outputDir) {
|
|
361396
362242
|
this.git = git;
|
|
361397
362243
|
this.lockManager = lockManager;
|
|
@@ -361418,23 +362264,15 @@ var require_dist14 = __commonJS({
|
|
|
361418
362264
|
return void 0;
|
|
361419
362265
|
}
|
|
361420
362266
|
}
|
|
361421
|
-
/** Reset inter-patch accumulator for a new cycle. */
|
|
361422
|
-
resetAccumulator() {
|
|
361423
|
-
this.fileTheirsAccumulator.clear();
|
|
361424
|
-
}
|
|
361425
362267
|
/**
|
|
361426
362268
|
* @internal Test-only.
|
|
361427
|
-
* Returns a defensive copy of the inter-patch accumulator. Used by
|
|
361428
|
-
*
|
|
361429
|
-
*
|
|
361430
|
-
*
|
|
362269
|
+
* Returns a defensive copy of the inter-patch accumulator. Used by
|
|
362270
|
+
* invariant I2 to assert that after every applied patch, the
|
|
362271
|
+
* accumulator has an entry for each file the patch touched, and the
|
|
362272
|
+
* entry's content matches on-disk.
|
|
361431
362273
|
*/
|
|
361432
362274
|
getAccumulatorSnapshot() {
|
|
361433
|
-
|
|
361434
|
-
for (const [path111, entry] of this.fileTheirsAccumulator) {
|
|
361435
|
-
snapshot.set(path111, { content: entry.content, baseGeneration: entry.baseGeneration });
|
|
361436
|
-
}
|
|
361437
|
-
return snapshot;
|
|
362275
|
+
return this.accumulator.snapshot();
|
|
361438
362276
|
}
|
|
361439
362277
|
/**
|
|
361440
362278
|
* Apply all patches, returning results for each.
|
|
@@ -361455,17 +362293,16 @@ var require_dist14 = __commonJS({
|
|
|
361455
362293
|
*/
|
|
361456
362294
|
async applyPatches(patches, opts) {
|
|
361457
362295
|
const applyMode = opts?.applyMode ?? "replay";
|
|
361458
|
-
this.currentApplyMode = applyMode;
|
|
361459
|
-
this.resetAccumulator();
|
|
361460
362296
|
const fileFreq = /* @__PURE__ */ new Map();
|
|
361461
362297
|
for (const p14 of patches) {
|
|
361462
362298
|
for (const f4 of p14.files) {
|
|
361463
362299
|
fileFreq.set(f4, (fileFreq.get(f4) ?? 0) + 1);
|
|
361464
362300
|
}
|
|
361465
362301
|
}
|
|
361466
|
-
|
|
362302
|
+
const sharedFiles = new Set(
|
|
361467
362303
|
Array.from(fileFreq.entries()).filter(([, n19]) => n19 > 1).map(([f4]) => f4)
|
|
361468
362304
|
);
|
|
362305
|
+
this.accumulator = new MergeAccumulator(applyMode, sharedFiles);
|
|
361469
362306
|
const results = [];
|
|
361470
362307
|
for (let i9 = 0; i9 < patches.length; i9++) {
|
|
361471
362308
|
const patch5 = patches[i9];
|
|
@@ -361496,11 +362333,11 @@ var require_dist14 = __commonJS({
|
|
|
361496
362333
|
if (fileResult.status !== "conflict") continue;
|
|
361497
362334
|
const originalPath = resolvedToOriginal.get(fileResult.file) ?? fileResult.file;
|
|
361498
362335
|
if (laterFiles.has(fileResult.file) || laterFiles.has(originalPath)) {
|
|
361499
|
-
const filePath = (0,
|
|
362336
|
+
const filePath = (0, import_node_path52.join)(this.outputDir, fileResult.file);
|
|
361500
362337
|
try {
|
|
361501
|
-
const content5 = await (0,
|
|
362338
|
+
const content5 = await (0, import_promises310.readFile)(filePath, "utf-8");
|
|
361502
362339
|
const stripped = stripConflictMarkers(content5);
|
|
361503
|
-
await (0,
|
|
362340
|
+
await (0, import_promises310.writeFile)(filePath, stripped);
|
|
361504
362341
|
} catch {
|
|
361505
362342
|
}
|
|
361506
362343
|
}
|
|
@@ -361520,7 +362357,7 @@ var require_dist14 = __commonJS({
|
|
|
361520
362357
|
async populateAccumulatorForPatch(patch5, baseGen, currentTreeHash) {
|
|
361521
362358
|
const fileResults = [];
|
|
361522
362359
|
if (!baseGen) return fileResults;
|
|
361523
|
-
const tempDir2 = await (0,
|
|
362360
|
+
const tempDir2 = await (0, import_promises310.mkdtemp)((0, import_node_path52.join)((0, import_node_os15.tmpdir)(), "replay-acc-"));
|
|
361524
362361
|
const { GitClient: GitClient2 } = await Promise.resolve().then(() => (init_GitClient(), GitClient_exports));
|
|
361525
362362
|
const tempGit = new GitClient2(tempDir2);
|
|
361526
362363
|
await tempGit.exec(["init"]);
|
|
@@ -361532,15 +362369,12 @@ var require_dist14 = __commonJS({
|
|
|
361532
362369
|
const resolvedPath = await this.resolveFilePath(filePath, baseGen.tree_hash, currentTreeHash);
|
|
361533
362370
|
const base4 = await this.git.showFile(baseGen.tree_hash, filePath);
|
|
361534
362371
|
const snapshotForFile = patch5.theirs_snapshot?.[resolvedPath] ?? patch5.theirs_snapshot?.[filePath];
|
|
361535
|
-
const fileIsShared = this.
|
|
362372
|
+
const fileIsShared = this.accumulator.isShared(resolvedPath) || this.accumulator.isShared(filePath);
|
|
361536
362373
|
const theirs = !fileIsShared && snapshotForFile != null ? snapshotForFile : await this.applyPatchToContent(base4, patch5.patch_content, filePath, tempGit, tempDir2);
|
|
361537
|
-
const finalOnDisk = await (0,
|
|
362374
|
+
const finalOnDisk = await (0, import_promises310.readFile)((0, import_node_path52.join)(this.outputDir, resolvedPath), "utf-8").catch(() => null);
|
|
361538
362375
|
const effectiveTheirs = theirs ?? finalOnDisk;
|
|
361539
362376
|
if (effectiveTheirs != null) {
|
|
361540
|
-
this.
|
|
361541
|
-
content: effectiveTheirs,
|
|
361542
|
-
baseGeneration: patch5.base_generation
|
|
361543
|
-
});
|
|
362377
|
+
this.accumulator.recordMerge(resolvedPath, effectiveTheirs, patch5.base_generation);
|
|
361544
362378
|
}
|
|
361545
362379
|
if (base4 != null && effectiveTheirs != null && finalOnDisk != null) {
|
|
361546
362380
|
const dropped = computeDroppedContextLinesForFile(base4, effectiveTheirs, finalOnDisk);
|
|
@@ -361554,7 +362388,7 @@ var require_dist14 = __commonJS({
|
|
|
361554
362388
|
}
|
|
361555
362389
|
}
|
|
361556
362390
|
} finally {
|
|
361557
|
-
await (0,
|
|
362391
|
+
await (0, import_promises310.rm)(tempDir2, { recursive: true }).catch(() => {
|
|
361558
362392
|
});
|
|
361559
362393
|
}
|
|
361560
362394
|
return fileResults;
|
|
@@ -361568,7 +362402,7 @@ var require_dist14 = __commonJS({
|
|
|
361568
362402
|
patch5.files.map(async (f4) => {
|
|
361569
362403
|
if (!baseGen) return false;
|
|
361570
362404
|
const resolved = await this.resolveFilePath(f4, baseGen.tree_hash, currentTreeHash);
|
|
361571
|
-
return this.
|
|
362405
|
+
return this.accumulator.has(resolved);
|
|
361572
362406
|
})
|
|
361573
362407
|
).then((results) => results.some(Boolean));
|
|
361574
362408
|
const resolvedFiles = {};
|
|
@@ -361583,8 +362417,8 @@ var require_dist14 = __commonJS({
|
|
|
361583
362417
|
if (!needsAccumulation && !hasGeneratorRename) {
|
|
361584
362418
|
const snapshots = /* @__PURE__ */ new Map();
|
|
361585
362419
|
for (const filePath of patch5.files) {
|
|
361586
|
-
const fullPath = (0,
|
|
361587
|
-
snapshots.set(filePath, await (0,
|
|
362420
|
+
const fullPath = (0, import_node_path52.join)(this.outputDir, filePath);
|
|
362421
|
+
snapshots.set(filePath, await (0, import_promises310.readFile)(fullPath, "utf-8").catch(() => null));
|
|
361588
362422
|
}
|
|
361589
362423
|
try {
|
|
361590
362424
|
await this.git.execWithInput(["apply", "--3way"], patch5.patch_content);
|
|
@@ -361598,11 +362432,11 @@ var require_dist14 = __commonJS({
|
|
|
361598
362432
|
};
|
|
361599
362433
|
} catch {
|
|
361600
362434
|
for (const [filePath, content5] of snapshots) {
|
|
361601
|
-
const fullPath = (0,
|
|
362435
|
+
const fullPath = (0, import_node_path52.join)(this.outputDir, filePath);
|
|
361602
362436
|
if (content5 != null) {
|
|
361603
|
-
await (0,
|
|
362437
|
+
await (0, import_promises310.writeFile)(fullPath, content5);
|
|
361604
362438
|
} else {
|
|
361605
|
-
await (0,
|
|
362439
|
+
await (0, import_promises310.unlink)(fullPath).catch(() => {
|
|
361606
362440
|
});
|
|
361607
362441
|
}
|
|
361608
362442
|
}
|
|
@@ -361613,7 +362447,7 @@ var require_dist14 = __commonJS({
|
|
|
361613
362447
|
async applyWithThreeWayMerge(patch5) {
|
|
361614
362448
|
const fileResults = [];
|
|
361615
362449
|
const resolvedFiles = {};
|
|
361616
|
-
const tempDir2 = await (0,
|
|
362450
|
+
const tempDir2 = await (0, import_promises310.mkdtemp)((0, import_node_path52.join)((0, import_node_os15.tmpdir)(), "replay-"));
|
|
361617
362451
|
const { GitClient: GitClient2 } = await Promise.resolve().then(() => (init_GitClient(), GitClient_exports));
|
|
361618
362452
|
const tempGit = new GitClient2(tempDir2);
|
|
361619
362453
|
await tempGit.exec(["init"]);
|
|
@@ -361636,7 +362470,7 @@ var require_dist14 = __commonJS({
|
|
|
361636
362470
|
fileResults.push(result);
|
|
361637
362471
|
}
|
|
361638
362472
|
} finally {
|
|
361639
|
-
await (0,
|
|
362473
|
+
await (0, import_promises310.rm)(tempDir2, { recursive: true }).catch(() => {
|
|
361640
362474
|
});
|
|
361641
362475
|
}
|
|
361642
362476
|
const conflictFiles = fileResults.filter((r5) => r5.status === "conflict");
|
|
@@ -361653,214 +362487,37 @@ var require_dist14 = __commonJS({
|
|
|
361653
362487
|
}
|
|
361654
362488
|
async mergeFile(patch5, filePath, tempGit, tempDir2) {
|
|
361655
362489
|
try {
|
|
361656
|
-
const
|
|
361657
|
-
|
|
361658
|
-
|
|
361659
|
-
|
|
361660
|
-
|
|
361661
|
-
|
|
361662
|
-
|
|
361663
|
-
|
|
361664
|
-
|
|
361665
|
-
|
|
361666
|
-
|
|
361667
|
-
|
|
361668
|
-
|
|
361669
|
-
|
|
361670
|
-
|
|
361671
|
-
|
|
361672
|
-
|
|
361673
|
-
|
|
361674
|
-
|
|
361675
|
-
|
|
361676
|
-
|
|
361677
|
-
|
|
361678
|
-
|
|
361679
|
-
|
|
361680
|
-
|
|
361681
|
-
|
|
361682
|
-
|
|
361683
|
-
|
|
361684
|
-
|
|
361685
|
-
|
|
361686
|
-
|
|
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
|
-
};
|
|
362490
|
+
const inputs = await resolveInputs({
|
|
362491
|
+
patch: patch5,
|
|
362492
|
+
filePath,
|
|
362493
|
+
git: this.git,
|
|
362494
|
+
lockManager: this.lockManager,
|
|
362495
|
+
outputDir: this.outputDir,
|
|
362496
|
+
isTreeReachable: (h5) => this.isTreeReachable(h5),
|
|
362497
|
+
resolveBaseGeneration: (s9) => this.resolveBaseGeneration(s9),
|
|
362498
|
+
resolveFilePath: (f4, b8, c6) => this.resolveFilePath(f4, b8, c6),
|
|
362499
|
+
extractRenameSource: (p14, f4) => this.extractRenameSource(p14, f4),
|
|
362500
|
+
extractFileDiff: (p14, f4) => this.extractFileDiff(p14, f4)
|
|
362501
|
+
});
|
|
362502
|
+
if (inputs.earlyExit) {
|
|
362503
|
+
return { file: filePath, ...inputs.earlyExit };
|
|
362504
|
+
}
|
|
362505
|
+
const plan = await buildMergePlan({
|
|
362506
|
+
inputs,
|
|
362507
|
+
patch: patch5,
|
|
362508
|
+
filePath,
|
|
362509
|
+
accumulator: this.accumulator,
|
|
362510
|
+
tempGit,
|
|
362511
|
+
tempDir: tempDir2,
|
|
362512
|
+
applyPatchToContent: (b8, p14, f4, g8, d5, s9) => this.applyPatchToContent(b8, p14, f4, g8, d5, s9),
|
|
362513
|
+
isPatchAlreadyApplied: (p14, f4, c6, g8, d5) => this.isPatchAlreadyApplied(p14, f4, c6, g8, d5)
|
|
362514
|
+
});
|
|
362515
|
+
return await runMergeAndRecord({
|
|
362516
|
+
plan,
|
|
362517
|
+
inputs,
|
|
362518
|
+
patch: patch5,
|
|
362519
|
+
accumulator: this.accumulator
|
|
362520
|
+
});
|
|
361864
362521
|
} catch (error50) {
|
|
361865
362522
|
return {
|
|
361866
362523
|
file: filePath,
|
|
@@ -361918,9 +362575,9 @@ var require_dist14 = __commonJS({
|
|
|
361918
362575
|
if (!fileDiff) return null;
|
|
361919
362576
|
try {
|
|
361920
362577
|
if (sourceFilePath) {
|
|
361921
|
-
const tempSourcePath = (0,
|
|
361922
|
-
await (0,
|
|
361923
|
-
await (0,
|
|
362578
|
+
const tempSourcePath = (0, import_node_path52.join)(tempDir2, sourceFilePath);
|
|
362579
|
+
await (0, import_promises310.mkdir)((0, import_node_path52.dirname)(tempSourcePath), { recursive: true });
|
|
362580
|
+
await (0, import_promises310.writeFile)(tempSourcePath, base4);
|
|
361924
362581
|
await tempGit.exec(["add", sourceFilePath]);
|
|
361925
362582
|
await tempGit.exec([
|
|
361926
362583
|
"commit",
|
|
@@ -361929,16 +362586,16 @@ var require_dist14 = __commonJS({
|
|
|
361929
362586
|
"--allow-empty"
|
|
361930
362587
|
]);
|
|
361931
362588
|
await tempGit.execWithInput(["apply", "--allow-empty"], fileDiff);
|
|
361932
|
-
const tempTargetPath = (0,
|
|
361933
|
-
return await (0,
|
|
362589
|
+
const tempTargetPath = (0, import_node_path52.join)(tempDir2, filePath);
|
|
362590
|
+
return await (0, import_promises310.readFile)(tempTargetPath, "utf-8");
|
|
361934
362591
|
}
|
|
361935
|
-
const tempFilePath = (0,
|
|
361936
|
-
await (0,
|
|
361937
|
-
await (0,
|
|
362592
|
+
const tempFilePath = (0, import_node_path52.join)(tempDir2, filePath);
|
|
362593
|
+
await (0, import_promises310.mkdir)((0, import_node_path52.dirname)(tempFilePath), { recursive: true });
|
|
362594
|
+
await (0, import_promises310.writeFile)(tempFilePath, base4);
|
|
361938
362595
|
await tempGit.exec(["add", filePath]);
|
|
361939
362596
|
await tempGit.exec(["commit", "-m", `base for ${filePath}`, "--allow-empty"]);
|
|
361940
362597
|
await tempGit.execWithInput(["apply", "--allow-empty"], fileDiff);
|
|
361941
|
-
return await (0,
|
|
362598
|
+
return await (0, import_promises310.readFile)(tempFilePath, "utf-8");
|
|
361942
362599
|
} catch {
|
|
361943
362600
|
return null;
|
|
361944
362601
|
}
|
|
@@ -361954,9 +362611,9 @@ var require_dist14 = __commonJS({
|
|
|
361954
362611
|
const fileDiff = this.extractFileDiff(patchContent, filePath);
|
|
361955
362612
|
if (!fileDiff) return false;
|
|
361956
362613
|
try {
|
|
361957
|
-
const tempFilePath = (0,
|
|
361958
|
-
await (0,
|
|
361959
|
-
await (0,
|
|
362614
|
+
const tempFilePath = (0, import_node_path52.join)(tempDir2, filePath);
|
|
362615
|
+
await (0, import_promises310.mkdir)((0, import_node_path52.dirname)(tempFilePath), { recursive: true });
|
|
362616
|
+
await (0, import_promises310.writeFile)(tempFilePath, content5);
|
|
361960
362617
|
await tempGit.exec(["add", filePath]);
|
|
361961
362618
|
await tempGit.exec(["commit", "-m", `check already-applied ${filePath}`, "--allow-empty"]);
|
|
361962
362619
|
await tempGit.execWithInput(["apply", "--reverse", "--check", "--allow-empty"], fileDiff);
|
|
@@ -362042,10 +362699,6 @@ var require_dist14 = __commonJS({
|
|
|
362042
362699
|
return addedLines.join("\n") + (noTrailingNewline ? "" : "\n");
|
|
362043
362700
|
}
|
|
362044
362701
|
};
|
|
362045
|
-
function isBinaryFile(filePath) {
|
|
362046
|
-
const ext2 = (0, import_node_path210.extname)(filePath).toLowerCase();
|
|
362047
|
-
return BINARY_EXTENSIONS2.has(ext2);
|
|
362048
|
-
}
|
|
362049
362702
|
function computeDroppedContextLinesForFile(base4, theirs, finalContent) {
|
|
362050
362703
|
const baseLines = base4.split("\n");
|
|
362051
362704
|
const theirsLines = theirs.split("\n");
|
|
@@ -362074,600 +362727,6 @@ var require_dist14 = __commonJS({
|
|
|
362074
362727
|
const match3 = diffLine.match(/^diff --git a\/.+ b\/(.+)$/);
|
|
362075
362728
|
return match3 !== null && match3[1] === filePath;
|
|
362076
362729
|
}
|
|
362077
|
-
var INFRASTRUCTURE_FILES = /* @__PURE__ */ new Set([".fernignore"]);
|
|
362078
|
-
function parsePatchFileHeaders(patchContent) {
|
|
362079
|
-
const results = [];
|
|
362080
|
-
let currentPath = null;
|
|
362081
|
-
let currentIsCreate = false;
|
|
362082
|
-
let currentIsDelete = false;
|
|
362083
|
-
const flush3 = () => {
|
|
362084
|
-
if (currentPath !== null) {
|
|
362085
|
-
results.push({ path: currentPath, isCreate: currentIsCreate, isDelete: currentIsDelete });
|
|
362086
|
-
}
|
|
362087
|
-
};
|
|
362088
|
-
for (const line of patchContent.split("\n")) {
|
|
362089
|
-
if (line.startsWith("diff --git ")) {
|
|
362090
|
-
flush3();
|
|
362091
|
-
currentPath = null;
|
|
362092
|
-
currentIsCreate = false;
|
|
362093
|
-
currentIsDelete = false;
|
|
362094
|
-
const match3 = line.match(/^diff --git a\/(.+?) b\/(.+?)$/);
|
|
362095
|
-
if (match3) {
|
|
362096
|
-
currentPath = match3[2] ?? null;
|
|
362097
|
-
}
|
|
362098
|
-
} else if (currentPath !== null) {
|
|
362099
|
-
if (line.startsWith("new file mode ")) {
|
|
362100
|
-
currentIsCreate = true;
|
|
362101
|
-
} else if (line.startsWith("deleted file mode ")) {
|
|
362102
|
-
currentIsDelete = true;
|
|
362103
|
-
}
|
|
362104
|
-
}
|
|
362105
|
-
}
|
|
362106
|
-
flush3();
|
|
362107
|
-
return results;
|
|
362108
|
-
}
|
|
362109
|
-
var ReplayDetector = class {
|
|
362110
|
-
git;
|
|
362111
|
-
lockManager;
|
|
362112
|
-
sdkOutputDir;
|
|
362113
|
-
warnings = [];
|
|
362114
|
-
constructor(git, lockManager, sdkOutputDir) {
|
|
362115
|
-
this.git = git;
|
|
362116
|
-
this.lockManager = lockManager;
|
|
362117
|
-
this.sdkOutputDir = sdkOutputDir;
|
|
362118
|
-
}
|
|
362119
|
-
async detectNewPatches() {
|
|
362120
|
-
const lock = this.lockManager.read();
|
|
362121
|
-
const lastGen = this.getLastGeneration(lock);
|
|
362122
|
-
if (!lastGen) {
|
|
362123
|
-
return { patches: [], revertedPatchIds: [] };
|
|
362124
|
-
}
|
|
362125
|
-
const exists2 = await this.git.commitExists(lastGen.commit_sha);
|
|
362126
|
-
if (!exists2) {
|
|
362127
|
-
this.warnings.push(
|
|
362128
|
-
`Generation commit ${lastGen.commit_sha.slice(0, 7)} not found in git history. Falling back to alternate detection.`
|
|
362129
|
-
);
|
|
362130
|
-
return this.detectPatchesViaTreeDiff(
|
|
362131
|
-
lastGen,
|
|
362132
|
-
/* commitKnownMissing */
|
|
362133
|
-
true
|
|
362134
|
-
);
|
|
362135
|
-
}
|
|
362136
|
-
const isAncestor = await this.git.isAncestor(lastGen.commit_sha, "HEAD");
|
|
362137
|
-
if (!isAncestor) {
|
|
362138
|
-
return this.detectPatchesViaMergeBase(lastGen, lock);
|
|
362139
|
-
}
|
|
362140
|
-
return this.detectPatchesInRange(lastGen.commit_sha, lastGen, lock);
|
|
362141
|
-
}
|
|
362142
|
-
/**
|
|
362143
|
-
* FER-10201 — Non-linear-history detection via `merge-base(prevGen, HEAD)`.
|
|
362144
|
-
*
|
|
362145
|
-
* After a squash-merge of a Fern-bot PR, `lastGen.commit_sha` (the previous
|
|
362146
|
-
* `[fern-generated]`) is orphaned but still in git's object database. The
|
|
362147
|
-
* merge-base is the commit on main from which the bot's branch was created —
|
|
362148
|
-
* a reachable ancestor of HEAD. Walking that range and classifying each
|
|
362149
|
-
* commit via `isGenerationCommit` mirrors the linear path and eliminates
|
|
362150
|
-
* the bug class where pipeline-driven changes (autoversion, replay,
|
|
362151
|
-
* generation) get bundled as customer customizations.
|
|
362152
|
-
*
|
|
362153
|
-
* Falls through to the legacy composite path when no common ancestor exists
|
|
362154
|
-
* (truly disjoint history — orphan branches with no shared root).
|
|
362155
|
-
*/
|
|
362156
|
-
async detectPatchesViaMergeBase(lastGen, lock) {
|
|
362157
|
-
let mergeBase = "";
|
|
362158
|
-
try {
|
|
362159
|
-
mergeBase = (await this.git.exec(["merge-base", lastGen.commit_sha, "HEAD"])).trim();
|
|
362160
|
-
} catch {
|
|
362161
|
-
}
|
|
362162
|
-
if (!mergeBase) {
|
|
362163
|
-
this.warnings.push(
|
|
362164
|
-
`No common ancestor between previous generation ${lastGen.commit_sha.slice(0, 7)} and HEAD. Falling back to composite tree-diff detection.`
|
|
362165
|
-
);
|
|
362166
|
-
return this.detectPatchesViaTreeDiff(
|
|
362167
|
-
lastGen,
|
|
362168
|
-
/* commitKnownMissing */
|
|
362169
|
-
false
|
|
362170
|
-
);
|
|
362171
|
-
}
|
|
362172
|
-
return this.detectPatchesInRange(mergeBase, lastGen, lock);
|
|
362173
|
-
}
|
|
362174
|
-
/**
|
|
362175
|
-
* FER-10201 — Walk `${rangeStart}..HEAD`, classify each commit, emit one
|
|
362176
|
-
* `StoredPatch` per customer commit. Body shared by the linear path
|
|
362177
|
-
* (rangeStart = lastGen.commit_sha) and the merge-base path.
|
|
362178
|
-
*
|
|
362179
|
-
* Patch `base_generation` is always `lastGen.commit_sha` — the
|
|
362180
|
-
* lockfile-tracked reference the applicator uses to fetch base file
|
|
362181
|
-
* content, regardless of where the walk actually started.
|
|
362182
|
-
*/
|
|
362183
|
-
async detectPatchesInRange(rangeStart, lastGen, lock) {
|
|
362184
|
-
const log4 = await this.git.exec([
|
|
362185
|
-
"log",
|
|
362186
|
-
"--first-parent",
|
|
362187
|
-
"--format=%H%x00%an%x00%ae%x00%s",
|
|
362188
|
-
`${rangeStart}..HEAD`,
|
|
362189
|
-
"--",
|
|
362190
|
-
this.sdkOutputDir
|
|
362191
|
-
]);
|
|
362192
|
-
if (!log4.trim()) {
|
|
362193
|
-
return { patches: [], revertedPatchIds: [] };
|
|
362194
|
-
}
|
|
362195
|
-
const commits = this.parseGitLog(log4);
|
|
362196
|
-
const newPatches = [];
|
|
362197
|
-
const forgottenHashes = new Set(lock.forgotten_hashes ?? []);
|
|
362198
|
-
for (const commit of commits) {
|
|
362199
|
-
if (isGenerationCommit(commit)) {
|
|
362200
|
-
continue;
|
|
362201
|
-
}
|
|
362202
|
-
if (lock.patches.find((p14) => p14.original_commit === commit.sha)) {
|
|
362203
|
-
continue;
|
|
362204
|
-
}
|
|
362205
|
-
const parents = await this.git.getCommitParents(commit.sha);
|
|
362206
|
-
if (parents.length > 1) {
|
|
362207
|
-
const compositePatch = await this.createMergeCompositePatch({
|
|
362208
|
-
mergeCommit: commit,
|
|
362209
|
-
firstParent: parents[0],
|
|
362210
|
-
lastGen,
|
|
362211
|
-
lock
|
|
362212
|
-
});
|
|
362213
|
-
if (compositePatch) {
|
|
362214
|
-
newPatches.push(compositePatch);
|
|
362215
|
-
}
|
|
362216
|
-
continue;
|
|
362217
|
-
}
|
|
362218
|
-
let patchContent;
|
|
362219
|
-
try {
|
|
362220
|
-
patchContent = await this.git.formatPatch(commit.sha);
|
|
362221
|
-
} catch {
|
|
362222
|
-
this.warnings.push(
|
|
362223
|
-
`Could not generate patch for commit ${commit.sha.slice(0, 7)} \u2014 it may be unreachable in a shallow clone. Skipping.`
|
|
362224
|
-
);
|
|
362225
|
-
continue;
|
|
362226
|
-
}
|
|
362227
|
-
let contentHash = this.computeContentHash(patchContent);
|
|
362228
|
-
if (lock.patches.find((p14) => p14.content_hash === contentHash) || forgottenHashes.has(contentHash)) {
|
|
362229
|
-
continue;
|
|
362230
|
-
}
|
|
362231
|
-
const filesOutput = await this.git.exec(["diff-tree", "--no-commit-id", "--name-only", "-r", commit.sha]);
|
|
362232
|
-
const allFiles = filesOutput.trim().split("\n").filter(Boolean).filter((f4) => !INFRASTRUCTURE_FILES.has(f4)).filter((f4) => !f4.startsWith(".fern/"));
|
|
362233
|
-
const sensitiveFiles = allFiles.filter((f4) => isSensitiveFile(f4));
|
|
362234
|
-
const files = allFiles.filter((f4) => !isSensitiveFile(f4));
|
|
362235
|
-
if (sensitiveFiles.length > 0) {
|
|
362236
|
-
this.warnings.push(
|
|
362237
|
-
`Sensitive file(s) excluded from patch for commit ${commit.sha.slice(0, 7)}: ${sensitiveFiles.join(", ")}. These files will not be tracked by replay.`
|
|
362238
|
-
);
|
|
362239
|
-
if (files.length > 0) {
|
|
362240
|
-
const parentSha = parents[0];
|
|
362241
|
-
if (parentSha) {
|
|
362242
|
-
try {
|
|
362243
|
-
patchContent = await this.git.exec(["diff", parentSha, commit.sha, "--", ...files]);
|
|
362244
|
-
} catch {
|
|
362245
|
-
continue;
|
|
362246
|
-
}
|
|
362247
|
-
if (!patchContent.trim()) continue;
|
|
362248
|
-
contentHash = this.computeContentHash(patchContent);
|
|
362249
|
-
}
|
|
362250
|
-
}
|
|
362251
|
-
}
|
|
362252
|
-
if (files.length === 0) {
|
|
362253
|
-
continue;
|
|
362254
|
-
}
|
|
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
|
-
}
|
|
362261
|
-
newPatches.push({
|
|
362262
|
-
id: `patch-${commit.sha.slice(0, 8)}`,
|
|
362263
|
-
content_hash: contentHash,
|
|
362264
|
-
original_commit: commit.sha,
|
|
362265
|
-
original_message: commit.message,
|
|
362266
|
-
original_author: `${commit.authorName} <${commit.authorEmail}>`,
|
|
362267
|
-
base_generation: lastGen.commit_sha,
|
|
362268
|
-
files,
|
|
362269
|
-
patch_content: patchContent,
|
|
362270
|
-
...Object.keys(theirsSnapshot).length > 0 ? { theirs_snapshot: theirsSnapshot } : {},
|
|
362271
|
-
...this.isCreationOnlyPatch(patchContent) ? { user_owned: true } : {}
|
|
362272
|
-
});
|
|
362273
|
-
}
|
|
362274
|
-
const survivingPatches = await this.collapseNetZeroFiles(newPatches);
|
|
362275
|
-
survivingPatches.reverse();
|
|
362276
|
-
const revertedPatchIdSet = /* @__PURE__ */ new Set();
|
|
362277
|
-
const revertIndicesToRemove = /* @__PURE__ */ new Set();
|
|
362278
|
-
for (let i9 = 0; i9 < survivingPatches.length; i9++) {
|
|
362279
|
-
const patch5 = survivingPatches[i9];
|
|
362280
|
-
if (!isRevertCommit(patch5.original_message)) continue;
|
|
362281
|
-
let body = "";
|
|
362282
|
-
try {
|
|
362283
|
-
body = await this.git.getCommitBody(patch5.original_commit);
|
|
362284
|
-
} catch {
|
|
362285
|
-
}
|
|
362286
|
-
const revertedSha = parseRevertedSha(body);
|
|
362287
|
-
const revertedMessage = parseRevertedMessage(patch5.original_message);
|
|
362288
|
-
let matchedExisting = false;
|
|
362289
|
-
if (revertedSha) {
|
|
362290
|
-
const existing = lock.patches.find((p14) => p14.original_commit === revertedSha);
|
|
362291
|
-
if (existing) {
|
|
362292
|
-
revertedPatchIdSet.add(existing.id);
|
|
362293
|
-
revertIndicesToRemove.add(i9);
|
|
362294
|
-
matchedExisting = true;
|
|
362295
|
-
}
|
|
362296
|
-
}
|
|
362297
|
-
if (!matchedExisting && revertedMessage) {
|
|
362298
|
-
const existing = lock.patches.find((p14) => p14.original_message === revertedMessage);
|
|
362299
|
-
if (existing) {
|
|
362300
|
-
revertedPatchIdSet.add(existing.id);
|
|
362301
|
-
revertIndicesToRemove.add(i9);
|
|
362302
|
-
matchedExisting = true;
|
|
362303
|
-
}
|
|
362304
|
-
}
|
|
362305
|
-
if (matchedExisting) continue;
|
|
362306
|
-
let matchedNew = false;
|
|
362307
|
-
if (revertedSha) {
|
|
362308
|
-
const idx = survivingPatches.findIndex(
|
|
362309
|
-
(p14, j7) => j7 !== i9 && !revertIndicesToRemove.has(j7) && p14.original_commit === revertedSha
|
|
362310
|
-
);
|
|
362311
|
-
if (idx !== -1) {
|
|
362312
|
-
revertIndicesToRemove.add(i9);
|
|
362313
|
-
revertIndicesToRemove.add(idx);
|
|
362314
|
-
matchedNew = true;
|
|
362315
|
-
}
|
|
362316
|
-
}
|
|
362317
|
-
if (!matchedNew && revertedMessage) {
|
|
362318
|
-
const idx = survivingPatches.findIndex(
|
|
362319
|
-
(p14, j7) => j7 !== i9 && !revertIndicesToRemove.has(j7) && p14.original_message === revertedMessage
|
|
362320
|
-
);
|
|
362321
|
-
if (idx !== -1) {
|
|
362322
|
-
revertIndicesToRemove.add(i9);
|
|
362323
|
-
revertIndicesToRemove.add(idx);
|
|
362324
|
-
}
|
|
362325
|
-
}
|
|
362326
|
-
if (!matchedExisting && !matchedNew) {
|
|
362327
|
-
revertIndicesToRemove.add(i9);
|
|
362328
|
-
}
|
|
362329
|
-
}
|
|
362330
|
-
const filteredPatches = survivingPatches.filter((_7, i9) => !revertIndicesToRemove.has(i9));
|
|
362331
|
-
return { patches: filteredPatches, revertedPatchIds: [...revertedPatchIdSet] };
|
|
362332
|
-
}
|
|
362333
|
-
/**
|
|
362334
|
-
* Compute content hash for deduplication.
|
|
362335
|
-
*
|
|
362336
|
-
* Produces a format-agnostic hash so both `git format-patch` output
|
|
362337
|
-
* (email-wrapped) and plain `git diff` output hash to the same value
|
|
362338
|
-
* when the underlying diff hunks are identical.
|
|
362339
|
-
*
|
|
362340
|
-
* Normalization:
|
|
362341
|
-
* 1. Strip email wrapper: everything before the first `diff --git` line
|
|
362342
|
-
* (From:, Subject:, Date:, diffstat, blank separators).
|
|
362343
|
-
* 2. Strip `index` lines (blob SHA pairs change across rebases).
|
|
362344
|
-
* 3. Strip trailing git version marker (`-- \n<version>`).
|
|
362345
|
-
*
|
|
362346
|
-
* This ensures content hashes match across the no-patches and normal-
|
|
362347
|
-
* regeneration flows, which store formatPatch and git-diff formats
|
|
362348
|
-
* respectively (FER-9850, D5-D9).
|
|
362349
|
-
*/
|
|
362350
|
-
computeContentHash(patchContent) {
|
|
362351
|
-
const lines = patchContent.split("\n");
|
|
362352
|
-
const diffStart = lines.findIndex((l9) => l9.startsWith("diff --git "));
|
|
362353
|
-
const relevantLines = diffStart > 0 ? lines.slice(diffStart) : lines;
|
|
362354
|
-
const normalized = relevantLines.filter((line) => !line.startsWith("index ")).join("\n").replace(/\n-- \n[\d]+\.[\d]+[^\n]*\s*$/, "");
|
|
362355
|
-
return `sha256:${(0, import_node_crypto7.createHash)("sha256").update(normalized).digest("hex")}`;
|
|
362356
|
-
}
|
|
362357
|
-
/**
|
|
362358
|
-
* FER-9805 — Drop patches whose files were transiently created and then
|
|
362359
|
-
* deleted within the current linear detection window, and are absent from
|
|
362360
|
-
* HEAD at the end of the window.
|
|
362361
|
-
*
|
|
362362
|
-
* Rationale: on a merge commit, createMergeCompositePatch already diffs
|
|
362363
|
-
* firstParent..mergeCommit so net-zero files never enter the composite. On
|
|
362364
|
-
* linear history each commit becomes its own patch, so a file that was
|
|
362365
|
-
* briefly added and later removed survives as a create+delete pair whose
|
|
362366
|
-
* cumulative diff is empty. We drop such patches before they reach the
|
|
362367
|
-
* lockfile.
|
|
362368
|
-
*
|
|
362369
|
-
* A file is "transient" when:
|
|
362370
|
-
* 1. some patch in the window sets `new file mode` for it (a creation), AND
|
|
362371
|
-
* 2. some patch in the window sets `deleted file mode` for it (a deletion), AND
|
|
362372
|
-
* 3. it is absent from HEAD's tree.
|
|
362373
|
-
*
|
|
362374
|
-
* The HEAD guard (#3) prevents false positives when a file is deleted
|
|
362375
|
-
* and then recreated with different content — the cumulative diff of
|
|
362376
|
-
* such a pair is non-empty and must be preserved.
|
|
362377
|
-
*
|
|
362378
|
-
* This only drops patches whose `files` are a subset of the transient
|
|
362379
|
-
* set. A partial-transient patch (one that touches a transient file
|
|
362380
|
-
* alongside a persistent one) is left unmodified; surgical stripping of
|
|
362381
|
-
* single files from a multi-file patch would require a follow-up that
|
|
362382
|
-
* regenerates the patch content via `git format-patch -- <files>`. No
|
|
362383
|
-
* current failing test exercises this, and leaving the patch intact
|
|
362384
|
-
* preserves today's behaviour. TODO(FER-9805): revisit if a future
|
|
362385
|
-
* adversarial test demands per-file stripping.
|
|
362386
|
-
*/
|
|
362387
|
-
async collapseNetZeroFiles(patches) {
|
|
362388
|
-
if (patches.length === 0) return patches;
|
|
362389
|
-
const stateByFile = /* @__PURE__ */ new Map();
|
|
362390
|
-
for (const patch5 of patches) {
|
|
362391
|
-
for (const header of parsePatchFileHeaders(patch5.patch_content)) {
|
|
362392
|
-
if (!header.isCreate && !header.isDelete) continue;
|
|
362393
|
-
const prior = stateByFile.get(header.path) ?? { created: false, deleted: false };
|
|
362394
|
-
if (header.isCreate) prior.created = true;
|
|
362395
|
-
if (header.isDelete) prior.deleted = true;
|
|
362396
|
-
stateByFile.set(header.path, prior);
|
|
362397
|
-
}
|
|
362398
|
-
}
|
|
362399
|
-
let anyCandidate = false;
|
|
362400
|
-
for (const state of stateByFile.values()) {
|
|
362401
|
-
if (state.created && state.deleted) {
|
|
362402
|
-
anyCandidate = true;
|
|
362403
|
-
break;
|
|
362404
|
-
}
|
|
362405
|
-
}
|
|
362406
|
-
if (!anyCandidate) return patches;
|
|
362407
|
-
const headFiles = await this.listHeadFiles();
|
|
362408
|
-
const transient = /* @__PURE__ */ new Set();
|
|
362409
|
-
for (const [file4, state] of stateByFile) {
|
|
362410
|
-
if (state.created && state.deleted && !headFiles.has(file4)) {
|
|
362411
|
-
transient.add(file4);
|
|
362412
|
-
}
|
|
362413
|
-
}
|
|
362414
|
-
if (transient.size === 0) return patches;
|
|
362415
|
-
return patches.filter((patch5) => !patch5.files.every((f4) => transient.has(f4)));
|
|
362416
|
-
}
|
|
362417
|
-
/**
|
|
362418
|
-
* List every tracked path at HEAD (one `git ls-tree -r` invocation).
|
|
362419
|
-
* Returns an empty Set if HEAD is unborn or the invocation fails — the
|
|
362420
|
-
* caller then treats every candidate as "not in HEAD" and may collapse
|
|
362421
|
-
* more aggressively. On typical SDK repos this is a single sub-10ms call.
|
|
362422
|
-
*/
|
|
362423
|
-
async listHeadFiles() {
|
|
362424
|
-
try {
|
|
362425
|
-
const out = await this.git.exec(["ls-tree", "-r", "--name-only", "HEAD"]);
|
|
362426
|
-
return new Set(out.split("\n").map((l9) => l9.trim()).filter(Boolean));
|
|
362427
|
-
} catch {
|
|
362428
|
-
return /* @__PURE__ */ new Set();
|
|
362429
|
-
}
|
|
362430
|
-
}
|
|
362431
|
-
/**
|
|
362432
|
-
* Create a single composite patch for a merge commit by diffing the merge
|
|
362433
|
-
* commit against its first parent. This captures the net change of the
|
|
362434
|
-
* entire merged branch as one patch, eliminating accumulator chaining and
|
|
362435
|
-
* zero-net-change ghost conflicts.
|
|
362436
|
-
*/
|
|
362437
|
-
async createMergeCompositePatch(input3) {
|
|
362438
|
-
const { mergeCommit, firstParent, lastGen, lock } = input3;
|
|
362439
|
-
const forgottenHashes = new Set(lock.forgotten_hashes ?? []);
|
|
362440
|
-
const filesOutput = await this.git.exec([
|
|
362441
|
-
"diff",
|
|
362442
|
-
"--name-only",
|
|
362443
|
-
firstParent,
|
|
362444
|
-
mergeCommit.sha
|
|
362445
|
-
]);
|
|
362446
|
-
const allMergeFiles = filesOutput.trim().split("\n").filter(Boolean).filter((f4) => !INFRASTRUCTURE_FILES.has(f4)).filter((f4) => !f4.startsWith(".fern/"));
|
|
362447
|
-
const sensitiveMergeFiles = allMergeFiles.filter((f4) => isSensitiveFile(f4));
|
|
362448
|
-
const files = allMergeFiles.filter((f4) => !isSensitiveFile(f4));
|
|
362449
|
-
if (sensitiveMergeFiles.length > 0) {
|
|
362450
|
-
this.warnings.push(
|
|
362451
|
-
`Sensitive file(s) excluded from merge patch for commit ${mergeCommit.sha.slice(0, 7)}: ${sensitiveMergeFiles.join(", ")}. These files will not be tracked by replay.`
|
|
362452
|
-
);
|
|
362453
|
-
}
|
|
362454
|
-
if (files.length === 0) return null;
|
|
362455
|
-
const diff2 = await this.git.exec([
|
|
362456
|
-
"diff",
|
|
362457
|
-
firstParent,
|
|
362458
|
-
mergeCommit.sha,
|
|
362459
|
-
"--",
|
|
362460
|
-
...files
|
|
362461
|
-
]);
|
|
362462
|
-
if (!diff2.trim()) return null;
|
|
362463
|
-
const contentHash = this.computeContentHash(diff2);
|
|
362464
|
-
if (lock.patches.some((p14) => p14.content_hash === contentHash) || forgottenHashes.has(contentHash)) {
|
|
362465
|
-
return null;
|
|
362466
|
-
}
|
|
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
|
-
}
|
|
362473
|
-
return {
|
|
362474
|
-
id: `patch-merge-${mergeCommit.sha.slice(0, 8)}`,
|
|
362475
|
-
content_hash: contentHash,
|
|
362476
|
-
original_commit: mergeCommit.sha,
|
|
362477
|
-
original_message: mergeCommit.message,
|
|
362478
|
-
original_author: `${mergeCommit.authorName} <${mergeCommit.authorEmail}>`,
|
|
362479
|
-
base_generation: lastGen.commit_sha,
|
|
362480
|
-
files,
|
|
362481
|
-
patch_content: diff2,
|
|
362482
|
-
...Object.keys(theirsSnapshot).length > 0 ? { theirs_snapshot: theirsSnapshot } : {},
|
|
362483
|
-
...this.isCreationOnlyPatch(diff2) ? { user_owned: true } : {}
|
|
362484
|
-
};
|
|
362485
|
-
}
|
|
362486
|
-
/**
|
|
362487
|
-
* Detect patches via tree diff for non-linear history. Returns a composite patch.
|
|
362488
|
-
* Revert reconciliation is skipped here because tree-diff produces a single composite
|
|
362489
|
-
* patch from the aggregate diff — individual revert commits are not distinguishable.
|
|
362490
|
-
*/
|
|
362491
|
-
async detectPatchesViaTreeDiff(lastGen, commitKnownMissing) {
|
|
362492
|
-
const diffBase = await this.resolveDiffBase(lastGen, commitKnownMissing);
|
|
362493
|
-
if (!diffBase) {
|
|
362494
|
-
return this.detectPatchesViaCommitScan();
|
|
362495
|
-
}
|
|
362496
|
-
const filesOutput = await this.git.exec(["diff", "--name-only", diffBase, "HEAD"]);
|
|
362497
|
-
const allTreeFiles = filesOutput.trim().split("\n").filter(Boolean).filter((f4) => !INFRASTRUCTURE_FILES.has(f4)).filter((f4) => !f4.startsWith(".fern/"));
|
|
362498
|
-
const sensitiveTreeFiles = allTreeFiles.filter((f4) => isSensitiveFile(f4));
|
|
362499
|
-
const files = allTreeFiles.filter((f4) => !isSensitiveFile(f4));
|
|
362500
|
-
if (sensitiveTreeFiles.length > 0) {
|
|
362501
|
-
this.warnings.push(
|
|
362502
|
-
`Sensitive file(s) excluded from composite patch: ${sensitiveTreeFiles.join(", ")}. These files will not be tracked by replay.`
|
|
362503
|
-
);
|
|
362504
|
-
}
|
|
362505
|
-
if (files.length === 0) return { patches: [], revertedPatchIds: [] };
|
|
362506
|
-
const diff2 = await this.git.exec(["diff", diffBase, "HEAD", "--", ...files]);
|
|
362507
|
-
if (!diff2.trim()) return { patches: [], revertedPatchIds: [] };
|
|
362508
|
-
const contentHash = this.computeContentHash(diff2);
|
|
362509
|
-
const lock = this.lockManager.read();
|
|
362510
|
-
if (lock.patches.some((p14) => p14.content_hash === contentHash) || (lock.forgotten_hashes ?? []).includes(contentHash)) {
|
|
362511
|
-
return { patches: [], revertedPatchIds: [] };
|
|
362512
|
-
}
|
|
362513
|
-
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
|
-
}
|
|
362520
|
-
const compositePatch = {
|
|
362521
|
-
id: `patch-composite-${headSha.slice(0, 8)}`,
|
|
362522
|
-
content_hash: contentHash,
|
|
362523
|
-
original_commit: headSha,
|
|
362524
|
-
original_message: "Customer customizations (composite)",
|
|
362525
|
-
original_author: "composite",
|
|
362526
|
-
// Use diffBase when commit is unreachable — the applicator needs a reachable
|
|
362527
|
-
// reference to find base file content. diffBase may be the tree_hash.
|
|
362528
|
-
base_generation: commitKnownMissing ? diffBase : lastGen.commit_sha,
|
|
362529
|
-
files,
|
|
362530
|
-
patch_content: diff2,
|
|
362531
|
-
...Object.keys(theirsSnapshot).length > 0 ? { theirs_snapshot: theirsSnapshot } : {},
|
|
362532
|
-
...this.isCreationOnlyPatch(diff2) ? { user_owned: true } : {}
|
|
362533
|
-
};
|
|
362534
|
-
return { patches: [compositePatch], revertedPatchIds: [] };
|
|
362535
|
-
}
|
|
362536
|
-
/**
|
|
362537
|
-
* Last-resort detection when both generation commit and tree are unreachable.
|
|
362538
|
-
* Scans all commits from HEAD, filters against known lockfile patches, and
|
|
362539
|
-
* skips creation-only commits (squashed history after force push).
|
|
362540
|
-
*
|
|
362541
|
-
* Detected patches use the commit's parent as base_generation so the applicator
|
|
362542
|
-
* can find base file content from the parent's tree (which IS reachable).
|
|
362543
|
-
*/
|
|
362544
|
-
async detectPatchesViaCommitScan() {
|
|
362545
|
-
const lock = this.lockManager.read();
|
|
362546
|
-
const log4 = await this.git.exec([
|
|
362547
|
-
"log",
|
|
362548
|
-
"--max-count=200",
|
|
362549
|
-
"--format=%H%x00%an%x00%ae%x00%s",
|
|
362550
|
-
"HEAD",
|
|
362551
|
-
"--",
|
|
362552
|
-
this.sdkOutputDir
|
|
362553
|
-
]);
|
|
362554
|
-
if (!log4.trim()) {
|
|
362555
|
-
return { patches: [], revertedPatchIds: [] };
|
|
362556
|
-
}
|
|
362557
|
-
const commits = this.parseGitLog(log4);
|
|
362558
|
-
const newPatches = [];
|
|
362559
|
-
const forgottenHashes = new Set(lock.forgotten_hashes ?? []);
|
|
362560
|
-
const existingHashes = new Set(lock.patches.map((p14) => p14.content_hash));
|
|
362561
|
-
const existingCommits = new Set(lock.patches.map((p14) => p14.original_commit));
|
|
362562
|
-
for (const commit of commits) {
|
|
362563
|
-
if (isGenerationCommit(commit)) {
|
|
362564
|
-
continue;
|
|
362565
|
-
}
|
|
362566
|
-
const parents = await this.git.getCommitParents(commit.sha);
|
|
362567
|
-
if (parents.length > 1) {
|
|
362568
|
-
continue;
|
|
362569
|
-
}
|
|
362570
|
-
if (existingCommits.has(commit.sha)) {
|
|
362571
|
-
continue;
|
|
362572
|
-
}
|
|
362573
|
-
let patchContent;
|
|
362574
|
-
try {
|
|
362575
|
-
patchContent = await this.git.formatPatch(commit.sha);
|
|
362576
|
-
} catch {
|
|
362577
|
-
continue;
|
|
362578
|
-
}
|
|
362579
|
-
let contentHash = this.computeContentHash(patchContent);
|
|
362580
|
-
if (existingHashes.has(contentHash) || forgottenHashes.has(contentHash)) {
|
|
362581
|
-
continue;
|
|
362582
|
-
}
|
|
362583
|
-
if (this.isCreationOnlyPatch(patchContent)) {
|
|
362584
|
-
continue;
|
|
362585
|
-
}
|
|
362586
|
-
const filesOutput = await this.git.exec(["diff-tree", "--no-commit-id", "--name-only", "-r", commit.sha]);
|
|
362587
|
-
const allScanFiles = filesOutput.trim().split("\n").filter(Boolean).filter((f4) => !INFRASTRUCTURE_FILES.has(f4));
|
|
362588
|
-
const sensitiveScanFiles = allScanFiles.filter((f4) => isSensitiveFile(f4));
|
|
362589
|
-
const files = allScanFiles.filter((f4) => !isSensitiveFile(f4));
|
|
362590
|
-
if (sensitiveScanFiles.length > 0) {
|
|
362591
|
-
this.warnings.push(
|
|
362592
|
-
`Sensitive file(s) excluded from patch for commit ${commit.sha.slice(0, 7)}: ${sensitiveScanFiles.join(", ")}. These files will not be tracked by replay.`
|
|
362593
|
-
);
|
|
362594
|
-
if (files.length > 0) {
|
|
362595
|
-
if (parents.length === 0) {
|
|
362596
|
-
continue;
|
|
362597
|
-
}
|
|
362598
|
-
try {
|
|
362599
|
-
patchContent = await this.git.exec(["diff", parents[0], commit.sha, "--", ...files]);
|
|
362600
|
-
} catch {
|
|
362601
|
-
continue;
|
|
362602
|
-
}
|
|
362603
|
-
if (!patchContent.trim()) continue;
|
|
362604
|
-
contentHash = this.computeContentHash(patchContent);
|
|
362605
|
-
}
|
|
362606
|
-
}
|
|
362607
|
-
if (files.length === 0) {
|
|
362608
|
-
continue;
|
|
362609
|
-
}
|
|
362610
|
-
if (parents.length === 0) {
|
|
362611
|
-
continue;
|
|
362612
|
-
}
|
|
362613
|
-
const parentSha = parents[0];
|
|
362614
|
-
const theirsSnapshot = {};
|
|
362615
|
-
for (const file4 of files) {
|
|
362616
|
-
if (isBinaryFile(file4)) continue;
|
|
362617
|
-
const content5 = await this.git.showFile(commit.sha, file4).catch(() => null);
|
|
362618
|
-
if (content5 != null) theirsSnapshot[file4] = content5;
|
|
362619
|
-
}
|
|
362620
|
-
newPatches.push({
|
|
362621
|
-
id: `patch-${commit.sha.slice(0, 8)}`,
|
|
362622
|
-
content_hash: contentHash,
|
|
362623
|
-
original_commit: commit.sha,
|
|
362624
|
-
original_message: commit.message,
|
|
362625
|
-
original_author: `${commit.authorName} <${commit.authorEmail}>`,
|
|
362626
|
-
base_generation: parentSha,
|
|
362627
|
-
files,
|
|
362628
|
-
patch_content: patchContent,
|
|
362629
|
-
...Object.keys(theirsSnapshot).length > 0 ? { theirs_snapshot: theirsSnapshot } : {}
|
|
362630
|
-
});
|
|
362631
|
-
}
|
|
362632
|
-
newPatches.reverse();
|
|
362633
|
-
return { patches: newPatches, revertedPatchIds: [] };
|
|
362634
|
-
}
|
|
362635
|
-
/**
|
|
362636
|
-
* Check if a format-patch consists entirely of new-file creations.
|
|
362637
|
-
* Used to identify squashed commits after force push, which create all files
|
|
362638
|
-
* from scratch (--- /dev/null) rather than modifying existing files.
|
|
362639
|
-
*/
|
|
362640
|
-
isCreationOnlyPatch(patchContent) {
|
|
362641
|
-
const diffOldHeaders = patchContent.split("\n").filter((l9) => l9.startsWith("--- "));
|
|
362642
|
-
if (diffOldHeaders.length === 0) {
|
|
362643
|
-
return false;
|
|
362644
|
-
}
|
|
362645
|
-
return diffOldHeaders.every((l9) => l9 === "--- /dev/null");
|
|
362646
|
-
}
|
|
362647
|
-
/**
|
|
362648
|
-
* Resolve the best available diff base for a generation record.
|
|
362649
|
-
* Prefers commit_sha, falls back to tree_hash for unreachable commits.
|
|
362650
|
-
* When commitKnownMissing is true, skips the redundant commitExists check.
|
|
362651
|
-
*/
|
|
362652
|
-
async resolveDiffBase(gen, commitKnownMissing) {
|
|
362653
|
-
if (!commitKnownMissing && await this.git.commitExists(gen.commit_sha)) {
|
|
362654
|
-
return gen.commit_sha;
|
|
362655
|
-
}
|
|
362656
|
-
if (await this.git.treeExists(gen.tree_hash)) {
|
|
362657
|
-
return gen.tree_hash;
|
|
362658
|
-
}
|
|
362659
|
-
return null;
|
|
362660
|
-
}
|
|
362661
|
-
parseGitLog(log4) {
|
|
362662
|
-
return log4.trim().split("\n").map((line) => {
|
|
362663
|
-
const [sha, authorName, authorEmail, message] = line.split("\0");
|
|
362664
|
-
return { sha, authorName, authorEmail, message };
|
|
362665
|
-
});
|
|
362666
|
-
}
|
|
362667
|
-
getLastGeneration(lock) {
|
|
362668
|
-
return lock.generations.find((g8) => g8.commit_sha === lock.current_generation);
|
|
362669
|
-
}
|
|
362670
|
-
};
|
|
362671
362730
|
var ReplayCommitter = class {
|
|
362672
362731
|
git;
|
|
362673
362732
|
outputDir;
|
|
@@ -362770,13 +362829,13 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
362770
362829
|
}
|
|
362771
362830
|
};
|
|
362772
362831
|
var import_node_fs210 = require("fs");
|
|
362773
|
-
var
|
|
362774
|
-
var
|
|
362775
|
-
var
|
|
362832
|
+
var import_promises610 = require("fs/promises");
|
|
362833
|
+
var import_node_path82 = require("path");
|
|
362834
|
+
var import_minimatch32 = require_commonjs3();
|
|
362776
362835
|
init_GitClient();
|
|
362777
|
-
var
|
|
362836
|
+
var import_promises410 = require("fs/promises");
|
|
362778
362837
|
var import_node_os22 = require("os");
|
|
362779
|
-
var
|
|
362838
|
+
var import_node_path62 = require("path");
|
|
362780
362839
|
var import_node_child_process8 = require("child_process");
|
|
362781
362840
|
init_HybridReconstruction();
|
|
362782
362841
|
function normalizeHunkBody(hunk) {
|
|
@@ -362983,17 +363042,17 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
362983
363042
|
}
|
|
362984
363043
|
async function unifiedDiff(beforeContent, afterContent, filePath) {
|
|
362985
363044
|
if (beforeContent === afterContent) return "";
|
|
362986
|
-
const tmp18 = await (0,
|
|
363045
|
+
const tmp18 = await (0, import_promises410.mkdtemp)((0, import_node_path62.join)((0, import_node_os22.tmpdir)(), "fern-replay-pp-"));
|
|
362987
363046
|
try {
|
|
362988
|
-
const beforePath = (0,
|
|
362989
|
-
const afterPath = (0,
|
|
362990
|
-
await (0,
|
|
362991
|
-
await (0,
|
|
363047
|
+
const beforePath = (0, import_node_path62.join)(tmp18, "before");
|
|
363048
|
+
const afterPath = (0, import_node_path62.join)(tmp18, "after");
|
|
363049
|
+
await (0, import_promises410.writeFile)(beforePath, beforeContent, "utf-8");
|
|
363050
|
+
await (0, import_promises410.writeFile)(afterPath, afterContent, "utf-8");
|
|
362992
363051
|
const raw = await runGitDiffNoIndex(beforePath, afterPath);
|
|
362993
363052
|
if (!raw.trim()) return "";
|
|
362994
363053
|
return rewriteDiffHeaders(raw, filePath);
|
|
362995
363054
|
} finally {
|
|
362996
|
-
await (0,
|
|
363055
|
+
await (0, import_promises410.rm)(tmp18, { recursive: true, force: true });
|
|
362997
363056
|
}
|
|
362998
363057
|
}
|
|
362999
363058
|
function runGitDiffNoIndex(beforePath, afterPath) {
|
|
@@ -363064,27 +363123,513 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363064
363123
|
const trailer = hasTrailingNewline ? "" : "\n\";
|
|
363065
363124
|
return header + "\n" + body + trailer + "\n";
|
|
363066
363125
|
}
|
|
363126
|
+
var import_minimatch22 = require_commonjs3();
|
|
363127
|
+
var FileOwnership = class {
|
|
363128
|
+
constructor(git) {
|
|
363129
|
+
this.git = git;
|
|
363130
|
+
}
|
|
363131
|
+
/**
|
|
363132
|
+
* Canonical resolution. Returns true if `file` is user-owned per:
|
|
363133
|
+
*
|
|
363134
|
+
* 1. `patch.user_owned === true` — authoritative, persisted flag.
|
|
363135
|
+
* 2. `file === ".fernignore"` — always user-owned.
|
|
363136
|
+
* 3. File matches a `.fernignore` pattern (via minimatch).
|
|
363137
|
+
* 4. Patch content shows `--- /dev/null` for this file (legacy-safe
|
|
363138
|
+
* signal for patches predating `user_owned` or whose flag was lost).
|
|
363139
|
+
* 5. Tree-based check against `patch.base_generation` when reachable
|
|
363140
|
+
* AND distinct from `currentGenSha`. Unreachable base → conservative
|
|
363141
|
+
* user-owned (silent-absorption safety net), with a one-time warning
|
|
363142
|
+
* per base SHA.
|
|
363143
|
+
* 6. Final fallback: check `currentGenSha` when no usable base exists
|
|
363144
|
+
* (legacy one-gen lockfile).
|
|
363145
|
+
*/
|
|
363146
|
+
async resolveCanonical(ctx) {
|
|
363147
|
+
const { file: file4, originalFileName, patch: patch5, currentGenSha, fernignorePatterns, warnedGens, warnings } = ctx;
|
|
363148
|
+
if (patch5.user_owned) return true;
|
|
363149
|
+
if (file4 === ".fernignore") return true;
|
|
363150
|
+
if (fernignorePatterns.some((p14) => file4 === p14 || (0, import_minimatch22.minimatch)(file4, p14))) return true;
|
|
363151
|
+
if (fileIsCreationInPatchContent(patch5.patch_content, originalFileName ?? file4)) {
|
|
363152
|
+
return true;
|
|
363153
|
+
}
|
|
363154
|
+
const base4 = patch5.base_generation;
|
|
363155
|
+
if (base4 && base4 !== currentGenSha) {
|
|
363156
|
+
const reachable = await this.git.commitExists(base4) || await this.git.treeExists(base4);
|
|
363157
|
+
if (!reachable) {
|
|
363158
|
+
if (!warnedGens.has(base4)) {
|
|
363159
|
+
warnedGens.add(base4);
|
|
363160
|
+
warnings.push(
|
|
363161
|
+
`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.`
|
|
363162
|
+
);
|
|
363163
|
+
}
|
|
363164
|
+
return true;
|
|
363165
|
+
}
|
|
363166
|
+
return await this.git.showFile(base4, originalFileName ?? file4) === null;
|
|
363167
|
+
}
|
|
363168
|
+
return await this.git.showFile(currentGenSha, originalFileName ?? file4) === null;
|
|
363169
|
+
}
|
|
363170
|
+
};
|
|
363171
|
+
function fileIsCreationInPatchContent(patchContent, file4) {
|
|
363172
|
+
const lines = patchContent.split("\n");
|
|
363173
|
+
let inFileSection = false;
|
|
363174
|
+
for (const line of lines) {
|
|
363175
|
+
if (line.startsWith("diff --git ")) {
|
|
363176
|
+
inFileSection = line.includes(` b/${file4}`) || line.endsWith(` b/${file4}`);
|
|
363177
|
+
continue;
|
|
363178
|
+
}
|
|
363179
|
+
if (inFileSection && line.startsWith("--- ")) {
|
|
363180
|
+
return line === "--- /dev/null";
|
|
363181
|
+
}
|
|
363182
|
+
}
|
|
363183
|
+
return false;
|
|
363184
|
+
}
|
|
363185
|
+
var FirstGenerationFlow = class {
|
|
363186
|
+
constructor(service) {
|
|
363187
|
+
this.service = service;
|
|
363188
|
+
}
|
|
363189
|
+
async prepare(options2) {
|
|
363190
|
+
if (options2?.dryRun) {
|
|
363191
|
+
const headSha = await this.service.git.exec(["rev-parse", "HEAD"]).then((s9) => s9.trim()).catch(() => "");
|
|
363192
|
+
return {
|
|
363193
|
+
flow: "first-generation",
|
|
363194
|
+
previousGenerationSha: null,
|
|
363195
|
+
currentGenerationSha: headSha,
|
|
363196
|
+
baseBranchHead: options2.baseBranchHead ?? null,
|
|
363197
|
+
_prepared: {
|
|
363198
|
+
kind: "terminal",
|
|
363199
|
+
flow: "first-generation",
|
|
363200
|
+
preparedReport: firstGenerationReport()
|
|
363201
|
+
}
|
|
363202
|
+
};
|
|
363203
|
+
}
|
|
363204
|
+
const commitOpts = options2 ? {
|
|
363205
|
+
cliVersion: options2.cliVersion ?? "unknown",
|
|
363206
|
+
generatorVersions: options2.generatorVersions ?? {},
|
|
363207
|
+
baseBranchHead: options2.baseBranchHead
|
|
363208
|
+
} : void 0;
|
|
363209
|
+
await this.service.committer.commitGeneration("Initial SDK generation", commitOpts);
|
|
363210
|
+
const genRecord = await this.service.committer.createGenerationRecord(commitOpts);
|
|
363211
|
+
this.service.lockManager.initializeInMemory(genRecord);
|
|
363212
|
+
return {
|
|
363213
|
+
flow: "first-generation",
|
|
363214
|
+
previousGenerationSha: null,
|
|
363215
|
+
currentGenerationSha: genRecord.commit_sha,
|
|
363216
|
+
baseBranchHead: options2?.baseBranchHead ?? null,
|
|
363217
|
+
_prepared: {
|
|
363218
|
+
kind: "active",
|
|
363219
|
+
flow: "first-generation",
|
|
363220
|
+
patchesToApply: [],
|
|
363221
|
+
newPatchIds: /* @__PURE__ */ new Set(),
|
|
363222
|
+
detectorWarnings: [],
|
|
363223
|
+
revertedCount: 0,
|
|
363224
|
+
genSha: genRecord.commit_sha,
|
|
363225
|
+
optionsSnapshot: options2
|
|
363226
|
+
}
|
|
363227
|
+
};
|
|
363228
|
+
}
|
|
363229
|
+
async apply(_prep) {
|
|
363230
|
+
this.service.lockManager.save();
|
|
363231
|
+
return firstGenerationReport();
|
|
363232
|
+
}
|
|
363233
|
+
};
|
|
363234
|
+
function firstGenerationReport() {
|
|
363235
|
+
return {
|
|
363236
|
+
flow: "first-generation",
|
|
363237
|
+
patchesDetected: 0,
|
|
363238
|
+
patchesApplied: 0,
|
|
363239
|
+
patchesWithConflicts: 0,
|
|
363240
|
+
patchesSkipped: 0,
|
|
363241
|
+
conflicts: []
|
|
363242
|
+
};
|
|
363243
|
+
}
|
|
363244
|
+
var SkipApplicationFlow = class {
|
|
363245
|
+
constructor(service) {
|
|
363246
|
+
this.service = service;
|
|
363247
|
+
}
|
|
363248
|
+
async prepare(options2) {
|
|
363249
|
+
const commitOpts = options2 ? {
|
|
363250
|
+
cliVersion: options2.cliVersion ?? "unknown",
|
|
363251
|
+
generatorVersions: options2.generatorVersions ?? {},
|
|
363252
|
+
baseBranchHead: options2.baseBranchHead
|
|
363253
|
+
} : void 0;
|
|
363254
|
+
await this.service.committer.commitGeneration("Update SDK (replay skipped)", commitOpts);
|
|
363255
|
+
await this.service.cleanupStaleConflictMarkers();
|
|
363256
|
+
const genRecord = await this.service.committer.createGenerationRecord(commitOpts);
|
|
363257
|
+
let previousGenerationSha = null;
|
|
363258
|
+
try {
|
|
363259
|
+
const lock = this.service.lockManager.read();
|
|
363260
|
+
previousGenerationSha = lock.current_generation ?? null;
|
|
363261
|
+
this.service.lockManager.addGeneration(genRecord);
|
|
363262
|
+
} catch (error50) {
|
|
363263
|
+
if (error50 instanceof LockfileNotFoundError) {
|
|
363264
|
+
this.service.lockManager.initializeInMemory(genRecord);
|
|
363265
|
+
} else {
|
|
363266
|
+
throw error50;
|
|
363267
|
+
}
|
|
363268
|
+
}
|
|
363269
|
+
this.service.lockManager.setReplaySkippedAt((/* @__PURE__ */ new Date()).toISOString());
|
|
363270
|
+
return {
|
|
363271
|
+
flow: "skip-application",
|
|
363272
|
+
previousGenerationSha,
|
|
363273
|
+
currentGenerationSha: genRecord.commit_sha,
|
|
363274
|
+
baseBranchHead: options2?.baseBranchHead ?? null,
|
|
363275
|
+
_prepared: {
|
|
363276
|
+
kind: "active",
|
|
363277
|
+
flow: "skip-application",
|
|
363278
|
+
patchesToApply: [],
|
|
363279
|
+
newPatchIds: /* @__PURE__ */ new Set(),
|
|
363280
|
+
detectorWarnings: [],
|
|
363281
|
+
revertedCount: 0,
|
|
363282
|
+
genSha: genRecord.commit_sha,
|
|
363283
|
+
optionsSnapshot: options2
|
|
363284
|
+
}
|
|
363285
|
+
};
|
|
363286
|
+
}
|
|
363287
|
+
async apply(_prep, options2) {
|
|
363288
|
+
this.service.lockManager.save();
|
|
363289
|
+
const credentialWarnings = [...this.service.lockManager.warnings];
|
|
363290
|
+
if (!options2?.stageOnly) {
|
|
363291
|
+
await this.service.committer.commitReplay(0);
|
|
363292
|
+
} else {
|
|
363293
|
+
await this.service.committer.stageAll();
|
|
363294
|
+
}
|
|
363295
|
+
return {
|
|
363296
|
+
flow: "skip-application",
|
|
363297
|
+
patchesDetected: 0,
|
|
363298
|
+
patchesApplied: 0,
|
|
363299
|
+
patchesWithConflicts: 0,
|
|
363300
|
+
patchesSkipped: 0,
|
|
363301
|
+
conflicts: [],
|
|
363302
|
+
warnings: credentialWarnings.length > 0 ? credentialWarnings : void 0
|
|
363303
|
+
};
|
|
363304
|
+
}
|
|
363305
|
+
};
|
|
363306
|
+
var NoPatchesFlow = class {
|
|
363307
|
+
constructor(service) {
|
|
363308
|
+
this.service = service;
|
|
363309
|
+
}
|
|
363310
|
+
async prepare(options2) {
|
|
363311
|
+
const preLock = this.service.lockManager.read();
|
|
363312
|
+
const previousGenerationSha = preLock.current_generation ?? null;
|
|
363313
|
+
let { patches: newPatches, revertedPatchIds } = await this.service.detector.detectNewPatches();
|
|
363314
|
+
const detectorWarnings = [...this.service.detector.warnings];
|
|
363315
|
+
if (options2?.dryRun) {
|
|
363316
|
+
const dryRunWarnings = [...detectorWarnings, ...this.service.warnings];
|
|
363317
|
+
const preparedReport = {
|
|
363318
|
+
flow: "no-patches",
|
|
363319
|
+
patchesDetected: newPatches.length,
|
|
363320
|
+
patchesApplied: 0,
|
|
363321
|
+
patchesWithConflicts: 0,
|
|
363322
|
+
patchesSkipped: 0,
|
|
363323
|
+
patchesReverted: revertedPatchIds.length,
|
|
363324
|
+
conflicts: [],
|
|
363325
|
+
wouldApply: newPatches,
|
|
363326
|
+
warnings: dryRunWarnings.length > 0 ? dryRunWarnings : void 0
|
|
363327
|
+
};
|
|
363328
|
+
const headSha = await this.service.git.exec(["rev-parse", "HEAD"]).then((s9) => s9.trim()).catch(() => "");
|
|
363329
|
+
return {
|
|
363330
|
+
flow: "no-patches",
|
|
363331
|
+
previousGenerationSha,
|
|
363332
|
+
currentGenerationSha: headSha,
|
|
363333
|
+
baseBranchHead: options2.baseBranchHead ?? null,
|
|
363334
|
+
_prepared: {
|
|
363335
|
+
kind: "terminal",
|
|
363336
|
+
flow: "no-patches",
|
|
363337
|
+
preparedReport
|
|
363338
|
+
}
|
|
363339
|
+
};
|
|
363340
|
+
}
|
|
363341
|
+
{
|
|
363342
|
+
const preCurrentGen = preLock.current_generation;
|
|
363343
|
+
const uniqueFiles = [...new Set(newPatches.flatMap((p14) => p14.files))];
|
|
363344
|
+
const absorbedFiles = /* @__PURE__ */ new Set();
|
|
363345
|
+
for (const file4 of uniqueFiles) {
|
|
363346
|
+
const diff2 = await this.service.git.exec(["diff", preCurrentGen, "HEAD", "--", file4]).catch(() => null);
|
|
363347
|
+
if (diff2 !== null && !diff2.trim()) {
|
|
363348
|
+
absorbedFiles.add(file4);
|
|
363349
|
+
}
|
|
363350
|
+
}
|
|
363351
|
+
if (absorbedFiles.size > 0) {
|
|
363352
|
+
newPatches = newPatches.filter(
|
|
363353
|
+
(p14) => p14.files.length === 0 || !p14.files.every((f4) => absorbedFiles.has(f4))
|
|
363354
|
+
);
|
|
363355
|
+
}
|
|
363356
|
+
}
|
|
363357
|
+
for (const id2 of revertedPatchIds) {
|
|
363358
|
+
try {
|
|
363359
|
+
this.service.lockManager.removePatch(id2);
|
|
363360
|
+
} catch {
|
|
363361
|
+
}
|
|
363362
|
+
}
|
|
363363
|
+
const commitOpts = options2 ? {
|
|
363364
|
+
cliVersion: options2.cliVersion ?? "unknown",
|
|
363365
|
+
generatorVersions: options2.generatorVersions ?? {},
|
|
363366
|
+
baseBranchHead: options2.baseBranchHead
|
|
363367
|
+
} : void 0;
|
|
363368
|
+
await this.service.committer.commitGeneration("Update SDK", commitOpts);
|
|
363369
|
+
await this.service.cleanupStaleConflictMarkers();
|
|
363370
|
+
const genRecord = await this.service.committer.createGenerationRecord(commitOpts);
|
|
363371
|
+
this.service.lockManager.addGeneration(genRecord);
|
|
363372
|
+
return {
|
|
363373
|
+
flow: "no-patches",
|
|
363374
|
+
previousGenerationSha,
|
|
363375
|
+
currentGenerationSha: genRecord.commit_sha,
|
|
363376
|
+
baseBranchHead: options2?.baseBranchHead ?? null,
|
|
363377
|
+
_prepared: {
|
|
363378
|
+
kind: "active",
|
|
363379
|
+
flow: "no-patches",
|
|
363380
|
+
patchesToApply: newPatches,
|
|
363381
|
+
newPatchIds: new Set(newPatches.map((p14) => p14.id)),
|
|
363382
|
+
detectorWarnings,
|
|
363383
|
+
revertedCount: revertedPatchIds.length,
|
|
363384
|
+
genSha: genRecord.commit_sha,
|
|
363385
|
+
optionsSnapshot: options2
|
|
363386
|
+
}
|
|
363387
|
+
};
|
|
363388
|
+
}
|
|
363389
|
+
async apply(prep, options2) {
|
|
363390
|
+
assertActive(prep);
|
|
363391
|
+
const newPatches = prep._prepared.patchesToApply;
|
|
363392
|
+
const detectorWarnings = prep._prepared.detectorWarnings;
|
|
363393
|
+
const genSha = prep._prepared.genSha;
|
|
363394
|
+
let results = [];
|
|
363395
|
+
if (newPatches.length > 0) {
|
|
363396
|
+
results = await this.service.applicator.applyPatches(newPatches);
|
|
363397
|
+
this.service._lastApplyResults = results;
|
|
363398
|
+
this.service.recordDroppedContextLineWarnings(results);
|
|
363399
|
+
this.service.revertConflictingFiles(results);
|
|
363400
|
+
for (const result of results) {
|
|
363401
|
+
if (result.status === "conflict") {
|
|
363402
|
+
result.patch.status = "unresolved";
|
|
363403
|
+
}
|
|
363404
|
+
}
|
|
363405
|
+
}
|
|
363406
|
+
const rebaseCounts = await this.service.rebasePatches(results, genSha);
|
|
363407
|
+
for (const patch5 of newPatches) {
|
|
363408
|
+
if (!rebaseCounts.absorbedPatchIds.has(patch5.id)) {
|
|
363409
|
+
this.service.lockManager.addPatch(patch5);
|
|
363410
|
+
}
|
|
363411
|
+
}
|
|
363412
|
+
this.service.lockManager.save();
|
|
363413
|
+
this.service.warnings.push(...this.service.lockManager.warnings);
|
|
363414
|
+
if (newPatches.length > 0) {
|
|
363415
|
+
if (!options2?.stageOnly) {
|
|
363416
|
+
const appliedCount = results.filter((r5) => r5.status === "applied").length;
|
|
363417
|
+
const buckets = computeCommitBuckets(results, rebaseCounts.absorbedPatchIds, newPatches);
|
|
363418
|
+
await this.service.committer.commitReplay(appliedCount, newPatches, void 0, { buckets });
|
|
363419
|
+
} else {
|
|
363420
|
+
await this.service.committer.stageAll();
|
|
363421
|
+
}
|
|
363422
|
+
}
|
|
363423
|
+
const warnings = [...detectorWarnings, ...this.service.warnings];
|
|
363424
|
+
return this.service.buildReport(
|
|
363425
|
+
"no-patches",
|
|
363426
|
+
newPatches,
|
|
363427
|
+
results,
|
|
363428
|
+
prep._prepared.optionsSnapshot,
|
|
363429
|
+
warnings,
|
|
363430
|
+
rebaseCounts,
|
|
363431
|
+
void 0,
|
|
363432
|
+
prep._prepared.revertedCount
|
|
363433
|
+
);
|
|
363434
|
+
}
|
|
363435
|
+
};
|
|
363436
|
+
var NormalRegenerationFlow = class {
|
|
363437
|
+
constructor(service) {
|
|
363438
|
+
this.service = service;
|
|
363439
|
+
}
|
|
363440
|
+
async prepare(options2) {
|
|
363441
|
+
const preLock = this.service.lockManager.read();
|
|
363442
|
+
const previousGenerationSha = preLock.current_generation ?? null;
|
|
363443
|
+
if (options2?.dryRun) {
|
|
363444
|
+
const existingPatches2 = this.service.lockManager.getPatches();
|
|
363445
|
+
const { patches: newPatches2, revertedPatchIds: dryRunReverted } = await this.service.detector.detectNewPatches();
|
|
363446
|
+
const warnings = [...this.service.detector.warnings, ...this.service.warnings];
|
|
363447
|
+
const allPatches2 = [...existingPatches2, ...newPatches2];
|
|
363448
|
+
const preparedReport = {
|
|
363449
|
+
flow: "normal-regeneration",
|
|
363450
|
+
patchesDetected: allPatches2.length,
|
|
363451
|
+
patchesApplied: 0,
|
|
363452
|
+
patchesWithConflicts: 0,
|
|
363453
|
+
patchesSkipped: 0,
|
|
363454
|
+
patchesReverted: dryRunReverted.length,
|
|
363455
|
+
conflicts: [],
|
|
363456
|
+
wouldApply: allPatches2,
|
|
363457
|
+
warnings: warnings.length > 0 ? warnings : void 0
|
|
363458
|
+
};
|
|
363459
|
+
const headSha = await this.service.git.exec(["rev-parse", "HEAD"]).then((s9) => s9.trim()).catch(() => "");
|
|
363460
|
+
return {
|
|
363461
|
+
flow: "normal-regeneration",
|
|
363462
|
+
previousGenerationSha,
|
|
363463
|
+
currentGenerationSha: headSha,
|
|
363464
|
+
baseBranchHead: options2.baseBranchHead ?? null,
|
|
363465
|
+
_prepared: {
|
|
363466
|
+
kind: "terminal",
|
|
363467
|
+
flow: "normal-regeneration",
|
|
363468
|
+
preparedReport
|
|
363469
|
+
}
|
|
363470
|
+
};
|
|
363471
|
+
}
|
|
363472
|
+
let existingPatches = this.service.lockManager.getPatches();
|
|
363473
|
+
const preRebasePatchIds = new Set(existingPatches.map((p14) => p14.id));
|
|
363474
|
+
const preRebaseCounts = await this.service.preGenerationRebase(existingPatches);
|
|
363475
|
+
const postRebasePatchIds = new Set(this.service.lockManager.getPatches().map((p14) => p14.id));
|
|
363476
|
+
const removedByPreRebase = existingPatches.filter(
|
|
363477
|
+
(p14) => preRebasePatchIds.has(p14.id) && !postRebasePatchIds.has(p14.id)
|
|
363478
|
+
);
|
|
363479
|
+
existingPatches = this.service.lockManager.getPatches();
|
|
363480
|
+
const seenHashes = /* @__PURE__ */ new Map();
|
|
363481
|
+
for (const p14 of existingPatches) {
|
|
363482
|
+
const priorPatchId = seenHashes.get(p14.content_hash);
|
|
363483
|
+
if (priorPatchId !== void 0) {
|
|
363484
|
+
this.service.lockManager.removePatch(p14.id);
|
|
363485
|
+
this.service.warnings.push(
|
|
363486
|
+
`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.`
|
|
363487
|
+
);
|
|
363488
|
+
} else {
|
|
363489
|
+
seenHashes.set(p14.content_hash, p14.id);
|
|
363490
|
+
}
|
|
363491
|
+
}
|
|
363492
|
+
existingPatches = this.service.lockManager.getPatches();
|
|
363493
|
+
let { patches: newPatches, revertedPatchIds } = await this.service.detector.detectNewPatches();
|
|
363494
|
+
const detectorWarnings = [...this.service.detector.warnings];
|
|
363495
|
+
if (removedByPreRebase.length > 0) {
|
|
363496
|
+
const removedOriginalCommits = new Set(removedByPreRebase.map((p14) => p14.original_commit));
|
|
363497
|
+
const removedOriginalMessages = new Set(removedByPreRebase.map((p14) => p14.original_message));
|
|
363498
|
+
newPatches = newPatches.filter((p14) => {
|
|
363499
|
+
if (removedOriginalCommits.has(p14.original_commit)) return false;
|
|
363500
|
+
if (isRevertCommit(p14.original_message)) {
|
|
363501
|
+
const revertedMsg = parseRevertedMessage(p14.original_message);
|
|
363502
|
+
if (revertedMsg && removedOriginalMessages.has(revertedMsg)) return false;
|
|
363503
|
+
}
|
|
363504
|
+
return true;
|
|
363505
|
+
});
|
|
363506
|
+
}
|
|
363507
|
+
for (const id2 of revertedPatchIds) {
|
|
363508
|
+
try {
|
|
363509
|
+
this.service.lockManager.removePatch(id2);
|
|
363510
|
+
} catch {
|
|
363511
|
+
}
|
|
363512
|
+
}
|
|
363513
|
+
const revertedSet = new Set(revertedPatchIds);
|
|
363514
|
+
existingPatches = existingPatches.filter((p14) => !revertedSet.has(p14.id));
|
|
363515
|
+
const allPatches = [...existingPatches, ...newPatches];
|
|
363516
|
+
const commitOpts = options2 ? {
|
|
363517
|
+
cliVersion: options2.cliVersion ?? "unknown",
|
|
363518
|
+
generatorVersions: options2.generatorVersions ?? {},
|
|
363519
|
+
baseBranchHead: options2.baseBranchHead
|
|
363520
|
+
} : void 0;
|
|
363521
|
+
await this.service.committer.commitGeneration("Update SDK", commitOpts);
|
|
363522
|
+
await this.service.cleanupStaleConflictMarkers();
|
|
363523
|
+
const genRecord = await this.service.committer.createGenerationRecord(commitOpts);
|
|
363524
|
+
this.service.lockManager.addGeneration(genRecord);
|
|
363525
|
+
return {
|
|
363526
|
+
flow: "normal-regeneration",
|
|
363527
|
+
previousGenerationSha,
|
|
363528
|
+
currentGenerationSha: genRecord.commit_sha,
|
|
363529
|
+
baseBranchHead: options2?.baseBranchHead ?? null,
|
|
363530
|
+
_prepared: {
|
|
363531
|
+
kind: "active",
|
|
363532
|
+
flow: "normal-regeneration",
|
|
363533
|
+
patchesToApply: allPatches,
|
|
363534
|
+
newPatchIds: new Set(newPatches.map((p14) => p14.id)),
|
|
363535
|
+
preRebaseCounts,
|
|
363536
|
+
detectorWarnings,
|
|
363537
|
+
revertedCount: revertedPatchIds.length,
|
|
363538
|
+
genSha: genRecord.commit_sha,
|
|
363539
|
+
optionsSnapshot: options2
|
|
363540
|
+
}
|
|
363541
|
+
};
|
|
363542
|
+
}
|
|
363543
|
+
async apply(prep, options2) {
|
|
363544
|
+
assertActive(prep);
|
|
363545
|
+
const allPatches = prep._prepared.patchesToApply;
|
|
363546
|
+
const newPatchIds = prep._prepared.newPatchIds;
|
|
363547
|
+
const detectorWarnings = prep._prepared.detectorWarnings;
|
|
363548
|
+
const genSha = prep._prepared.genSha;
|
|
363549
|
+
const results = await this.service.applicator.applyPatches(allPatches);
|
|
363550
|
+
this.service._lastApplyResults = results;
|
|
363551
|
+
this.service.recordDroppedContextLineWarnings(results);
|
|
363552
|
+
this.service.revertConflictingFiles(results);
|
|
363553
|
+
for (const result of results) {
|
|
363554
|
+
if (result.status === "conflict") {
|
|
363555
|
+
result.patch.status = "unresolved";
|
|
363556
|
+
}
|
|
363557
|
+
}
|
|
363558
|
+
const rebaseCounts = await this.service.rebasePatches(results, genSha);
|
|
363559
|
+
const newPatches = allPatches.filter((p14) => newPatchIds.has(p14.id));
|
|
363560
|
+
for (const patch5 of newPatches) {
|
|
363561
|
+
if (!rebaseCounts.absorbedPatchIds.has(patch5.id)) {
|
|
363562
|
+
this.service.lockManager.addPatch(patch5);
|
|
363563
|
+
}
|
|
363564
|
+
}
|
|
363565
|
+
for (const result of results) {
|
|
363566
|
+
if (result.status === "conflict") {
|
|
363567
|
+
try {
|
|
363568
|
+
this.service.lockManager.markPatchUnresolved(result.patch.id);
|
|
363569
|
+
} catch {
|
|
363570
|
+
}
|
|
363571
|
+
}
|
|
363572
|
+
}
|
|
363573
|
+
this.service.lockManager.save();
|
|
363574
|
+
this.service.warnings.push(...this.service.lockManager.warnings);
|
|
363575
|
+
if (options2?.stageOnly) {
|
|
363576
|
+
await this.service.committer.stageAll();
|
|
363577
|
+
} else {
|
|
363578
|
+
const appliedCount = results.filter((r5) => r5.status === "applied").length;
|
|
363579
|
+
const buckets = computeCommitBuckets(results, rebaseCounts.absorbedPatchIds, allPatches);
|
|
363580
|
+
await this.service.committer.commitReplay(appliedCount, allPatches, void 0, { buckets });
|
|
363581
|
+
}
|
|
363582
|
+
const warnings = [...detectorWarnings, ...this.service.warnings];
|
|
363583
|
+
return this.service.buildReport(
|
|
363584
|
+
"normal-regeneration",
|
|
363585
|
+
allPatches,
|
|
363586
|
+
results,
|
|
363587
|
+
prep._prepared.optionsSnapshot,
|
|
363588
|
+
warnings,
|
|
363589
|
+
rebaseCounts,
|
|
363590
|
+
prep._prepared.preRebaseCounts,
|
|
363591
|
+
prep._prepared.revertedCount
|
|
363592
|
+
);
|
|
363593
|
+
}
|
|
363594
|
+
};
|
|
363595
|
+
function assertActive(prep) {
|
|
363596
|
+
if (prep._prepared.kind !== "active") {
|
|
363597
|
+
throw new Error(
|
|
363598
|
+
`Flow.apply() called with terminal preparation (kind=${prep._prepared.kind}); terminal cases must be handled by applyPreparedReplay's dispatcher`
|
|
363599
|
+
);
|
|
363600
|
+
}
|
|
363601
|
+
}
|
|
363067
363602
|
var ReplayService = class {
|
|
363603
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
363068
363604
|
git;
|
|
363605
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
363069
363606
|
detector;
|
|
363607
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
363070
363608
|
applicator;
|
|
363609
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
363071
363610
|
committer;
|
|
363611
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
363072
363612
|
lockManager;
|
|
363613
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
363614
|
+
fileOwnership;
|
|
363615
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
363073
363616
|
outputDir;
|
|
363074
363617
|
/** @internal Test-only. Captures the last ReplayResult[] returned from applicator.applyPatches(). */
|
|
363075
363618
|
_lastApplyResults = [];
|
|
363076
363619
|
/**
|
|
363077
363620
|
* Service-level warnings accumulated during a single runReplay() call.
|
|
363078
363621
|
* Merged with `detector.warnings` into `ReplayReport.warnings` by each flow handler.
|
|
363079
|
-
* Populated by `
|
|
363080
|
-
* (shallow clone / aggressive `git gc --prune`) and we fall back to a
|
|
363081
|
-
* heuristic. Cleared at the top of `runReplay()`.
|
|
363622
|
+
* Populated by `FileOwnership.resolveCanonical` when a patch's base generation tree
|
|
363623
|
+
* is unreachable (shallow clone / aggressive `git gc --prune`) and we fall back to a
|
|
363624
|
+
* conservative heuristic. Cleared at the top of `runReplay()`.
|
|
363625
|
+
*
|
|
363626
|
+
* @internal Used by Flow implementations in `src/flows/`.
|
|
363082
363627
|
*/
|
|
363083
363628
|
warnings = [];
|
|
363084
363629
|
/**
|
|
363085
363630
|
* @internal Test-only accessor.
|
|
363086
363631
|
* Returns the ReplayApplicator instance used for the last run. Tests use this
|
|
363087
|
-
* to inspect the inter-patch accumulator after runReplay() completes (
|
|
363632
|
+
* to inspect the inter-patch accumulator after runReplay() completes (invariant I2).
|
|
363088
363633
|
*/
|
|
363089
363634
|
get applicatorRef() {
|
|
363090
363635
|
return this.applicator;
|
|
@@ -363092,8 +363637,8 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363092
363637
|
/**
|
|
363093
363638
|
* @internal Test-only accessor.
|
|
363094
363639
|
* Returns the ReplayResult[] from the most recent applyPatches() call. Tests use
|
|
363095
|
-
* this to filter applied-vs-conflicted-vs-absorbed patches for invariant
|
|
363096
|
-
*
|
|
363640
|
+
* this to filter applied-vs-conflicted-vs-absorbed patches for invariant
|
|
363641
|
+
* assertions. Returns an empty array if no patches have been applied yet.
|
|
363097
363642
|
*/
|
|
363098
363643
|
getLastResults() {
|
|
363099
363644
|
return this._lastApplyResults;
|
|
@@ -363106,6 +363651,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363106
363651
|
this.detector = new ReplayDetector(git, this.lockManager, outputDir);
|
|
363107
363652
|
this.applicator = new ReplayApplicator(git, this.lockManager, outputDir);
|
|
363108
363653
|
this.committer = new ReplayCommitter(git, outputDir);
|
|
363654
|
+
this.fileOwnership = new FileOwnership(git);
|
|
363109
363655
|
}
|
|
363110
363656
|
/**
|
|
363111
363657
|
* Phase 1 of the two-phase replay flow. Runs preGenerationRebase (when applicable),
|
|
@@ -363122,18 +363668,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363122
363668
|
async prepareReplay(options2) {
|
|
363123
363669
|
this.warnings = [];
|
|
363124
363670
|
this.detector.warnings.length = 0;
|
|
363125
|
-
|
|
363126
|
-
return this._prepareSkipApplication(options2);
|
|
363127
|
-
}
|
|
363128
|
-
const flow4 = this.determineFlow();
|
|
363129
|
-
switch (flow4) {
|
|
363130
|
-
case "first-generation":
|
|
363131
|
-
return this._prepareFirstGeneration(options2);
|
|
363132
|
-
case "no-patches":
|
|
363133
|
-
return this._prepareNoPatchesRegeneration(options2);
|
|
363134
|
-
case "normal-regeneration":
|
|
363135
|
-
return this._prepareNormalRegeneration(options2);
|
|
363136
|
-
}
|
|
363671
|
+
return this.selectFlow(options2).prepare(options2);
|
|
363137
363672
|
}
|
|
363138
363673
|
/**
|
|
363139
363674
|
* Phase 2 of the two-phase replay flow. Applies patches, rebases them against
|
|
@@ -363145,18 +363680,33 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363145
363680
|
* its own post-commit logic), this returns the precomputed report.
|
|
363146
363681
|
*/
|
|
363147
363682
|
async applyPreparedReplay(prep, options2) {
|
|
363148
|
-
if (prep._prepared.terminal) {
|
|
363683
|
+
if (prep._prepared.kind === "terminal") {
|
|
363149
363684
|
return prep._prepared.preparedReport;
|
|
363150
363685
|
}
|
|
363151
|
-
|
|
363686
|
+
return this.flowForKind(prep._prepared.flow).apply(prep, options2);
|
|
363687
|
+
}
|
|
363688
|
+
/**
|
|
363689
|
+
* Construct the flow for the current run. `selectFlow` is called from
|
|
363690
|
+
* `prepareReplay` and decides based on options + lockfile state.
|
|
363691
|
+
*/
|
|
363692
|
+
selectFlow(options2) {
|
|
363693
|
+
if (options2?.skipApplication) return new SkipApplicationFlow(this);
|
|
363694
|
+
return this.flowForKind(this.determineFlow());
|
|
363695
|
+
}
|
|
363696
|
+
/**
|
|
363697
|
+
* Construct a flow given its kind. Used by `applyPreparedReplay` to
|
|
363698
|
+
* route to the same kind that was selected in phase 1.
|
|
363699
|
+
*/
|
|
363700
|
+
flowForKind(kind) {
|
|
363701
|
+
switch (kind) {
|
|
363152
363702
|
case "first-generation":
|
|
363153
|
-
return this
|
|
363154
|
-
case "skip-application":
|
|
363155
|
-
return this._applySkipApplication(prep, options2);
|
|
363703
|
+
return new FirstGenerationFlow(this);
|
|
363156
363704
|
case "no-patches":
|
|
363157
|
-
return this
|
|
363705
|
+
return new NoPatchesFlow(this);
|
|
363158
363706
|
case "normal-regeneration":
|
|
363159
|
-
return this
|
|
363707
|
+
return new NormalRegenerationFlow(this);
|
|
363708
|
+
case "skip-application":
|
|
363709
|
+
return new SkipApplicationFlow(this);
|
|
363160
363710
|
}
|
|
363161
363711
|
}
|
|
363162
363712
|
/**
|
|
@@ -363240,423 +363790,12 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363240
363790
|
throw error50;
|
|
363241
363791
|
}
|
|
363242
363792
|
}
|
|
363243
|
-
firstGenerationReport() {
|
|
363244
|
-
return {
|
|
363245
|
-
flow: "first-generation",
|
|
363246
|
-
patchesDetected: 0,
|
|
363247
|
-
patchesApplied: 0,
|
|
363248
|
-
patchesWithConflicts: 0,
|
|
363249
|
-
patchesSkipped: 0,
|
|
363250
|
-
conflicts: []
|
|
363251
|
-
};
|
|
363252
|
-
}
|
|
363253
|
-
async _prepareFirstGeneration(options2) {
|
|
363254
|
-
if (options2?.dryRun) {
|
|
363255
|
-
const headSha = await this.git.exec(["rev-parse", "HEAD"]).then((s9) => s9.trim()).catch(() => "");
|
|
363256
|
-
return {
|
|
363257
|
-
flow: "first-generation",
|
|
363258
|
-
previousGenerationSha: null,
|
|
363259
|
-
currentGenerationSha: headSha,
|
|
363260
|
-
baseBranchHead: options2.baseBranchHead ?? null,
|
|
363261
|
-
_prepared: {
|
|
363262
|
-
flow: "first-generation",
|
|
363263
|
-
terminal: true,
|
|
363264
|
-
preparedReport: this.firstGenerationReport(),
|
|
363265
|
-
patchesToApply: [],
|
|
363266
|
-
newPatchIds: /* @__PURE__ */ new Set(),
|
|
363267
|
-
detectorWarnings: [],
|
|
363268
|
-
revertedCount: 0,
|
|
363269
|
-
genSha: headSha,
|
|
363270
|
-
optionsSnapshot: options2
|
|
363271
|
-
}
|
|
363272
|
-
};
|
|
363273
|
-
}
|
|
363274
|
-
const commitOpts = options2 ? {
|
|
363275
|
-
cliVersion: options2.cliVersion ?? "unknown",
|
|
363276
|
-
generatorVersions: options2.generatorVersions ?? {},
|
|
363277
|
-
baseBranchHead: options2.baseBranchHead
|
|
363278
|
-
} : void 0;
|
|
363279
|
-
await this.committer.commitGeneration("Initial SDK generation", commitOpts);
|
|
363280
|
-
const genRecord = await this.committer.createGenerationRecord(commitOpts);
|
|
363281
|
-
this.lockManager.initializeInMemory(genRecord);
|
|
363282
|
-
return {
|
|
363283
|
-
flow: "first-generation",
|
|
363284
|
-
previousGenerationSha: null,
|
|
363285
|
-
currentGenerationSha: genRecord.commit_sha,
|
|
363286
|
-
baseBranchHead: options2?.baseBranchHead ?? null,
|
|
363287
|
-
_prepared: {
|
|
363288
|
-
flow: "first-generation",
|
|
363289
|
-
terminal: false,
|
|
363290
|
-
patchesToApply: [],
|
|
363291
|
-
newPatchIds: /* @__PURE__ */ new Set(),
|
|
363292
|
-
detectorWarnings: [],
|
|
363293
|
-
revertedCount: 0,
|
|
363294
|
-
genSha: genRecord.commit_sha,
|
|
363295
|
-
optionsSnapshot: options2
|
|
363296
|
-
}
|
|
363297
|
-
};
|
|
363298
|
-
}
|
|
363299
|
-
async _applyFirstGeneration(_prep) {
|
|
363300
|
-
this.lockManager.save();
|
|
363301
|
-
return this.firstGenerationReport();
|
|
363302
|
-
}
|
|
363303
|
-
/**
|
|
363304
|
-
* Skip-application mode phase 1: commit the generation and update the
|
|
363305
|
-
* in-memory lockfile, but don't detect or apply patches. Sets a marker
|
|
363306
|
-
* so the next normal run skips revert detection in preGenerationRebase().
|
|
363307
|
-
*
|
|
363308
|
-
* Disk save is deferred to `_applySkipApplication`.
|
|
363309
|
-
*/
|
|
363310
|
-
async _prepareSkipApplication(options2) {
|
|
363311
|
-
const commitOpts = options2 ? {
|
|
363312
|
-
cliVersion: options2.cliVersion ?? "unknown",
|
|
363313
|
-
generatorVersions: options2.generatorVersions ?? {},
|
|
363314
|
-
baseBranchHead: options2.baseBranchHead
|
|
363315
|
-
} : void 0;
|
|
363316
|
-
await this.committer.commitGeneration("Update SDK (replay skipped)", commitOpts);
|
|
363317
|
-
await this.cleanupStaleConflictMarkers();
|
|
363318
|
-
const genRecord = await this.committer.createGenerationRecord(commitOpts);
|
|
363319
|
-
let previousGenerationSha = null;
|
|
363320
|
-
try {
|
|
363321
|
-
const lock = this.lockManager.read();
|
|
363322
|
-
previousGenerationSha = lock.current_generation ?? null;
|
|
363323
|
-
this.lockManager.addGeneration(genRecord);
|
|
363324
|
-
} catch (error50) {
|
|
363325
|
-
if (error50 instanceof LockfileNotFoundError) {
|
|
363326
|
-
this.lockManager.initializeInMemory(genRecord);
|
|
363327
|
-
} else {
|
|
363328
|
-
throw error50;
|
|
363329
|
-
}
|
|
363330
|
-
}
|
|
363331
|
-
this.lockManager.setReplaySkippedAt((/* @__PURE__ */ new Date()).toISOString());
|
|
363332
|
-
return {
|
|
363333
|
-
flow: "skip-application",
|
|
363334
|
-
previousGenerationSha,
|
|
363335
|
-
currentGenerationSha: genRecord.commit_sha,
|
|
363336
|
-
baseBranchHead: options2?.baseBranchHead ?? null,
|
|
363337
|
-
_prepared: {
|
|
363338
|
-
flow: "skip-application",
|
|
363339
|
-
terminal: false,
|
|
363340
|
-
patchesToApply: [],
|
|
363341
|
-
newPatchIds: /* @__PURE__ */ new Set(),
|
|
363342
|
-
detectorWarnings: [],
|
|
363343
|
-
revertedCount: 0,
|
|
363344
|
-
genSha: genRecord.commit_sha,
|
|
363345
|
-
optionsSnapshot: options2
|
|
363346
|
-
}
|
|
363347
|
-
};
|
|
363348
|
-
}
|
|
363349
|
-
async _applySkipApplication(_prep, options2) {
|
|
363350
|
-
this.lockManager.save();
|
|
363351
|
-
const credentialWarnings = [...this.lockManager.warnings];
|
|
363352
|
-
if (!options2?.stageOnly) {
|
|
363353
|
-
await this.committer.commitReplay(0);
|
|
363354
|
-
} else {
|
|
363355
|
-
await this.committer.stageAll();
|
|
363356
|
-
}
|
|
363357
|
-
return {
|
|
363358
|
-
flow: "skip-application",
|
|
363359
|
-
patchesDetected: 0,
|
|
363360
|
-
patchesApplied: 0,
|
|
363361
|
-
patchesWithConflicts: 0,
|
|
363362
|
-
patchesSkipped: 0,
|
|
363363
|
-
conflicts: [],
|
|
363364
|
-
warnings: credentialWarnings.length > 0 ? credentialWarnings : void 0
|
|
363365
|
-
};
|
|
363366
|
-
}
|
|
363367
|
-
async _prepareNoPatchesRegeneration(options2) {
|
|
363368
|
-
const preLock = this.lockManager.read();
|
|
363369
|
-
const previousGenerationSha = preLock.current_generation ?? null;
|
|
363370
|
-
let { patches: newPatches, revertedPatchIds } = await this.detector.detectNewPatches();
|
|
363371
|
-
const detectorWarnings = [...this.detector.warnings];
|
|
363372
|
-
if (options2?.dryRun) {
|
|
363373
|
-
const dryRunWarnings = [...detectorWarnings, ...this.warnings];
|
|
363374
|
-
const preparedReport = {
|
|
363375
|
-
flow: "no-patches",
|
|
363376
|
-
patchesDetected: newPatches.length,
|
|
363377
|
-
patchesApplied: 0,
|
|
363378
|
-
patchesWithConflicts: 0,
|
|
363379
|
-
patchesSkipped: 0,
|
|
363380
|
-
patchesReverted: revertedPatchIds.length,
|
|
363381
|
-
conflicts: [],
|
|
363382
|
-
wouldApply: newPatches,
|
|
363383
|
-
warnings: dryRunWarnings.length > 0 ? dryRunWarnings : void 0
|
|
363384
|
-
};
|
|
363385
|
-
const headSha = await this.git.exec(["rev-parse", "HEAD"]).then((s9) => s9.trim()).catch(() => "");
|
|
363386
|
-
return {
|
|
363387
|
-
flow: "no-patches",
|
|
363388
|
-
previousGenerationSha,
|
|
363389
|
-
currentGenerationSha: headSha,
|
|
363390
|
-
baseBranchHead: options2.baseBranchHead ?? null,
|
|
363391
|
-
_prepared: {
|
|
363392
|
-
flow: "no-patches",
|
|
363393
|
-
terminal: true,
|
|
363394
|
-
preparedReport,
|
|
363395
|
-
patchesToApply: [],
|
|
363396
|
-
newPatchIds: /* @__PURE__ */ new Set(),
|
|
363397
|
-
detectorWarnings,
|
|
363398
|
-
revertedCount: revertedPatchIds.length,
|
|
363399
|
-
genSha: headSha,
|
|
363400
|
-
optionsSnapshot: options2
|
|
363401
|
-
}
|
|
363402
|
-
};
|
|
363403
|
-
}
|
|
363404
|
-
{
|
|
363405
|
-
const preCurrentGen = preLock.current_generation;
|
|
363406
|
-
const uniqueFiles = [...new Set(newPatches.flatMap((p14) => p14.files))];
|
|
363407
|
-
const absorbedFiles = /* @__PURE__ */ new Set();
|
|
363408
|
-
for (const file4 of uniqueFiles) {
|
|
363409
|
-
const diff2 = await this.git.exec(["diff", preCurrentGen, "HEAD", "--", file4]).catch(() => null);
|
|
363410
|
-
if (diff2 !== null && !diff2.trim()) {
|
|
363411
|
-
absorbedFiles.add(file4);
|
|
363412
|
-
}
|
|
363413
|
-
}
|
|
363414
|
-
if (absorbedFiles.size > 0) {
|
|
363415
|
-
newPatches = newPatches.filter(
|
|
363416
|
-
(p14) => p14.files.length === 0 || !p14.files.every((f4) => absorbedFiles.has(f4))
|
|
363417
|
-
);
|
|
363418
|
-
}
|
|
363419
|
-
}
|
|
363420
|
-
for (const id2 of revertedPatchIds) {
|
|
363421
|
-
try {
|
|
363422
|
-
this.lockManager.removePatch(id2);
|
|
363423
|
-
} catch {
|
|
363424
|
-
}
|
|
363425
|
-
}
|
|
363426
|
-
const commitOpts = options2 ? {
|
|
363427
|
-
cliVersion: options2.cliVersion ?? "unknown",
|
|
363428
|
-
generatorVersions: options2.generatorVersions ?? {},
|
|
363429
|
-
baseBranchHead: options2.baseBranchHead
|
|
363430
|
-
} : void 0;
|
|
363431
|
-
await this.committer.commitGeneration("Update SDK", commitOpts);
|
|
363432
|
-
await this.cleanupStaleConflictMarkers();
|
|
363433
|
-
const genRecord = await this.committer.createGenerationRecord(commitOpts);
|
|
363434
|
-
this.lockManager.addGeneration(genRecord);
|
|
363435
|
-
return {
|
|
363436
|
-
flow: "no-patches",
|
|
363437
|
-
previousGenerationSha,
|
|
363438
|
-
currentGenerationSha: genRecord.commit_sha,
|
|
363439
|
-
baseBranchHead: options2?.baseBranchHead ?? null,
|
|
363440
|
-
_prepared: {
|
|
363441
|
-
flow: "no-patches",
|
|
363442
|
-
terminal: false,
|
|
363443
|
-
patchesToApply: newPatches,
|
|
363444
|
-
newPatchIds: new Set(newPatches.map((p14) => p14.id)),
|
|
363445
|
-
detectorWarnings,
|
|
363446
|
-
revertedCount: revertedPatchIds.length,
|
|
363447
|
-
genSha: genRecord.commit_sha,
|
|
363448
|
-
optionsSnapshot: options2
|
|
363449
|
-
}
|
|
363450
|
-
};
|
|
363451
|
-
}
|
|
363452
|
-
async _applyNoPatchesRegeneration(prep, options2) {
|
|
363453
|
-
const newPatches = prep._prepared.patchesToApply;
|
|
363454
|
-
const detectorWarnings = prep._prepared.detectorWarnings;
|
|
363455
|
-
const genSha = prep._prepared.genSha;
|
|
363456
|
-
let results = [];
|
|
363457
|
-
if (newPatches.length > 0) {
|
|
363458
|
-
results = await this.applicator.applyPatches(newPatches);
|
|
363459
|
-
this._lastApplyResults = results;
|
|
363460
|
-
this.recordDroppedContextLineWarnings(results);
|
|
363461
|
-
this.revertConflictingFiles(results);
|
|
363462
|
-
for (const result of results) {
|
|
363463
|
-
if (result.status === "conflict") {
|
|
363464
|
-
result.patch.status = "unresolved";
|
|
363465
|
-
}
|
|
363466
|
-
}
|
|
363467
|
-
}
|
|
363468
|
-
const rebaseCounts = await this.rebasePatches(results, genSha);
|
|
363469
|
-
for (const patch5 of newPatches) {
|
|
363470
|
-
if (!rebaseCounts.absorbedPatchIds.has(patch5.id)) {
|
|
363471
|
-
this.lockManager.addPatch(patch5);
|
|
363472
|
-
}
|
|
363473
|
-
}
|
|
363474
|
-
this.lockManager.save();
|
|
363475
|
-
this.warnings.push(...this.lockManager.warnings);
|
|
363476
|
-
if (newPatches.length > 0) {
|
|
363477
|
-
if (!options2?.stageOnly) {
|
|
363478
|
-
const appliedCount = results.filter((r5) => r5.status === "applied").length;
|
|
363479
|
-
const buckets = computeCommitBuckets(results, rebaseCounts.absorbedPatchIds, newPatches);
|
|
363480
|
-
await this.committer.commitReplay(appliedCount, newPatches, void 0, { buckets });
|
|
363481
|
-
} else {
|
|
363482
|
-
await this.committer.stageAll();
|
|
363483
|
-
}
|
|
363484
|
-
}
|
|
363485
|
-
const warnings = [...detectorWarnings, ...this.warnings];
|
|
363486
|
-
return this.buildReport(
|
|
363487
|
-
"no-patches",
|
|
363488
|
-
newPatches,
|
|
363489
|
-
results,
|
|
363490
|
-
prep._prepared.optionsSnapshot,
|
|
363491
|
-
warnings,
|
|
363492
|
-
rebaseCounts,
|
|
363493
|
-
void 0,
|
|
363494
|
-
prep._prepared.revertedCount
|
|
363495
|
-
);
|
|
363496
|
-
}
|
|
363497
|
-
async _prepareNormalRegeneration(options2) {
|
|
363498
|
-
const preLock = this.lockManager.read();
|
|
363499
|
-
const previousGenerationSha = preLock.current_generation ?? null;
|
|
363500
|
-
if (options2?.dryRun) {
|
|
363501
|
-
const existingPatches2 = this.lockManager.getPatches();
|
|
363502
|
-
const { patches: newPatches2, revertedPatchIds: dryRunReverted } = await this.detector.detectNewPatches();
|
|
363503
|
-
const warnings = [...this.detector.warnings, ...this.warnings];
|
|
363504
|
-
const allPatches2 = [...existingPatches2, ...newPatches2];
|
|
363505
|
-
const preparedReport = {
|
|
363506
|
-
flow: "normal-regeneration",
|
|
363507
|
-
patchesDetected: allPatches2.length,
|
|
363508
|
-
patchesApplied: 0,
|
|
363509
|
-
patchesWithConflicts: 0,
|
|
363510
|
-
patchesSkipped: 0,
|
|
363511
|
-
patchesReverted: dryRunReverted.length,
|
|
363512
|
-
conflicts: [],
|
|
363513
|
-
wouldApply: allPatches2,
|
|
363514
|
-
warnings: warnings.length > 0 ? warnings : void 0
|
|
363515
|
-
};
|
|
363516
|
-
const headSha = await this.git.exec(["rev-parse", "HEAD"]).then((s9) => s9.trim()).catch(() => "");
|
|
363517
|
-
return {
|
|
363518
|
-
flow: "normal-regeneration",
|
|
363519
|
-
previousGenerationSha,
|
|
363520
|
-
currentGenerationSha: headSha,
|
|
363521
|
-
baseBranchHead: options2.baseBranchHead ?? null,
|
|
363522
|
-
_prepared: {
|
|
363523
|
-
flow: "normal-regeneration",
|
|
363524
|
-
terminal: true,
|
|
363525
|
-
preparedReport,
|
|
363526
|
-
patchesToApply: [],
|
|
363527
|
-
newPatchIds: /* @__PURE__ */ new Set(),
|
|
363528
|
-
detectorWarnings: [...this.detector.warnings],
|
|
363529
|
-
revertedCount: dryRunReverted.length,
|
|
363530
|
-
genSha: headSha,
|
|
363531
|
-
optionsSnapshot: options2
|
|
363532
|
-
}
|
|
363533
|
-
};
|
|
363534
|
-
}
|
|
363535
|
-
let existingPatches = this.lockManager.getPatches();
|
|
363536
|
-
const preRebasePatchIds = new Set(existingPatches.map((p14) => p14.id));
|
|
363537
|
-
const preRebaseCounts = await this.preGenerationRebase(existingPatches);
|
|
363538
|
-
const postRebasePatchIds = new Set(this.lockManager.getPatches().map((p14) => p14.id));
|
|
363539
|
-
const removedByPreRebase = existingPatches.filter(
|
|
363540
|
-
(p14) => preRebasePatchIds.has(p14.id) && !postRebasePatchIds.has(p14.id)
|
|
363541
|
-
);
|
|
363542
|
-
existingPatches = this.lockManager.getPatches();
|
|
363543
|
-
const seenHashes = /* @__PURE__ */ new Map();
|
|
363544
|
-
for (const p14 of existingPatches) {
|
|
363545
|
-
const priorPatchId = seenHashes.get(p14.content_hash);
|
|
363546
|
-
if (priorPatchId !== void 0) {
|
|
363547
|
-
this.lockManager.removePatch(p14.id);
|
|
363548
|
-
this.warnings.push(
|
|
363549
|
-
`Consolidated patch ${p14.id} (commit ${(p14.original_commit || "<missing>").slice(0, 7)}) into prior patch ${priorPatchId}: byte-identical patch_content. Per-commit attribution preserved in git log; lockfile collapsed to avoid duplicate-apply corruption on next regen.`
|
|
363550
|
-
);
|
|
363551
|
-
} else {
|
|
363552
|
-
seenHashes.set(p14.content_hash, p14.id);
|
|
363553
|
-
}
|
|
363554
|
-
}
|
|
363555
|
-
existingPatches = this.lockManager.getPatches();
|
|
363556
|
-
let { patches: newPatches, revertedPatchIds } = await this.detector.detectNewPatches();
|
|
363557
|
-
const detectorWarnings = [...this.detector.warnings];
|
|
363558
|
-
if (removedByPreRebase.length > 0) {
|
|
363559
|
-
const removedOriginalCommits = new Set(removedByPreRebase.map((p14) => p14.original_commit));
|
|
363560
|
-
const removedOriginalMessages = new Set(removedByPreRebase.map((p14) => p14.original_message));
|
|
363561
|
-
newPatches = newPatches.filter((p14) => {
|
|
363562
|
-
if (removedOriginalCommits.has(p14.original_commit)) return false;
|
|
363563
|
-
if (isRevertCommit(p14.original_message)) {
|
|
363564
|
-
const revertedMsg = parseRevertedMessage(p14.original_message);
|
|
363565
|
-
if (revertedMsg && removedOriginalMessages.has(revertedMsg)) return false;
|
|
363566
|
-
}
|
|
363567
|
-
return true;
|
|
363568
|
-
});
|
|
363569
|
-
}
|
|
363570
|
-
for (const id2 of revertedPatchIds) {
|
|
363571
|
-
try {
|
|
363572
|
-
this.lockManager.removePatch(id2);
|
|
363573
|
-
} catch {
|
|
363574
|
-
}
|
|
363575
|
-
}
|
|
363576
|
-
const revertedSet = new Set(revertedPatchIds);
|
|
363577
|
-
existingPatches = existingPatches.filter((p14) => !revertedSet.has(p14.id));
|
|
363578
|
-
const allPatches = [...existingPatches, ...newPatches];
|
|
363579
|
-
const commitOpts = options2 ? {
|
|
363580
|
-
cliVersion: options2.cliVersion ?? "unknown",
|
|
363581
|
-
generatorVersions: options2.generatorVersions ?? {},
|
|
363582
|
-
baseBranchHead: options2.baseBranchHead
|
|
363583
|
-
} : void 0;
|
|
363584
|
-
await this.committer.commitGeneration("Update SDK", commitOpts);
|
|
363585
|
-
await this.cleanupStaleConflictMarkers();
|
|
363586
|
-
const genRecord = await this.committer.createGenerationRecord(commitOpts);
|
|
363587
|
-
this.lockManager.addGeneration(genRecord);
|
|
363588
|
-
return {
|
|
363589
|
-
flow: "normal-regeneration",
|
|
363590
|
-
previousGenerationSha,
|
|
363591
|
-
currentGenerationSha: genRecord.commit_sha,
|
|
363592
|
-
baseBranchHead: options2?.baseBranchHead ?? null,
|
|
363593
|
-
_prepared: {
|
|
363594
|
-
flow: "normal-regeneration",
|
|
363595
|
-
terminal: false,
|
|
363596
|
-
patchesToApply: allPatches,
|
|
363597
|
-
newPatchIds: new Set(newPatches.map((p14) => p14.id)),
|
|
363598
|
-
preRebaseCounts,
|
|
363599
|
-
detectorWarnings,
|
|
363600
|
-
revertedCount: revertedPatchIds.length,
|
|
363601
|
-
genSha: genRecord.commit_sha,
|
|
363602
|
-
optionsSnapshot: options2
|
|
363603
|
-
}
|
|
363604
|
-
};
|
|
363605
|
-
}
|
|
363606
|
-
async _applyNormalRegeneration(prep, options2) {
|
|
363607
|
-
const allPatches = prep._prepared.patchesToApply;
|
|
363608
|
-
const newPatchIds = prep._prepared.newPatchIds;
|
|
363609
|
-
const detectorWarnings = prep._prepared.detectorWarnings;
|
|
363610
|
-
const genSha = prep._prepared.genSha;
|
|
363611
|
-
const results = await this.applicator.applyPatches(allPatches);
|
|
363612
|
-
this._lastApplyResults = results;
|
|
363613
|
-
this.recordDroppedContextLineWarnings(results);
|
|
363614
|
-
this.revertConflictingFiles(results);
|
|
363615
|
-
for (const result of results) {
|
|
363616
|
-
if (result.status === "conflict") {
|
|
363617
|
-
result.patch.status = "unresolved";
|
|
363618
|
-
}
|
|
363619
|
-
}
|
|
363620
|
-
const rebaseCounts = await this.rebasePatches(results, genSha);
|
|
363621
|
-
const newPatches = allPatches.filter((p14) => newPatchIds.has(p14.id));
|
|
363622
|
-
for (const patch5 of newPatches) {
|
|
363623
|
-
if (!rebaseCounts.absorbedPatchIds.has(patch5.id)) {
|
|
363624
|
-
this.lockManager.addPatch(patch5);
|
|
363625
|
-
}
|
|
363626
|
-
}
|
|
363627
|
-
for (const result of results) {
|
|
363628
|
-
if (result.status === "conflict") {
|
|
363629
|
-
try {
|
|
363630
|
-
this.lockManager.markPatchUnresolved(result.patch.id);
|
|
363631
|
-
} catch {
|
|
363632
|
-
}
|
|
363633
|
-
}
|
|
363634
|
-
}
|
|
363635
|
-
this.lockManager.save();
|
|
363636
|
-
this.warnings.push(...this.lockManager.warnings);
|
|
363637
|
-
if (options2?.stageOnly) {
|
|
363638
|
-
await this.committer.stageAll();
|
|
363639
|
-
} else {
|
|
363640
|
-
const appliedCount = results.filter((r5) => r5.status === "applied").length;
|
|
363641
|
-
const buckets = computeCommitBuckets(results, rebaseCounts.absorbedPatchIds, allPatches);
|
|
363642
|
-
await this.committer.commitReplay(appliedCount, allPatches, void 0, { buckets });
|
|
363643
|
-
}
|
|
363644
|
-
const warnings = [...detectorWarnings, ...this.warnings];
|
|
363645
|
-
return this.buildReport(
|
|
363646
|
-
"normal-regeneration",
|
|
363647
|
-
allPatches,
|
|
363648
|
-
results,
|
|
363649
|
-
prep._prepared.optionsSnapshot,
|
|
363650
|
-
warnings,
|
|
363651
|
-
rebaseCounts,
|
|
363652
|
-
prep._prepared.preRebaseCounts,
|
|
363653
|
-
prep._prepared.revertedCount
|
|
363654
|
-
);
|
|
363655
|
-
}
|
|
363656
363793
|
/**
|
|
363657
363794
|
* Rebase cleanly applied patches so they are relative to the current generation.
|
|
363658
363795
|
* This prevents recurring conflicts on subsequent regenerations.
|
|
363659
363796
|
* Returns the number of patches rebased.
|
|
363797
|
+
*
|
|
363798
|
+
* @internal Used by Flow implementations in `src/flows/`.
|
|
363660
363799
|
*/
|
|
363661
363800
|
async rebasePatches(results, currentGenSha) {
|
|
363662
363801
|
let absorbed = 0;
|
|
@@ -363666,6 +363805,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363666
363805
|
const seenContentHashes = /* @__PURE__ */ new Map();
|
|
363667
363806
|
const absorbedPatchIds = /* @__PURE__ */ new Set();
|
|
363668
363807
|
const fernignorePatterns = this.readFernignorePatterns();
|
|
363808
|
+
const warnedGens = /* @__PURE__ */ new Set();
|
|
363669
363809
|
for (const result of results) {
|
|
363670
363810
|
if (result.resolvedFiles && Object.keys(result.resolvedFiles).length > 0) {
|
|
363671
363811
|
const patch5 = result.patch;
|
|
@@ -363710,21 +363850,20 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363710
363850
|
}
|
|
363711
363851
|
}
|
|
363712
363852
|
const isUserOwnedPerFile = await Promise.all(
|
|
363713
|
-
patch5.files.map(
|
|
363714
|
-
|
|
363715
|
-
|
|
363716
|
-
|
|
363717
|
-
|
|
363718
|
-
|
|
363719
|
-
|
|
363720
|
-
|
|
363721
|
-
|
|
363722
|
-
|
|
363723
|
-
|
|
363724
|
-
})
|
|
363853
|
+
patch5.files.map(
|
|
363854
|
+
(file4) => this.fileOwnership.resolveCanonical({
|
|
363855
|
+
file: file4,
|
|
363856
|
+
originalFileName: originalByResolved[file4],
|
|
363857
|
+
patch: patch5,
|
|
363858
|
+
currentGenSha,
|
|
363859
|
+
fernignorePatterns,
|
|
363860
|
+
warnedGens,
|
|
363861
|
+
warnings: this.warnings
|
|
363862
|
+
})
|
|
363863
|
+
)
|
|
363725
363864
|
);
|
|
363726
363865
|
const hasProtectedFiles = patch5.files.some(
|
|
363727
|
-
(file4) => file4 === ".fernignore" || fernignorePatterns.some((p14) => file4 === p14 || (0,
|
|
363866
|
+
(file4) => file4 === ".fernignore" || fernignorePatterns.some((p14) => file4 === p14 || (0, import_minimatch32.minimatch)(file4, p14))
|
|
363728
363867
|
);
|
|
363729
363868
|
const allUserOwned = isUserOwnedPerFile.every(Boolean);
|
|
363730
363869
|
if (allUserOwned && patch5.files.length > 0) {
|
|
@@ -363848,7 +363987,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363848
363987
|
for (const file4 of files) {
|
|
363849
363988
|
if (isBinaryFile(file4)) continue;
|
|
363850
363989
|
try {
|
|
363851
|
-
const content5 = await (0,
|
|
363990
|
+
const content5 = await (0, import_promises610.readFile)((0, import_node_path82.join)(this.outputDir, file4), "utf-8");
|
|
363852
363991
|
snapshot[file4] = content5;
|
|
363853
363992
|
} catch {
|
|
363854
363993
|
}
|
|
@@ -363856,10 +363995,16 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363856
363995
|
return snapshot;
|
|
363857
363996
|
}
|
|
363858
363997
|
/**
|
|
363859
|
-
*
|
|
363860
|
-
* `fromSha` and `toSha` touched any
|
|
363861
|
-
*
|
|
363862
|
-
*
|
|
363998
|
+
* Detects autoversion contamination — returns true when a
|
|
363999
|
+
* `[fern-autoversion]` commit between `fromSha` and `toSha` touched any
|
|
364000
|
+
* of the patch's `files`. Re-classifies each grep match via
|
|
364001
|
+
* `isGenerationCommit` so a customer commit that merely quotes the
|
|
364002
|
+
* marker in its body doesn't false-positive.
|
|
364003
|
+
*
|
|
364004
|
+
* Used by preGenerationRebase to skip the customer-content rebuild for
|
|
364005
|
+
* patches whose files were last touched by an autoversion step — a
|
|
364006
|
+
* naive `git diff currentGen HEAD` there would capture the
|
|
364007
|
+
* placeholder→semver swap as customer customization.
|
|
363863
364008
|
*/
|
|
363864
364009
|
async hasAutoversionContamination(fromSha, toSha, files) {
|
|
363865
364010
|
if (files.length === 0) return false;
|
|
@@ -363888,10 +364033,15 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363888
364033
|
return false;
|
|
363889
364034
|
}
|
|
363890
364035
|
/**
|
|
363891
|
-
*
|
|
363892
|
-
*
|
|
363893
|
-
*
|
|
363894
|
-
*
|
|
364036
|
+
* Rebuild patch_content from `theirs_snapshot` (captured at detection
|
|
364037
|
+
* time, predates pipeline content) rather than a HEAD-relative diff.
|
|
364038
|
+
* Returns `null` when snapshot is missing or doesn't cover every file
|
|
364039
|
+
* in `patch.files` — caller leaves the patch unchanged.
|
|
364040
|
+
*
|
|
364041
|
+
* Used as the autoversion-contamination escape hatch: when the
|
|
364042
|
+
* HEAD-relative diff would capture pipeline content (autoversion's
|
|
364043
|
+
* placeholder swap) as customer customization, the snapshot path
|
|
364044
|
+
* preserves the customer's actual intent.
|
|
363895
364045
|
*/
|
|
363896
364046
|
async computeSnapshotBasedPatchDiff(patch5, currentGen) {
|
|
363897
364047
|
if (!patch5.theirs_snapshot) return null;
|
|
@@ -363939,7 +364089,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
363939
364089
|
* **Skips patches sharing files with other patches.** HEAD's content
|
|
363940
364090
|
* for a file shared by N patches is cumulative across all of them, so
|
|
363941
364091
|
* a HEAD-fallback would falsely encode other patches' contributions
|
|
363942
|
-
* into one patch's snapshot — breaking
|
|
364092
|
+
* into one patch's snapshot — breaking cross-patch isolation
|
|
363943
364093
|
* if the snapshot fallback later fires. Per-patch snapshots only
|
|
363944
364094
|
* make sense when the patch owns its file. Multi-patch-same-file
|
|
363945
364095
|
* legacy lockfiles keep using line-anchored reconstruction (the
|
|
@@ -364001,72 +364151,6 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
364001
364151
|
);
|
|
364002
364152
|
}
|
|
364003
364153
|
}
|
|
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
364154
|
/**
|
|
364071
364155
|
* For conflict patches with mixed results (some files merged, some conflicted),
|
|
364072
364156
|
* check if the cleanly merged files were absorbed by the generator (empty diff).
|
|
@@ -364085,7 +364169,15 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
364085
364169
|
const warnedGens = /* @__PURE__ */ new Set();
|
|
364086
364170
|
const generatorCleanFiles = [];
|
|
364087
364171
|
for (const file4 of cleanFiles) {
|
|
364088
|
-
|
|
364172
|
+
const userOwned = await this.fileOwnership.resolveCanonical({
|
|
364173
|
+
file: file4,
|
|
364174
|
+
patch: patch5,
|
|
364175
|
+
currentGenSha,
|
|
364176
|
+
fernignorePatterns,
|
|
364177
|
+
warnedGens,
|
|
364178
|
+
warnings: this.warnings
|
|
364179
|
+
});
|
|
364180
|
+
if (userOwned) {
|
|
364089
364181
|
continue;
|
|
364090
364182
|
}
|
|
364091
364183
|
generatorCleanFiles.push(file4);
|
|
@@ -364114,6 +364206,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
364114
364206
|
* Pre-generation rebase: update patches using the customer's current state.
|
|
364115
364207
|
* Called BEFORE commitGeneration() while HEAD has customer code.
|
|
364116
364208
|
*/
|
|
364209
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
364117
364210
|
async preGenerationRebase(patches) {
|
|
364118
364211
|
const lock = this.lockManager.read();
|
|
364119
364212
|
const currentGen = lock.current_generation;
|
|
@@ -364152,7 +364245,15 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
364152
364245
|
if (patch5.user_owned) return true;
|
|
364153
364246
|
if (patch5.files.length === 0) return false;
|
|
364154
364247
|
for (const file4 of patch5.files) {
|
|
364155
|
-
|
|
364248
|
+
const userOwned = await this.fileOwnership.resolveCanonical({
|
|
364249
|
+
file: file4,
|
|
364250
|
+
patch: patch5,
|
|
364251
|
+
currentGenSha: currentGen,
|
|
364252
|
+
fernignorePatterns,
|
|
364253
|
+
warnedGens,
|
|
364254
|
+
warnings: this.warnings
|
|
364255
|
+
});
|
|
364256
|
+
if (!userOwned) {
|
|
364156
364257
|
return false;
|
|
364157
364258
|
}
|
|
364158
364259
|
}
|
|
@@ -364202,7 +364303,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
364202
364303
|
);
|
|
364203
364304
|
if (snapHasStaleMarkers) continue;
|
|
364204
364305
|
const isProtectedSnap = patch5.files.some(
|
|
364205
|
-
(file4) => file4 === ".fernignore" || fernignorePatterns.some((p14) => file4 === p14 || (0,
|
|
364306
|
+
(file4) => file4 === ".fernignore" || fernignorePatterns.some((p14) => file4 === p14 || (0, import_minimatch32.minimatch)(file4, p14))
|
|
364206
364307
|
);
|
|
364207
364308
|
if (isProtectedSnap) continue;
|
|
364208
364309
|
const snapHash = this.detector.computeContentHash(snapshotDiff);
|
|
@@ -364237,7 +364338,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
364237
364338
|
continue;
|
|
364238
364339
|
}
|
|
364239
364340
|
const isProtected = patch5.files.some(
|
|
364240
|
-
(file4) => file4 === ".fernignore" || fernignorePatterns.some((p14) => file4 === p14 || (0,
|
|
364341
|
+
(file4) => file4 === ".fernignore" || fernignorePatterns.some((p14) => file4 === p14 || (0, import_minimatch32.minimatch)(file4, p14))
|
|
364241
364342
|
);
|
|
364242
364343
|
if (isProtected) {
|
|
364243
364344
|
continue;
|
|
@@ -364344,6 +364445,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
364344
364445
|
* relied on them. This is the "surface or preserve, never silently drop"
|
|
364345
364446
|
* contract for legitimate-deletion cases.
|
|
364346
364447
|
*/
|
|
364448
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
364347
364449
|
recordDroppedContextLineWarnings(results) {
|
|
364348
364450
|
const PREVIEW_COUNT = 5;
|
|
364349
364451
|
for (const result of results) {
|
|
@@ -364366,12 +364468,13 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
364366
364468
|
* After applyPatches(), strip conflict markers from conflicting files
|
|
364367
364469
|
* so only clean content is committed. Keeps the Generated (OURS) side.
|
|
364368
364470
|
*/
|
|
364471
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
364369
364472
|
revertConflictingFiles(results) {
|
|
364370
364473
|
for (const result of results) {
|
|
364371
364474
|
if (result.status !== "conflict" || !result.fileResults) continue;
|
|
364372
364475
|
for (const fileResult of result.fileResults) {
|
|
364373
364476
|
if (fileResult.status !== "conflict") continue;
|
|
364374
|
-
const filePath = (0,
|
|
364477
|
+
const filePath = (0, import_node_path82.join)(this.outputDir, fileResult.file);
|
|
364375
364478
|
try {
|
|
364376
364479
|
const content5 = (0, import_node_fs210.readFileSync)(filePath, "utf-8");
|
|
364377
364480
|
const stripped = stripConflictMarkers(content5);
|
|
@@ -364387,13 +364490,14 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
364387
364490
|
* Restores files to their clean generated state from HEAD.
|
|
364388
364491
|
* Skips .fernignore-protected files to prevent overwriting user content.
|
|
364389
364492
|
*/
|
|
364493
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
364390
364494
|
async cleanupStaleConflictMarkers() {
|
|
364391
364495
|
const markerFiles = await this.git.exec(["grep", "-l", "<<<<<<< Generated", "--", "."]).catch(() => "");
|
|
364392
364496
|
const files = markerFiles.trim().split("\n").filter(Boolean);
|
|
364393
364497
|
if (files.length === 0) return;
|
|
364394
364498
|
const fernignorePatterns = this.readFernignorePatterns();
|
|
364395
364499
|
for (const file4 of files) {
|
|
364396
|
-
if (fernignorePatterns.some((pattern) => (0,
|
|
364500
|
+
if (fernignorePatterns.some((pattern) => (0, import_minimatch32.minimatch)(file4, pattern))) continue;
|
|
364397
364501
|
try {
|
|
364398
364502
|
await this.git.exec(["checkout", "HEAD", "--", file4]);
|
|
364399
364503
|
} catch {
|
|
@@ -364401,10 +364505,11 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
364401
364505
|
}
|
|
364402
364506
|
}
|
|
364403
364507
|
readFernignorePatterns() {
|
|
364404
|
-
const fernignorePath = (0,
|
|
364508
|
+
const fernignorePath = (0, import_node_path82.join)(this.outputDir, ".fernignore");
|
|
364405
364509
|
if (!(0, import_node_fs210.existsSync)(fernignorePath)) return [];
|
|
364406
364510
|
return (0, import_node_fs210.readFileSync)(fernignorePath, "utf-8").split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
364407
364511
|
}
|
|
364512
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
364408
364513
|
buildReport(flow4, patches, results, options2, warnings, rebaseCounts, preRebaseCounts, patchesReverted) {
|
|
364409
364514
|
const conflictResults = results.filter((r5) => r5.status === "conflict");
|
|
364410
364515
|
const conflictDetails = conflictResults.map((r5) => {
|
|
@@ -364478,8 +364583,8 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
364478
364583
|
}
|
|
364479
364584
|
var import_node_crypto22 = require("crypto");
|
|
364480
364585
|
var import_node_fs33 = require("fs");
|
|
364481
|
-
var
|
|
364482
|
-
var
|
|
364586
|
+
var import_node_path92 = require("path");
|
|
364587
|
+
var import_minimatch42 = require_commonjs3();
|
|
364483
364588
|
var import_yaml22 = require_dist11();
|
|
364484
364589
|
var FernignoreMigrator = class {
|
|
364485
364590
|
git;
|
|
@@ -364491,10 +364596,10 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
364491
364596
|
this.outputDir = outputDir;
|
|
364492
364597
|
}
|
|
364493
364598
|
fernignoreExists() {
|
|
364494
|
-
return (0, import_node_fs33.existsSync)((0,
|
|
364599
|
+
return (0, import_node_fs33.existsSync)((0, import_node_path92.join)(this.outputDir, ".fernignore"));
|
|
364495
364600
|
}
|
|
364496
364601
|
readFernignorePatterns() {
|
|
364497
|
-
const fernignorePath = (0,
|
|
364602
|
+
const fernignorePath = (0, import_node_path92.join)(this.outputDir, ".fernignore");
|
|
364498
364603
|
if (!(0, import_node_fs33.existsSync)(fernignorePath)) {
|
|
364499
364604
|
return [];
|
|
364500
364605
|
}
|
|
@@ -364511,7 +364616,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
364511
364616
|
const commitsOnly = [];
|
|
364512
364617
|
const syntheticPatches = [];
|
|
364513
364618
|
for (const pattern of patterns) {
|
|
364514
|
-
const matchingPatch = patches.find((p14) => p14.files.some((f4) => (0,
|
|
364619
|
+
const matchingPatch = patches.find((p14) => p14.files.some((f4) => (0, import_minimatch42.minimatch)(f4, pattern) || f4 === pattern));
|
|
364515
364620
|
if (matchingPatch) {
|
|
364516
364621
|
trackedByBoth.push({
|
|
364517
364622
|
file: pattern,
|
|
@@ -364584,7 +364689,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
364584
364689
|
fernignoreOnly.push(...remainingFernignoreOnly);
|
|
364585
364690
|
}
|
|
364586
364691
|
for (const patch5 of patches) {
|
|
364587
|
-
const hasUnprotectedFiles = patch5.files.some((f4) => !patterns.some((p14) => (0,
|
|
364692
|
+
const hasUnprotectedFiles = patch5.files.some((f4) => !patterns.some((p14) => (0, import_minimatch42.minimatch)(f4, p14) || f4 === p14));
|
|
364588
364693
|
if (hasUnprotectedFiles) {
|
|
364589
364694
|
commitsOnly.push(patch5);
|
|
364590
364695
|
}
|
|
@@ -364596,14 +364701,14 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
364596
364701
|
const results = [];
|
|
364597
364702
|
for (const pattern of patterns) {
|
|
364598
364703
|
const matching = allFiles.filter(
|
|
364599
|
-
(f4) => (0,
|
|
364704
|
+
(f4) => (0, import_minimatch42.minimatch)(f4, pattern) || f4 === pattern || f4.startsWith(pattern + "/")
|
|
364600
364705
|
);
|
|
364601
364706
|
results.push({ pattern, files: matching.length > 0 ? matching : [pattern] });
|
|
364602
364707
|
}
|
|
364603
364708
|
return results;
|
|
364604
364709
|
}
|
|
364605
364710
|
readFileContent(filePath) {
|
|
364606
|
-
const fullPath = (0,
|
|
364711
|
+
const fullPath = (0, import_node_path92.join)(this.outputDir, filePath);
|
|
364607
364712
|
if (!(0, import_node_fs33.existsSync)(fullPath)) {
|
|
364608
364713
|
return null;
|
|
364609
364714
|
}
|
|
@@ -364668,7 +364773,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
364668
364773
|
};
|
|
364669
364774
|
}
|
|
364670
364775
|
movePatternsToReplayYml(patterns) {
|
|
364671
|
-
const replayYmlPath = (0,
|
|
364776
|
+
const replayYmlPath = (0, import_node_path92.join)(this.outputDir, ".fern", "replay.yml");
|
|
364672
364777
|
let config5 = {};
|
|
364673
364778
|
if ((0, import_node_fs33.existsSync)(replayYmlPath)) {
|
|
364674
364779
|
const content5 = (0, import_node_fs33.readFileSync)(replayYmlPath, "utf-8");
|
|
@@ -364677,7 +364782,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
364677
364782
|
const existing = config5.exclude ?? [];
|
|
364678
364783
|
const merged = [.../* @__PURE__ */ new Set([...existing, ...patterns])];
|
|
364679
364784
|
config5.exclude = merged;
|
|
364680
|
-
const dir = (0,
|
|
364785
|
+
const dir = (0, import_node_path92.dirname)(replayYmlPath);
|
|
364681
364786
|
if (!(0, import_node_fs33.existsSync)(dir)) {
|
|
364682
364787
|
(0, import_node_fs33.mkdirSync)(dir, { recursive: true });
|
|
364683
364788
|
}
|
|
@@ -364686,7 +364791,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
364686
364791
|
};
|
|
364687
364792
|
var import_node_crypto32 = require("crypto");
|
|
364688
364793
|
var import_node_fs42 = require("fs");
|
|
364689
|
-
var
|
|
364794
|
+
var import_node_path102 = require("path");
|
|
364690
364795
|
init_GitClient();
|
|
364691
364796
|
async function bootstrap(outputDir, options2) {
|
|
364692
364797
|
const git = new GitClient(outputDir);
|
|
@@ -364853,7 +364958,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
364853
364958
|
}
|
|
364854
364959
|
var REPLAY_FERNIGNORE_ENTRIES = [".fern/replay.lock", ".fern/replay.yml", ".gitattributes"];
|
|
364855
364960
|
function ensureFernignoreEntries(outputDir) {
|
|
364856
|
-
const fernignorePath = (0,
|
|
364961
|
+
const fernignorePath = (0, import_node_path102.join)(outputDir, ".fernignore");
|
|
364857
364962
|
let content5 = "";
|
|
364858
364963
|
if ((0, import_node_fs42.existsSync)(fernignorePath)) {
|
|
364859
364964
|
content5 = (0, import_node_fs42.readFileSync)(fernignorePath, "utf-8");
|
|
@@ -364877,7 +364982,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
364877
364982
|
}
|
|
364878
364983
|
var GITATTRIBUTES_ENTRIES = [".fern/replay.lock linguist-generated=true"];
|
|
364879
364984
|
function ensureGitattributesEntries(outputDir) {
|
|
364880
|
-
const gitattributesPath = (0,
|
|
364985
|
+
const gitattributesPath = (0, import_node_path102.join)(outputDir, ".gitattributes");
|
|
364881
364986
|
let content5 = "";
|
|
364882
364987
|
if ((0, import_node_fs42.existsSync)(gitattributesPath)) {
|
|
364883
364988
|
content5 = (0, import_node_fs42.readFileSync)(gitattributesPath, "utf-8");
|
|
@@ -364905,7 +365010,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
364905
365010
|
const normalized = relevantLines.filter((line) => !line.startsWith("index ")).join("\n").replace(/\n-- \n[\d]+\.[\d]+[^\n]*\s*$/, "");
|
|
364906
365011
|
return `sha256:${(0, import_node_crypto32.createHash)("sha256").update(normalized).digest("hex")}`;
|
|
364907
365012
|
}
|
|
364908
|
-
var
|
|
365013
|
+
var import_minimatch52 = require_commonjs3();
|
|
364909
365014
|
function parseDiffStat(patchContent) {
|
|
364910
365015
|
let additions = 0;
|
|
364911
365016
|
let deletions = 0;
|
|
@@ -364935,7 +365040,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
364935
365040
|
}
|
|
364936
365041
|
function matchesPatch(patch5, pattern) {
|
|
364937
365042
|
const fileMatch = patch5.files.some(
|
|
364938
|
-
(file4) => file4 === pattern || (0,
|
|
365043
|
+
(file4) => file4 === pattern || (0, import_minimatch52.minimatch)(file4, pattern)
|
|
364939
365044
|
);
|
|
364940
365045
|
if (fileMatch) return true;
|
|
364941
365046
|
return patch5.original_message.toLowerCase().includes(pattern.toLowerCase());
|
|
@@ -365069,8 +365174,8 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
365069
365174
|
nothingToReset: false
|
|
365070
365175
|
};
|
|
365071
365176
|
}
|
|
365072
|
-
var
|
|
365073
|
-
var
|
|
365177
|
+
var import_promises710 = require("fs/promises");
|
|
365178
|
+
var import_node_path112 = require("path");
|
|
365074
365179
|
init_GitClient();
|
|
365075
365180
|
async function resolve15(outputDir, options2) {
|
|
365076
365181
|
const lockManager = new LockfileManager(outputDir);
|
|
@@ -365119,7 +365224,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
365119
365224
|
const result = await computePerPatchDiffWithFallback(
|
|
365120
365225
|
patch5,
|
|
365121
365226
|
(f4) => git.showFile(currentGen, f4),
|
|
365122
|
-
(f4) => (0,
|
|
365227
|
+
(f4) => (0, import_promises710.readFile)((0, import_node_path112.join)(outputDir, f4), "utf-8").catch(() => null),
|
|
365123
365228
|
(files) => git.exec(["diff", currentGen, "--", ...files]).catch(() => null)
|
|
365124
365229
|
);
|
|
365125
365230
|
if (result === null) {
|
|
@@ -365175,7 +365280,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
365175
365280
|
const theirsSnapshot = {};
|
|
365176
365281
|
for (const file4 of filesForSnapshot) {
|
|
365177
365282
|
if (isBinaryFile(file4)) continue;
|
|
365178
|
-
const content5 = await (0,
|
|
365283
|
+
const content5 = await (0, import_promises710.readFile)((0, import_node_path112.join)(outputDir, file4), "utf-8").catch(() => null);
|
|
365179
365284
|
if (content5 != null) {
|
|
365180
365285
|
theirsSnapshot[file4] = content5;
|
|
365181
365286
|
}
|
|
@@ -667965,7 +668070,7 @@ var AccessTokenPosthogManager = class {
|
|
|
667965
668070
|
properties: {
|
|
667966
668071
|
...event,
|
|
667967
668072
|
...event.properties,
|
|
667968
|
-
version: "5.27.
|
|
668073
|
+
version: "5.27.7",
|
|
667969
668074
|
usingAccessToken: true,
|
|
667970
668075
|
...getRunIdProperties()
|
|
667971
668076
|
}
|
|
@@ -668029,7 +668134,7 @@ var UserPosthogManager = class {
|
|
|
668029
668134
|
distinctId: this.userId ?? await this.getPersistedDistinctId(),
|
|
668030
668135
|
event: "CLI",
|
|
668031
668136
|
properties: {
|
|
668032
|
-
version: "5.27.
|
|
668137
|
+
version: "5.27.7",
|
|
668033
668138
|
...event,
|
|
668034
668139
|
...event.properties,
|
|
668035
668140
|
usingAccessToken: false,
|
|
@@ -859608,7 +859713,7 @@ var LOCAL_STORAGE_FOLDER4 = ".fern-dev";
|
|
|
859608
859713
|
var LOGS_FOLDER_NAME = "logs";
|
|
859609
859714
|
var MAX_LOGS_DIR_SIZE_BYTES = 100 * 1024 * 1024;
|
|
859610
859715
|
function getCliSource() {
|
|
859611
|
-
const version7 = "5.27.
|
|
859716
|
+
const version7 = "5.27.7";
|
|
859612
859717
|
return `cli@${version7}`;
|
|
859613
859718
|
}
|
|
859614
859719
|
var DebugLogger = class {
|
|
@@ -873608,7 +873713,7 @@ var LegacyDocsPublisher = class {
|
|
|
873608
873713
|
previewId,
|
|
873609
873714
|
disableTemplates: void 0,
|
|
873610
873715
|
skipUpload,
|
|
873611
|
-
cliVersion: "5.27.
|
|
873716
|
+
cliVersion: "5.27.7",
|
|
873612
873717
|
loginCommand: "fern auth login"
|
|
873613
873718
|
});
|
|
873614
873719
|
if (taskContext.getResult() === TaskResult.Failure) {
|
|
@@ -948013,7 +948118,7 @@ function getAutomationContextFromEnv() {
|
|
|
948013
948118
|
config_branch: process.env.FERN_CONFIG_BRANCH,
|
|
948014
948119
|
config_pr_number: process.env.FERN_CONFIG_PR_NUMBER,
|
|
948015
948120
|
trigger: process.env.GITHUB_EVENT_NAME,
|
|
948016
|
-
cli_version: "5.27.
|
|
948121
|
+
cli_version: "5.27.7"
|
|
948017
948122
|
};
|
|
948018
948123
|
}
|
|
948019
948124
|
function isAutomationMode() {
|
|
@@ -948839,7 +948944,7 @@ var CliContext = class _CliContext {
|
|
|
948839
948944
|
if (false) {
|
|
948840
948945
|
this.logger.error("CLI_VERSION is not defined");
|
|
948841
948946
|
}
|
|
948842
|
-
return "5.27.
|
|
948947
|
+
return "5.27.7";
|
|
948843
948948
|
}
|
|
948844
948949
|
getCliName() {
|
|
948845
948950
|
if (false) {
|