From e66854340e6b2141dca0f6d3e0e9064e49e8e4f5 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 25 Aug 2026 10:29:41 +0000 Subject: [PATCH 1/2] fix(git): prefer remote tracking branch in defaultBranch for --scope changed When running --scope changed without an explicit --base, the defaultBranch() function now returns origin/ instead of just when the remote tracking branch exists. This fixes the issue where local commits ahead of the remote were not detected. Fixes #1674 Co-authored-by: Skosh --- packages/core/src/services/git.ts | 13 ++- ...sue-1674-scope-changed-remote-base.test.ts | 84 +++++++++++++++++++ 2 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 packages/core/tests/regressions/issue-1674-scope-changed-remote-base.test.ts diff --git a/packages/core/src/services/git.ts b/packages/core/src/services/git.ts index 4772dd3642..64f55a6560 100644 --- a/packages/core/src/services/git.ts +++ b/packages/core/src/services/git.ts @@ -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}`, @@ -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 = ( diff --git a/packages/core/tests/regressions/issue-1674-scope-changed-remote-base.test.ts b/packages/core/tests/regressions/issue-1674-scope-changed-remote-base.test.ts new file mode 100644 index 0000000000..2a403d6a3d --- /dev/null +++ b/packages/core/tests/regressions/issue-1674-scope-changed-remote-base.test.ts @@ -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"); + }); +}); From 6e628f62b1296204e4e451efb992f02a79b3ba3a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 25 Aug 2026 10:31:37 +0000 Subject: [PATCH 2/2] chore: add changeset for scope changed fix Co-authored-by: Skosh --- .changeset/fix-scope-changed-remote-base.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .changeset/fix-scope-changed-remote-base.md diff --git a/.changeset/fix-scope-changed-remote-base.md b/.changeset/fix-scope-changed-remote-base.md new file mode 100644 index 0000000000..56e3f27391 --- /dev/null +++ b/.changeset/fix-scope-changed-remote-base.md @@ -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/` instead of just `` 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