@fern-api/replay 0.8.1 → 0.9.1
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 +812 -137
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +566 -72
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +64 -16
- package/dist/index.d.ts +64 -16
- package/dist/index.js +566 -72
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -751,7 +751,7 @@ var require_dist2 = __commonJS({
|
|
|
751
751
|
function deferred2() {
|
|
752
752
|
let done;
|
|
753
753
|
let fail;
|
|
754
|
-
let
|
|
754
|
+
let status2 = "pending";
|
|
755
755
|
const promise = new Promise((_done, _fail) => {
|
|
756
756
|
done = _done;
|
|
757
757
|
fail = _fail;
|
|
@@ -759,22 +759,22 @@ var require_dist2 = __commonJS({
|
|
|
759
759
|
return {
|
|
760
760
|
promise,
|
|
761
761
|
done(result) {
|
|
762
|
-
if (
|
|
763
|
-
|
|
762
|
+
if (status2 === "pending") {
|
|
763
|
+
status2 = "resolved";
|
|
764
764
|
done(result);
|
|
765
765
|
}
|
|
766
766
|
},
|
|
767
767
|
fail(error) {
|
|
768
|
-
if (
|
|
769
|
-
|
|
768
|
+
if (status2 === "pending") {
|
|
769
|
+
status2 = "rejected";
|
|
770
770
|
fail(error);
|
|
771
771
|
}
|
|
772
772
|
},
|
|
773
773
|
get fulfilled() {
|
|
774
|
-
return
|
|
774
|
+
return status2 !== "pending";
|
|
775
775
|
},
|
|
776
776
|
get status() {
|
|
777
|
-
return
|
|
777
|
+
return status2;
|
|
778
778
|
}
|
|
779
779
|
};
|
|
780
780
|
}
|
|
@@ -1817,10 +1817,10 @@ function mergeTask(customArgs) {
|
|
|
1817
1817
|
}
|
|
1818
1818
|
};
|
|
1819
1819
|
}
|
|
1820
|
-
function pushResultPushedItem(local, remote,
|
|
1821
|
-
const deleted =
|
|
1822
|
-
const tag =
|
|
1823
|
-
const alreadyUpdated = !
|
|
1820
|
+
function pushResultPushedItem(local, remote, status2) {
|
|
1821
|
+
const deleted = status2.includes("deleted");
|
|
1822
|
+
const tag = status2.includes("tag") || /^refs\/tags/.test(local);
|
|
1823
|
+
const alreadyUpdated = !status2.includes("new");
|
|
1824
1824
|
return {
|
|
1825
1825
|
deleted,
|
|
1826
1826
|
tag,
|
|
@@ -3659,7 +3659,7 @@ var init_esm = __esm({
|
|
|
3659
3659
|
nameStatusParser = [
|
|
3660
3660
|
new LineParser(
|
|
3661
3661
|
/([ACDMRTUXB])([0-9]{0,3})\t(.[^\t]*)(\t(.[^\t]*))?$/,
|
|
3662
|
-
(result, [
|
|
3662
|
+
(result, [status2, similarity, from, _to, to]) => {
|
|
3663
3663
|
result.changed++;
|
|
3664
3664
|
result.files.push({
|
|
3665
3665
|
file: to ?? from,
|
|
@@ -3667,7 +3667,7 @@ var init_esm = __esm({
|
|
|
3667
3667
|
insertions: 0,
|
|
3668
3668
|
deletions: 0,
|
|
3669
3669
|
binary: false,
|
|
3670
|
-
status: orVoid(isDiffNameStatus(
|
|
3670
|
+
status: orVoid(isDiffNameStatus(status2) && status2),
|
|
3671
3671
|
from: orVoid(!!to && from !== to && from),
|
|
3672
3672
|
similarity: asNumber(similarity)
|
|
3673
3673
|
});
|
|
@@ -4214,7 +4214,7 @@ var init_esm = __esm({
|
|
|
4214
4214
|
]);
|
|
4215
4215
|
parseStatusSummary = function(text) {
|
|
4216
4216
|
const lines = text.split(NULL);
|
|
4217
|
-
const
|
|
4217
|
+
const status2 = new StatusSummary();
|
|
4218
4218
|
for (let i = 0, l = lines.length; i < l; ) {
|
|
4219
4219
|
let line = lines[i++].trim();
|
|
4220
4220
|
if (!line) {
|
|
@@ -4223,9 +4223,9 @@ var init_esm = __esm({
|
|
|
4223
4223
|
if (line.charAt(0) === "R") {
|
|
4224
4224
|
line += NULL + (lines[i++] || "");
|
|
4225
4225
|
}
|
|
4226
|
-
splitLine(
|
|
4226
|
+
splitLine(status2, line);
|
|
4227
4227
|
}
|
|
4228
|
-
return
|
|
4228
|
+
return status2;
|
|
4229
4229
|
};
|
|
4230
4230
|
}
|
|
4231
4231
|
});
|
|
@@ -4514,15 +4514,15 @@ var init_esm = __esm({
|
|
|
4514
4514
|
this.current = "";
|
|
4515
4515
|
this.detached = false;
|
|
4516
4516
|
}
|
|
4517
|
-
push(
|
|
4518
|
-
if (
|
|
4517
|
+
push(status2, detached, name, commit, label) {
|
|
4518
|
+
if (status2 === "*") {
|
|
4519
4519
|
this.detached = detached;
|
|
4520
4520
|
this.current = name;
|
|
4521
4521
|
}
|
|
4522
4522
|
this.all.push(name);
|
|
4523
4523
|
this.branches[name] = {
|
|
4524
|
-
current:
|
|
4525
|
-
linkedWorkTree:
|
|
4524
|
+
current: status2 === "*",
|
|
4525
|
+
linkedWorkTree: status2 === "+",
|
|
4526
4526
|
name,
|
|
4527
4527
|
commit,
|
|
4528
4528
|
label
|
|
@@ -5335,6 +5335,14 @@ var init_GitClient = __esm({
|
|
|
5335
5335
|
return false;
|
|
5336
5336
|
}
|
|
5337
5337
|
}
|
|
5338
|
+
async treeExists(treeHash) {
|
|
5339
|
+
try {
|
|
5340
|
+
const type = await this.exec(["cat-file", "-t", treeHash]);
|
|
5341
|
+
return type.trim() === "tree";
|
|
5342
|
+
} catch {
|
|
5343
|
+
return false;
|
|
5344
|
+
}
|
|
5345
|
+
}
|
|
5338
5346
|
async getCommitBody(commitSha) {
|
|
5339
5347
|
return this.exec(["log", "-1", "--format=%B", commitSha]);
|
|
5340
5348
|
}
|
|
@@ -12843,9 +12851,259 @@ var require_brace_expansion = __commonJS({
|
|
|
12843
12851
|
}
|
|
12844
12852
|
});
|
|
12845
12853
|
|
|
12854
|
+
// src/HybridReconstruction.ts
|
|
12855
|
+
var HybridReconstruction_exports = {};
|
|
12856
|
+
__export(HybridReconstruction_exports, {
|
|
12857
|
+
assembleHybrid: () => assembleHybrid,
|
|
12858
|
+
locateHunksInOurs: () => locateHunksInOurs,
|
|
12859
|
+
parseHunks: () => parseHunks,
|
|
12860
|
+
reconstructFromGhostPatch: () => reconstructFromGhostPatch
|
|
12861
|
+
});
|
|
12862
|
+
function parseHunks(fileDiff) {
|
|
12863
|
+
const lines = fileDiff.split("\n");
|
|
12864
|
+
const hunks = [];
|
|
12865
|
+
let currentHunk = null;
|
|
12866
|
+
for (const line of lines) {
|
|
12867
|
+
const headerMatch = line.match(
|
|
12868
|
+
/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/
|
|
12869
|
+
);
|
|
12870
|
+
if (headerMatch) {
|
|
12871
|
+
if (currentHunk) {
|
|
12872
|
+
hunks.push(currentHunk);
|
|
12873
|
+
}
|
|
12874
|
+
currentHunk = {
|
|
12875
|
+
oldStart: parseInt(headerMatch[1], 10),
|
|
12876
|
+
oldCount: headerMatch[2] != null ? parseInt(headerMatch[2], 10) : 1,
|
|
12877
|
+
newStart: parseInt(headerMatch[3], 10),
|
|
12878
|
+
newCount: headerMatch[4] != null ? parseInt(headerMatch[4], 10) : 1,
|
|
12879
|
+
lines: []
|
|
12880
|
+
};
|
|
12881
|
+
continue;
|
|
12882
|
+
}
|
|
12883
|
+
if (!currentHunk) continue;
|
|
12884
|
+
if (line.startsWith("diff --git") || line.startsWith("index ") || line.startsWith("---") || line.startsWith("+++") || line.startsWith("old mode") || line.startsWith("new mode") || line.startsWith("similarity index") || line.startsWith("rename from") || line.startsWith("rename to") || line.startsWith("new file mode") || line.startsWith("deleted file mode")) {
|
|
12885
|
+
continue;
|
|
12886
|
+
}
|
|
12887
|
+
if (line === "\") {
|
|
12888
|
+
continue;
|
|
12889
|
+
}
|
|
12890
|
+
if (line.startsWith("-")) {
|
|
12891
|
+
currentHunk.lines.push({ type: "remove", content: line.slice(1) });
|
|
12892
|
+
} else if (line.startsWith("+")) {
|
|
12893
|
+
currentHunk.lines.push({ type: "add", content: line.slice(1) });
|
|
12894
|
+
} else if (line.startsWith(" ") || line === "") {
|
|
12895
|
+
currentHunk.lines.push({
|
|
12896
|
+
type: "context",
|
|
12897
|
+
content: line.startsWith(" ") ? line.slice(1) : line
|
|
12898
|
+
});
|
|
12899
|
+
}
|
|
12900
|
+
}
|
|
12901
|
+
if (currentHunk) {
|
|
12902
|
+
hunks.push(currentHunk);
|
|
12903
|
+
}
|
|
12904
|
+
return hunks;
|
|
12905
|
+
}
|
|
12906
|
+
function extractLeadingContext(hunk) {
|
|
12907
|
+
const result = [];
|
|
12908
|
+
for (const line of hunk.lines) {
|
|
12909
|
+
if (line.type !== "context") break;
|
|
12910
|
+
result.push(line.content);
|
|
12911
|
+
}
|
|
12912
|
+
return result;
|
|
12913
|
+
}
|
|
12914
|
+
function extractTrailingContext(hunk) {
|
|
12915
|
+
const result = [];
|
|
12916
|
+
for (let i = hunk.lines.length - 1; i >= 0; i--) {
|
|
12917
|
+
if (hunk.lines[i].type !== "context") break;
|
|
12918
|
+
result.unshift(hunk.lines[i].content);
|
|
12919
|
+
}
|
|
12920
|
+
return result;
|
|
12921
|
+
}
|
|
12922
|
+
function countOursLinesBeforeTrailing(hunk) {
|
|
12923
|
+
let count = 0;
|
|
12924
|
+
const trailingStart = findTrailingContextStart(hunk);
|
|
12925
|
+
for (let i = 0; i < trailingStart; i++) {
|
|
12926
|
+
if (hunk.lines[i].type === "context") count++;
|
|
12927
|
+
}
|
|
12928
|
+
return count;
|
|
12929
|
+
}
|
|
12930
|
+
function findTrailingContextStart(hunk) {
|
|
12931
|
+
let i = hunk.lines.length - 1;
|
|
12932
|
+
while (i >= 0 && hunk.lines[i].type === "context") {
|
|
12933
|
+
i--;
|
|
12934
|
+
}
|
|
12935
|
+
return i + 1;
|
|
12936
|
+
}
|
|
12937
|
+
function matchesAt(needle, haystack, offset) {
|
|
12938
|
+
for (let i = 0; i < needle.length; i++) {
|
|
12939
|
+
if (haystack[offset + i] !== needle[i]) return false;
|
|
12940
|
+
}
|
|
12941
|
+
return true;
|
|
12942
|
+
}
|
|
12943
|
+
function findContextInOurs(contextLines, oursLines, minIndex, hint) {
|
|
12944
|
+
const SEARCH_WINDOW = 200;
|
|
12945
|
+
const maxStart = oursLines.length - contextLines.length;
|
|
12946
|
+
const clampedHint = Math.max(minIndex, Math.min(hint, maxStart));
|
|
12947
|
+
if (clampedHint >= minIndex && clampedHint <= maxStart) {
|
|
12948
|
+
if (matchesAt(contextLines, oursLines, clampedHint)) {
|
|
12949
|
+
return clampedHint;
|
|
12950
|
+
}
|
|
12951
|
+
}
|
|
12952
|
+
for (let delta = 1; delta <= SEARCH_WINDOW; delta++) {
|
|
12953
|
+
for (const sign of [1, -1]) {
|
|
12954
|
+
const idx = clampedHint + delta * sign;
|
|
12955
|
+
if (idx < minIndex || idx > maxStart) continue;
|
|
12956
|
+
if (matchesAt(contextLines, oursLines, idx)) {
|
|
12957
|
+
return idx;
|
|
12958
|
+
}
|
|
12959
|
+
}
|
|
12960
|
+
}
|
|
12961
|
+
return -1;
|
|
12962
|
+
}
|
|
12963
|
+
function computeOursSpan(hunk, oursLines, oursOffset) {
|
|
12964
|
+
const leading = extractLeadingContext(hunk);
|
|
12965
|
+
const trailing = extractTrailingContext(hunk);
|
|
12966
|
+
if (trailing.length === 0) {
|
|
12967
|
+
const contextCount2 = hunk.lines.filter(
|
|
12968
|
+
(l) => l.type === "context"
|
|
12969
|
+
).length;
|
|
12970
|
+
return Math.min(contextCount2, oursLines.length - oursOffset);
|
|
12971
|
+
}
|
|
12972
|
+
const searchStart = oursOffset + leading.length;
|
|
12973
|
+
for (let i = searchStart; i <= oursLines.length - trailing.length; i++) {
|
|
12974
|
+
if (matchesAt(trailing, oursLines, i)) {
|
|
12975
|
+
return i + trailing.length - oursOffset;
|
|
12976
|
+
}
|
|
12977
|
+
}
|
|
12978
|
+
const contextCount = hunk.lines.filter(
|
|
12979
|
+
(l) => l.type === "context"
|
|
12980
|
+
).length;
|
|
12981
|
+
return Math.min(contextCount, oursLines.length - oursOffset);
|
|
12982
|
+
}
|
|
12983
|
+
function locateHunksInOurs(hunks, oursLines) {
|
|
12984
|
+
const located = [];
|
|
12985
|
+
let minOursIndex = 0;
|
|
12986
|
+
for (const hunk of hunks) {
|
|
12987
|
+
const contextLines = extractLeadingContext(hunk);
|
|
12988
|
+
let oursOffset;
|
|
12989
|
+
if (contextLines.length > 0) {
|
|
12990
|
+
const found = findContextInOurs(
|
|
12991
|
+
contextLines,
|
|
12992
|
+
oursLines,
|
|
12993
|
+
minOursIndex,
|
|
12994
|
+
hunk.newStart - 1
|
|
12995
|
+
);
|
|
12996
|
+
if (found === -1) {
|
|
12997
|
+
const trailingContext = extractTrailingContext(hunk);
|
|
12998
|
+
if (trailingContext.length > 0) {
|
|
12999
|
+
const trailingFound = findContextInOurs(
|
|
13000
|
+
trailingContext,
|
|
13001
|
+
oursLines,
|
|
13002
|
+
minOursIndex,
|
|
13003
|
+
hunk.newStart - 1
|
|
13004
|
+
);
|
|
13005
|
+
if (trailingFound === -1) return null;
|
|
13006
|
+
const nonTrailingCount = countOursLinesBeforeTrailing(hunk);
|
|
13007
|
+
oursOffset = trailingFound - nonTrailingCount;
|
|
13008
|
+
if (oursOffset < minOursIndex) return null;
|
|
13009
|
+
} else {
|
|
13010
|
+
return null;
|
|
13011
|
+
}
|
|
13012
|
+
} else {
|
|
13013
|
+
oursOffset = found;
|
|
13014
|
+
}
|
|
13015
|
+
} else if (hunk.oldStart === 1 && hunk.oldCount === 0) {
|
|
13016
|
+
oursOffset = 0;
|
|
13017
|
+
} else {
|
|
13018
|
+
oursOffset = Math.max(hunk.newStart - 1, minOursIndex);
|
|
13019
|
+
}
|
|
13020
|
+
const oursSpan = computeOursSpan(hunk, oursLines, oursOffset);
|
|
13021
|
+
located.push({ hunk, oursOffset, oursSpan });
|
|
13022
|
+
minOursIndex = oursOffset + oursSpan;
|
|
13023
|
+
}
|
|
13024
|
+
return located;
|
|
13025
|
+
}
|
|
13026
|
+
function assembleHybrid(locatedHunks, oursLines) {
|
|
13027
|
+
const baseLines = [];
|
|
13028
|
+
const theirsLines = [];
|
|
13029
|
+
let oursCursor = 0;
|
|
13030
|
+
for (const { hunk, oursOffset, oursSpan } of locatedHunks) {
|
|
13031
|
+
if (oursOffset > oursCursor) {
|
|
13032
|
+
const gapLines = oursLines.slice(oursCursor, oursOffset);
|
|
13033
|
+
baseLines.push(...gapLines);
|
|
13034
|
+
theirsLines.push(...gapLines);
|
|
13035
|
+
}
|
|
13036
|
+
for (const line of hunk.lines) {
|
|
13037
|
+
switch (line.type) {
|
|
13038
|
+
case "context":
|
|
13039
|
+
baseLines.push(line.content);
|
|
13040
|
+
theirsLines.push(line.content);
|
|
13041
|
+
break;
|
|
13042
|
+
case "remove":
|
|
13043
|
+
baseLines.push(line.content);
|
|
13044
|
+
break;
|
|
13045
|
+
case "add":
|
|
13046
|
+
theirsLines.push(line.content);
|
|
13047
|
+
break;
|
|
13048
|
+
}
|
|
13049
|
+
}
|
|
13050
|
+
oursCursor = oursOffset + oursSpan;
|
|
13051
|
+
}
|
|
13052
|
+
if (oursCursor < oursLines.length) {
|
|
13053
|
+
const gapLines = oursLines.slice(oursCursor);
|
|
13054
|
+
baseLines.push(...gapLines);
|
|
13055
|
+
theirsLines.push(...gapLines);
|
|
13056
|
+
}
|
|
13057
|
+
return {
|
|
13058
|
+
base: baseLines.join("\n"),
|
|
13059
|
+
theirs: theirsLines.join("\n")
|
|
13060
|
+
};
|
|
13061
|
+
}
|
|
13062
|
+
function reconstructFromGhostPatch(fileDiff, ours) {
|
|
13063
|
+
const hunks = parseHunks(fileDiff);
|
|
13064
|
+
if (hunks.length === 0) {
|
|
13065
|
+
return null;
|
|
13066
|
+
}
|
|
13067
|
+
const isPureAddition = hunks.every(
|
|
13068
|
+
(h) => h.oldCount === 0 && h.lines.every((l) => l.type !== "remove")
|
|
13069
|
+
);
|
|
13070
|
+
if (isPureAddition) {
|
|
13071
|
+
return null;
|
|
13072
|
+
}
|
|
13073
|
+
const isPureDeletion = hunks.every(
|
|
13074
|
+
(h) => h.newCount === 0 && h.lines.every((l) => l.type !== "add")
|
|
13075
|
+
);
|
|
13076
|
+
if (isPureDeletion) {
|
|
13077
|
+
const baseLines = [];
|
|
13078
|
+
for (const hunk of hunks) {
|
|
13079
|
+
for (const line of hunk.lines) {
|
|
13080
|
+
if (line.type === "context" || line.type === "remove") {
|
|
13081
|
+
baseLines.push(line.content);
|
|
13082
|
+
}
|
|
13083
|
+
}
|
|
13084
|
+
}
|
|
13085
|
+
return {
|
|
13086
|
+
base: baseLines.join("\n"),
|
|
13087
|
+
theirs: ""
|
|
13088
|
+
};
|
|
13089
|
+
}
|
|
13090
|
+
const oursLines = ours.split("\n");
|
|
13091
|
+
const located = locateHunksInOurs(hunks, oursLines);
|
|
13092
|
+
if (!located) {
|
|
13093
|
+
return null;
|
|
13094
|
+
}
|
|
13095
|
+
return assembleHybrid(located, oursLines);
|
|
13096
|
+
}
|
|
13097
|
+
var init_HybridReconstruction = __esm({
|
|
13098
|
+
"src/HybridReconstruction.ts"() {
|
|
13099
|
+
"use strict";
|
|
13100
|
+
}
|
|
13101
|
+
});
|
|
13102
|
+
|
|
12846
13103
|
// src/cli.ts
|
|
12847
13104
|
var import_node_path7 = require("path");
|
|
12848
13105
|
var import_node_fs6 = require("fs");
|
|
13106
|
+
var import_node_readline = require("readline");
|
|
12849
13107
|
init_GitClient();
|
|
12850
13108
|
|
|
12851
13109
|
// src/LockfileManager.ts
|
|
@@ -12938,6 +13196,15 @@ var LockfileManager = class {
|
|
|
12938
13196
|
this.ensureLoaded();
|
|
12939
13197
|
this.lock.patches = [];
|
|
12940
13198
|
}
|
|
13199
|
+
addForgottenHash(hash) {
|
|
13200
|
+
this.ensureLoaded();
|
|
13201
|
+
if (!this.lock.forgotten_hashes) {
|
|
13202
|
+
this.lock.forgotten_hashes = [];
|
|
13203
|
+
}
|
|
13204
|
+
if (!this.lock.forgotten_hashes.includes(hash)) {
|
|
13205
|
+
this.lock.forgotten_hashes.push(hash);
|
|
13206
|
+
}
|
|
13207
|
+
}
|
|
12941
13208
|
getUnresolvedPatches() {
|
|
12942
13209
|
this.ensureLoaded();
|
|
12943
13210
|
return this.lock.patches.filter((p) => p.status === "unresolved");
|
|
@@ -13065,6 +13332,7 @@ var ReplayDetector = class {
|
|
|
13065
13332
|
}
|
|
13066
13333
|
const commits = this.parseGitLog(log);
|
|
13067
13334
|
const newPatches = [];
|
|
13335
|
+
const forgottenHashes = new Set(lock.forgotten_hashes ?? []);
|
|
13068
13336
|
for (const commit of commits) {
|
|
13069
13337
|
if (isGenerationCommit(commit)) {
|
|
13070
13338
|
continue;
|
|
@@ -13078,7 +13346,7 @@ var ReplayDetector = class {
|
|
|
13078
13346
|
}
|
|
13079
13347
|
const patchContent = await this.git.formatPatch(commit.sha);
|
|
13080
13348
|
const contentHash = this.computeContentHash(patchContent);
|
|
13081
|
-
if (lock.patches.find((p) => p.content_hash === contentHash)) {
|
|
13349
|
+
if (lock.patches.find((p) => p.content_hash === contentHash) || forgottenHashes.has(contentHash)) {
|
|
13082
13350
|
continue;
|
|
13083
13351
|
}
|
|
13084
13352
|
const filesOutput = await this.git.exec(["diff-tree", "--no-commit-id", "--name-only", "-r", commit.sha]);
|
|
@@ -13177,7 +13445,7 @@ var ReplayDetector = class {
|
|
|
13177
13445
|
if (!diff.trim()) return { patches: [], revertedPatchIds: [] };
|
|
13178
13446
|
const contentHash = this.computeContentHash(diff);
|
|
13179
13447
|
const lock = this.lockManager.read();
|
|
13180
|
-
if (lock.patches.some((p) => p.content_hash === contentHash)) {
|
|
13448
|
+
if (lock.patches.some((p) => p.content_hash === contentHash) || (lock.forgotten_hashes ?? []).includes(contentHash)) {
|
|
13181
13449
|
return { patches: [], revertedPatchIds: [] };
|
|
13182
13450
|
}
|
|
13183
13451
|
const headSha = (await this.git.exec(["rev-parse", "HEAD"])).trim();
|
|
@@ -14546,6 +14814,34 @@ var import_promises = require("fs/promises");
|
|
|
14546
14814
|
var import_node_os = require("os");
|
|
14547
14815
|
var import_node_path3 = require("path");
|
|
14548
14816
|
|
|
14817
|
+
// src/conflict-utils.ts
|
|
14818
|
+
function stripConflictMarkers(content) {
|
|
14819
|
+
const lines = content.split("\n");
|
|
14820
|
+
const result = [];
|
|
14821
|
+
let inConflict = false;
|
|
14822
|
+
let inOurs = false;
|
|
14823
|
+
for (const line of lines) {
|
|
14824
|
+
if (line.startsWith("<<<<<<< ")) {
|
|
14825
|
+
inConflict = true;
|
|
14826
|
+
inOurs = true;
|
|
14827
|
+
continue;
|
|
14828
|
+
}
|
|
14829
|
+
if (inConflict && line === "=======") {
|
|
14830
|
+
inOurs = false;
|
|
14831
|
+
continue;
|
|
14832
|
+
}
|
|
14833
|
+
if (inConflict && line.startsWith(">>>>>>> ")) {
|
|
14834
|
+
inConflict = false;
|
|
14835
|
+
inOurs = false;
|
|
14836
|
+
continue;
|
|
14837
|
+
}
|
|
14838
|
+
if (!inConflict || inOurs) {
|
|
14839
|
+
result.push(line);
|
|
14840
|
+
}
|
|
14841
|
+
}
|
|
14842
|
+
return result.join("\n");
|
|
14843
|
+
}
|
|
14844
|
+
|
|
14549
14845
|
// node_modules/node-diff3/dist/diff3.mjs
|
|
14550
14846
|
function LCS(buffer1, buffer2) {
|
|
14551
14847
|
let equivalenceClasses = {};
|
|
@@ -14852,6 +15148,7 @@ var ReplayApplicator = class {
|
|
|
14852
15148
|
lockManager;
|
|
14853
15149
|
outputDir;
|
|
14854
15150
|
renameCache = /* @__PURE__ */ new Map();
|
|
15151
|
+
treeExistsCache = /* @__PURE__ */ new Map();
|
|
14855
15152
|
fileTheirsAccumulator = /* @__PURE__ */ new Map();
|
|
14856
15153
|
constructor(git, lockManager, outputDir) {
|
|
14857
15154
|
this.git = git;
|
|
@@ -14869,7 +15166,8 @@ var ReplayApplicator = class {
|
|
|
14869
15166
|
async applyPatches(patches) {
|
|
14870
15167
|
this.resetAccumulator();
|
|
14871
15168
|
const results = [];
|
|
14872
|
-
for (
|
|
15169
|
+
for (let i = 0; i < patches.length; i++) {
|
|
15170
|
+
const patch = patches[i];
|
|
14873
15171
|
if (this.isExcluded(patch)) {
|
|
14874
15172
|
results.push({
|
|
14875
15173
|
patch,
|
|
@@ -14880,6 +15178,33 @@ var ReplayApplicator = class {
|
|
|
14880
15178
|
}
|
|
14881
15179
|
const result = await this.applyPatchWithFallback(patch);
|
|
14882
15180
|
results.push(result);
|
|
15181
|
+
if (result.status === "conflict" && result.fileResults) {
|
|
15182
|
+
const laterFiles = /* @__PURE__ */ new Set();
|
|
15183
|
+
for (let j = i + 1; j < patches.length; j++) {
|
|
15184
|
+
for (const f of patches[j].files) {
|
|
15185
|
+
laterFiles.add(f);
|
|
15186
|
+
}
|
|
15187
|
+
}
|
|
15188
|
+
const resolvedToOriginal = /* @__PURE__ */ new Map();
|
|
15189
|
+
if (result.resolvedFiles) {
|
|
15190
|
+
for (const [orig, resolved] of Object.entries(result.resolvedFiles)) {
|
|
15191
|
+
resolvedToOriginal.set(resolved, orig);
|
|
15192
|
+
}
|
|
15193
|
+
}
|
|
15194
|
+
for (const fileResult of result.fileResults) {
|
|
15195
|
+
if (fileResult.status !== "conflict") continue;
|
|
15196
|
+
const originalPath = resolvedToOriginal.get(fileResult.file) ?? fileResult.file;
|
|
15197
|
+
if (laterFiles.has(fileResult.file) || laterFiles.has(originalPath)) {
|
|
15198
|
+
const filePath = (0, import_node_path3.join)(this.outputDir, fileResult.file);
|
|
15199
|
+
try {
|
|
15200
|
+
const content = await (0, import_promises.readFile)(filePath, "utf-8");
|
|
15201
|
+
const stripped = stripConflictMarkers(content);
|
|
15202
|
+
await (0, import_promises.writeFile)(filePath, stripped);
|
|
15203
|
+
} catch {
|
|
15204
|
+
}
|
|
15205
|
+
}
|
|
15206
|
+
}
|
|
15207
|
+
}
|
|
14883
15208
|
}
|
|
14884
15209
|
return results;
|
|
14885
15210
|
}
|
|
@@ -14898,7 +15223,7 @@ var ReplayApplicator = class {
|
|
|
14898
15223
|
const resolvedPath = await this.resolveFilePath(filePath, baseGen.tree_hash, currentTreeHash);
|
|
14899
15224
|
const base = await this.git.showFile(baseGen.tree_hash, filePath);
|
|
14900
15225
|
const theirs = await this.applyPatchToContent(base, patch.patch_content, filePath, tempGit, tempDir);
|
|
14901
|
-
if (theirs
|
|
15226
|
+
if (theirs) {
|
|
14902
15227
|
this.fileTheirsAccumulator.set(resolvedPath, {
|
|
14903
15228
|
content: theirs,
|
|
14904
15229
|
baseGeneration: patch.base_generation
|
|
@@ -15020,17 +15345,36 @@ var ReplayApplicator = class {
|
|
|
15020
15345
|
}
|
|
15021
15346
|
const oursPath = (0, import_node_path3.join)(this.outputDir, resolvedPath);
|
|
15022
15347
|
const ours = await (0, import_promises.readFile)(oursPath, "utf-8").catch(() => null);
|
|
15023
|
-
let
|
|
15024
|
-
|
|
15025
|
-
|
|
15026
|
-
|
|
15027
|
-
|
|
15028
|
-
|
|
15029
|
-
|
|
15030
|
-
|
|
15348
|
+
let ghostReconstructed = false;
|
|
15349
|
+
let theirs = null;
|
|
15350
|
+
if (!base && ours && !renameSourcePath) {
|
|
15351
|
+
const treeReachable = await this.isTreeReachable(baseGen.tree_hash);
|
|
15352
|
+
if (!treeReachable) {
|
|
15353
|
+
const fileDiff = this.extractFileDiff(patch.patch_content, filePath);
|
|
15354
|
+
if (fileDiff) {
|
|
15355
|
+
const { reconstructFromGhostPatch: reconstructFromGhostPatch2 } = await Promise.resolve().then(() => (init_HybridReconstruction(), HybridReconstruction_exports));
|
|
15356
|
+
const result = reconstructFromGhostPatch2(fileDiff, ours);
|
|
15357
|
+
if (result) {
|
|
15358
|
+
base = result.base;
|
|
15359
|
+
theirs = result.theirs;
|
|
15360
|
+
ghostReconstructed = true;
|
|
15361
|
+
}
|
|
15362
|
+
}
|
|
15363
|
+
}
|
|
15364
|
+
}
|
|
15365
|
+
if (!ghostReconstructed) {
|
|
15366
|
+
theirs = await this.applyPatchToContent(
|
|
15367
|
+
base,
|
|
15368
|
+
patch.patch_content,
|
|
15369
|
+
filePath,
|
|
15370
|
+
tempGit,
|
|
15371
|
+
tempDir,
|
|
15372
|
+
renameSourcePath
|
|
15373
|
+
);
|
|
15374
|
+
}
|
|
15031
15375
|
let useAccumulatorAsMergeBase = false;
|
|
15032
15376
|
const accumulatorEntry = this.fileTheirsAccumulator.get(resolvedPath);
|
|
15033
|
-
if (!theirs &&
|
|
15377
|
+
if (!theirs && accumulatorEntry) {
|
|
15034
15378
|
theirs = await this.applyPatchToContent(
|
|
15035
15379
|
accumulatorEntry.content,
|
|
15036
15380
|
patch.patch_content,
|
|
@@ -15060,13 +15404,13 @@ var ReplayApplicator = class {
|
|
|
15060
15404
|
baseMismatchSkipped = true;
|
|
15061
15405
|
}
|
|
15062
15406
|
}
|
|
15063
|
-
if (
|
|
15407
|
+
if (base == null && !ours && effective_theirs) {
|
|
15064
15408
|
const outDir2 = (0, import_node_path3.dirname)(oursPath);
|
|
15065
15409
|
await (0, import_promises.mkdir)(outDir2, { recursive: true });
|
|
15066
15410
|
await (0, import_promises.writeFile)(oursPath, effective_theirs);
|
|
15067
15411
|
return { file: resolvedPath, status: "merged", reason: "new-file" };
|
|
15068
15412
|
}
|
|
15069
|
-
if (
|
|
15413
|
+
if (base == null && ours && effective_theirs) {
|
|
15070
15414
|
const merged2 = threeWayMerge("", ours, effective_theirs);
|
|
15071
15415
|
const outDir2 = (0, import_node_path3.dirname)(oursPath);
|
|
15072
15416
|
await (0, import_promises.mkdir)(outDir2, { recursive: true });
|
|
@@ -15089,7 +15433,7 @@ var ReplayApplicator = class {
|
|
|
15089
15433
|
reason: "missing-content"
|
|
15090
15434
|
};
|
|
15091
15435
|
}
|
|
15092
|
-
if (
|
|
15436
|
+
if (base == null && !useAccumulatorAsMergeBase || !ours) {
|
|
15093
15437
|
return {
|
|
15094
15438
|
file: resolvedPath,
|
|
15095
15439
|
status: "skipped",
|
|
@@ -15097,11 +15441,18 @@ var ReplayApplicator = class {
|
|
|
15097
15441
|
};
|
|
15098
15442
|
}
|
|
15099
15443
|
const mergeBase = useAccumulatorAsMergeBase && accumulatorEntry ? accumulatorEntry.content : base;
|
|
15444
|
+
if (mergeBase == null) {
|
|
15445
|
+
return {
|
|
15446
|
+
file: resolvedPath,
|
|
15447
|
+
status: "skipped",
|
|
15448
|
+
reason: "missing-content"
|
|
15449
|
+
};
|
|
15450
|
+
}
|
|
15100
15451
|
const merged = threeWayMerge(mergeBase, ours, effective_theirs);
|
|
15101
15452
|
const outDir = (0, import_node_path3.dirname)(oursPath);
|
|
15102
15453
|
await (0, import_promises.mkdir)(outDir, { recursive: true });
|
|
15103
15454
|
await (0, import_promises.writeFile)(oursPath, merged.content);
|
|
15104
|
-
if (effective_theirs
|
|
15455
|
+
if (effective_theirs) {
|
|
15105
15456
|
this.fileTheirsAccumulator.set(resolvedPath, {
|
|
15106
15457
|
content: effective_theirs,
|
|
15107
15458
|
baseGeneration: patch.base_generation
|
|
@@ -15125,6 +15476,14 @@ var ReplayApplicator = class {
|
|
|
15125
15476
|
};
|
|
15126
15477
|
}
|
|
15127
15478
|
}
|
|
15479
|
+
async isTreeReachable(treeHash) {
|
|
15480
|
+
let result = this.treeExistsCache.get(treeHash);
|
|
15481
|
+
if (result === void 0) {
|
|
15482
|
+
result = await this.git.treeExists(treeHash);
|
|
15483
|
+
this.treeExistsCache.set(treeHash, result);
|
|
15484
|
+
}
|
|
15485
|
+
return result;
|
|
15486
|
+
}
|
|
15128
15487
|
isExcluded(patch) {
|
|
15129
15488
|
const config = this.lockManager.getCustomizationsConfig();
|
|
15130
15489
|
if (!config.exclude) return false;
|
|
@@ -15339,34 +15698,6 @@ CLI Version: ${options.cliVersion}`;
|
|
|
15339
15698
|
}
|
|
15340
15699
|
};
|
|
15341
15700
|
|
|
15342
|
-
// src/conflict-utils.ts
|
|
15343
|
-
function stripConflictMarkers(content) {
|
|
15344
|
-
const lines = content.split("\n");
|
|
15345
|
-
const result = [];
|
|
15346
|
-
let inConflict = false;
|
|
15347
|
-
let inOurs = false;
|
|
15348
|
-
for (const line of lines) {
|
|
15349
|
-
if (line.startsWith("<<<<<<< ")) {
|
|
15350
|
-
inConflict = true;
|
|
15351
|
-
inOurs = true;
|
|
15352
|
-
continue;
|
|
15353
|
-
}
|
|
15354
|
-
if (inConflict && line === "=======") {
|
|
15355
|
-
inOurs = false;
|
|
15356
|
-
continue;
|
|
15357
|
-
}
|
|
15358
|
-
if (inConflict && line.startsWith(">>>>>>> ")) {
|
|
15359
|
-
inConflict = false;
|
|
15360
|
-
inOurs = false;
|
|
15361
|
-
continue;
|
|
15362
|
-
}
|
|
15363
|
-
if (!inConflict || inOurs) {
|
|
15364
|
-
result.push(line);
|
|
15365
|
-
}
|
|
15366
|
-
}
|
|
15367
|
-
return result.join("\n");
|
|
15368
|
-
}
|
|
15369
|
-
|
|
15370
15701
|
// src/ReplayService.ts
|
|
15371
15702
|
var ReplayService = class {
|
|
15372
15703
|
git;
|
|
@@ -16222,6 +16553,7 @@ async function bootstrap(outputDir, options) {
|
|
|
16222
16553
|
}
|
|
16223
16554
|
lockManager.save();
|
|
16224
16555
|
const fernignoreUpdated = ensureFernignoreEntries(outputDir);
|
|
16556
|
+
ensureGitattributesEntries(outputDir);
|
|
16225
16557
|
if (migrator.fernignoreExists() && fernignorePatterns.length > 0) {
|
|
16226
16558
|
const action = options?.fernignoreAction ?? "skip";
|
|
16227
16559
|
if (action === "migrate") {
|
|
@@ -16303,7 +16635,7 @@ function parseGitLog(log) {
|
|
|
16303
16635
|
return { sha, authorName, authorEmail, message };
|
|
16304
16636
|
});
|
|
16305
16637
|
}
|
|
16306
|
-
var REPLAY_FERNIGNORE_ENTRIES = [".fern/replay.lock", ".fern/replay.yml"];
|
|
16638
|
+
var REPLAY_FERNIGNORE_ENTRIES = [".fern/replay.lock", ".fern/replay.yml", ".gitattributes"];
|
|
16307
16639
|
function ensureFernignoreEntries(outputDir) {
|
|
16308
16640
|
const fernignorePath = (0, import_node_path6.join)(outputDir, ".fernignore");
|
|
16309
16641
|
let content = "";
|
|
@@ -16327,36 +16659,174 @@ function ensureFernignoreEntries(outputDir) {
|
|
|
16327
16659
|
(0, import_node_fs4.writeFileSync)(fernignorePath, content, "utf-8");
|
|
16328
16660
|
return true;
|
|
16329
16661
|
}
|
|
16662
|
+
var GITATTRIBUTES_ENTRIES = [".fern/replay.lock linguist-generated=true"];
|
|
16663
|
+
function ensureGitattributesEntries(outputDir) {
|
|
16664
|
+
const gitattributesPath = (0, import_node_path6.join)(outputDir, ".gitattributes");
|
|
16665
|
+
let content = "";
|
|
16666
|
+
if ((0, import_node_fs4.existsSync)(gitattributesPath)) {
|
|
16667
|
+
content = (0, import_node_fs4.readFileSync)(gitattributesPath, "utf-8");
|
|
16668
|
+
}
|
|
16669
|
+
const lines = content.split("\n");
|
|
16670
|
+
const toAdd = [];
|
|
16671
|
+
for (const entry of GITATTRIBUTES_ENTRIES) {
|
|
16672
|
+
if (!lines.some((line) => line.trim() === entry)) {
|
|
16673
|
+
toAdd.push(entry);
|
|
16674
|
+
}
|
|
16675
|
+
}
|
|
16676
|
+
if (toAdd.length === 0) {
|
|
16677
|
+
return;
|
|
16678
|
+
}
|
|
16679
|
+
if (content && !content.endsWith("\n")) {
|
|
16680
|
+
content += "\n";
|
|
16681
|
+
}
|
|
16682
|
+
content += toAdd.join("\n") + "\n";
|
|
16683
|
+
(0, import_node_fs4.writeFileSync)(gitattributesPath, content, "utf-8");
|
|
16684
|
+
}
|
|
16330
16685
|
function computeContentHash(patchContent) {
|
|
16331
16686
|
const normalized = patchContent.split("\n").filter((line) => !line.startsWith("From ") && !line.startsWith("index ") && !line.startsWith("Date: ")).join("\n");
|
|
16332
16687
|
return `sha256:${(0, import_node_crypto3.createHash)("sha256").update(normalized).digest("hex")}`;
|
|
16333
16688
|
}
|
|
16334
16689
|
|
|
16335
16690
|
// src/commands/forget.ts
|
|
16336
|
-
function
|
|
16691
|
+
function parseDiffStat(patchContent) {
|
|
16692
|
+
let additions = 0;
|
|
16693
|
+
let deletions = 0;
|
|
16694
|
+
let inDiffHunk = false;
|
|
16695
|
+
for (const line of patchContent.split("\n")) {
|
|
16696
|
+
if (line.startsWith("diff --git ")) {
|
|
16697
|
+
inDiffHunk = true;
|
|
16698
|
+
continue;
|
|
16699
|
+
}
|
|
16700
|
+
if (!inDiffHunk) continue;
|
|
16701
|
+
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
16702
|
+
additions++;
|
|
16703
|
+
} else if (line.startsWith("-") && !line.startsWith("---")) {
|
|
16704
|
+
deletions++;
|
|
16705
|
+
}
|
|
16706
|
+
}
|
|
16707
|
+
return { additions, deletions };
|
|
16708
|
+
}
|
|
16709
|
+
function toMatchedPatch(patch) {
|
|
16710
|
+
return {
|
|
16711
|
+
id: patch.id,
|
|
16712
|
+
message: patch.original_message,
|
|
16713
|
+
files: patch.files,
|
|
16714
|
+
diffstat: parseDiffStat(patch.patch_content),
|
|
16715
|
+
...patch.status ? { status: patch.status } : {}
|
|
16716
|
+
};
|
|
16717
|
+
}
|
|
16718
|
+
function matchesPatch(patch, pattern) {
|
|
16719
|
+
const fileMatch = patch.files.some(
|
|
16720
|
+
(file) => file === pattern || minimatch(file, pattern)
|
|
16721
|
+
);
|
|
16722
|
+
if (fileMatch) return true;
|
|
16723
|
+
return patch.original_message.toLowerCase().includes(pattern.toLowerCase());
|
|
16724
|
+
}
|
|
16725
|
+
function buildWarnings(patches) {
|
|
16726
|
+
const warnings = [];
|
|
16727
|
+
for (const patch of patches) {
|
|
16728
|
+
if (patch.status === "resolving") {
|
|
16729
|
+
warnings.push(
|
|
16730
|
+
`patch ${patch.id} has conflict markers in these files: ${patch.files.join(", ")}. Run \`git checkout -- <files>\` to restore the generated versions.`
|
|
16731
|
+
);
|
|
16732
|
+
} else if (patch.status === "unresolved") {
|
|
16733
|
+
warnings.push(
|
|
16734
|
+
`patch ${patch.id} had unresolved conflicts (files: ${patch.files.join(", ")}).`
|
|
16735
|
+
);
|
|
16736
|
+
}
|
|
16737
|
+
}
|
|
16738
|
+
return warnings;
|
|
16739
|
+
}
|
|
16740
|
+
var EMPTY_RESULT = {
|
|
16741
|
+
initialized: false,
|
|
16742
|
+
removed: [],
|
|
16743
|
+
remaining: 0,
|
|
16744
|
+
notFound: false,
|
|
16745
|
+
alreadyForgotten: [],
|
|
16746
|
+
totalPatches: 0,
|
|
16747
|
+
warnings: []
|
|
16748
|
+
};
|
|
16749
|
+
function forget(outputDir, options) {
|
|
16337
16750
|
const lockManager = new LockfileManager(outputDir);
|
|
16338
16751
|
if (!lockManager.exists()) {
|
|
16339
|
-
return {
|
|
16752
|
+
return { ...EMPTY_RESULT };
|
|
16340
16753
|
}
|
|
16341
16754
|
const lock = lockManager.read();
|
|
16342
|
-
const
|
|
16343
|
-
|
|
16344
|
-
|
|
16345
|
-
|
|
16346
|
-
|
|
16755
|
+
const totalPatches = lock.patches.length;
|
|
16756
|
+
if (options?.all) {
|
|
16757
|
+
const removed = lock.patches.map(toMatchedPatch);
|
|
16758
|
+
const warnings = buildWarnings(lock.patches);
|
|
16759
|
+
if (!options.dryRun) {
|
|
16760
|
+
for (const patch of lock.patches) {
|
|
16761
|
+
lockManager.addForgottenHash(patch.content_hash);
|
|
16762
|
+
}
|
|
16763
|
+
lockManager.clearPatches();
|
|
16764
|
+
lockManager.save();
|
|
16765
|
+
}
|
|
16766
|
+
return {
|
|
16767
|
+
initialized: true,
|
|
16768
|
+
removed,
|
|
16769
|
+
remaining: 0,
|
|
16770
|
+
notFound: false,
|
|
16771
|
+
alreadyForgotten: [],
|
|
16772
|
+
totalPatches,
|
|
16773
|
+
warnings
|
|
16774
|
+
};
|
|
16347
16775
|
}
|
|
16348
|
-
|
|
16349
|
-
|
|
16350
|
-
|
|
16351
|
-
|
|
16352
|
-
|
|
16353
|
-
|
|
16354
|
-
|
|
16355
|
-
|
|
16776
|
+
if (options?.patchIds && options.patchIds.length > 0) {
|
|
16777
|
+
const removed = [];
|
|
16778
|
+
const alreadyForgotten = [];
|
|
16779
|
+
const patchesToRemove = [];
|
|
16780
|
+
for (const id of options.patchIds) {
|
|
16781
|
+
const patch = lock.patches.find((p) => p.id === id);
|
|
16782
|
+
if (patch) {
|
|
16783
|
+
removed.push(toMatchedPatch(patch));
|
|
16784
|
+
patchesToRemove.push(patch);
|
|
16785
|
+
} else {
|
|
16786
|
+
alreadyForgotten.push(id);
|
|
16787
|
+
}
|
|
16356
16788
|
}
|
|
16357
|
-
|
|
16789
|
+
const warnings = buildWarnings(patchesToRemove);
|
|
16790
|
+
if (!options.dryRun) {
|
|
16791
|
+
for (const patch of patchesToRemove) {
|
|
16792
|
+
lockManager.addForgottenHash(patch.content_hash);
|
|
16793
|
+
lockManager.removePatch(patch.id);
|
|
16794
|
+
}
|
|
16795
|
+
lockManager.save();
|
|
16796
|
+
}
|
|
16797
|
+
return {
|
|
16798
|
+
initialized: true,
|
|
16799
|
+
removed,
|
|
16800
|
+
remaining: totalPatches - removed.length,
|
|
16801
|
+
notFound: removed.length === 0 && alreadyForgotten.length > 0,
|
|
16802
|
+
alreadyForgotten,
|
|
16803
|
+
totalPatches,
|
|
16804
|
+
warnings
|
|
16805
|
+
};
|
|
16806
|
+
}
|
|
16807
|
+
if (options?.pattern) {
|
|
16808
|
+
const matched = lock.patches.filter((p) => matchesPatch(p, options.pattern)).map(toMatchedPatch);
|
|
16809
|
+
return {
|
|
16810
|
+
initialized: true,
|
|
16811
|
+
removed: [],
|
|
16812
|
+
remaining: totalPatches,
|
|
16813
|
+
notFound: matched.length === 0,
|
|
16814
|
+
alreadyForgotten: [],
|
|
16815
|
+
totalPatches,
|
|
16816
|
+
warnings: [],
|
|
16817
|
+
matched
|
|
16818
|
+
};
|
|
16358
16819
|
}
|
|
16359
|
-
return {
|
|
16820
|
+
return {
|
|
16821
|
+
initialized: true,
|
|
16822
|
+
removed: [],
|
|
16823
|
+
remaining: totalPatches,
|
|
16824
|
+
notFound: totalPatches === 0,
|
|
16825
|
+
alreadyForgotten: [],
|
|
16826
|
+
totalPatches,
|
|
16827
|
+
warnings: [],
|
|
16828
|
+
matched: lock.patches.map(toMatchedPatch)
|
|
16829
|
+
};
|
|
16360
16830
|
}
|
|
16361
16831
|
|
|
16362
16832
|
// src/commands/reset.ts
|
|
@@ -16474,6 +16944,55 @@ async function getChangedFiles(git, currentGen, files) {
|
|
|
16474
16944
|
return changed.length > 0 ? changed : files;
|
|
16475
16945
|
}
|
|
16476
16946
|
|
|
16947
|
+
// src/commands/status.ts
|
|
16948
|
+
function status(outputDir) {
|
|
16949
|
+
const lockManager = new LockfileManager(outputDir);
|
|
16950
|
+
if (!lockManager.exists()) {
|
|
16951
|
+
return {
|
|
16952
|
+
initialized: false,
|
|
16953
|
+
generationCount: 0,
|
|
16954
|
+
lastGeneration: void 0,
|
|
16955
|
+
patches: [],
|
|
16956
|
+
unresolvedCount: 0,
|
|
16957
|
+
excludePatterns: []
|
|
16958
|
+
};
|
|
16959
|
+
}
|
|
16960
|
+
const lock = lockManager.read();
|
|
16961
|
+
const patches = lock.patches.map((patch) => ({
|
|
16962
|
+
id: patch.id,
|
|
16963
|
+
type: patch.patch_content.includes("new file mode") ? "added" : "modified",
|
|
16964
|
+
message: patch.original_message,
|
|
16965
|
+
author: patch.original_author.split("<")[0]?.trim() || "unknown",
|
|
16966
|
+
sha: patch.original_commit.slice(0, 7),
|
|
16967
|
+
files: patch.files,
|
|
16968
|
+
fileCount: patch.files.length,
|
|
16969
|
+
...patch.status ? { status: patch.status } : {}
|
|
16970
|
+
}));
|
|
16971
|
+
const unresolvedCount = lock.patches.filter(
|
|
16972
|
+
(p) => p.status === "unresolved" || p.status === "resolving"
|
|
16973
|
+
).length;
|
|
16974
|
+
let lastGeneration;
|
|
16975
|
+
const lastGen = lock.generations.find((g) => g.commit_sha === lock.current_generation);
|
|
16976
|
+
if (lastGen) {
|
|
16977
|
+
lastGeneration = {
|
|
16978
|
+
sha: lastGen.commit_sha.slice(0, 7),
|
|
16979
|
+
timestamp: lastGen.timestamp,
|
|
16980
|
+
cliVersion: lastGen.cli_version,
|
|
16981
|
+
generatorVersions: lastGen.generator_versions
|
|
16982
|
+
};
|
|
16983
|
+
}
|
|
16984
|
+
const config = lockManager.getCustomizationsConfig();
|
|
16985
|
+
const excludePatterns = config.exclude ?? [];
|
|
16986
|
+
return {
|
|
16987
|
+
initialized: true,
|
|
16988
|
+
generationCount: lock.generations.length,
|
|
16989
|
+
lastGeneration,
|
|
16990
|
+
patches,
|
|
16991
|
+
unresolvedCount,
|
|
16992
|
+
excludePatterns
|
|
16993
|
+
};
|
|
16994
|
+
}
|
|
16995
|
+
|
|
16477
16996
|
// src/cli.ts
|
|
16478
16997
|
var COMMANDS = ["bootstrap", "status", "detect", "replay", "migrate", "forget", "reset", "resolve"];
|
|
16479
16998
|
function usage() {
|
|
@@ -16485,7 +17004,7 @@ Commands:
|
|
|
16485
17004
|
detect Detect new customization patches (dry-run)
|
|
16486
17005
|
replay Run full replay (detect + apply patches)
|
|
16487
17006
|
migrate Analyze .fernignore migration
|
|
16488
|
-
forget Remove specific patches by
|
|
17007
|
+
forget Remove specific patches (by ID, search, or --all)
|
|
16489
17008
|
reset Remove all Replay state (nuclear option)
|
|
16490
17009
|
resolve Finish replay after manually resolving conflicts
|
|
16491
17010
|
|
|
@@ -16495,6 +17014,8 @@ Options:
|
|
|
16495
17014
|
--no-check Skip conflict marker check (resolve command only)
|
|
16496
17015
|
--migrate-fernignore Move untrackable .fernignore patterns to replay.yml
|
|
16497
17016
|
--max-commits <n> Max commits to scan (default: 500)
|
|
17017
|
+
--all Remove all tracked patches (forget only)
|
|
17018
|
+
--yes, -y Skip interactive selection/confirmation (forget only)
|
|
16498
17019
|
|
|
16499
17020
|
Examples:
|
|
16500
17021
|
fern-replay bootstrap ./my-sdk
|
|
@@ -16504,8 +17025,9 @@ Examples:
|
|
|
16504
17025
|
fern-replay detect ./my-sdk
|
|
16505
17026
|
fern-replay replay ./my-sdk --dry-run
|
|
16506
17027
|
fern-replay migrate ./my-sdk
|
|
17028
|
+
fern-replay forget ./my-sdk patch-def45678
|
|
16507
17029
|
fern-replay forget ./my-sdk "src/utils/retry.ts"
|
|
16508
|
-
fern-replay forget ./my-sdk
|
|
17030
|
+
fern-replay forget ./my-sdk --all
|
|
16509
17031
|
fern-replay reset ./my-sdk
|
|
16510
17032
|
fern-replay reset ./my-sdk --dry-run`);
|
|
16511
17033
|
}
|
|
@@ -16530,7 +17052,7 @@ function parseArgs(argv) {
|
|
|
16530
17052
|
process.exit(1);
|
|
16531
17053
|
}
|
|
16532
17054
|
const flags = {};
|
|
16533
|
-
|
|
17055
|
+
const positionals = [];
|
|
16534
17056
|
for (let i = 2; i < args.length; i++) {
|
|
16535
17057
|
const arg = args[i];
|
|
16536
17058
|
if (arg === "--dry-run") {
|
|
@@ -16545,11 +17067,17 @@ function parseArgs(argv) {
|
|
|
16545
17067
|
flags.force = true;
|
|
16546
17068
|
} else if (arg === "--no-check") {
|
|
16547
17069
|
flags.noCheck = true;
|
|
16548
|
-
} else if (
|
|
16549
|
-
|
|
17070
|
+
} else if (arg === "--verbose" || arg === "-v") {
|
|
17071
|
+
flags.verbose = true;
|
|
17072
|
+
} else if (arg === "--all") {
|
|
17073
|
+
flags.all = true;
|
|
17074
|
+
} else if (arg === "--yes" || arg === "-y") {
|
|
17075
|
+
flags.yes = true;
|
|
17076
|
+
} else if (!arg.startsWith("--")) {
|
|
17077
|
+
positionals.push(arg);
|
|
16550
17078
|
}
|
|
16551
17079
|
}
|
|
16552
|
-
return { command, dir: (0, import_node_path7.resolve)(dir), flags,
|
|
17080
|
+
return { command, dir: (0, import_node_path7.resolve)(dir), flags, positionals };
|
|
16553
17081
|
}
|
|
16554
17082
|
async function runBootstrap(dir, flags) {
|
|
16555
17083
|
const dryRun = !!flags.dryRun;
|
|
@@ -16602,43 +17130,67 @@ async function runBootstrap(dir, flags) {
|
|
|
16602
17130
|
for (const w of result.warnings) console.log(` ${w}`);
|
|
16603
17131
|
}
|
|
16604
17132
|
}
|
|
16605
|
-
async function runStatus(dir) {
|
|
16606
|
-
const
|
|
16607
|
-
|
|
16608
|
-
|
|
17133
|
+
async function runStatus(dir, flags) {
|
|
17134
|
+
const result = status(dir);
|
|
17135
|
+
const verbose = !!flags.verbose;
|
|
17136
|
+
if (!result.initialized) {
|
|
17137
|
+
console.log("Replay is not initialized. Run 'fern replay init' to get started.");
|
|
16609
17138
|
return;
|
|
16610
17139
|
}
|
|
16611
|
-
|
|
16612
|
-
|
|
16613
|
-
|
|
16614
|
-
|
|
16615
|
-
|
|
16616
|
-
|
|
16617
|
-
|
|
16618
|
-
console.log(` CLI: ${lastGen.cli_version}`);
|
|
16619
|
-
if (Object.keys(lastGen.generator_versions).length > 0) {
|
|
16620
|
-
for (const [name, ver] of Object.entries(lastGen.generator_versions)) {
|
|
17140
|
+
console.log("Replay: initialized");
|
|
17141
|
+
if (result.lastGeneration) {
|
|
17142
|
+
const gen = result.lastGeneration;
|
|
17143
|
+
if (verbose) {
|
|
17144
|
+
console.log(`Last generation: ${gen.sha} (${gen.timestamp})`);
|
|
17145
|
+
console.log(` CLI: ${gen.cliVersion}`);
|
|
17146
|
+
for (const [name, ver] of Object.entries(gen.generatorVersions)) {
|
|
16621
17147
|
console.log(` ${name}: ${ver}`);
|
|
16622
17148
|
}
|
|
17149
|
+
console.log(`Generations tracked: ${result.generationCount}`);
|
|
17150
|
+
} else {
|
|
17151
|
+
const genVersions = Object.entries(gen.generatorVersions);
|
|
17152
|
+
const versionSuffix = genVersions.length > 0 ? `, ${genVersions[0][0]}@${genVersions[0][1]}` : "";
|
|
17153
|
+
console.log(`Last generation: ${gen.sha} (${gen.timestamp}${versionSuffix})`);
|
|
16623
17154
|
}
|
|
16624
17155
|
}
|
|
17156
|
+
if (result.patches.length === 0) {
|
|
17157
|
+
console.log("\nNo customizations tracked. Edit SDK files and commit \u2014 Replay will detect them on next generation.");
|
|
17158
|
+
return;
|
|
17159
|
+
}
|
|
17160
|
+
const unresolvedSuffix = result.unresolvedCount > 0 ? `, ${result.unresolvedCount} unresolved` : "";
|
|
16625
17161
|
console.log(`
|
|
16626
|
-
Patches: ${
|
|
16627
|
-
if (
|
|
16628
|
-
for (const patch of
|
|
16629
|
-
const
|
|
16630
|
-
console.log(`
|
|
16631
|
-
|
|
17162
|
+
Patches: ${result.patches.length} tracked${unresolvedSuffix}`);
|
|
17163
|
+
if (verbose) {
|
|
17164
|
+
for (const patch of result.patches) {
|
|
17165
|
+
const prefix = patch.status ? "\u26A0 " : " ";
|
|
17166
|
+
console.log(`
|
|
17167
|
+
${prefix}${patch.id} [${patch.type}] "${patch.message}"`);
|
|
17168
|
+
console.log(` by ${patch.author} (${patch.sha})`);
|
|
16632
17169
|
for (const f of patch.files) {
|
|
16633
17170
|
console.log(` ${f}`);
|
|
16634
17171
|
}
|
|
17172
|
+
if (patch.status) {
|
|
17173
|
+
console.log(" Run `fern replay resolve` to fix.");
|
|
17174
|
+
}
|
|
17175
|
+
}
|
|
17176
|
+
if (result.excludePatterns.length > 0) {
|
|
17177
|
+
console.log("\nExclude patterns (from replay.yml):");
|
|
17178
|
+
for (const p of result.excludePatterns) {
|
|
17179
|
+
console.log(` ${p}`);
|
|
17180
|
+
}
|
|
17181
|
+
}
|
|
17182
|
+
} else {
|
|
17183
|
+
console.log("");
|
|
17184
|
+
const maxDisplay = 10;
|
|
17185
|
+
const displayPatches = result.patches.slice(0, maxDisplay);
|
|
17186
|
+
for (const patch of displayPatches) {
|
|
17187
|
+
const prefix = patch.status ? "\u26A0 " : " ";
|
|
17188
|
+
const filesLabel = patch.fileCount === 1 ? "1 file" : `${patch.fileCount} files`;
|
|
17189
|
+
console.log(`${prefix}${patch.id} "${patch.message}" ${filesLabel}`);
|
|
17190
|
+
}
|
|
17191
|
+
if (result.patches.length > maxDisplay) {
|
|
17192
|
+
console.log(` ... and ${result.patches.length - maxDisplay} more`);
|
|
16635
17193
|
}
|
|
16636
|
-
}
|
|
16637
|
-
const config = lockManager.getCustomizationsConfig();
|
|
16638
|
-
if (config.exclude && config.exclude.length > 0) {
|
|
16639
|
-
console.log(`
|
|
16640
|
-
Exclude patterns: ${config.exclude.length}`);
|
|
16641
|
-
for (const p of config.exclude) console.log(` ${p}`);
|
|
16642
17194
|
}
|
|
16643
17195
|
}
|
|
16644
17196
|
async function runDetect(dir) {
|
|
@@ -16807,32 +17359,155 @@ Unprotected customizations (${analysis.commitsOnly.length}):`);
|
|
|
16807
17359
|
console.log("Run: fern-replay bootstrap <dir>");
|
|
16808
17360
|
}
|
|
16809
17361
|
}
|
|
16810
|
-
|
|
16811
|
-
|
|
16812
|
-
|
|
16813
|
-
|
|
16814
|
-
|
|
17362
|
+
function formatDiffStat(patch) {
|
|
17363
|
+
return `+${patch.diffstat.additions} -${patch.diffstat.deletions}`;
|
|
17364
|
+
}
|
|
17365
|
+
function formatPatchLine(index, patch) {
|
|
17366
|
+
const status2 = patch.status ? ` [${patch.status}]` : "";
|
|
17367
|
+
const filesStr = patch.files.join(", ");
|
|
17368
|
+
return ` [${index}] ${patch.id} "${patch.message}" ${formatDiffStat(patch)} ${filesStr}${status2}`;
|
|
17369
|
+
}
|
|
17370
|
+
function printForgetWarnings(warnings) {
|
|
17371
|
+
for (const w of warnings) {
|
|
17372
|
+
console.log(`Warning: ${w}`);
|
|
16815
17373
|
}
|
|
17374
|
+
}
|
|
17375
|
+
function promptLine(question) {
|
|
17376
|
+
const rl = (0, import_node_readline.createInterface)({ input: process.stdin, output: process.stdout });
|
|
17377
|
+
return new Promise((resolve2) => {
|
|
17378
|
+
rl.question(question, (answer) => {
|
|
17379
|
+
rl.close();
|
|
17380
|
+
resolve2(answer.trim());
|
|
17381
|
+
});
|
|
17382
|
+
});
|
|
17383
|
+
}
|
|
17384
|
+
function parseSelection(input, max) {
|
|
17385
|
+
const trimmed2 = input.trim().toLowerCase();
|
|
17386
|
+
if (trimmed2 === "all") return "all";
|
|
17387
|
+
if (trimmed2 === "none" || trimmed2 === "") return "none";
|
|
17388
|
+
const nums = [];
|
|
17389
|
+
for (const part of trimmed2.split(",")) {
|
|
17390
|
+
const n = parseInt(part.trim(), 10);
|
|
17391
|
+
if (isNaN(n) || n < 1 || n > max) return null;
|
|
17392
|
+
if (!nums.includes(n)) nums.push(n);
|
|
17393
|
+
}
|
|
17394
|
+
return nums.length > 0 ? nums : null;
|
|
17395
|
+
}
|
|
17396
|
+
async function runForget(dir, flags, positionals) {
|
|
16816
17397
|
const dryRun = !!flags.dryRun;
|
|
16817
|
-
|
|
16818
|
-
|
|
16819
|
-
const
|
|
16820
|
-
|
|
16821
|
-
|
|
17398
|
+
const yes = !!flags.yes;
|
|
17399
|
+
const all = !!flags.all;
|
|
17400
|
+
const isPatchIdMode = positionals.length > 0 && positionals.every((a) => a.startsWith("patch-"));
|
|
17401
|
+
const pattern = !isPatchIdMode && positionals.length > 0 ? positionals[0] : void 0;
|
|
17402
|
+
const patchIds = isPatchIdMode ? positionals : void 0;
|
|
17403
|
+
if (all) {
|
|
17404
|
+
const preview = forget(dir, { all: true, dryRun: true });
|
|
17405
|
+
if (!preview.initialized) {
|
|
17406
|
+
console.log("Replay is not initialized. Run 'fern replay init' to get started.");
|
|
17407
|
+
process.exit(1);
|
|
17408
|
+
}
|
|
17409
|
+
if (preview.totalPatches === 0) {
|
|
17410
|
+
console.log("No patches tracked. Nothing to remove.");
|
|
17411
|
+
return;
|
|
17412
|
+
}
|
|
17413
|
+
if (!dryRun && !yes) {
|
|
17414
|
+
console.log(`WARNING: This will remove all ${preview.totalPatches} tracked patches.`);
|
|
17415
|
+
console.log("Affected files will be overwritten on next generation.");
|
|
17416
|
+
console.log("Replay will remain initialized and detect new customizations.\n");
|
|
17417
|
+
const confirm = await promptLine("Proceed? [y/N] ");
|
|
17418
|
+
if (confirm.toLowerCase() !== "y") {
|
|
17419
|
+
console.log("Aborted.");
|
|
17420
|
+
return;
|
|
17421
|
+
}
|
|
17422
|
+
}
|
|
17423
|
+
const finalResult = dryRun ? preview : forget(dir, { all: true });
|
|
17424
|
+
printForgetWarnings(finalResult.warnings);
|
|
17425
|
+
const verb2 = dryRun ? "Would remove" : "Removed";
|
|
17426
|
+
console.log(`${verb2} ${finalResult.removed.length} patches. ${finalResult.remaining} patches remaining.`);
|
|
16822
17427
|
return;
|
|
16823
17428
|
}
|
|
16824
|
-
|
|
16825
|
-
|
|
16826
|
-
|
|
16827
|
-
|
|
16828
|
-
|
|
16829
|
-
for (const f of patch.files) {
|
|
16830
|
-
console.log(` ${f}`);
|
|
17429
|
+
if (isPatchIdMode && patchIds) {
|
|
17430
|
+
const result2 = forget(dir, { patchIds, dryRun });
|
|
17431
|
+
if (!result2.initialized) {
|
|
17432
|
+
console.log("Replay is not initialized. Run 'fern replay init' to get started.");
|
|
17433
|
+
process.exit(1);
|
|
16831
17434
|
}
|
|
17435
|
+
if (result2.alreadyForgotten.length > 0 && result2.removed.length === 0) {
|
|
17436
|
+
for (const id of result2.alreadyForgotten) {
|
|
17437
|
+
console.log(`${id} is not tracked. Nothing to remove.`);
|
|
17438
|
+
}
|
|
17439
|
+
return;
|
|
17440
|
+
}
|
|
17441
|
+
printForgetWarnings(result2.warnings);
|
|
17442
|
+
if (result2.alreadyForgotten.length > 0) {
|
|
17443
|
+
for (const id of result2.alreadyForgotten) {
|
|
17444
|
+
console.log(`${id} is not tracked (skipped).`);
|
|
17445
|
+
}
|
|
17446
|
+
}
|
|
17447
|
+
const verb2 = dryRun ? "Would remove" : "Removed";
|
|
17448
|
+
if (result2.removed.length === 1) {
|
|
17449
|
+
const p = result2.removed[0];
|
|
17450
|
+
console.log(`${verb2} 1 patch: "${p.message}" (${p.files.length} files)`);
|
|
17451
|
+
} else {
|
|
17452
|
+
console.log(`${verb2} ${result2.removed.length} patches.`);
|
|
17453
|
+
}
|
|
17454
|
+
console.log(`${result2.remaining} patches remaining.`);
|
|
17455
|
+
return;
|
|
16832
17456
|
}
|
|
16833
|
-
|
|
16834
|
-
|
|
17457
|
+
const searchResult = pattern ? forget(dir, { pattern }) : forget(dir);
|
|
17458
|
+
if (!searchResult.initialized) {
|
|
17459
|
+
console.log("Replay is not initialized. Run 'fern replay init' to get started.");
|
|
17460
|
+
process.exit(1);
|
|
16835
17461
|
}
|
|
17462
|
+
const matches = searchResult.matched ?? [];
|
|
17463
|
+
if (matches.length === 0) {
|
|
17464
|
+
if (pattern) {
|
|
17465
|
+
console.log(`No patches found matching "${pattern}". Run 'fern replay status -v' to see all tracked patches.`);
|
|
17466
|
+
} else {
|
|
17467
|
+
console.log("No patches tracked. Nothing to remove.");
|
|
17468
|
+
}
|
|
17469
|
+
return;
|
|
17470
|
+
}
|
|
17471
|
+
if (pattern) {
|
|
17472
|
+
console.log(`Found ${matches.length} patch(es) matching "${pattern}":
|
|
17473
|
+
`);
|
|
17474
|
+
} else {
|
|
17475
|
+
console.log("All tracked patches:\n");
|
|
17476
|
+
}
|
|
17477
|
+
for (let i = 0; i < matches.length; i++) {
|
|
17478
|
+
console.log(formatPatchLine(i + 1, matches[i]));
|
|
17479
|
+
}
|
|
17480
|
+
if (!process.stdin.isTTY && !yes) {
|
|
17481
|
+
console.error("\nError: Interactive selection required. Use patch IDs directly or add --yes to remove all matches.");
|
|
17482
|
+
process.exit(1);
|
|
17483
|
+
}
|
|
17484
|
+
let selectedIds;
|
|
17485
|
+
if (yes) {
|
|
17486
|
+
selectedIds = matches.map((m) => m.id);
|
|
17487
|
+
} else {
|
|
17488
|
+
console.log();
|
|
17489
|
+
const input = await promptLine("Remove which? (comma-separated numbers, 'all', or 'none'): ");
|
|
17490
|
+
const selection = parseSelection(input, matches.length);
|
|
17491
|
+
if (selection === "none" || selection === null) {
|
|
17492
|
+
if (selection === null) console.log("Invalid selection.");
|
|
17493
|
+
console.log("Aborted.");
|
|
17494
|
+
return;
|
|
17495
|
+
}
|
|
17496
|
+
selectedIds = selection === "all" ? matches.map((m) => m.id) : selection.map((i) => matches[i - 1].id);
|
|
17497
|
+
console.log(`
|
|
17498
|
+
This will remove ${selectedIds.length} patch(es). Affected files will be overwritten on next generation.`);
|
|
17499
|
+
const confirm = await promptLine("Proceed? [y/N] ");
|
|
17500
|
+
if (confirm.toLowerCase() !== "y") {
|
|
17501
|
+
console.log("Aborted.");
|
|
17502
|
+
return;
|
|
17503
|
+
}
|
|
17504
|
+
}
|
|
17505
|
+
const result = forget(dir, { patchIds: selectedIds, dryRun });
|
|
17506
|
+
printForgetWarnings(result.warnings);
|
|
17507
|
+
const verb = dryRun ? "Would remove" : "Removed";
|
|
17508
|
+
console.log(`
|
|
17509
|
+
${verb} ${result.removed.length} patch(es).`);
|
|
17510
|
+
console.log(`${result.remaining} patches remaining.`);
|
|
16836
17511
|
}
|
|
16837
17512
|
async function runReset(dir, flags) {
|
|
16838
17513
|
const dryRun = !!flags.dryRun;
|
|
@@ -16898,7 +17573,7 @@ Resolve the conflicts in your editor, then run 'fern-replay resolve ${dir}' agai
|
|
|
16898
17573
|
console.log("Push to update the PR.");
|
|
16899
17574
|
}
|
|
16900
17575
|
async function main() {
|
|
16901
|
-
const { command, dir, flags,
|
|
17576
|
+
const { command, dir, flags, positionals } = parseArgs(process.argv);
|
|
16902
17577
|
if (!(0, import_node_fs6.existsSync)(dir)) {
|
|
16903
17578
|
console.error(`Directory not found: ${dir}`);
|
|
16904
17579
|
process.exit(1);
|
|
@@ -16908,7 +17583,7 @@ async function main() {
|
|
|
16908
17583
|
await runBootstrap(dir, flags);
|
|
16909
17584
|
break;
|
|
16910
17585
|
case "status":
|
|
16911
|
-
await runStatus(dir);
|
|
17586
|
+
await runStatus(dir, flags);
|
|
16912
17587
|
break;
|
|
16913
17588
|
case "detect":
|
|
16914
17589
|
await runDetect(dir);
|
|
@@ -16920,7 +17595,7 @@ async function main() {
|
|
|
16920
17595
|
await runMigrate(dir);
|
|
16921
17596
|
break;
|
|
16922
17597
|
case "forget":
|
|
16923
|
-
await runForget(dir, flags,
|
|
17598
|
+
await runForget(dir, flags, positionals);
|
|
16924
17599
|
break;
|
|
16925
17600
|
case "reset":
|
|
16926
17601
|
await runReset(dir, flags);
|