@fern-api/replay 0.16.1 → 0.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +206 -29
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +223 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -1
- package/dist/index.d.ts +15 -1
- package/dist/index.js +223 -45
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -801,6 +801,7 @@ var LockfileManager = class {
|
|
|
801
801
|
|
|
802
802
|
// src/ReplayDetector.ts
|
|
803
803
|
var import_node_crypto = require("crypto");
|
|
804
|
+
var import_minimatch2 = require("minimatch");
|
|
804
805
|
|
|
805
806
|
// src/shared/binary.ts
|
|
806
807
|
var import_node_path2 = require("path");
|
|
@@ -860,6 +861,15 @@ function isBinaryFile(filePath) {
|
|
|
860
861
|
|
|
861
862
|
// src/ReplayDetector.ts
|
|
862
863
|
var INFRASTRUCTURE_FILES = /* @__PURE__ */ new Set([".fernignore"]);
|
|
864
|
+
function matchesFernignorePattern(filePath, patterns) {
|
|
865
|
+
if (patterns.length === 0) return false;
|
|
866
|
+
return patterns.some((p) => {
|
|
867
|
+
if (filePath === p) return true;
|
|
868
|
+
if ((0, import_minimatch2.minimatch)(filePath, p)) return true;
|
|
869
|
+
if (!p.includes("*") && !p.includes("?") && filePath.startsWith(p + "/")) return true;
|
|
870
|
+
return false;
|
|
871
|
+
});
|
|
872
|
+
}
|
|
863
873
|
function parsePatchFileHeaders(patchContent) {
|
|
864
874
|
const results = [];
|
|
865
875
|
let currentPath = null;
|
|
@@ -900,21 +910,25 @@ async function capturePatchSnapshot(git, files, treeish) {
|
|
|
900
910
|
}
|
|
901
911
|
return snapshot;
|
|
902
912
|
}
|
|
903
|
-
function filterInfrastructureAndSensitiveFiles(rawFilesOutput) {
|
|
913
|
+
function filterInfrastructureAndSensitiveFiles(rawFilesOutput, fernignorePatterns) {
|
|
904
914
|
const allFiles = rawFilesOutput.trim().split("\n").filter(Boolean).filter((f) => !INFRASTRUCTURE_FILES.has(f)).filter((f) => !f.startsWith(".fern/"));
|
|
905
915
|
const sensitiveFiles = allFiles.filter((f) => isSensitiveFile(f));
|
|
906
|
-
const
|
|
907
|
-
|
|
916
|
+
const nonSensitive = allFiles.filter((f) => !isSensitiveFile(f));
|
|
917
|
+
const fernignoredFiles = nonSensitive.filter((f) => matchesFernignorePattern(f, fernignorePatterns));
|
|
918
|
+
const files = nonSensitive.filter((f) => !matchesFernignorePattern(f, fernignorePatterns));
|
|
919
|
+
return { files, sensitiveFiles, fernignoredFiles };
|
|
908
920
|
}
|
|
909
921
|
var ReplayDetector = class {
|
|
910
922
|
git;
|
|
911
923
|
lockManager;
|
|
912
924
|
sdkOutputDir;
|
|
925
|
+
fernignorePatterns;
|
|
913
926
|
warnings = [];
|
|
914
|
-
constructor(git, lockManager, sdkOutputDir) {
|
|
927
|
+
constructor(git, lockManager, sdkOutputDir, fernignorePatterns = []) {
|
|
915
928
|
this.git = git;
|
|
916
929
|
this.lockManager = lockManager;
|
|
917
930
|
this.sdkOutputDir = sdkOutputDir;
|
|
931
|
+
this.fernignorePatterns = fernignorePatterns;
|
|
918
932
|
}
|
|
919
933
|
/**
|
|
920
934
|
* Derive the previous-generation SHA from git history instead of trusting
|
|
@@ -1021,22 +1035,30 @@ var ReplayDetector = class {
|
|
|
1021
1035
|
continue;
|
|
1022
1036
|
}
|
|
1023
1037
|
const filesOutput = await this.git.exec(["diff-tree", "--no-commit-id", "--name-only", "-r", commit.sha]);
|
|
1024
|
-
const { files, sensitiveFiles } = filterInfrastructureAndSensitiveFiles(
|
|
1038
|
+
const { files, sensitiveFiles, fernignoredFiles } = filterInfrastructureAndSensitiveFiles(
|
|
1039
|
+
filesOutput,
|
|
1040
|
+
this.fernignorePatterns
|
|
1041
|
+
);
|
|
1025
1042
|
if (sensitiveFiles.length > 0) {
|
|
1026
1043
|
this.warnings.push(
|
|
1027
1044
|
`Sensitive file(s) excluded from patch for commit ${commit.sha.slice(0, 7)}: ${sensitiveFiles.join(", ")}. These files will not be tracked by replay.`
|
|
1028
1045
|
);
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1046
|
+
}
|
|
1047
|
+
if (fernignoredFiles.length > 0) {
|
|
1048
|
+
this.warnings.push(
|
|
1049
|
+
`.fernignore-protected file(s) excluded from patch for commit ${commit.sha.slice(0, 7)}: ${fernignoredFiles.join(", ")}. These files are managed by .fernignore, not Replay.`
|
|
1050
|
+
);
|
|
1051
|
+
}
|
|
1052
|
+
if ((sensitiveFiles.length > 0 || fernignoredFiles.length > 0) && files.length > 0) {
|
|
1053
|
+
const parentSha = parents[0];
|
|
1054
|
+
if (parentSha) {
|
|
1055
|
+
try {
|
|
1056
|
+
patchContent = await this.git.exec(["diff", parentSha, commit.sha, "--", ...files]);
|
|
1057
|
+
} catch {
|
|
1058
|
+
continue;
|
|
1039
1059
|
}
|
|
1060
|
+
if (!patchContent.trim()) continue;
|
|
1061
|
+
contentHash = this.computeContentHash(patchContent);
|
|
1040
1062
|
}
|
|
1041
1063
|
}
|
|
1042
1064
|
if (files.length === 0) {
|
|
@@ -1235,12 +1257,20 @@ var ReplayDetector = class {
|
|
|
1235
1257
|
resolvedBaseGeneration = fallback;
|
|
1236
1258
|
}
|
|
1237
1259
|
const filesOutput = await this.git.exec(["diff", "--name-only", diffBase, "HEAD"]);
|
|
1238
|
-
const { files, sensitiveFiles } = filterInfrastructureAndSensitiveFiles(
|
|
1260
|
+
const { files, sensitiveFiles, fernignoredFiles } = filterInfrastructureAndSensitiveFiles(
|
|
1261
|
+
filesOutput,
|
|
1262
|
+
this.fernignorePatterns
|
|
1263
|
+
);
|
|
1239
1264
|
if (sensitiveFiles.length > 0) {
|
|
1240
1265
|
this.warnings.push(
|
|
1241
1266
|
`Sensitive file(s) excluded from composite patch: ${sensitiveFiles.join(", ")}. These files will not be tracked by replay.`
|
|
1242
1267
|
);
|
|
1243
1268
|
}
|
|
1269
|
+
if (fernignoredFiles.length > 0) {
|
|
1270
|
+
this.warnings.push(
|
|
1271
|
+
`.fernignore-protected file(s) excluded from composite patch: ${fernignoredFiles.join(", ")}. These files are managed by .fernignore, not Replay.`
|
|
1272
|
+
);
|
|
1273
|
+
}
|
|
1244
1274
|
if (files.length === 0) return { patches: [], revertedPatchIds: [] };
|
|
1245
1275
|
const diff = await this.git.exec(["diff", diffBase, "HEAD", "--", ...files]);
|
|
1246
1276
|
if (!diff.trim()) return { patches: [], revertedPatchIds: [] };
|
|
@@ -1323,23 +1353,31 @@ var ReplayDetector = class {
|
|
|
1323
1353
|
continue;
|
|
1324
1354
|
}
|
|
1325
1355
|
const filesOutput = await this.git.exec(["diff-tree", "--no-commit-id", "--name-only", "-r", commit.sha]);
|
|
1326
|
-
const { files, sensitiveFiles } = filterInfrastructureAndSensitiveFiles(
|
|
1356
|
+
const { files, sensitiveFiles, fernignoredFiles } = filterInfrastructureAndSensitiveFiles(
|
|
1357
|
+
filesOutput,
|
|
1358
|
+
this.fernignorePatterns
|
|
1359
|
+
);
|
|
1327
1360
|
if (sensitiveFiles.length > 0) {
|
|
1328
1361
|
this.warnings.push(
|
|
1329
1362
|
`Sensitive file(s) excluded from patch for commit ${commit.sha.slice(0, 7)}: ${sensitiveFiles.join(", ")}. These files will not be tracked by replay.`
|
|
1330
1363
|
);
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
if (!patchContent.trim()) continue;
|
|
1341
|
-
contentHash = this.computeContentHash(patchContent);
|
|
1364
|
+
}
|
|
1365
|
+
if (fernignoredFiles.length > 0) {
|
|
1366
|
+
this.warnings.push(
|
|
1367
|
+
`.fernignore-protected file(s) excluded from patch for commit ${commit.sha.slice(0, 7)}: ${fernignoredFiles.join(", ")}. These files are managed by .fernignore, not Replay.`
|
|
1368
|
+
);
|
|
1369
|
+
}
|
|
1370
|
+
if ((sensitiveFiles.length > 0 || fernignoredFiles.length > 0) && files.length > 0) {
|
|
1371
|
+
if (parents.length === 0) {
|
|
1372
|
+
continue;
|
|
1342
1373
|
}
|
|
1374
|
+
try {
|
|
1375
|
+
patchContent = await this.git.exec(["diff", parents[0], commit.sha, "--", ...files]);
|
|
1376
|
+
} catch {
|
|
1377
|
+
continue;
|
|
1378
|
+
}
|
|
1379
|
+
if (!patchContent.trim()) continue;
|
|
1380
|
+
contentHash = this.computeContentHash(patchContent);
|
|
1343
1381
|
}
|
|
1344
1382
|
if (files.length === 0) {
|
|
1345
1383
|
continue;
|
|
@@ -1575,7 +1613,7 @@ function applyBothPatches(baseLines, oursPatches, theirsPatches) {
|
|
|
1575
1613
|
var import_promises3 = require("fs/promises");
|
|
1576
1614
|
var import_node_os = require("os");
|
|
1577
1615
|
var import_node_path5 = require("path");
|
|
1578
|
-
var
|
|
1616
|
+
var import_minimatch3 = require("minimatch");
|
|
1579
1617
|
|
|
1580
1618
|
// src/accumulator/MergeAccumulator.ts
|
|
1581
1619
|
var MergeAccumulator = class {
|
|
@@ -2291,13 +2329,13 @@ var ReplayApplicator = class {
|
|
|
2291
2329
|
isExcluded(patch) {
|
|
2292
2330
|
const config = this.lockManager.getCustomizationsConfig();
|
|
2293
2331
|
if (!config.exclude) return false;
|
|
2294
|
-
return patch.files.some((file) => config.exclude.some((pattern) => (0,
|
|
2332
|
+
return patch.files.some((file) => config.exclude.some((pattern) => (0, import_minimatch3.minimatch)(file, pattern)));
|
|
2295
2333
|
}
|
|
2296
2334
|
async resolveFilePath(filePath, baseTreeHash, currentTreeHash) {
|
|
2297
2335
|
const config = this.lockManager.getCustomizationsConfig();
|
|
2298
2336
|
if (config.moves) {
|
|
2299
2337
|
for (const move of config.moves) {
|
|
2300
|
-
if ((0,
|
|
2338
|
+
if ((0, import_minimatch3.minimatch)(filePath, move.from) || filePath === move.from) {
|
|
2301
2339
|
if (filePath === move.from) {
|
|
2302
2340
|
return move.to;
|
|
2303
2341
|
}
|
|
@@ -2588,7 +2626,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
|
|
|
2588
2626
|
var import_node_fs2 = require("fs");
|
|
2589
2627
|
var import_promises6 = require("fs/promises");
|
|
2590
2628
|
var import_node_path8 = require("path");
|
|
2591
|
-
var
|
|
2629
|
+
var import_minimatch5 = require("minimatch");
|
|
2592
2630
|
init_GitClient();
|
|
2593
2631
|
|
|
2594
2632
|
// src/PatchRegionDiff.ts
|
|
@@ -2895,7 +2933,7 @@ function synthesizeDeleteFileDiff(file, content) {
|
|
|
2895
2933
|
}
|
|
2896
2934
|
|
|
2897
2935
|
// src/classifier/FileOwnership.ts
|
|
2898
|
-
var
|
|
2936
|
+
var import_minimatch4 = require("minimatch");
|
|
2899
2937
|
var FileOwnership = class {
|
|
2900
2938
|
constructor(git) {
|
|
2901
2939
|
this.git = git;
|
|
@@ -2919,7 +2957,7 @@ var FileOwnership = class {
|
|
|
2919
2957
|
const { file, originalFileName, patch, currentGenSha, fernignorePatterns, warnedGens, warnings } = ctx;
|
|
2920
2958
|
if (patch.user_owned) return true;
|
|
2921
2959
|
if (file === ".fernignore") return true;
|
|
2922
|
-
if (fernignorePatterns.some((p) => file === p || (0,
|
|
2960
|
+
if (fernignorePatterns.some((p) => file === p || (0, import_minimatch4.minimatch)(file, p))) return true;
|
|
2923
2961
|
if (fileIsCreationInPatchContent(patch.patch_content, originalFileName ?? file)) {
|
|
2924
2962
|
return true;
|
|
2925
2963
|
}
|
|
@@ -3425,7 +3463,8 @@ var ReplayService = class {
|
|
|
3425
3463
|
this.git = git;
|
|
3426
3464
|
this.outputDir = outputDir;
|
|
3427
3465
|
this.lockManager = new LockfileManager(outputDir);
|
|
3428
|
-
|
|
3466
|
+
const fernignorePatterns = this.readFernignorePatterns();
|
|
3467
|
+
this.detector = new ReplayDetector(git, this.lockManager, outputDir, fernignorePatterns);
|
|
3429
3468
|
this.applicator = new ReplayApplicator(git, this.lockManager, outputDir);
|
|
3430
3469
|
this.committer = new ReplayCommitter(git, outputDir);
|
|
3431
3470
|
this.fileOwnership = new FileOwnership(git);
|
|
@@ -3445,8 +3484,60 @@ var ReplayService = class {
|
|
|
3445
3484
|
async prepareReplay(options) {
|
|
3446
3485
|
this.warnings = [];
|
|
3447
3486
|
this.detector.warnings.length = 0;
|
|
3487
|
+
this.cleanseLegacyFernignoreEntries();
|
|
3448
3488
|
return this.selectFlow(options).prepare(options);
|
|
3449
3489
|
}
|
|
3490
|
+
/**
|
|
3491
|
+
* ADR 0002 migration step. Strips `.fernignore`-matched entries from
|
|
3492
|
+
* `patch.files`, `patch.theirs_snapshot` keys, and `diff --git` sections
|
|
3493
|
+
* in `patch.patch_content` for every patch in the lockfile. Patches that
|
|
3494
|
+
* end up with no surviving files are removed entirely.
|
|
3495
|
+
*
|
|
3496
|
+
* Idempotent: a second run is a no-op because the first run already
|
|
3497
|
+
* matches the contract. Per-patch try/catch ensures one bad patch does
|
|
3498
|
+
* not block the rest. Each affected patch emits a warning naming the
|
|
3499
|
+
* stripped paths. `patch_content` is left untouched if no parseable
|
|
3500
|
+
* `diff --git` headers are found (fail-safe for exotic legacy formats).
|
|
3501
|
+
*/
|
|
3502
|
+
cleanseLegacyFernignoreEntries() {
|
|
3503
|
+
const patterns = this.readFernignorePatterns();
|
|
3504
|
+
if (patterns.length === 0) return;
|
|
3505
|
+
if (!this.lockManager.exists()) return;
|
|
3506
|
+
this.lockManager.read();
|
|
3507
|
+
let lockfileMutated = false;
|
|
3508
|
+
const patches = this.lockManager.getPatches();
|
|
3509
|
+
for (const patch of patches) {
|
|
3510
|
+
try {
|
|
3511
|
+
const result = computeLegacyFernignoreCleanse(
|
|
3512
|
+
patch,
|
|
3513
|
+
patterns,
|
|
3514
|
+
(content) => this.detector.computeContentHash(content)
|
|
3515
|
+
);
|
|
3516
|
+
if (result.unchanged) continue;
|
|
3517
|
+
if (result.remove) {
|
|
3518
|
+
this.lockManager.removePatch(patch.id);
|
|
3519
|
+
this.warnings.push(
|
|
3520
|
+
`Cleansed legacy patch ${patch.id}: all referenced files match .fernignore \u2014 patch removed. Customer's on-disk content for those files is preserved by .fernignore. (ADR 0002: .fernignore-protected files are not tracked by Replay.)`
|
|
3521
|
+
);
|
|
3522
|
+
} else {
|
|
3523
|
+
this.lockManager.updatePatch(patch.id, {
|
|
3524
|
+
files: result.files,
|
|
3525
|
+
patch_content: result.patch_content,
|
|
3526
|
+
content_hash: result.content_hash,
|
|
3527
|
+
...result.theirs_snapshot !== void 0 ? { theirs_snapshot: result.theirs_snapshot } : {}
|
|
3528
|
+
});
|
|
3529
|
+
this.warnings.push(
|
|
3530
|
+
`Cleansed legacy patch ${patch.id}: stripped .fernignore-matched entries (${result.strippedPaths.join(", ")}). Customer's on-disk content for those files is preserved by .fernignore. (ADR 0002.)`
|
|
3531
|
+
);
|
|
3532
|
+
}
|
|
3533
|
+
lockfileMutated = true;
|
|
3534
|
+
} catch {
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
if (lockfileMutated) {
|
|
3538
|
+
this.lockManager.save();
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3450
3541
|
/**
|
|
3451
3542
|
* Phase 2 of the two-phase replay flow. Applies patches, rebases them against
|
|
3452
3543
|
* the generation, saves the lockfile, and commits `[fern-replay]` (or stages
|
|
@@ -3578,7 +3669,7 @@ var ReplayService = class {
|
|
|
3578
3669
|
)
|
|
3579
3670
|
);
|
|
3580
3671
|
const hasProtectedFiles = patch.files.some(
|
|
3581
|
-
(file) => file === ".fernignore" || fernignorePatterns.some((p) => file === p || (0,
|
|
3672
|
+
(file) => file === ".fernignore" || fernignorePatterns.some((p) => file === p || (0, import_minimatch5.minimatch)(file, p))
|
|
3582
3673
|
);
|
|
3583
3674
|
const allUserOwned = isUserOwnedPerFile.every(Boolean);
|
|
3584
3675
|
if (allUserOwned && patch.files.length > 0) {
|
|
@@ -4018,7 +4109,7 @@ var ReplayService = class {
|
|
|
4018
4109
|
);
|
|
4019
4110
|
if (snapHasStaleMarkers) continue;
|
|
4020
4111
|
const isProtectedSnap = patch.files.some(
|
|
4021
|
-
(file) => file === ".fernignore" || fernignorePatterns.some((p) => file === p || (0,
|
|
4112
|
+
(file) => file === ".fernignore" || fernignorePatterns.some((p) => file === p || (0, import_minimatch5.minimatch)(file, p))
|
|
4022
4113
|
);
|
|
4023
4114
|
if (isProtectedSnap) continue;
|
|
4024
4115
|
const snapHash = this.detector.computeContentHash(snapshotDiff);
|
|
@@ -4058,7 +4149,7 @@ var ReplayService = class {
|
|
|
4058
4149
|
continue;
|
|
4059
4150
|
}
|
|
4060
4151
|
const isProtected = patch.files.some(
|
|
4061
|
-
(file) => file === ".fernignore" || fernignorePatterns.some((p) => file === p || (0,
|
|
4152
|
+
(file) => file === ".fernignore" || fernignorePatterns.some((p) => file === p || (0, import_minimatch5.minimatch)(file, p))
|
|
4062
4153
|
);
|
|
4063
4154
|
if (isProtected) {
|
|
4064
4155
|
continue;
|
|
@@ -4222,7 +4313,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
|
|
|
4222
4313
|
if (files.length === 0) return;
|
|
4223
4314
|
const fernignorePatterns = this.readFernignorePatterns();
|
|
4224
4315
|
for (const file of files) {
|
|
4225
|
-
if (fernignorePatterns.some((pattern) => (0,
|
|
4316
|
+
if (fernignorePatterns.some((pattern) => (0, import_minimatch5.minimatch)(file, pattern))) continue;
|
|
4226
4317
|
try {
|
|
4227
4318
|
await this.git.exec(["checkout", "HEAD", "--", file]);
|
|
4228
4319
|
} catch {
|
|
@@ -4307,12 +4398,99 @@ function computeCommitBuckets(results, absorbedPatchIds, patchesPassedToCommit)
|
|
|
4307
4398
|
}
|
|
4308
4399
|
return { applied, unresolved, absorbed };
|
|
4309
4400
|
}
|
|
4401
|
+
function computeLegacyFernignoreCleanse(patch, fernignorePatterns, computeContentHash2) {
|
|
4402
|
+
const matchesFernignore = (filePath) => fernignorePatterns.some((p) => {
|
|
4403
|
+
if (filePath === p) return true;
|
|
4404
|
+
if ((0, import_minimatch5.minimatch)(filePath, p)) return true;
|
|
4405
|
+
if (!p.includes("*") && !p.includes("?") && filePath.startsWith(p + "/")) return true;
|
|
4406
|
+
return false;
|
|
4407
|
+
});
|
|
4408
|
+
const strippedFromFiles = patch.files.filter(matchesFernignore);
|
|
4409
|
+
const survivingFiles = patch.files.filter((f) => !matchesFernignore(f));
|
|
4410
|
+
let survivingSnapshot = void 0;
|
|
4411
|
+
let snapshotChanged = false;
|
|
4412
|
+
if (patch.theirs_snapshot) {
|
|
4413
|
+
survivingSnapshot = {};
|
|
4414
|
+
for (const [key, value] of Object.entries(patch.theirs_snapshot)) {
|
|
4415
|
+
if (matchesFernignore(key)) {
|
|
4416
|
+
snapshotChanged = true;
|
|
4417
|
+
} else {
|
|
4418
|
+
survivingSnapshot[key] = value;
|
|
4419
|
+
}
|
|
4420
|
+
}
|
|
4421
|
+
}
|
|
4422
|
+
const { stripped: patchContentStripped, content: newPatchContent, strippedSectionPaths } = stripFernignoreDiffSections(patch.patch_content, matchesFernignore);
|
|
4423
|
+
const unchanged = strippedFromFiles.length === 0 && !snapshotChanged && !patchContentStripped;
|
|
4424
|
+
if (unchanged) {
|
|
4425
|
+
return {
|
|
4426
|
+
unchanged: true,
|
|
4427
|
+
remove: false,
|
|
4428
|
+
files: patch.files,
|
|
4429
|
+
patch_content: patch.patch_content,
|
|
4430
|
+
content_hash: patch.content_hash,
|
|
4431
|
+
theirs_snapshot: patch.theirs_snapshot,
|
|
4432
|
+
strippedPaths: []
|
|
4433
|
+
};
|
|
4434
|
+
}
|
|
4435
|
+
const strippedPaths = Array.from(/* @__PURE__ */ new Set([...strippedFromFiles, ...strippedSectionPaths]));
|
|
4436
|
+
if (survivingFiles.length === 0 || newPatchContent.trim() === "") {
|
|
4437
|
+
return {
|
|
4438
|
+
unchanged: false,
|
|
4439
|
+
remove: true,
|
|
4440
|
+
files: [],
|
|
4441
|
+
patch_content: "",
|
|
4442
|
+
content_hash: "",
|
|
4443
|
+
strippedPaths
|
|
4444
|
+
};
|
|
4445
|
+
}
|
|
4446
|
+
const newContentHash = patchContentStripped ? computeContentHash2(newPatchContent) : patch.content_hash;
|
|
4447
|
+
return {
|
|
4448
|
+
unchanged: false,
|
|
4449
|
+
remove: false,
|
|
4450
|
+
files: survivingFiles,
|
|
4451
|
+
patch_content: newPatchContent,
|
|
4452
|
+
content_hash: newContentHash,
|
|
4453
|
+
...survivingSnapshot !== void 0 ? { theirs_snapshot: survivingSnapshot } : {},
|
|
4454
|
+
strippedPaths
|
|
4455
|
+
};
|
|
4456
|
+
}
|
|
4457
|
+
function stripFernignoreDiffSections(patchContent, matchesFernignore) {
|
|
4458
|
+
const lines = patchContent.split("\n");
|
|
4459
|
+
const sectionStarts = [];
|
|
4460
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4461
|
+
const line = lines[i];
|
|
4462
|
+
if (line.startsWith("diff --git ")) {
|
|
4463
|
+
const match = line.match(/^diff --git a\/(.+?) b\/(.+?)$/);
|
|
4464
|
+
const path = match ? match[2] : null;
|
|
4465
|
+
sectionStarts.push({ idx: i, path });
|
|
4466
|
+
}
|
|
4467
|
+
}
|
|
4468
|
+
if (sectionStarts.length === 0) {
|
|
4469
|
+
return { stripped: false, content: patchContent, strippedSectionPaths: [] };
|
|
4470
|
+
}
|
|
4471
|
+
const preamble = lines.slice(0, sectionStarts[0].idx);
|
|
4472
|
+
const newLines = [...preamble];
|
|
4473
|
+
const strippedSectionPaths = [];
|
|
4474
|
+
let stripped = false;
|
|
4475
|
+
for (let i = 0; i < sectionStarts.length; i++) {
|
|
4476
|
+
const section = sectionStarts[i];
|
|
4477
|
+
const next = sectionStarts[i + 1];
|
|
4478
|
+
const endIdx = next ? next.idx : lines.length;
|
|
4479
|
+
if (section.path !== null && matchesFernignore(section.path)) {
|
|
4480
|
+
stripped = true;
|
|
4481
|
+
strippedSectionPaths.push(section.path);
|
|
4482
|
+
continue;
|
|
4483
|
+
}
|
|
4484
|
+
newLines.push(...lines.slice(section.idx, endIdx));
|
|
4485
|
+
}
|
|
4486
|
+
return { stripped, content: newLines.join("\n"), strippedSectionPaths };
|
|
4487
|
+
}
|
|
4310
4488
|
|
|
4311
4489
|
// src/FernignoreMigrator.ts
|
|
4312
4490
|
var import_node_crypto2 = require("crypto");
|
|
4313
4491
|
var import_node_fs3 = require("fs");
|
|
4314
4492
|
var import_node_path9 = require("path");
|
|
4315
|
-
var
|
|
4493
|
+
var import_minimatch6 = require("minimatch");
|
|
4316
4494
|
var import_yaml2 = require("yaml");
|
|
4317
4495
|
var FernignoreMigrator = class {
|
|
4318
4496
|
git;
|
|
@@ -4344,7 +4522,7 @@ var FernignoreMigrator = class {
|
|
|
4344
4522
|
const commitsOnly = [];
|
|
4345
4523
|
const syntheticPatches = [];
|
|
4346
4524
|
for (const pattern of patterns) {
|
|
4347
|
-
const matchingPatch = patches.find((p) => p.files.some((f) => (0,
|
|
4525
|
+
const matchingPatch = patches.find((p) => p.files.some((f) => (0, import_minimatch6.minimatch)(f, pattern) || f === pattern));
|
|
4348
4526
|
if (matchingPatch) {
|
|
4349
4527
|
trackedByBoth.push({
|
|
4350
4528
|
file: pattern,
|
|
@@ -4417,7 +4595,7 @@ var FernignoreMigrator = class {
|
|
|
4417
4595
|
fernignoreOnly.push(...remainingFernignoreOnly);
|
|
4418
4596
|
}
|
|
4419
4597
|
for (const patch of patches) {
|
|
4420
|
-
const hasUnprotectedFiles = patch.files.some((f) => !patterns.some((p) => (0,
|
|
4598
|
+
const hasUnprotectedFiles = patch.files.some((f) => !patterns.some((p) => (0, import_minimatch6.minimatch)(f, p) || f === p));
|
|
4421
4599
|
if (hasUnprotectedFiles) {
|
|
4422
4600
|
commitsOnly.push(patch);
|
|
4423
4601
|
}
|
|
@@ -4429,7 +4607,7 @@ var FernignoreMigrator = class {
|
|
|
4429
4607
|
const results = [];
|
|
4430
4608
|
for (const pattern of patterns) {
|
|
4431
4609
|
const matching = allFiles.filter(
|
|
4432
|
-
(f) => (0,
|
|
4610
|
+
(f) => (0, import_minimatch6.minimatch)(f, pattern) || f === pattern || f.startsWith(pattern + "/")
|
|
4433
4611
|
);
|
|
4434
4612
|
results.push({ pattern, files: matching.length > 0 ? matching : [pattern] });
|
|
4435
4613
|
}
|
|
@@ -4742,7 +4920,7 @@ function computeContentHash(patchContent) {
|
|
|
4742
4920
|
}
|
|
4743
4921
|
|
|
4744
4922
|
// src/commands/forget.ts
|
|
4745
|
-
var
|
|
4923
|
+
var import_minimatch7 = require("minimatch");
|
|
4746
4924
|
function parseDiffStat(patchContent) {
|
|
4747
4925
|
let additions = 0;
|
|
4748
4926
|
let deletions = 0;
|
|
@@ -4772,7 +4950,7 @@ function toMatchedPatch(patch) {
|
|
|
4772
4950
|
}
|
|
4773
4951
|
function matchesPatch(patch, pattern) {
|
|
4774
4952
|
const fileMatch = patch.files.some(
|
|
4775
|
-
(file) => file === pattern || (0,
|
|
4953
|
+
(file) => file === pattern || (0, import_minimatch7.minimatch)(file, pattern)
|
|
4776
4954
|
);
|
|
4777
4955
|
if (fileMatch) return true;
|
|
4778
4956
|
return patch.original_message.toLowerCase().includes(pattern.toLowerCase());
|