Files
werkator/docs/prs/2026-09-27-PR#81-pull-request-links.md
c07a22d5b6
werkator build succeeded after 03:13
werkator/deploy build succeeded after 00:07
feat(ui): show the open pull request of a branch as PR#<number> (#81)
Wherever a branch name is shown, its open Gitea pull request stands
beside it, linked to the pull request. The watcher reads the open pull
requests once per poll cycle into PullRequestIndex; pages and API only
read from there. Needs the Gitea connection and token; forks, private
repositories for visitors, and gone branches show none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: mhoennig <michael@hoennig.de>
Reviewed-on: #81
2026-09-27 13:57:57 +02:00

6.5 KiB

WARNING: This document describes only the change applied in this PR. It may already be outdated once the next PR is merged. Historic PR-documentation is not maintained along with new PRs — treat it as a snapshot, not as current documentation.

The Problem

Werkator works on branches; the pull requests in Gitea do not show anywhere in its pages. A reviewer who sees a red build on a branch has to look up the pull request of that branch in Gitea by hand. The branch name alone often says little; PR#12 is the name the work is discussed under.

Non-Goals

  • No builds of pull requests or of merge results; Werkator still builds branches.
  • No pull requests from forks: their head branch belongs to another repository.
  • No closed or merged pull requests; the link shows while a pull request is open.
  • No other forge than Gitea, and nothing without a token.
  • No change to the pull-request gate (requirePullRequest); it keeps reading refs/pull/*/head without a token.

The Scenarios

Feature: the open pull request beside every branch name

Background

  • A branch heads a pull request when the pull request's head is that branch of the repository itself.
  • The Gitea connection is gitea.baseUrl, gitea.owner, gitea.repo and git.token — the one the statuses are published with.

Scenario#81.01: Werkator knows the open pull requests of each repository

So that the pages can show them without asking Gitea on every request.

  • Given a repository with a Gitea connection and a token
  • When the watcher's poll cycle reaches origin
  • Then it reads the open pull requests from Gitea, keyed by head branch
    • and a pull request from a fork is left out
    • and of several pull requests from one branch, the newest wins
    • and a list longer than one page is read page by page
  • And nothing is read before the first poll cycle, so CLI runs and tests never ask Gitea
Verified by
  • GiteaClientTest — readOpenPullRequests: own branches only, the newest wins, paging, token header
  • PullRequestIndexTest — nothing known before the first refresh, a refresh replaces one repository's list
  • WatcherTest — every poll cycle that reached origin refreshes the pull requests

Scenario#81.02: Every branch name carries its pull request

So that a reader gets from a build to the discussion of its work in one click.

  • Given a branch that heads an open pull request
  • When the Latest, Branches, History or Current Builds view, the build page, the Repositories page or the home page shows the branch
  • Then PR#<number> stands beside the branch name, linked to the pull request in Gitea
    • and on a home card it is plain text, because the card is a link already
  • And the JSON API carries it as pullRequest with number and url, so every poll renders it too
Verified by

Scenario#81.03: A pull request is content

So that a private repository still gives out no content (RFC 0003, D8).

  • Given a private repository and a visitor without the control token
  • When a page or the API shows its builds
  • Then no pull request is shown, like the branch name it belongs to
  • And a build whose branch is gone from origin shows no pull request either
Verified by

So that the feature costs nothing where it cannot work.

  • Given no Gitea connection or no token, then no request is made and no pull request is shown
  • Given Gitea fails or answers garbage, then the last known pull requests stay
    • and the failure is logged once, not once per poll cycle, and so is the recovery
Verified by

The Solution

GiteaClient.readOpenPullRequests lists GET /api/v1/repos/{owner}/{repo}/pulls?state=open, 50 per page, at most ten pages. It keeps a pull request only when head.repo_id equals base.repo_id, and it takes the link from Gitea's own html_url.

PullRequestIndex holds the result per RepoContext. The watcher refreshes it at the end of each repository's poll cycle, after a successful fetch: an unreachable origin most likely means an unreachable forge, and the last list stays. The pages and the API only read from the index. A page is polled every few seconds by every open browser; a slow forge must never slow a page down.

The DTOs BuildResultDto, BranchDto and CurrentBuildDto carry pullRequest (number, url). BuildResultDto.from drops it for a gone branch, and Redaction drops it with the branch. The views take it from the DTO, so the first paint and every poll say the same. werkator.js renders the same link with pullRequestLink, and UiFormats.pullRequest writes the home card's text like the script does.

Additional Changes

  • docs/configuration.md explains the links under the gitea notes; docs/llms.md and the architecture skill name them.
  • Version 1.23.0 with its release note. 1.22.0 is taken by the open branch of RFC 0006.