Skip to content

Resolve a renamed fork's head when checking a pull request's provenance - #720

Draft
coopernetes wants to merge 1 commit into
mainfrom
fix/scm-api-renamed-fork-head
Draft

coopernetes wants to merge 1 commit into
mainfrom
fix/scm-api-renamed-fork-head

Conversation

@coopernetes

Copy link
Copy Markdown
Member

With require-validated-head on, gh pr create from a fork renamed away from the upstream's name (e.g. RBC/coopernetes-test-repo of coopernetes/test-repo) was refused with "Could not resolve head branch ... to a commit upstream". The GitHub and Gitea/Forgejo head resolvers looked the branch up in <head-owner>/<upstream-name>.

The head repository is now identified by fork relationship, in this order:

  • input.headRepositoryId, when sent (GitHub only; Gitea/Forgejo's API has no equivalent). An owner: prefix that disagrees with that repository's owner is refused.
  • The head owner's repository of the upstream's name, only if its parent is the upstream. A same-name non-fork, or a fork of something else, is not used.
  • The upstream's own parent, when the head owner owns it (a PR from upstream into a fork, which resolved before when names matched).
  • The head owner's forks, most recently pushed/updated first, capped at 3 pages (GitHub: repositoryOwner { repositories(isFork: true) }, 100 per page; Gitea/Forgejo: /repos/search?uid=&exclusive=true&mode=fork, 50 per page), matching on parent.

The upstream's fork network is never listed. Anything unidentified still resolves empty and is refused as before. GitLab is unaffected: mr create already POSTs to the source project's own URL, and the resolver reads the branch there.

ScmApiHeadValidationFilter.HeadShaResolution now also receives the parsed request body, so the GitHub resolver can read headRepositoryId.

Tests: GitHubHeadShaResolverTest and ForgejoHeadShaResolverTest cover each resolution step, same-name non-fork, same-name fork of another repo, cursor paging, the page cap, unknown owner, and an upstream error stopping resolution. The wiring change in FogwallServletRegistrar is compile-checked only; no e2e covers a renamed fork.

Decisions for review

  • Fork-of-a-fork heads (head repo's parent is not the upstream itself) are not resolved and are refused. GitHub would accept them as part of the network; GitHub's GraphQL exposes no network root to match on cheaply.
  • Page cap is 3 for both dialects (300 GitHub forks, 150 Gitea/Forgejo forks searched), most recently pushed/updated first, since the head was normally just pushed through fogwall.
  • The search stops at the first fork whose parent is the upstream; both SCMs allow one fork of a repository per owner.
  • Owner and repository name comparisons are case-insensitive, matching both SCMs.
  • A non-404 upstream error at any step ends resolution (refused) rather than falling through to the next step. Gitea/Forgejo path segments are now URL-encoded.
  • The refusal text is unchanged ("Could not resolve head branch '...' to a commit upstream"); the server log names the head owner and upstream when no fork was found.
  • Unverified live: the new GraphQL query shapes were checked with gh api graphql and the Gitea/Forgejo endpoints against codeberg.org, but no PR was opened through a running fogwall.

🤖 Generated with Claude Code

With require-validated-head on, opening a pull request from a fork whose name differs from the upstream's (RBC/mlflow-mlflow of mlflow/mlflow) was refused through the SCM API proxy with "Could not resolve head branch ... to a commit upstream". The head resolvers for GitHub and Gitea/Forgejo looked the branch up in <head-owner>/<upstream-name>, which only exists when the fork kept the upstream's name.

The head repository is now identified from the fork relationship: GitHub's headRepositoryId when sent, then the owner's same-name repository only if it is a fork of the upstream, then the upstream's own parent when the owner owns it, then a search of the owner's forks capped at a few pages. The upstream's fork network is never listed. A head that cannot be identified is still refused. GitLab is unaffected: its create request already addresses the source project.

closes #719

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch has not been deployed

No deployments
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