Version: 0.1 ยท Status: Draft
gitxt is a purely decentralised git forge. There is no federation protocol, no instance-to-instance API, no accounts on other people’s servers. Each forge instance is fully independent; collaboration is an act of publishing and reading plain text files over HTTP โ exactly the twtxt model, applied to code forges.
Everything below can be implemented with git, an HTTP server, and a text
editor. A conforming instance interoperates with every other conforming
instance and with the existing twtxt ecosystem.
1. The forge feed
Every repository publishes one forge feed: an append-only, UTF-8, plain-text file conventionally served at:
https://example.com/~user/repo/forge.txt
The feed MUST be a valid twtxt feed: one entry per
line, each line an RFC3339 timestamp, a TAB (\t), and the entry text.
Multi-line text is escaped with U+2028 (LINE SEPARATOR) per the twtxt
Multiline Extension. Any stock twtxt client can follow, render, and reply
to a forge feed; forge semantics are carried entirely by the conventions in
ยง3 and degrade gracefully to readable text.
2. Feed metadata
The feed begins with twtxt Metadata Extension comments:
# nick = myproj
# url = https://example.com/~alice/myproj/forge.txt
# type = forge
# clone = https://example.com/~alice/myproj.git
# description = a tiny project
# key = ssh-ed25519 AAAAโฆ
# origin = https://upstream.example/~bob/myproj/forge.txt
# ping = https://example.com/ping
| field | req. | meaning |
|---|---|---|
nick |
MUST | the repository name |
url |
MUST | the feed’s canonical URL โ the identity of this repo (ยง5) |
type |
MUST | literally forge; distinguishes forge feeds from personal twtxt feeds |
clone |
MUST | anonymous HTTP(S) clone URL of the repository |
description |
MAY | one-line description |
key |
MAY | the maintainer’s SSH public key, for optional signing/verification |
origin |
MAY | for forks: the upstream repository’s feed URL |
ping |
MAY | endpoint accepting ping nudges (ยง6) |
3. Entries
Entry text follows plain twtxt conventions โ Twt Hash threading with
(#hash) subjects and @<nick url> mentions โ plus two forge conventions:
- an optional verb, a leading bracketed token after the subject;
- optional trailers, trailing
key=valuetokens.
<timestamp> TAB [(#root)] [verb] free text [key=value โฆ]
Recognised verbs:
| verb | starts a thread? | meaning |
|---|---|---|
[issue] |
yes | opens an issue; the text is its title |
[patch] |
either | proposes changes; carries repo= and ref= trailers (ยง4) |
[close] |
no | closes the thread it replies to |
[reopen] |
no | reopens the thread it replies to |
[merge] |
no | records that a patch in this thread was merged; MAY carry commit= |
| (none) | no | a plain comment in the thread |
Recognised trailers: repo, ref, branch, commit. Unknown verbs and
trailers MUST be ignored (they remain visible as text).
Threading is exactly the twtxt Twt Hash mechanism: a reply carries
(#hash) of the thread root. Hashes are computed from the feed’s canonical
url, the entry timestamp, and the entry text, per the Twt Hash spec (v1
or v2 according to the entry’s timestamp epoch).
An example conversation across two independent instances:
# bob's fork feed (https://bob.example/~bob/myproj/forge.txt)
2026-09-01T10:00:00Z [issue] Crash on empty input
2026-09-01T12:30:00Z (#a3f9k2c) [patch] fix crash repo=https://bob.example/~bob/myproj.git ref=fix-crash
# alice's upstream feed (https://alice.example/~alice/myproj/forge.txt)
2026-09-01T13:00:00Z (#a3f9k2c) [merge] merged @<myproj https://bob.example/~bob/myproj/forge.txt> (#p7x1m9q) into main commit=deadbee
2026-09-01T13:01:00Z (#a3f9k2c) [close] fixed by the patch
4. Patches
A patch is a pointer, not a payload: the code travels by git itself.
- The contributor pushes a branch to their own instance’s copy of the
repository and publishes a
[patch]entry whose trailers name the anonymous clone URL (repo=) and branch or ref (ref=). - A maintainer’s instance fetches
reffromrepo(git fetch <repo> <ref>), renders the commits and diff against its default branch, and โ on the maintainer’s say-so โ merges with ordinary git machinery. - The maintainer then publishes
[merge]into the same thread so every follower (including the contributor’s instance) learns the outcome.
Instances SHOULD fetch patch refs only over http(s) and treat fetched objects as untrusted input.
5. Identity and trust
A feed URL is an identity. There is no global namespace, registration,
or cryptographic requirement. @<nick url> mentions name people and repos;
following a feed is trusting its URL.
Trust in content is human: a maintainer reviews a patch’s diff before
merging, exactly as they would review an emailed patch. A feed MAY
advertise an SSH public key (key =); entries and git objects MAY be
signed with it and clients MAY verify โ but verification is a client
feature, never a protocol requirement.
6. Transport: pull, plus an optional ping
The only required transport is HTTP GET. Instances follow feeds and
poll them (honouring ETag / Last-Modified). A follower SHOULD send a
descriptive User-Agent naming its own feed, twtxt-style, so that being
fetched doubles as passive discovery:
gitxt/0.1 (+https://bob.example/; @bob)
As a timeliness optimisation โ never a requirement โ an instance MAY
expose a ping endpoint (advertised via ping =) accepting:
POST /ping
url=https://bob.example/~bob/myproj/forge.txt
The receiver MAY fetch a followed feed immediately, MAY queue an unknown feed for its owner to inspect, and MUST NOT trust the pinged URL beyond fetching it. Receivers SHOULD rate-limit. If every ping in the world is lost, nothing breaks: polling still delivers everything.
7. Repositories
Repositories are served with the standard git smart-HTTP protocol at the
clone URL. Anonymous read (git-upload-pack) MUST be open; write
(git-receive-pack) MUST be authenticated โ how is an instance-local
matter (gitxt uses a bearer token as the HTTP basic-auth password).
Forking is a local act: clone the upstream (found via its feed’s clone),
publish your own feed for the fork with origin = naming the upstream
feed, and follow that upstream feed.
8. What is deliberately absent
No instance-to-instance API. No shared databases. No global identity. No
required cryptography. No JSON. A forge feed is useful with nothing but
curl and git โ that is the point.