@fern-api/replay 0.10.3 → 0.10.4

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/index.cjs CHANGED
@@ -661,6 +661,7 @@ var ReplayDetector = class {
661
661
  }
662
662
  const log = await this.git.exec([
663
663
  "log",
664
+ "--first-parent",
664
665
  "--format=%H%x00%an%x00%ae%x00%s",
665
666
  `${lastGen.commit_sha}..HEAD`,
666
667
  "--",
@@ -676,11 +677,20 @@ var ReplayDetector = class {
676
677
  if (isGenerationCommit(commit)) {
677
678
  continue;
678
679
  }
679
- const parents = await this.git.getCommitParents(commit.sha);
680
- if (parents.length > 1) {
680
+ if (lock.patches.find((p) => p.original_commit === commit.sha)) {
681
681
  continue;
682
682
  }
683
- if (lock.patches.find((p) => p.original_commit === commit.sha)) {
683
+ const parents = await this.git.getCommitParents(commit.sha);
684
+ if (parents.length > 1) {
685
+ const compositePatch = await this.createMergeCompositePatch({
686
+ mergeCommit: commit,
687
+ firstParent: parents[0],
688
+ lastGen,
689
+ lock
690
+ });
691
+ if (compositePatch) {
692
+ newPatches.push(compositePatch);
693
+ }
684
694
  continue;
685
695
  }
686
696
  let patchContent;
@@ -779,6 +789,46 @@ var ReplayDetector = class {
779
789
  const normalized = patchContent.split("\n").filter((line) => !line.startsWith("From ") && !line.startsWith("index ") && !line.startsWith("Date: ")).join("\n");
780
790
  return `sha256:${(0, import_node_crypto.createHash)("sha256").update(normalized).digest("hex")}`;
781
791
  }
792
+ /**
793
+ * Create a single composite patch for a merge commit by diffing the merge
794
+ * commit against its first parent. This captures the net change of the
795
+ * entire merged branch as one patch, eliminating accumulator chaining and
796
+ * zero-net-change ghost conflicts.
797
+ */
798
+ async createMergeCompositePatch(input) {
799
+ const { mergeCommit, firstParent, lastGen, lock } = input;
800
+ const forgottenHashes = new Set(lock.forgotten_hashes ?? []);
801
+ const filesOutput = await this.git.exec([
802
+ "diff",
803
+ "--name-only",
804
+ firstParent,
805
+ mergeCommit.sha
806
+ ]);
807
+ const files = filesOutput.trim().split("\n").filter(Boolean).filter((f) => !INFRASTRUCTURE_FILES.has(f)).filter((f) => !f.startsWith(".fern/"));
808
+ if (files.length === 0) return null;
809
+ const diff = await this.git.exec([
810
+ "diff",
811
+ firstParent,
812
+ mergeCommit.sha,
813
+ "--",
814
+ ...files
815
+ ]);
816
+ if (!diff.trim()) return null;
817
+ const contentHash = this.computeContentHash(diff);
818
+ if (lock.patches.some((p) => p.content_hash === contentHash) || forgottenHashes.has(contentHash)) {
819
+ return null;
820
+ }
821
+ return {
822
+ id: `patch-merge-${mergeCommit.sha.slice(0, 8)}`,
823
+ content_hash: contentHash,
824
+ original_commit: mergeCommit.sha,
825
+ original_message: mergeCommit.message,
826
+ original_author: `${mergeCommit.authorName} <${mergeCommit.authorEmail}>`,
827
+ base_generation: lastGen.commit_sha,
828
+ files,
829
+ patch_content: diff
830
+ };
831
+ }
782
832
  /**
783
833
  * Detect patches via tree diff for non-linear history. Returns a composite patch.
784
834
  * Revert reconciliation is skipped here because tree-diff produces a single composite