Repository navigation
Conversation
repo create posts to user/repos when OWNER is the authenticated login and
to orgs/{OWNER}/repos otherwise, requires exactly one of --private or
--public, and returns a repository already at that address with
created: false and a differs list instead of modifying it. Both creation
routes are probed as the repo_create capability. The repository object
shared by repo view and repo create gains clone_url, ssh_url and empty.
The live lane creates and deletes a repository through both routes.
docs/adr/0005 records the design decisions.
…wner An organisation-owned lane repository made the assignee probe post the organisation as an assignee, which Forgejo 15.0.9 answers with a 500 and which aborted the run before the later probes. The login the token authenticates as is resolved once and used for assignment and for the user creation route.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Clarification on live coverage, so nobody has to guess: the lane 15 run against Forgejo 15.0.9 was on the head before the review fix rounds (drop the passthrough create flags, skip redirected existence reads, report a requested description in differs, lane owner compared case-insensitively). The final head f47d01c passed npm run check and the fixture-backed CLI transcripts only; neither lane has been re-run on it. |
Intent
Contribute a first-class 'repo create' command to the upstream forgejo-axi project (escidmore/forgejo-axi), opening the pull request against upstream main from the owner's fork M-Kerr/forgejo-axi. The command requires exactly one of --private or --public (neither or both is a usage error, exit 2) so a repository is never published by omission; routes by comparing OWNER case-insensitively to the authenticated login from GET /user (user/repos vs orgs/{OWNER}/repos, nothing inferred from the owner name); is idempotent - an existing OWNER/REPO, found up front or behind a 409, is returned with created:false and exit 0, never modified, with unmet requested fields listed in 'differs'; a bare 409 stays CONFLICT and 403 names the route. The repo_create capability is probed from the runtime Swagger document (both creation routes must be present) and reports unsupported otherwise. repo view and repo create share one repository object that gains clone_url, ssh_url (null when absent) and empty. The change is additive only (minor release per docs/contract.md). Also included: the live lane's assignee probe uses the authenticated login rather than the repository owner (an org-owned lane repo made Forgejo return 500), and a repo create lane probe beside repo view. Deliberate decisions: CHANGELOG.md is intentionally untouched (upstream maintainer adds it at release); SKILL.md is regenerated via npm run gen:skill, never hand-edited; fixtures for Forgejo 15 and 16 were captured from real hosts with identity fields renamed to acme/widgets. Live lane 15 was run against a Forgejo 15.0.9 host (org route passed; user route refused for lack of write:user token scope; three pre-existing probe failures unrelated to this change); lane 16 was not run for lack of a host, which upstream CONTRIBUTING says to state in the PR so a maintainer covers it. The PR description must be written by no-mistakes and say which lanes were run. An earlier hand-opened PR on the fork (M-Kerr#1) is superseded by this one.
What Changed
forgejo-axi repo create --repo OWNER/REPO (--private|--public) [--description TEXT] [--default-branch NAME]. It requires exactly one visibility flag, so passing neither or both is a usage error (exit 2). It routes touser/reposwhen OWNER matches the login fromGET /user(case-insensitive) and toorgs/{OWNER}/reposotherwise. It is idempotent: if OWNER/REPO already exists, whether found up front or behind a 409, the command returns it withcreated:falseand exit 0, does not modify it, and lists requested fields the repository does not match indiffers. A 409 with no repository behind it staysCONFLICT, and a 403 names the route indetails.route. The newrepo_createcapability is probed from the runtime Swagger document and requires both creation routes; if either is missing, the command returns the unsupported result.repo viewandrepo createnow share one repository object, which gainsclone_url,ssh_url(nullwhen the host reports none) andempty.reponow has its own family help. The change is additive only:docs/contract.md, README, help text, the regeneratedSKILL.mdand ADR 0005 are updated, andtest/repo.test.tsis added with Forgejo 15/16 fixtures captured from real hosts (identity fields renamed toacme/widgets).scripts/live.mjs): the assignee probe now uses the authenticated login instead of the repository owner, because an org-owned lane repo made Forgejo return 500. A newrepo createprobe runs besiderepo viewand covers the org and user routes, the no-op re-create, visibility reported indifferswithout being applied, refusal when no visibility flag is given, and cleanup of every created repository.Live lanes: lane 15 was run against a Forgejo 15.0.9 host. The org-route
repo createprobes passed. The user-route create was refused because the token lacks thewrite:userscope. Three probe failures predate this change and are unrelated to it. Lane 16 was not run because no host was available, so a maintainer needs to run it. CHANGELOG.md is intentionally untouched and left for the maintainer at release. This supersedes M-Kerr#1.Risk Assessment
✅ Low: The fix rounds did what was asked and add no new defects: the passthrough flags are gone, redirected reads are skipped, the live lane compares owners case-insensitively,
descriptionis now indiffers, and the fixture URLs are corrected; the additive command matches the intent and the contract.Testing
I ran the targeted
test/repo.test.tsfile (all 19 tests pass). Then I built the CLI and ran it as a user against a local fake Forgejo host serving the repo's captured 15 and 16 fixtures, saving full transcripts with each request the host received. The explicit-visibility guard passed in a real run: neither flag or both flags exits 2 with no request sent. Everything else passed only against the fake host: user and org routing, including case-insensitive login matching; no mutation on reconcile, with differs covering private, default_branch and description; a 409 with the repository behind it reconciles to created:false while a bare 409 stays CONFLICT; a 403 names orgs/acme/repos; a renamed or redirected address is created rather than reported as existing; the Swagger-probed repo_create capability and the unsupported reply; the clone_url, ssh_url (null when the host omits it) and empty fields. None of this ran against a real Forgejo. The configured host rejects calls without a token, and the tokens are in sops-encrypted.env.jsonwith no sops or mise installed. The Docker daemon is not running, so I couldn't start a local Forgejo either. The live-lane script changes (assignee probe uses the login, the new repo create probe) were therefore not run. The overall verdict is inconclusive for live evidence, though every scenario that ran behaved correctly. No UI is involved, so there are no screenshots.repo createwith neither --private nor --public, or both: exit 2 VALIDATION_ERROR and no request reaches the hostrepo createreports unsupported without a request otherwisenpm run test:live -- 15/-- 16with mise loadin…Evidence: CLI transcript: repo create/view/status against the Forgejo 15/16 fixture fake host, with each request the host received
Evidence: Standalone fake Forgejo host used to run the CLI (not a real Forgejo)
Evidence: Visibility guard: refused before any request
Evidence: Reconcile onto existing repo lists differs and does not mutate
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 3 issues found → auto-fixed (2) ✅
src/forgejo.ts:448- The up-front existence check can report a different, renamed repository as the one at OWNER/REPO, and then nothing gets created.getRepoIfExistscalls GET repos/{owner}/{repo} through the HTTP client, which follows same-origin redirects for GET (src/http.ts:206-257). When a repository or its owner has been renamed, Forgejo answers the old name with a redirect to the new one. Example: rename acme/widgets to acme/widgets-old, then runrepo create --repo acme/widgets --private. The command returnscreated:falseand exit 0 withfull_name:"acme/widgets-old", whileurl,api_urlandclone_urlpoint at acme/widgets. It never POSTs, although Forgejo would accept the create (creating a repository deletes a stale redirect for that name). This contradicts the intent that only "an existing OWNER/REPO" is returned. Fix: ingetRepoIfExists(used by both the up-front check and the post-409 read-back), treat a response whosefull_namedoes not match the requestedrepo.fullNamecase-insensitively as not existing, and add a fixture test for a redirected name.scripts/live.mjs:676- The live lane compares the lane owner and the login case-sensitively, unlike the CLI.new Set([laneOwner, ME])andowner === MEare exact comparisons, while the CLI now routes case-insensitively. If the configured REPO owner differs from the login only in case (for exampleAcme/...with loginacme), the loop runs twice against the same user account. The second iteration then fails 'repo create makes a organization repository' because it getscreated:false, and the target is pushed ontocreated.repostwice, so cleanup reports a false leak after the second DELETE returns 404. Fix: normalise the owner with toLowerCase() before building the Set and the route label.src/cli.ts:236- The intent requires --private/--public, idempotent reconcile, anddiffersfor requested fields. It does not name the passthrough creation options --auto-init, --gitignores, --license, --readme, --template, --trust-model and --object-format (with their TRUST_MODELS/OBJECT_FORMATS validation lists). None of them take part in reconcile ordiffers, and none is exercised by the live lane. If the author does not intend them as part of the first-class surface, the smallest change is to drop them and keep --description and --default-branch. Otherwise, confirm they are deliberate additions to the upstream contract.🔧 Fix applied.
2 issues (1 warning, 1 info) still open:
src/forgejo.ts:569- The intent states an existing repository is returned "with unmet requested fields listed in 'differs'", but reconcileRepo compares onlyprivateanddefault_branch; a requested--descriptionis silently ignored on reconcile. Concrete trace: acme/widgets exists with description "Widgets" (the fixture); runrepo create --repo acme/widgets --private --description Docs. createRepo finds it up front, reconcileRepo returns{created:false, differs:[]}and exit 0, which reads as "everything requested is satisfied" although the description is not. docs/contract.md:110 and the ADR document the exclusion ("coveringprivatealways anddefault_branchwhen requested"), so this is a deliberate scope that contradicts the intent wording rather than an accident. Two remedies: add adescriptionentry to differs wheninput.description !== undefinedand it differs fromexisting.description ?? ''(plus the contract, ADR, and help sentences), or confirm the exclusion and leave the docs as they are. This is product behavior, so the user decides.test/fixtures/forgejo-16.json:278- The intent says fixture identity fields were renamed to acme/widgets, but the Forgejo 16 repository object still carrieslanguages_url(line 278) andurl(line 280) pointing atrepos/forgejo/forgejo, whilefull_name,html_url,ssh_url, andclone_urlsay acme/widgets. The CLI rebuildsurl/api_url/clone_urlfrom the base URL and never reads these two fields, so no behavior depends on them; the fixture is simply internally inconsistent with its stated anonymisation. Remedy: rename both to.../repos/acme/widgets....🔧 Fix applied.
✅ Re-checked - no issues remain.
repo createbehaviour and the changed live-lane script could not be run against a real Forgejo in this step. FORGEJO_BASE_URL answers 403 without a token. The tokens and FORGEJO_LIVE_REPO are sops-encrypted in .env.json, and neither sops nor mise is installed. The Docker daemon is not running, so a local Forgejo could not be started. All behaviour was confirmed only against a fake host built from the captured 15/16 fixtures. To get live evidence, runnpm run test:live -- 15(and-- 16when a host exists) from a shell where mise decrypts .env.json, or start Docker so a disposable Forgejo container can be used.repo createreports unsupported without a request otherwise, repo view and repo create share one repository object with clone_url, ssh_url (null when absent) and empty, Live lane: assignee probe uses the authenticated login, and the new repo create lane probe passes on 15 and 16repo createwith neither --private nor --public, or both: exit 2 VALIDATION_ERROR and no request reaches the hostrepo createreports unsupported without a request otherwisenpm run test:live -- 15/-- 16with mise loadin…npm test -- test/repo.test.ts(19 tests pass: routing, case-insensitive login match, redirect/rename handling, reconcile with differs including description, 409 reconcile vs bare CONFLICT, 403 route, 422, CLI usage errors, JSON/TOON shape, unsupported probe, repo view clone_url/ssh_url)npm run build, then rannode dist/bin/forgejo-axi.js repo create ...against a standalone fake host (fake-host.mjs, modes normal/race409/bare409/forbidden/nocreate/renamed) for both the Forgejo 15 and 16 fixtures, logging every request the host receivedforgejo-axi repo create --repo acme/new1and... --private --public: exit 2 and zero requests sent to the hostforgejo-axi repo create --repo ACME-User/fresh --private --description Docs --default-branch trunkwith loginacme-user: sent POST /user/reposforgejo-axi repo create --repo acme/fresh --public: sent POST /orgs/acme/reposforgejo-axi repo create --repo acme/widgets --public --description Other --default-branch develop: created:false, exit 0, only a GET, differs listedforgejo-axi statuswith and without /orgs/{org}/repos in Swagger: repo_create true/falseforgejo-axi repo create ... --jsonoutput shape, including ssh_url null when the host omits itforgejo-axi repo view --repo acme/widgets: clone_url, ssh_url, empty presentTried to reach the real host:curl $FORGEJO_BASE_URL/api/v1/versionanswered 403 without a token;.env.jsonis sops-encrypted, and neither sops nor mise is installeddocker info: daemon not running✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.