Skip to content
Draft
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
9 changes: 9 additions & 0 deletions .changeset/fix-scope-changed-remote-base.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@react-doctor/core": patch
---

fix(git): prefer remote tracking branch for --scope changed auto-detection

When running `--scope changed` without an explicit `--base` flag, React Doctor now auto-detects `origin/<branch>` instead of just `<branch>` when the remote tracking branch exists. This fixes the issue where committed changes on a feature branch (or on main ahead of origin/main) were not detected unless `--base origin/main` was explicitly specified.

Fixes #1674
13 changes: 11 additions & 2 deletions packages/core/src/services/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,12 @@ export class Git extends Context.Service<
const symref = yield* runGit(directory, ["symbolic-ref", "refs/remotes/origin/HEAD"]);
if (symref.status === 0) {
const trimmed = trimOrNull(symref.stdout);
if (trimmed !== null) return trimmed.replace("refs/remotes/origin/", "");
if (trimmed !== null) {
const branchName = trimmed.replace("refs/remotes/origin/", "");
const remoteBranch = `origin/${branchName}`;
const remoteExists = yield* branchExists(directory, remoteBranch);
return remoteExists ? remoteBranch : branchName;
}
}
const candidateRefs = DEFAULT_BRANCH_CANDIDATES.map(
(candidate) => `refs/heads/${candidate}`,
Expand All @@ -484,7 +489,11 @@ export class Git extends Context.Service<
...candidateRefs,
]);
if (candidates.status !== 0) return null;
return trimOrNull(candidates.stdout.split("\n")[0] ?? "");
const localBranch = trimOrNull(candidates.stdout.split("\n")[0] ?? "");
if (localBranch === null) return null;
const remoteBranch = `origin/${localBranch}`;
const remoteExists = yield* branchExists(directory, remoteBranch);
return remoteExists ? remoteBranch : localBranch;
}).pipe(Effect.withSpan("Git.defaultBranch"));

const branchExists = (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { exec as execCallback } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { promisify } from "node:util";
import { afterEach, beforeEach, describe, expect, it } from "vite-plus/test";
import { getDiffInfo } from "../../src/get-diff-files.js";

const exec = promisify(execCallback);

describe("issue #1674: --scope changed should detect commits relative to remote base", () => {
let testDirectory: string;

beforeEach(async () => {
testDirectory = await fs.promises.mkdtemp(path.join(os.tmpdir(), "react-doctor-test-"));
});

afterEach(async () => {
if (testDirectory) {
await fs.promises.rm(testDirectory, { recursive: true, force: true });
}
});

it("detects committed changes on feature branch when local main doesn't exist", async () => {
await exec("git init", { cwd: testDirectory });
await exec('git config user.email "test@example.com"', { cwd: testDirectory });
await exec('git config user.name "Test User"', { cwd: testDirectory });

await fs.promises.writeFile(path.join(testDirectory, "initial.txt"), "initial content");
await exec("git add .", { cwd: testDirectory });
await exec('git commit -m "initial commit"', { cwd: testDirectory });
await exec("git branch -m main", { cwd: testDirectory });

await exec("git remote add origin https://github.com/test/repo.git", { cwd: testDirectory });
await exec("git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/main", {
cwd: testDirectory,
});
await exec("git update-ref refs/remotes/origin/main HEAD", { cwd: testDirectory });

await exec("git checkout -b feature-branch", { cwd: testDirectory });
await exec("git branch -D main", { cwd: testDirectory });

await fs.promises.writeFile(path.join(testDirectory, "feature.txt"), "feature content");
await exec("git add .", { cwd: testDirectory });
await exec('git commit -m "add feature"', { cwd: testDirectory });

const diffInfo = await getDiffInfo(testDirectory);

expect(diffInfo).not.toBeNull();
expect(diffInfo?.changedFiles).toContain("feature.txt");
expect(diffInfo?.isCurrentChanges).toBeFalsy();
expect(diffInfo?.currentBranch).toBe("feature-branch");
expect(diffInfo?.baseBranch).toBe("origin/main");
});

it("detects committed changes on main when ahead of origin/main", async () => {
await exec("git init", { cwd: testDirectory });
await exec('git config user.email "test@example.com"', { cwd: testDirectory });
await exec('git config user.name "Test User"', { cwd: testDirectory });

await fs.promises.writeFile(path.join(testDirectory, "initial.txt"), "initial content");
await exec("git add .", { cwd: testDirectory });
await exec('git commit -m "initial commit"', { cwd: testDirectory });
await exec("git branch -m main", { cwd: testDirectory });

await exec("git remote add origin https://github.com/test/repo.git", { cwd: testDirectory });
await exec("git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/main", {
cwd: testDirectory,
});
await exec("git update-ref refs/remotes/origin/main HEAD", { cwd: testDirectory });

await fs.promises.writeFile(path.join(testDirectory, "local-commit.txt"), "local change");
await exec("git add .", { cwd: testDirectory });
await exec('git commit -m "local commit ahead of origin"', { cwd: testDirectory });

const diffInfo = await getDiffInfo(testDirectory);

expect(diffInfo).not.toBeNull();
expect(diffInfo?.changedFiles).toContain("local-commit.txt");
expect(diffInfo?.isCurrentChanges).toBeFalsy();
expect(diffInfo?.currentBranch).toBe("main");
expect(diffInfo?.baseBranch).toBe("origin/main");
});
});
Loading