@@ -1,28 +1,22 @@ |
| 1 |
# dumbforge |
1 |
# dumbforge |
| 2 |
|
2 |
|
| 3 |
`dumbforge` turns an S3-compatible bucket into a static Git host. Authors push |
3 |
Well, everyone's [been talking about GitHub alternatives](https://mitchellh.com/writing/ghostty-leaving-github) lately. I'm pretty sympathetic to that on an ideological level (and just the fundamental problem of it being down all the time), but historically the alternatives kind of suck, and I think still miss the point of Git being decentralized in the first place. Gitea/Forgejo are cool, but require you to set up a server and configure it. Even though it's pretty easy, it's still annoying and requires you to pay for and babysit a VPS or whatever to run the forge program. Codeberg has been a platform of choice for freedom-heads to get away from GitHub, but they recently instituted a no-crypto and no-LLM rule. I respect what they're doing, but I'm pretty AI-pilled so that's not going to work for me. |
| 4 |
through a Git remote helper; everybody else clones with ordinary Git over dumb |
|
|
| 5 |
HTTP. Each push also publishes a static repository browser at the same URL. |
|
|
| 6 |
|
4 |
|
| 7 |
This is an early implementation. It has been exercised against Cloudflare R2, |
5 |
It occurred to me that you don't actually need to run a forge or git daemon at all, since Git supports cloning via the so-called ["dumb" HTTP protocol](https://git-scm.com/book/en/v2/Git-Internals-Transfer-Protocols). You could theoretically just throw your git repo into an s3 bucket, and it would be clone-able, no daemon or VPS required. So that's cool, and even more decentralized; but the problem with self-hosting git like that, is that then you don't have the nice landing page, file browser, commit log, etc etc. It's small, but it makes a big difference to have a GitHubby UI for each project. SourceHut and Savannah struggle with this I think, since they're pretty alien to normies. |
| 8 |
including a full public clone and incremental multi-pack pushes. |
|
|
| 9 |
|
6 |
|
| 10 |
## Install |
7 |
So I slopped `dumbforge` together as a proof of concept; it's a static-site-generator like thing that functions as a git adapter which pushes directly to an s3 bucket, and generates a GitHub-like UI. So you get some really nice advantages: |
| 11 |
|
8 |
|
| 12 |
The planned primary distribution is npm: |
9 |
1. It's super fast to browse, since it's just static HTML. GitLab and GitHub itself are really slow clunky beasts. |
|
|
10 |
2. You don't need to administer a git daemon or forge server. |
|
|
11 |
3. It's free if you're storing less than 10 gigs, using CloudFlare R2. |
|
|
12 |
4. You're decentralized, bro! Well, mostly. |
| 13 |
|
13 |
|
| 14 |
```sh |
14 |
I'll grant that the implementation could most certainly use some improvement but this is mostly just to show off the idea. I hope it piques your curiosity. 🤔 |
| 15 |
npm install --global dumbforge |
|
|
| 16 |
``` |
|
|
| 17 |
|
15 |
|
| 18 |
The npm package selects a native Go binary for the current OS and CPU from an |
16 |
## Install |
| 19 |
npm-hosted platform package. It does not run an installer that downloads a |
|
|
| 20 |
GitHub release. Until the first npm release, build locally with Go 1.26 or |
|
|
| 21 |
newer: |
|
|
| 22 |
|
17 |
|
| 23 |
```sh |
18 |
```sh |
| 24 |
go build -o dumbforge ./cmd/dumbforge |
19 |
npm install --global dumbforge |
| 25 |
ln -s dumbforge git-remote-dumbforge |
|
|
| 26 |
``` |
20 |
``` |
| 27 |
|
21 |
|
| 28 |
Git itself must also be installed. The implementation deliberately asks Git to |
22 |
Git itself must also be installed. The implementation deliberately asks Git to |
@@ -68,86 +62,9 @@ git clone https://pub-EXAMPLE.r2.dev/projects/example.git |
| 68 |
The repository browser is served from that exact URL. Git appends |
62 |
The repository browser is served from that exact URL. Git appends |
| 69 |
`/info/refs`; browsers receive the HTML object stored at the repository prefix. |
63 |
`/info/refs`; browsers receive the HTML object stored at the repository prefix. |
| 70 |
|
64 |
|
| 71 |
## How a push works |
|
|
| 72 |
|
|
|
| 73 |
1. Acquire an S3 conditional-write lease for the repository. |
|
|
| 74 |
2. Read the currently advertised refs and pack list. |
|
|
| 75 |
3. Validate fast-forward and concurrent-ref constraints. |
|
|
| 76 |
4. Ask local Git for a non-thin pack containing objects reachable from the new |
|
|
| 77 |
tips but not the known remote tips. |
|
|
| 78 |
5. Upload the immutable `.pack` and `.idx`. |
|
|
| 79 |
6. Publish `objects/info/packs`, individual refs, `HEAD`, and finally |
|
|
| 80 |
`info/refs`. |
|
|
| 81 |
7. Generate changed source/raw pages, affected ancestor tree pages, and the |
|
|
| 82 |
branch's commit and repository-summary pages. A compact repository ref |
|
|
| 83 |
manifest and per-branch file index power navigation without rebuilding every |
|
|
| 84 |
existing page. |
|
|
| 85 |
|
|
|
| 86 |
Git data is never rebuilt on an ordinary push. Two small commits produce two |
|
|
| 87 |
small packs. The mutable dumb-HTTP indexes are tiny and served with revalidation; |
|
|
| 88 |
content-addressed packfiles are served as immutable objects. |
|
|
| 89 |
|
|
|
| 90 |
## Static browser |
|
|
| 91 |
|
|
|
| 92 |
The generated browser includes: |
|
|
| 93 |
|
|
|
| 94 |
- repository and directory trees; |
|
|
| 95 |
- per-file latest commit messages linked into branch history; |
|
|
| 96 |
- rendered GitHub-flavored Markdown README files; |
|
|
| 97 |
- syntax highlighting and linked line numbers; |
|
|
| 98 |
- raw file URLs and image previews; |
|
|
| 99 |
- a live branch switcher with branch-aware paths; |
|
|
| 100 |
- a searchable Go-to-file dialog; |
|
|
| 101 |
- full commit history with contributor Gravatars; |
|
|
| 102 |
- top contributors and Linguist-compatible language percentages; |
|
|
| 103 |
- a clone popover with copy confirmation and the public HTTPS URL; |
|
|
| 104 |
- a GitHub Importer shortcut that copies the clone URL before opening the |
|
|
| 105 |
importer, ready to paste into GitHub's source URL field; |
|
|
| 106 |
- an About panel derived from package metadata, the README, and the upstream |
|
|
| 107 |
remote; |
|
|
| 108 |
- incremental regeneration based on `git diff --name-status`. |
|
|
| 109 |
|
|
|
| 110 |
GitHub's repository description is not part of Git data. Override the inferred |
|
|
| 111 |
About metadata in the local repository when needed: |
|
|
| 112 |
|
|
|
| 113 |
```sh |
|
|
| 114 |
git config dumbforge.description "A short repository description" |
|
|
| 115 |
git config dumbforge.homepage "https://example.com/project" |
|
|
| 116 |
``` |
|
|
| 117 |
|
|
|
| 118 |
Contributor avatars are loaded from Gravatar using the normalized SHA-256 hash |
|
|
| 119 |
of each commit author's email address. Gravatar's generated initials are used |
|
|
| 120 |
when an author has no uploaded image. |
|
|
| 121 |
|
|
|
| 122 |
The browser uses [Primer Octicons](https://github.com/primer/octicons), GitHub's |
|
|
| 123 |
MIT-licensed icon set. See `THIRD_PARTY_NOTICES.md` for attribution. The "Fork |
|
|
| 124 |
to GitHub" action uses GitHub Importer; it creates an independent repository and |
|
|
| 125 |
does not add the imported repository to a GitHub fork network. |
|
|
| 126 |
|
|
|
| 127 |
## Current constraints |
|
|
| 128 |
|
|
|
| 129 |
- Shallow repositories are rejected because static dumb HTTP has no shallow |
|
|
| 130 |
boundary negotiation. |
|
|
| 131 |
- There is no pack compaction command yet. A future `dumbforge gc` will combine |
|
|
| 132 |
accumulated incremental packs outside the normal push path. |
|
|
| 133 |
- Git LFS is not implemented. |
|
|
| 134 |
- Branch deletion removes Git refs, but full static-page cleanup for a deleted |
|
|
| 135 |
branch is not implemented yet. |
|
|
| 136 |
- The `r2.dev` hostname is intended for development; use an R2 custom domain for |
|
|
| 137 |
production traffic and explicit cache rules. |
|
|
| 138 |
|
|
|
| 139 |
## Development |
65 |
## Development |
| 140 |
|
66 |
|
| 141 |
```sh |
67 |
```sh |
| 142 |
go test ./... |
68 |
go test ./... |
| 143 |
go build ./cmd/dumbforge |
69 |
go build ./cmd/dumbforge |
| 144 |
``` |
70 |
``` |
| 145 |
|
|
|
| 146 |
Build all npm platform packages with: |
|
|
| 147 |
|
|
|
| 148 |
```sh |
|
|
| 149 |
./scripts/build-npm-packages.sh 0.1.0 |
|
|
| 150 |
``` |
|
|
| 151 |
|
|
|
| 152 |
The resulting tarballs are written to `dist/npm`. Publish the six platform |
|
|
| 153 |
packages first and the `dumbforge` launcher package last. |
|
|