Skip to content

fix: use branch upstream (not default branch) for --branch reviews#660

Merged
wesm merged 1 commit intoroborev-dev:mainfrom
cpcloud:fix/branch-review-upstream
Apr 22, 2026
Merged

fix: use branch upstream (not default branch) for --branch reviews#660
wesm merged 1 commit intoroborev-dev:mainfrom
cpcloud:fix/branch-review-upstream

Conversation

@cpcloud
Copy link
Copy Markdown
Contributor

@cpcloud cpcloud commented Apr 19, 2026

Summary

  • review --branch, tryBranchReview, analyze --branch, and refine resolved the merge-base against the repository's default branch (typically origin/main). In fork workflows where the current branch's actual upstream (e.g. upstream/main) is ahead of origin/main, this pulled in commits that were already merged upstream. Each command now prefers @{upstream} of the current branch and falls back to GetDefaultBranch() only when no upstream is configured.
  • Added git.GetUpstream(repoPath, ref) (returns the upstream tracking branch via git rev-parse --abbrev-ref <ref>@{upstream}, empty on no upstream).
  • Replaced the currentBranch == LocalBranchName(base) guardrails with git.IsOnBaseBranch(repoPath, currentBranch, base), which generalizes the origin/ shortcut by verifying refs/remotes/<base> exists before stripping the remote prefix — so non-origin remotes are handled and local branches with slashes (e.g. feature/foo) are not misclassified.
  • New tests: TestGetUpstream, TestIsOnBaseBranch, and TestTryBranchReview cases for the non-origin upstream scenario and for the feature/foo misclassification guard.

@roborev-ci
Copy link
Copy Markdown

roborev-ci Bot commented Apr 19, 2026

roborev: Combined Review (7ba54e7)

Verdict: Changes look mostly sound, but there is one medium-severity correctness issue to address before merge.

Medium

  • internal/git/git.go:110-123
    IsOnBaseBranch can still misclassify slash-containing local base branches as remote-tracking refs. If a user passes a local base like feature/foo and the repo also has refs/remotes/feature/foo, the helper may strip the prefix and incorrectly conclude the current branch foo is already on the base branch. That can wrongly block valid review --branch / analyze --branch runs.
    Suggested fix: Compare fully resolved refs explicitly (refs/heads/<base> vs refs/remotes/<base>) rather than inferring remote-tracking status from the existence of refs/remotes/<base>, or pass through whether the base originated from an upstream/default remote-tracking ref.

Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@roborev-ci
Copy link
Copy Markdown

roborev-ci Bot commented Apr 19, 2026

roborev: Combined Review (2c3cce0)

Verdict: Changes look mostly sound, but there is one medium-severity correctness issue that should be addressed before merge.

Medium

  • Location: internal/git/git.go:1147
    Problem: GetUpstream now returns the configured upstream name without verifying that the corresponding ref actually exists locally. If a branch has tracking config but the remote-tracking ref has not been fetched yet, the analyze/review/refine flow can choose that missing ref as base and then fail at git merge-base. Previously, the default-branch fallback would still work in that case.
    Fix: After resolving @{upstream}, verify the ref with git rev-parse --verify --quiet; if it is missing, ignore it so callers can fall back to GetDefaultBranch.

Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@roborev-ci
Copy link
Copy Markdown

roborev-ci Bot commented Apr 19, 2026

roborev: Combined Review (d432531)

Summary verdict: Changes are directionally correct, but there are 2 medium-severity correctness issues in upstream/base-branch handling that should be addressed before merge.

Medium

  • internal/git/git.go:110
    IsOnBaseBranch derives the local branch name from base by stripping everything through the first /. That breaks valid setups where the checked-out branch tracks a differently named upstream (for example, local stable tracking upstream/main) and can also mis-handle remotes whose names contain /. In those cases, the new analyze / review / refine guardrails can incorrectly treat the base branch as a feature branch and operate on it.

    Suggested fix: Compare against the branch’s configured upstream metadata instead of string slicing, or strip only a matched remote prefix rather than splitting on the first /.

  • internal/git/git.go:1183
    GetUpstream now rejects upstreams unless they exist under refs/remotes/. Git also allows tracking a local branch under refs/heads/..., so valid upstream configurations will silently fall back to GetDefaultBranch. That can make review/refine analyze the wrong range despite the new “prefer upstream” behavior.

    Suggested fix: Verify the upstream ref generically (for example via git rev-parse --verify <upstream>) or accept both refs/remotes/... and refs/heads/... targets.


Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@roborev-ci
Copy link
Copy Markdown

roborev-ci Bot commented Apr 19, 2026

roborev: Combined Review (176a2fe)

Verdict: changes are directionally correct, but there is one medium-severity correctness issue to address before merge.

Medium

  • internal/git/git.go:1187-1228, cmd/roborev/analyze.go:964-970, cmd/roborev/refine.go:240-247, cmd/roborev/review.go:162-168, cmd/roborev/review.go:489-493
    GetUpstream currently collapses a configured-but-unavailable tracking ref into "", and each caller then falls back to GetDefaultBranch(). In multi-remote or fork setups, this can silently analyze/review/refine against the wrong base branch. For example, if a branch tracks upstream/main but refs/remotes/upstream/main is not present locally, the code may incorrectly use origin/main or local main instead, producing the wrong commit range.
    Recommended fix: distinguish between “no upstream configured” and “upstream configured but not resolvable locally”; return an error for the latter and propagate it from the analyze/review/refine call sites instead of silently selecting another base branch.

Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@roborev-ci
Copy link
Copy Markdown

roborev-ci Bot commented Apr 19, 2026

roborev: Combined Review (5ba30f6)

Verdict: Changes improve upstream tracking, but there are 2 medium-severity regressions that should be addressed before merge.

Medium

  • cmd/roborev/refine.go:243
    validateRefineContext resolves HEAD's upstream before checking whether --since was provided. That means roborev refine --since <sha> can now fail with UpstreamMissingError when the branch has tracking config but the upstream ref is missing locally, even though --since should make upstream/default-branch resolution unnecessary.
    Suggested fix: Only resolve upstream/default branch when since == "", or ignore UpstreamMissingError when --since is set.

  • cmd/roborev/review.go:495
    tryBranchReview falls back to GetDefaultBranch on any GetUpstream error, including the new “configured but missing locally” case. In fork workflows this can produce the wrong mergeBase..HEAD range and enqueue review against origin/main/main instead of the branch’s intended upstream.
    Suggested fix: Treat UpstreamMissingError as “skip branch review” (return "", false) instead of falling back to the default branch.


Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@roborev-ci
Copy link
Copy Markdown

roborev-ci Bot commented Apr 19, 2026

roborev: Combined Review (90333f2)

Verdict: No medium-or-higher issues found; the changes look clean.

The submitted reviews did not identify any Medium, High, or Critical findings.

Low-severity notes were reported in other reviews, but per instructions they are omitted here.


Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@cpcloud cpcloud force-pushed the fix/branch-review-upstream branch from 90333f2 to b536913 Compare April 20, 2026 11:27
@roborev-ci
Copy link
Copy Markdown

roborev-ci Bot commented Apr 20, 2026

roborev: Combined Review (b536913)

Verdict: One medium-severity correctness issue should be addressed before merge.

Medium

  • internal/git/git.go:1250
    GetUpstream validates the resolved upstream with refExists(repoPath, upstream), but that check uses an unqualified ref name. If the configured upstream is actually missing locally and a different ref with the same short name exists, the check can still succeed and the code will silently treat the upstream as present. This can select the wrong commit range, which is the behavior this change is meant to avoid.
    Suggested fix: validate the exact qualified ref from config instead: refs/remotes/<remote>/<branch> for remote-tracking upstreams, and refs/heads/<branch> when remote = .. Add a regression test covering a missing configured upstream with a same-named local ref present.

Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@cpcloud cpcloud force-pushed the fix/branch-review-upstream branch from b536913 to de995a7 Compare April 20, 2026 11:34
@roborev-ci
Copy link
Copy Markdown

roborev-ci Bot commented Apr 20, 2026

roborev: Combined Review (de995a7)

Verdict: Changes are close, but there is one medium-severity correctness issue that should be fixed before merge.

Medium

  • internal/git/git.go:1252
    GetUpstream validates the resolved upstream with refExists(repoPath, upstream), but upstream is a short ref like upstream/main, not a fully qualified ref. If refs/remotes/upstream/main is missing while a different local ref with the same short name exists, this check can still pass and later merge-base logic will use the wrong ref, causing an incorrect commit range.
    Fix by validating the exact expected namespace from tracking config:
    • branch.<name>.remote = . -> require refs/heads/<merge>
    • otherwise -> require refs/remotes/<remote>/<mergeBranch>
      Alternatively, have GetUpstream return a fully qualified ref and use that downstream. Add a regression test for a missing remote-tracking ref plus a colliding local ref.

Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@roborev-ci
Copy link
Copy Markdown

roborev-ci Bot commented Apr 20, 2026

roborev: Combined Review (21e8f35)

Verdict: No Medium, High, or Critical issues identified across the provided reviews.

The reviewed changes appear clean based on the supplied agent outputs.


Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@roborev-ci
Copy link
Copy Markdown

roborev-ci Bot commented Apr 20, 2026

roborev: Combined Review (c675687)

Verdict: Changes introduce a high-severity regression in branch base selection for common pushed feature-branch workflows.

High

  • cmd/roborev/review.go:161-176, cmd/roborev/review.go:518-531, cmd/roborev/analyze.go:968-983, cmd/roborev/refine.go:281-307
    The new default-base logic always prefers @{upstream}. On a typical pushed feature branch, that upstream is usually the branch’s own remote-tracking ref (for example origin/feature), not the parent branch. That changes review --branch, analyze --branch, refine, and post-commit branch reviews from comparing against the branch point with main/the base branch to comparing only against commits not yet pushed, which can skip already-pushed branch commits entirely or incorrectly report that there are no commits on the branch.
    Suggested fix: only use the upstream ref as the base when it is a different branch from the branch being reviewed/refined (for example upstream/main); otherwise fall back to the existing default/base-branch resolution.

Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@cpcloud cpcloud force-pushed the fix/branch-review-upstream branch from c675687 to f791e69 Compare April 20, 2026 12:41
@roborev-ci
Copy link
Copy Markdown

roborev-ci Bot commented Apr 20, 2026

roborev: Combined Review (b6902aa)

Verdict: changes are not ready to merge due to 1 high-severity regression in branch-base detection.

High

  • internal/git/git.go:104
    Affected guardrails: cmd/roborev/analyze.go:998, cmd/roborev/review.go:191, cmd/roborev/review.go:549, cmd/roborev/refine.go:311
    IsOnBaseBranch now misclassifies ordinary feature branches as “already on the base branch” when the branch tracks its own remote counterpart (for example, local feature tracking origin/feature). With the new upstream-first logic, that scenario becomes common, so review --branch, analyze --branch, refine, and post-commit branch review can incorrectly error or skip for normal feature-branch workflows, including cases with unpushed commits.
    Recommended fix: keep using upstream for merge-base selection, but determine “am I on the repo base branch?” separately, or explicitly exclude same-named tracking branches such as origin/<currentBranch>. Add a regression test covering feature tracking origin/feature.

Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@roborev-ci
Copy link
Copy Markdown

roborev-ci Bot commented Apr 20, 2026

roborev: Combined Review (6fdd5d6)

Summary verdict: Changes introduce 1 High and 1 Medium regression that should be addressed before merge.

High

  • cmd/roborev/review.go:164, cmd/roborev/analyze.go:971, cmd/roborev/refine.go:282
    The new upstream-first base selection changes branch-based review scope from “all commits on the current branch since trunk” to “only commits ahead of @{upstream}”. On feature branches that track origin/<feature>, review --branch, analyze --branch, and refine can now skip already-pushed commits on that branch, which conflicts with documented behavior and can silently miss earlier failed reviews.
    Suggested fix: keep trunk/default-base behavior for interactive --branch/refine flows, or only prefer @{upstream} when it clearly resolves to trunk rather than the branch’s own remote tracking branch.

Medium

  • internal/git/git.go:141, with callers in cmd/roborev/review.go:188, cmd/roborev/analyze.go:995, cmd/roborev/refine.go:311
    IsOnRepoDefaultBranch now depends on GetDefaultBranch, but GetDefaultBranch still only recognizes origin/HEAD, main, and master. In repositories with a differently named trunk and no usable origin/HEAD, the guard can return false even when running from the actual default branch, so explicit --base flows may no longer block execution on trunk.
    Suggested fix: fall back to comparing against the resolved base when default-branch detection fails, or make GetDefaultBranch reliably detect nonstandard default branch names before using it as the sole guard.

Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@roborev-ci
Copy link
Copy Markdown

roborev-ci Bot commented Apr 20, 2026

roborev: Combined Review (a95a47c)

Verdict: 2 medium-severity issues need follow-up before merge.

Medium

  • internal/git/git.go around UpstreamIsTrunk

    • UpstreamIsTrunk currently treats any upstream whose last path component matches the default-branch leaf as trunk. This misclassifies feature branches such as origin/team/main or origin/foo/master as trunk, which can cause review --branch, analyze --branch, and refine to skip already-pushed feature commits or incorrectly refuse to run because IsOnBaseBranch believes the current branch is the base branch.
    • Fix: determine trunk from the actual tracked branch identity, not just branchLeaf(). Require the upstream to resolve to the repository’s real default-branch ref (or a local branch exactly matching the default branch), and add tests for feature branches ending in main/master.
  • internal/git/git.go:195 in listRemotes

    • strings.Split(trimmed, "\n") can leave trailing \r on remote names under Windows/CRLF output, which breaks strings.HasPrefix matching later in IsOnBaseBranch.
    • Fix: normalize CRLF before splitting, or use strings.Fields(trimmed) since Git remote names cannot contain whitespace.

Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@roborev-ci
Copy link
Copy Markdown

roborev-ci Bot commented Apr 20, 2026

roborev: Combined Review (9d4a74d)

Verdict: Changes are not ready to merge due to 2 medium-severity correctness issues in upstream/base-branch resolution.

Medium

  • internal/git/git.go:117-118
    IsOnBaseBranch returns true on raw string equality before distinguishing a same-named local branch from a remote-tracking ref. A user on a local branch named origin/main or upstream/main can be incorrectly treated as already being on the base branch when the resolved base is actually refs/remotes/... of the same name.
    Suggested fix: remove the unconditional equality fast-path for slash-containing refs, and compare qualified refs (refs/heads/... vs refs/remotes/...) or only accept equality when the ref is unambiguously local.

  • internal/git/git.go:163-185
    UpstreamIsTrunk uses stripRemotePrefix without verifying that the upstream is actually a remote-tracking ref. Since GetUpstream now supports local-branch upstreams, a local upstream like origin/main configured via branch.<name>.remote = . can be misclassified as trunk if an origin remote exists, leading to the wrong base ref and an incorrect merge-base/range.
    Suggested fix: apply the same namespace checks used by IsOnBaseBranch before stripping the remote prefix, or have GetUpstream return whether the upstream is local vs remote-tracking and branch on that explicitly.


Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@roborev-ci
Copy link
Copy Markdown

roborev-ci Bot commented Apr 20, 2026

roborev: Combined Review (16b1f4a)

Verdict: 1 medium-severity issue needs to be addressed before merge.

Medium

  • internal/git/git.go (readUpstreamConfig / readGitConfig): The missing-upstream fallback builds git config keys as branch.<branch>.remote / branch.<branch>.merge without quoting the branch subsection. Branch names containing dots, such as release/1.2.3, are parsed incorrectly by Git, so GetUpstream can misclassify the branch as having no upstream and silently fall back to the default branch instead of raising UpstreamMissingError. That can cause review/analyze/refine to use the wrong commit range.

Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@roborev-ci
Copy link
Copy Markdown

roborev-ci Bot commented Apr 20, 2026

roborev: Combined Review (f0091ac)

Verdict: No medium-or-higher issues identified across the combined reviews.

No findings to report.

The reviewed changes around git ref/upstream resolution and related tests appear clean based on the available review outputs.


Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@roborev-ci
Copy link
Copy Markdown

roborev-ci Bot commented Apr 21, 2026

roborev: Combined Review (7e3c891)

Verdict: No Medium, High, or Critical issues found across the review outputs.

All agents agree the code is clean.


Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@roborev-ci
Copy link
Copy Markdown

roborev-ci Bot commented Apr 21, 2026

roborev: Combined Review (408a5c2)

Clean review: all agents agree there are no Medium, High, or Critical findings.

No issues found.


Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@roborev-ci
Copy link
Copy Markdown

roborev-ci Bot commented Apr 21, 2026

roborev: Combined Review (1c70d90)

No Medium, High, or Critical findings were reported.

All agents agree the code is clean for medium-or-higher severity issues.


Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

review --branch, tryBranchReview, analyze --branch, and refine
resolved the merge-base against the repository's default branch
(typically origin/main). In fork workflows where the current branch's
actual upstream (e.g. upstream/main) is ahead of origin/main, this
pulled in commits that were already merged upstream. Each command now
prefers @{upstream} of the current branch — but only when @{upstream}
resolves to trunk, not when it's the branch's own remote counterpart
— and falls back to the default branch otherwise.

Helpers in internal/git:
- GetUpstream(repoPath, ref) returns ("", nil) when no @{upstream} is
  set, the resolved short name when the configured ref resolves, and
  ("", *UpstreamMissingError) when @{upstream} is configured but the
  referenced ref does not resolve. Verification uses the fully-
  qualified ref (refs/heads/<merge> for branch.<name>.remote = ".",
  refs/remotes/<remote>/<merge> otherwise) so a lookalike tag or
  local branch with the same short name can't shadow a missing
  remote-tracking ref.
- UpstreamIsTrunk(repoPath, ref) reports whether ref's @{upstream}
  points to the repository's trunk. Only refs/remotes/... qualified
  upstreams can be trunk; refs/heads/... (local-branch tracking via
  branch.<name>.remote = ".") is rejected even if the short name
  happens to match the default branch's. Compares the full branch
  name after stripping the longest configured-remote prefix so
  "origin/team/main" (branch "team/main") never matches
  "origin/main" (branch "main").
- IsOnBaseBranch(repoPath, currentBranch, base) classifies bases by
  namespace: bare local names match directly, unambiguous remote-
  tracking refs strip the configured-remote prefix, unambiguous
  local branches match verbatim, and ambiguous names (refs/heads/
  and refs/remotes/ both resolve) refuse to match either way.
- readUpstreamConfig accepts fully-qualified local branch refs
  ("refs/heads/<name>") so callers passing a ResolveSHA-style ref
  still hit the correct branch.<name>.* config keys.
- stripRemotePrefix removes the longest matching configured-remote
  prefix (handling multi-slash names like "company/fork/main").
- listRemotes uses strings.Fields so CRLF output on Windows doesn't
  leave stray "\r" on remote names.

Callers (review, analyze, refine, tryBranchReview):
- Propagate any GetUpstream error: interactive commands surface
  UpstreamMissingError as a user-visible error with a "pass --base
  <ref>" / "pass --since" hint, and generic errors as "resolve
  upstream for <ref>: ..."; the post-commit hook path
  (tryBranchReview) skips ("", false) on any GetUpstream error
  rather than silently falling back — falling back with a missing-
  but-configured upstream would enqueue a review against the wrong
  commit range in fork workflows.
- Pass the ref (not the resolved upstream) to UpstreamIsTrunk so it
  can consult branch config to distinguish local vs remote tracking.
- refine's validateRefineContext only resolves @{upstream} when
  --since is empty, so an unresolvable upstream doesn't block an
  otherwise-valid --since invocation.

Tests:
- TestGetUpstream covers: no upstream; remote tracking branch; named
  ref; empty ref defaults to HEAD; local-branch upstream; missing-
  ref surfaces UpstreamMissingError; configured-but-never-fetched
  tracking; colliding local ref at the same short name as a missing
  remote-tracking ref; dotted branch names (release/1.2.3).
- TestIsOnBaseBranch covers: bare local; origin/-prefixed;
  non-origin remote prefix; multi-slash remote name; the
  "feature/foo" local-branch trap; pathological local "origin/foo";
  pathological local "origin/main" with a matching remote-tracking
  ref (ambiguous refuse); ambiguous slash refs.
- TestUpstreamIsTrunk covers: trunk-named upstream matches; self-
  counterpart does not match; multi-remote trunk matches; no
  upstream configured; default branch undetectable; feature branches
  like "origin/team/main" are not trunk; local-branch upstream
  literally named "origin/main" is not trunk; refs/heads/-qualified
  ref input.
- TestTryBranchReview: prefers branch upstream over default branch;
  allows feature tracking origin/feature (guardrail targets trunk,
  not the branch's own counterpart); skips when upstream configured
  but unresolvable; blocks local main tracking non-origin upstream.
- TestGetBranchFiles: prefers non-origin upstream; blocks on local
  main tracking non-origin upstream.
- TestValidateRefineContext: prefers non-origin upstream; refuses
  local main tracking non-origin upstream; --since bypasses
  upstream resolution; UpstreamMissingError surfaces without --since.
@cpcloud cpcloud force-pushed the fix/branch-review-upstream branch from 1c70d90 to 365a01d Compare April 21, 2026 12:53
@cpcloud
Copy link
Copy Markdown
Contributor Author

cpcloud commented Apr 21, 2026

@wesm this is ready to go

@roborev-ci
Copy link
Copy Markdown

roborev-ci Bot commented Apr 21, 2026

roborev: Combined Review (365a01d)

No Medium, High, or Critical findings were reported by any reviewer.

All agents agreed the code is clean.


Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@wesm wesm merged commit e71a50e into roborev-dev:main Apr 22, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants