Public: anyone can read this; nobody can change it.

An account that can only read an origin repository proposes through its own fork there, and a protected repository is always proposed to that way #72

opened this view on origin

Check · Review · Land · Live — No review yet. Next: reviewer, no review yet.

Merges on the origin

**Stacked on #71** (which is stacked on #70). This branch has those two commits first. Only the top commit is new. Land #70 and #71 first.

0 files · +0 −0

waiting checks and the preview wait for a writer to approve this head

Check on trevato-apps/forge: running, as of

Checks and the preview wait for a writer to approve this head

A guest

This pull comes from a guest: its preview runs without the app's variables or a forge token until a person who can write here approves it.

This pull cannot land as it is, whatever its review says, because it changes guarded paths: (the diff) (it could not be read).

opened this view on origin

Stacked on #71 (which is stacked on #70). This branch has those two commits first. Only the top commit is new. Land #70 and #71 first.

To carry a branch into an origin repository, the forge pushed the branch there. So the account needed push rights on that repository. An account that could only read it could not propose at all.

Now such an account proposes through its own fork at the origin (src/origins/fork-route.ts). The fork is found or made. Found: in the picker's cache, by its two possible names, then by GET /repos/{o}/{r}/forks (bounded) for a fork the account owns under any name. Made: POST /forks, as <owner>-<name> when the name is taken. The branch is pushed into the fork, with a lease on where it stands there. The pull opens on the origin with head <account>:<branch>. The origin's permissions.push decides the way when the pull opens, and a direct push that the origin refuses falls back to the fork.

A repository that the operator protects (FORGE_ORIGIN_DENY_PUSH, from #71) is always proposed to through a fork, whatever the credential can do. A branch pushed into a repository runs its workflows with that repository's Actions secrets; a pull from a fork runs them without the secrets (Forgejo services/actions/secret.go). So push-through, a carried branch and a branch made on its mirror are refused there (origin_protected). The fork step of contribute uses the same fork (ensureFork), and a fork that reaches its origin through a fork says Propose via your fork. A push to the fork waits on the guard, as every other write does.

The owner:branch head is Forgejo's own form (routers/api/v1/repo/pull.go, parseCompareInfo and GetForkedRepo). The note in the Forgejo provider that called it unverified is updated. This very pull is opened that way: head tdobbertin-espo:feat/propose-via-fork, from an account with read access only on this repository (this instance runs Forgejo 15.0.6).

What is unchanged: an account with push rights on a repository that is not protected proposes as before, by a branch in the repository. Nothing new is stored, and there is no migration.

Tests

bun 1.3.13, a scrubbed environment (no agent variables):

  • test/propose-via-fork.test.ts: 10 pass, 0 fail, with a real forge serving git behind the fake API: a reader proposes through its fork with head <owner>:<branch>; the fork is reused, found under a name of its own, or made under a second name; an account that can push still pushes directly; a push the origin refuses falls back to the fork; a protected repository always takes the fork, even for an account that could push; a push into the fork waits on the guard.
  • With the guard's and #70's suites and the origin, fork, following, proposal, migrate and cross suites, in one bun test --timeout 60000 call: 544 pass, 0 fail across 41 files.
  • bun scripts/gate.ts and tsc --noEmit: clean.
the 41 files

propose-via-fork, origin-guard, origin-token-scopes, following, following-card, forks, fork-flow, fork-pull-approval, repo-fork-bar, crew-proposal, origins, origins-oauth, origins-operator, origins-reconnect, origins-security-auth, origins-security-provider, origins-security-push, origins-sync, origins-sync-settled, web-origins, web-origin-link, mcp-origins, mcp-origin-apps, push-through, trusted-hosts, trusted-hosts-ui, steps-lists, no-polling, refresh-meta, mirror-outbound, mirror-out, carried, migrate, migrate-after, tui-migrate, apps-feedback-mirror, and the five cross files reserved-names, studio, shards, check-list, hollow (each test/<name>.test.ts).

🤖 Generated with Claude Code

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs