markdown · 3984 bytes Raw Blame History

Repository follow-ups — README + topics + merge-upstream

Three additive endpoints that round out the §2 repos surface. The core repos CRUD (list/single/create/patch/delete) lives in Repositories; this page covers the three that arrived later: rendering-free README fetch, topic replacement, and fork sync.

Scopes:

  • repo:read on README.
  • repo:write on topics replace/clear and merge-upstream.

Endpoints

GET    /api/v1/repos/{o}/{r}/readme[?ref=]
PUT    /api/v1/repos/{o}/{r}/topics
DELETE /api/v1/repos/{o}/{r}/topics
POST   /api/v1/repos/{o}/{r}/merge-upstream

README

GET /api/v1/repos/{o}/{r}/readme returns the repo's README at ref (default branch when omitted). The handler walks the root tree, picks the first entry whose name starts with readme (case-insensitive), and prefers .md / .markdown over plain text so a repo with both README.md and README.rst returns the markdown variant — matching the HTML code-view's choice.

Response shape:

{
  "name":         "README.md",
  "path":         "README.md",
  "size":         312,
  "encoding":     "base64",
  "content":      "IyBEZW1vIHJlcG8K...",
  "download_url": "http://shithub.local/alice/demo/raw/trunk/README.md"
}

content is base64-encoded so binary or UTF-16 READMEs round-trip cleanly. size matches the decoded byte count. The handler caps reads at 1 MiB (matching the HTML render cap); larger READMEs are truncated to that cap but the response still succeeds. Use download_url to stream the full blob when the cap matters.

Errors:

Status Cause
404 Repo missing, caller lacks read, ref absent, or no README

The 404 is existence-leak-safe: a private repo the caller can't read returns the same 404 as a public repo with no README.

Topics

PUT /api/v1/repos/{o}/{r}/topics replaces the full topic set atomically. Body:

{ "names": ["go", "rest-api", "shithub"] }

Response (200):

{ "names": ["go", "rest-api", "shithub"] }

Topics are normalized server-side (lowercased, deduped) and validated:

  • Max 20 per repo.
  • Each name 1–50 chars, lowercase letters / digits / hyphens only.

Invalid input returns 422 with a JSON error describing the violated constraint.

DELETE /api/v1/repos/{o}/{r}/topics clears all topics. Returns 204 No Content. Idempotent — clearing an empty set still returns 204.

Merge-upstream (fork sync)

POST /api/v1/repos/{o}/{r}/merge-upstream fast-forwards a fork's default branch to its upstream. Mirrors GitHub's "Sync fork" button.

The handler refuses non-forks with 422. For a real fork it calls the shared fork.Sync orchestrator, which only proceeds when the merge is a clean fast-forward — divergent forks return 409 and must be reconciled by the user via their git client.

Successful response:

{
  "merged":      true,
  "old_oid":     "a1b2...",
  "new_oid":     "c3d4...",
  "base_branch": "trunk",
  "message":     "fast-forwarded to upstream"
}

Already-up-to-date (200, not an error):

{
  "merged":  false,
  "message": "already up to date"
}

Errors:

Status Cause
409 Fork has diverged from upstream — sync via your client.
409 Ref changed concurrently — retry.
409 Fork still being initialized — retry shortly.
422 Repo is not a fork.
422 Source or fork default branch is empty.

The endpoint is intentionally narrower than GitHub's: we only fast-forward. A "fork sync with merge commit" mode would require the runner to pick an author identity and resolve conflicts, both of which we'd rather the caller do locally.

View source
1 # Repository follow-ups — README + topics + merge-upstream
2
3 Three additive endpoints that round out the §2 repos surface. The
4 core repos CRUD (list/single/create/patch/delete) lives in
5 [Repositories](./repos.md); this page covers the three that arrived
6 later: rendering-free README fetch, topic replacement, and fork
7 sync.
8
9 Scopes:
10
11 - `repo:read` on README.
12 - `repo:write` on topics replace/clear and merge-upstream.
13
14 ## Endpoints
15
16 ```
17 GET /api/v1/repos/{o}/{r}/readme[?ref=]
18 PUT /api/v1/repos/{o}/{r}/topics
19 DELETE /api/v1/repos/{o}/{r}/topics
20 POST /api/v1/repos/{o}/{r}/merge-upstream
21 ```
22
23 ## README
24
25 `GET /api/v1/repos/{o}/{r}/readme` returns the repo's README at
26 `ref` (default branch when omitted). The handler walks the root
27 tree, picks the first entry whose name starts with `readme`
28 (case-insensitive), and prefers `.md` / `.markdown` over plain
29 text so a repo with both `README.md` and `README.rst` returns the
30 markdown variant — matching the HTML code-view's choice.
31
32 Response shape:
33
34 ```json
35 {
36 "name": "README.md",
37 "path": "README.md",
38 "size": 312,
39 "encoding": "base64",
40 "content": "IyBEZW1vIHJlcG8K...",
41 "download_url": "http://shithub.local/alice/demo/raw/trunk/README.md"
42 }
43 ```
44
45 `content` is base64-encoded so binary or UTF-16 READMEs round-trip
46 cleanly. `size` matches the decoded byte count. The handler caps
47 reads at 1 MiB (matching the HTML render cap); larger READMEs are
48 truncated to that cap but the response still succeeds. Use
49 `download_url` to stream the full blob when the cap matters.
50
51 Errors:
52
53 | Status | Cause |
54 |------:|-----------------------------------------------------------|
55 | 404 | Repo missing, caller lacks read, ref absent, or no README |
56
57 The 404 is existence-leak-safe: a private repo the caller can't
58 read returns the same 404 as a public repo with no README.
59
60 ## Topics
61
62 `PUT /api/v1/repos/{o}/{r}/topics` replaces the full topic set
63 atomically. Body:
64
65 ```json
66 { "names": ["go", "rest-api", "shithub"] }
67 ```
68
69 Response (200):
70
71 ```json
72 { "names": ["go", "rest-api", "shithub"] }
73 ```
74
75 Topics are normalized server-side (lowercased, deduped) and
76 validated:
77
78 - Max 20 per repo.
79 - Each name 1–50 chars, lowercase letters / digits / hyphens
80 only.
81
82 Invalid input returns `422` with a JSON error describing the
83 violated constraint.
84
85 `DELETE /api/v1/repos/{o}/{r}/topics` clears all topics. Returns
86 `204 No Content`. Idempotent — clearing an empty set still
87 returns 204.
88
89 ## Merge-upstream (fork sync)
90
91 `POST /api/v1/repos/{o}/{r}/merge-upstream` fast-forwards a fork's
92 default branch to its upstream. Mirrors GitHub's "Sync fork"
93 button.
94
95 The handler refuses non-forks with `422`. For a real fork it
96 calls the shared `fork.Sync` orchestrator, which only proceeds
97 when the merge is a clean fast-forward — divergent forks return
98 `409` and must be reconciled by the user via their git client.
99
100 Successful response:
101
102 ```json
103 {
104 "merged": true,
105 "old_oid": "a1b2...",
106 "new_oid": "c3d4...",
107 "base_branch": "trunk",
108 "message": "fast-forwarded to upstream"
109 }
110 ```
111
112 Already-up-to-date (200, not an error):
113
114 ```json
115 {
116 "merged": false,
117 "message": "already up to date"
118 }
119 ```
120
121 Errors:
122
123 | Status | Cause |
124 |------:|-----------------------------------------------------------|
125 | 409 | Fork has diverged from upstream — sync via your client. |
126 | 409 | Ref changed concurrently — retry. |
127 | 409 | Fork still being initialized — retry shortly. |
128 | 422 | Repo is not a fork. |
129 | 422 | Source or fork default branch is empty. |
130
131 The endpoint is intentionally narrower than GitHub's: we only
132 fast-forward. A "fork sync with merge commit" mode would require
133 the runner to pick an author identity and resolve conflicts, both
134 of which we'd rather the caller do locally.