Skip to content

feat: add --upstream to review a fork branch against its upstream repo#137

Open
hetaoBackend wants to merge 8 commits into
alibaba:mainfrom
hetaoBackend:feat/review-upstream
Open

feat: add --upstream to review a fork branch against its upstream repo#137
hetaoBackend wants to merge 8 commits into
alibaba:mainfrom
hetaoBackend:feat/review-upstream

Conversation

@hetaoBackend

Copy link
Copy Markdown
Contributor

Summary

Adds ocr review --upstream <remote|url> so a fork user can review their current branch against the original (upstream) repository — directly addressing the case where reviewing a fork against the upstream fails today because the upstream ref isn't present locally (not a valid git ref), and --from requires a mandatory --to.

  • Configured remote: ocr review --upstream upstream — auto-discovers the upstream's default branch via git ls-remote --symref and fetches it.
  • Raw URL (no remote configured): ocr review --upstream https://github.com/org/repo — anonymous fetch to FETCH_HEAD; no permanent remote is added, so the user's repo config is untouched.
  • --upstream-branch <branch> overrides the upstream branch; --to defaults to HEAD; --no-fetch uses the local remote-tracking ref for offline use.
  • Reviews merge-base(upstream, HEAD)..HEAD by reusing the existing range mode--upstream is purely a front-end resolution step in runReview, so the diff/agent path is unchanged.
  • Empty merge-base errors now hint at git fetch --unshallow (helps shallow clones).
  • --upstream is mutually exclusive with --from/--commit.

Test Plan

  • go test ./... — new tests in cmd/opencodereview/upstream_test.go (URL detection, symref parsing, and file:// integration tests for remote-name / URL / --no-fetch paths), cmd/opencodereview/flags_test.go (flag validation), and internal/diff/git_test.go (unshallow hint).
  • ocr review --upstream upstream --preview — configured remote, real fetch.
  • ocr review --upstream https://github.com/alibaba/open-code-review --preview — URL form.
  • ocr review --upstream origin --no-fetch --preview — offline path.

🤖 Generated with Claude Code

hetaoBackend and others added 7 commits June 15, 2026 14:00
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Local-only default-branch discovery uses `git symbolic-ref` on the
remote HEAD symref; `git rev-parse --abbrev-ref --end-of-options`
echoes the --end-of-options token and is unsafe to parse.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 OpenCodeReview found 2 issue(s) in this PR.

  • ✅ 2 posted as inline comment(s)
  • 📝 0 posted as summary

Comment thread internal/diff/git.go Outdated
base := p.MergeBase(ctx)
if base == "" {
return nil, fmt.Errorf("cannot find merge-base between %s and %s", p.from, p.to)
return nil, fmt.Errorf("cannot find merge-base between %s and %s (if this is a shallow clone, run: git fetch --unshallow)", p.from, p.to)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While adding the shallow clone hint is helpful, the underlying issue is that computeMergeBase (line 259-265) silently discards the actual git merge-base error output. There are multiple possible causes for merge-base failure (unrelated histories, invalid/missing refs, permission issues, etc.), and the real git error message would help users diagnose the root cause.

Consider propagating the error from computeMergeBase so the actual git error can be included in this message, e.g.:

cannot find merge-base between X and Y: <actual git error> (if this is a shallow clone, run: git fetch --unshallow)

This way the shallow clone hint remains as a suggestion while the real error provides actionable diagnostics for all failure modes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in caa3dce. computeMergeBase now returns git's diagnostic instead of discarding it, and the range-mode error includes it when present:

cannot find merge-base between X and Y: <actual git error> (if this is a shallow clone, run: git fetch --unshallow)

The unrelated-histories case (git exits non-zero with empty output) appends no detail, so the message stays clean there. Added TestRangeMergeBaseErrorIncludesGitDetail covering the missing-ref path.

Comment on lines +89 to +91
if !isURL && !upstreamRemoteExists(repoDir, target) {
return "", "", fmt.Errorf("--upstream %q: no git remote named %q; pass a git URL or run \"git remote add %s <url>\"", target, target, target)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential issue: When isUpstreamURL returns true for a spec that also happens to be a configured remote name (e.g., someone named their remote https://github.com/orig/repo), the code skips the upstreamRemoteExists check entirely and treats it purely as a URL. This means git will fetch using the raw URL rather than the configured remote, ignoring any custom fetch refspecs or credentials set up for that remote.

Consider checking upstreamRemoteExists first (or additionally) when the spec looks like a URL, and preferring the remote configuration when both match. For example:

if !isURL && !upstreamRemoteExists(repoDir, target) {
    ...
}

could become:

remoteExists := upstreamRemoteExists(repoDir, target)
if !isURL && !remoteExists {
    ...
}

and then later logic could prefer the remote name when remoteExists is true even if isURL is also true.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked into this and the described collision can't actually occur: git rejects every spec shape that isUpstreamURL classifies as a URL as an invalid remote name. Tested locally:

  • https://github.com/orig/repofatal: '...' is not a valid remote name
  • ./rel, /abs, ../up, ~home, git@host:path → all rejected as remote names

isUpstreamURL only returns true for ://, scp syntax (user@host:path), or path-like prefixes (/, ./, ../, ~) — none of which are valid remote names. A name like a/b is a valid remote but isn't flagged as a URL, so it correctly takes the remote path.

So a configured remote can never have a name that isUpstreamURL treats as a URL, which means the custom-refspec/credentials concern isn't reachable. I'd lean toward not adding the extra branch (dead code), but happy to add an explicit remote-first guard if you'd still prefer the defensive check.

@hetaoBackend

Copy link
Copy Markdown
Contributor Author

Thanks for taking a look! This PR adds --upstream so fork contributors can review their current branch against the upstream repo in one command — previously this was awkward because the upstream ref often isn't fetched locally (not a valid git ref) and --from requires a mandatory --to.

A few notes to make review easier:

  • Minimal blast radius: --upstream is a thin front-end resolver in runReview. It reuses the existing range mode (merge-base(upstream, HEAD)..HEAD), so the diff/agent path is unchanged.
  • Two entry points: a configured remote (auto-discovers the default branch via git ls-remote --symref and fetches it), or a raw git URL (anonymous fetch to FETCH_HEAD — no permanent remote is added, so the user's repo config stays clean).
  • Flags: --upstream-branch overrides the branch, --to defaults to HEAD, --no-fetch enables offline use; --upstream is mutually exclusive with --from/--commit.
  • Tests: URL/remote/--no-fetch paths are covered with file:// repos in cmd/opencodereview/upstream_test.go, plus flag-validation and the merge-base hint. go test ./... is green.

@lizhengfeng101 could you help review this when you get a chance? Happy to adjust naming, UX, or anything else. 🙏

computeMergeBase swallowed git's output on failure, so all merge-base
failures looked the same. Propagate the real diagnostic (invalid ref,
permission, etc.) while keeping the shallow-clone hint.

Addresses review feedback on PR alibaba#137.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant