Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-staged-ignore-divergence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-doctor": patch
---

Fix `--staged` false positive on git-ignored config files. Git-ignored config files (e.g. `.opencode/package.json`) can never be staged, so they should not block staged scans with divergence errors.
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const parseStagedSnapshotDivergences = (statusOutput: string): ReadonlyAr
const sourcePath = hasRenameOrCopySource ? statusEntries[entryIndex + 1] : undefined;
const worktreePaths =
worktreeStatus === "R" || worktreeStatus === "C" ? [filePath, sourcePath] : [filePath];
if (worktreeStatus !== " ") {
if (worktreeStatus !== " " && worktreeStatus !== "!") {
for (const worktreePath of worktreePaths) {
if (worktreePath && SNAPSHOT_CONFIG_FILENAMES.has(path.basename(worktreePath))) {
divergentConfigFiles.add(worktreePath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,18 +74,24 @@ describe("findStagedSnapshotDivergences", () => {
]);
});

it("reports ordinary and ignored untracked configuration", () => {
it("reports ordinary untracked configuration", () => {
const directory = createRepository();
fs.writeFileSync(path.join(directory, ".gitignore"), "next.config.mjs\n");
fs.writeFileSync(path.join(directory, "eslint.config.mjs"), "export default [];\n");

expect(findStagedSnapshotDivergences(directory)).toEqual(["eslint.config.mjs"]);
});

it("accepts git-ignored configuration that can never be staged", () => {
const directory = createRepository();
fs.writeFileSync(path.join(directory, ".gitignore"), "next.config.mjs\n.opencode/\n");
execFileSync("git", ["add", ".gitignore"], { cwd: directory });
execFileSync("git", ["commit", "-q", "-m", "ignore config"], { cwd: directory });
fs.writeFileSync(path.join(directory, "eslint.config.mjs"), "export default [];\n");
fs.writeFileSync(path.join(directory, "next.config.mjs"), "export default {};\n");
fs.mkdirSync(path.join(directory, ".opencode"), { recursive: true });
fs.writeFileSync(path.join(directory, ".opencode/package.json"), '{"name":"local-plugin"}\n');
execFileSync("git", ["add", "src/app.tsx"], { cwd: directory });

expect(findStagedSnapshotDivergences(directory)).toEqual([
"eslint.config.mjs",
"next.config.mjs",
]);
expect(findStagedSnapshotDivergences(directory)).toEqual([]);
});

it("returns null outside a Git worktree", () => {
Expand Down Expand Up @@ -115,6 +121,10 @@ describe("findStagedSnapshotDivergences", () => {
expect(parseStagedSnapshotDivergences("R archive.txt\0a/b/doctor.config.json\0")).toEqual([]);
});

it("accepts git-ignored configuration with worktree status !", () => {
expect(parseStagedSnapshotDivergences("!! package.json\0")).toEqual([]);
});

it("ignores unstaged lockfile and .gitignore edits that cannot shape a staged scan", () => {
const directory = createRepository();
fs.writeFileSync(path.join(directory, "pnpm-lock.yaml"), "lockfileVersion: '9.0'\n");
Expand Down
Loading