git-pr

created pr with 60.1 on 2025-04-07T22:44:49Z · by 964fa508
added 60.2 on 2025-08-10T19:03:50Z · by c8ef7d19
-: ------- > 1: 867f3be Closing for now
1: 6a52e9e ! 2: 867f3be docs proposal
cmds
checkout latest patchset:
ssh pr.pico.sh print 60 | git am -3
checkout any patchset in a patch request:
ssh pr.pico.sh print 60.[rev] | git am -3
add changes to patch request:
git format-patch main --stdout | ssh pr.pico.sh pr add 60

Patchset 60.2 on 2025-08-10T19:03:50Z · commit 867f3be

docs proposal
jolheiser 2025-04-07T22:42:31Z
This is closer to what I was thinking for docs.
This keeps a lot of the exposition off the main page and moves it to a separate page.

Signed-off-by: jolheiser <git@jolheiser.com>
Semantic diff summary
1 added, 1 modified, 0 signature changed, 0 removed across 1 analyzed file (3 files skipped: unsupported file type)
+2 -0 .gitignore #
......@@ -15,3 +15,5 @@ __debug_bin
1515 /public/
1616 test.db
1717 review.patch
18+git-web*
19+git-ssh*
+235 -0 tmpl/docs.html #
......@@ -0,0 +1,235 @@
1+{{template "base" .}}
2+
3+{{define "title"}}docs{{end}}
4+
5+{{define "meta"}}{{end}}
6+
7+{{define "body"}}
8+<header class="group">
9+ <h1 class="text-2xl">docs</h1>
10+ <div>
11+ <a href="/">dashboard</a> &middot;
12+ <a href="https://github.com/picosh/git-pr">github</a> &middot;
13+ <a href="https://youtu.be/d28Dih-BBUw">demo video</a>
14+ </div>
15+</header>
16+<main class="group">
17+ <details>
18+ <summary>Intro</summary>
19+
20+ <div>
21+ <p>
22+ We are trying to build the simplest git collaboration tool. The goal is to make
23+ self-hosting as simple as running an SSH server -- all without
24+ sacrificing external collaborators time and energy.
25+ </p>
26+
27+ <blockquote>
28+ <code>git format-patch</code> isn't the problem and pull requests aren't the solution.
29+ </blockquote>
30+
31+ <p>
32+ We are combining mailing list and pull request workflows. In order to build the
33+ simplest collaboration tool, we needed something as simple as generating patches
34+ but the ease-of-use of pull requests.
35+ </p>
36+
37+ <p>
38+ The goal is not to create another code forge, the goal is to create a very
39+ simple self-hosted git solution with the ability to collaborate with external
40+ contributors. All the code owner needs to setup a running git server:
41+ </p>
42+
43+ <ul>
44+ <li>A single golang binary</li>
45+ </ul>
46+
47+ <div>
48+ All an external contributor needs is:
49+ </div>
50+
51+ <ul>
52+ <li>An SSH keypair</li>
53+ <li>An SSH client</li>
54+ </ul>
55+
56+ <p>Then everyone subscribes to our RSS feeds to receive updates to patch requests.</p>
57+
58+ <h2 class="text-xl">the problem</h2>
59+
60+ <p>
61+ Email is great as a decentralized system to send and receive changes (patchsets)
62+ to a git repo. However, onboarding a new user to a mailing list, properly
63+ setting up their email client, and then finally submitting the code contribution
64+ is enough to make many developers give up. Further, because we are leveraging
65+ the email protocol for collaboration, we are limited by its feature-set. For
66+ example, it is not possible to make edits to emails, everyone has a different
67+ client, those clients have different limitations around plain text email and
68+ downloading patches from it.
69+ </p>
70+
71+ <p>
72+ Github pull requests are easy to use, easy to edit, and easy to manage. The
73+ downside is it forces the user to be inside their website to perform reviews.
74+ For quick changes, this is great, but when you start reading code within a web
75+ browser, there are quite a few downsides. At a certain point, it makes more
76+ sense to review code inside your local development environment, IDE, etc. There
77+ are tools and plugins that allow users to review PRs inside their IDE, but it
78+ requires a herculean effort to make it usable.
79+ </p>
80+
81+ <p>
82+ Further, self-hosted solutions that mimic a pull request require a lot of
83+ infrastructure in order to manage it. A database, a web site connected to git,
84+ admin management, and services to manage it all. Another big point of friction:
85+ before an external user submits a code change, they first need to create an
86+ account and then login. This adds quite a bit of friction for a self-hosted
87+ solution, not only for an external contributor, but also for the code owner who
88+ has to provision the infra. Often times they also have to fork the repo within
89+ the code forge before submitting a PR. Then they never make a contribution ever
90+ again and keep a forked repo around forever. That seems silly.
91+ </p>
92+
93+ <h2 class="text-xl">introducing patch requests (PR)</h2>
94+
95+ <p>
96+ Instead, we want to create a self-hosted git "server" that can handle sending
97+ and receiving patches without the cumbersome nature of setting up email or the
98+ limitations imposed by the email protocol. Further, we want the primary workflow
99+ to surround the local development environment. Github is bringing the IDE to the
100+ browser in order to support their workflow, we want to flip that idea on its
101+ head by making code reviews a first-class citizen inside your local development
102+ environment.
103+ </p>
104+
105+ <p>
106+ We see this as a hybrid between the github workflow of a pull request and
107+ sending and receiving patches over email.
108+ </p>
109+
110+ <p>
111+ The basic idea is to leverage an SSH app to handle most of the interaction
112+ between contributor and owner of a project. Everything can be done completely
113+ within the terminal, in a way that is ergonomic and fully featured.
114+ </p>
115+
116+ <p>
117+ Notifications would happen with RSS and all state mutations would result in the
118+ generation of static web assets so it can all be hosted using a simple file web
119+ server.
120+ </p>
121+
122+ <h3 class="text-lg">format-patch workflow</h3>
123+
124+ <p>
125+ The fundamental collaboration tool here is <code>format-patch</code>. Whether you a
126+ submitting code changes or you are reviewing code changes, it all happens in
127+ code. Both contributor and owner are simply creating new commits and generating
128+ patches on top of each other. This obviates the need to have a web viewer where
129+ the reviewing can "comment" on a line of code block. There's no need, apply the
130+ contributor's patches, write comments or code changes, generate a new patch,
131+ send the patch to the git server as a "review." This flow also works the exact
132+ same if two users are collaborating on a set of changes.
133+ </p>
134+
135+ <p>
136+ This also solves the problem of sending multiple patchsets for the same code
137+ change. There's a single, central Patch Request where all changes and
138+ collaboration happens.
139+ </p>
140+
141+ <p>
142+ We could figure out a way to leverage <code>git notes</code> for reviews / comments, but
143+ honestly, that solution feels brutal and outside the comfort level of most git
144+ users. Just send reviews as code and write comments in the programming language
145+ you are using. It's the job of the contributor to "address" those comments and
146+ then remove them in subsequent patches. This is the forcing function to address
147+ all comments: the patch won't be merged if there are comment unaddressed in
148+ code; they cannot be ignored or else they will be upstreamed erroneously.
149+ </p>
150+ </div>
151+ </details>
152+
153+ <details>
154+ <summary>How do Patch Requests work?</summary>
155+ <div>
156+ Patch requests (PR) are the simplest way to submit, review, and accept changes to your git repository.
157+ Here's how it works:
158+ </div>
159+
160+ <ol>
161+ <li>External contributor clones repo (<code>git-clone</code>)</li>
162+ <li>External contributor makes a code change (<code>git-add</code> & <code>git-commit</code>)</li>
163+ <li>External contributor generates patches (<code>git-format-patch</code>)</li>
164+ <li>External contributor submits a PR to SSH server</li>
165+ <li>Owner receives RSS notification that there's a new PR</li>
166+ <li>Owner applies patches locally (<code>git-am</code>) from SSH server</li>
167+ <li>Owner makes suggestions in code! (<code>git-add</code> & <code>git-commit</code>)</li>
168+ <li>Owner submits review by piping patch to SSH server (<code>git-format-patch</code>)</li>
169+ <li>External contributor receives RSS notification of the PR review</li>
170+ <li>External contributor re-applies patches (<code>git-am</code>)</li>
171+ <li>External contributor reviews and removes comments in code!</li>
172+ <li>External contributor submits another patch (<code>git-format-patch</code>)</li>
173+ <li>Owner applies patches locally (<code>git-am</code>)</li>
174+ <li>Owner marks PR as accepted and pushes code to main (<code>git-push</code>)</li>
175+ </ol>
176+
177+ <div>Example commands</div>
178+
179+ <pre># Owner hosts repo `test.git` using github
180+
181+ # Contributor clones repo
182+ git clone git@github.com:picosh/test.git
183+
184+ # Contributor wants to make a change
185+ # Contributor makes changes via commits
186+ git add -A && git commit -m "fix: some bugs"
187+
188+ # Contributor runs:
189+ git format-patch origin/main --stdout | ssh {{.MetaData.URL}} pr create test
190+ # > Patch Request has been created (ID: 1)
191+
192+ # Owner can checkout patch:
193+ ssh {{.MetaData.URL}} pr print 1 | git am -3
194+
195+ # Owner can comment (IN CODE), commit, then send another format-patch
196+ # on top of the PR:
197+ git format-patch origin/main --stdout | ssh {{.MetaData.URL}} pr add --review 1
198+ # UI clearly marks patch as a review
199+
200+ # Contributor can checkout reviews
201+ ssh {{.MetaData.URL}} print pr-1 | git am -3
202+
203+ # Owner can reject a pr:
204+ ssh {{.MetaData.URL}} pr close 1
205+
206+ # Owner can accept a pr:
207+ ssh {{.MetaData.URL}} pr accept 1
208+
209+ # Owner can prep PR for upstream:
210+ git rebase -i origin/main
211+
212+ # Then push to upstream
213+ git push origin main
214+
215+ # Done!
216+ </pre>
217+ </details>
218+
219+ <details>
220+ <summary>First time user?</summary>
221+
222+ <div>
223+ Using this service for the first time? Creating a patch request is simple:
224+ </div>
225+
226+ <pre>git format-patch main --stdout | ssh {{.MetaData.URL}} pr create {repo}</pre>
227+
228+ <div>When running that command we will automatically create a user and a repo if one doesn't exist.</div>
229+
230+ <div>Want to submit a v2 of the patch request?</div>
231+
232+ <pre>git format-patch main --stdout | ssh {{.MetaData.URL}} pr add {prID}</pre>
233+ </details>
234+</main>
235+{{end}}
+3 -222 tmpl/index.html #
......@@ -3,9 +3,7 @@
33 {{define "title"}}git-pr{{end}}
44
55 {{define "meta"}}
6-<link rel="alternate" type="application/atom+xml"
7- title="RSS feed for git collaboration server"
8- href="/rss" />
6+<link rel="alternate" type="application/atom+xml" title="RSS feed for git collaboration server" href="/rss">
97 {{end}}
108
119 {{define "body"}}
......@@ -13,8 +11,7 @@
1311 <h1 class="text-2xl">git-pr</h1>
1412 <div>
1513 <span>A pastebin supercharged for git collaboration</span> &middot;
16- <a href="https://github.com/picosh/git-pr">github</a> &middot;
17- <a href="https://youtu.be/d28Dih-BBUw">demo video</a>
14+ <a href="/docs">docs</a>
1815 </div>
1916
2017 {{if .MetaData.Desc}}
......@@ -22,222 +19,6 @@
2219 <div>{{.MetaData.Desc}}</div>
2320 </div>
2421 {{end}}
25-
26- <details>
27- <summary>Intro</summary>
28-
29- <div>
30- <p>
31- We are trying to build the simplest git collaboration tool. The goal is to make
32- self-hosting as simple as running an SSH server -- all without
33- sacrificing external collaborators time and energy.
34- </p>
35-
36- <blockquote>
37- <code>git format-patch</code> isn't the problem and pull requests aren't the solution.
38- </blockquote>
39-
40- <p>
41- We are combining mailing list and pull request workflows. In order to build the
42- simplest collaboration tool, we needed something as simple as generating patches
43- but the ease-of-use of pull requests.
44- </p>
45-
46- <p>
47- The goal is not to create another code forge, the goal is to create a very
48- simple self-hosted git solution with the ability to collaborate with external
49- contributors. All the code owner needs to setup a running git server:
50- </p>
51-
52- <ul><li>A single golang binary</li></ul>
53-
54- <div>
55- All an external contributor needs is:
56- </div>
57-
58- <ul>
59- <li>An SSH keypair</li>
60- <li>An SSH client</li>
61- </ul>
62-
63- <p>Then everyone subscribes to our RSS feeds to receive updates to patch requests.</p>
64-
65- <h2 class="text-xl">the problem</h2>
66-
67- <p>
68- Email is great as a decentralized system to send and receive changes (patchsets)
69- to a git repo. However, onboarding a new user to a mailing list, properly
70- setting up their email client, and then finally submitting the code contribution
71- is enough to make many developers give up. Further, because we are leveraging
72- the email protocol for collaboration, we are limited by its feature-set. For
73- example, it is not possible to make edits to emails, everyone has a different
74- client, those clients have different limitations around plain text email and
75- downloading patches from it.
76- </p>
77-
78- <p>
79- Github pull requests are easy to use, easy to edit, and easy to manage. The
80- downside is it forces the user to be inside their website to perform reviews.
81- For quick changes, this is great, but when you start reading code within a web
82- browser, there are quite a few downsides. At a certain point, it makes more
83- sense to review code inside your local development environment, IDE, etc. There
84- are tools and plugins that allow users to review PRs inside their IDE, but it
85- requires a herculean effort to make it usable.
86- </p>
87-
88- <p>
89- Further, self-hosted solutions that mimic a pull request require a lot of
90- infrastructure in order to manage it. A database, a web site connected to git,
91- admin management, and services to manage it all. Another big point of friction:
92- before an external user submits a code change, they first need to create an
93- account and then login. This adds quite a bit of friction for a self-hosted
94- solution, not only for an external contributor, but also for the code owner who
95- has to provision the infra. Often times they also have to fork the repo within
96- the code forge before submitting a PR. Then they never make a contribution ever
97- again and keep a forked repo around forever. That seems silly.
98- </p>
99-
100- <h2 class="text-xl">introducing patch requests (PR)</h2>
101-
102- <p>
103- Instead, we want to create a self-hosted git "server" that can handle sending
104- and receiving patches without the cumbersome nature of setting up email or the
105- limitations imposed by the email protocol. Further, we want the primary workflow
106- to surround the local development environment. Github is bringing the IDE to the
107- browser in order to support their workflow, we want to flip that idea on its
108- head by making code reviews a first-class citizen inside your local development
109- environment.
110- </p>
111-
112- <p>
113- We see this as a hybrid between the github workflow of a pull request and
114- sending and receiving patches over email.
115- </p>
116-
117- <p>
118- The basic idea is to leverage an SSH app to handle most of the interaction
119- between contributor and owner of a project. Everything can be done completely
120- within the terminal, in a way that is ergonomic and fully featured.
121- </p>
122-
123- <p>
124- Notifications would happen with RSS and all state mutations would result in the
125- generation of static web assets so it can all be hosted using a simple file web
126- server.
127- </p>
128-
129- <h3 class="text-lg">format-patch workflow</h3>
130-
131- <p>
132- The fundamental collaboration tool here is <code>format-patch</code>. Whether you a
133- submitting code changes or you are reviewing code changes, it all happens in
134- code. Both contributor and owner are simply creating new commits and generating
135- patches on top of each other. This obviates the need to have a web viewer where
136- the reviewing can "comment" on a line of code block. There's no need, apply the
137- contributor's patches, write comments or code changes, generate a new patch,
138- send the patch to the git server as a "review." This flow also works the exact
139- same if two users are collaborating on a set of changes.
140- </p>
141-
142- <p>
143- This also solves the problem of sending multiple patchsets for the same code
144- change. There's a single, central Patch Request where all changes and
145- collaboration happens.
146- </p>
147-
148- <p>
149- We could figure out a way to leverage <code>git notes</code> for reviews / comments, but
150- honestly, that solution feels brutal and outside the comfort level of most git
151- users. Just send reviews as code and write comments in the programming language
152- you are using. It's the job of the contributor to "address" those comments and
153- then remove them in subsequent patches. This is the forcing function to address
154- all comments: the patch won't be merged if there are comment unaddressed in
155- code; they cannot be ignored or else they will be upstreamed erroneously.
156- </p>
157- </div>
158- </details>
159-
160- <details>
161- <summary>How do Patch Requests work?</summary>
162- <div>
163- Patch requests (PR) are the simplest way to submit, review, and accept changes to your git repository.
164- Here's how it works:
165- </div>
166-
167- <ol>
168- <li>External contributor clones repo (<code>git-clone</code>)</li>
169- <li>External contributor makes a code change (<code>git-add</code> & <code>git-commit</code>)</li>
170- <li>External contributor generates patches (<code>git-format-patch</code>)</li>
171- <li>External contributor submits a PR to SSH server</li>
172- <li>Owner receives RSS notification that there's a new PR</li>
173- <li>Owner applies patches locally (<code>git-am</code>) from SSH server</li>
174- <li>Owner makes suggestions in code! (<code>git-add</code> & <code>git-commit</code>)</li>
175- <li>Owner submits review by piping patch to SSH server (<code>git-format-patch</code>)</li>
176- <li>External contributor receives RSS notification of the PR review</li>
177- <li>External contributor re-applies patches (<code>git-am</code>)</li>
178- <li>External contributor reviews and removes comments in code!</li>
179- <li>External contributor submits another patch (<code>git-format-patch</code>)</li>
180- <li>Owner applies patches locally (<code>git-am</code>)</li>
181- <li>Owner marks PR as accepted and pushes code to main (<code>git-push</code>)</li>
182- </ol>
183-
184- <div>Example commands</div>
185-
186- <pre># Owner hosts repo `test.git` using github
187-
188-# Contributor clones repo
189-git clone git@github.com:picosh/test.git
190-
191-# Contributor wants to make a change
192-# Contributor makes changes via commits
193-git add -A && git commit -m "fix: some bugs"
194-
195-# Contributor runs:
196-git format-patch origin/main --stdout | ssh {{.MetaData.URL}} pr create test
197-# > Patch Request has been created (ID: 1)
198-
199-# Owner can checkout patch:
200-ssh {{.MetaData.URL}} pr print 1 | git am -3
201-
202-# Owner can comment (IN CODE), commit, then send another format-patch
203-# on top of the PR:
204-git format-patch origin/main --stdout | ssh {{.MetaData.URL}} pr add --review 1
205-# UI clearly marks patch as a review
206-
207-# Contributor can checkout reviews
208-ssh {{.MetaData.URL}} print pr-1 | git am -3
209-
210-# Owner can reject a pr:
211-ssh {{.MetaData.URL}} pr close 1
212-
213-# Owner can accept a pr:
214-ssh {{.MetaData.URL}} pr accept 1
215-
216-# Owner can prep PR for upstream:
217-git rebase -i origin/main
218-
219-# Then push to upstream
220-git push origin main
221-
222-# Done!
223-</pre>
224- </details>
225-
226- <details>
227- <summary>First time user?</summary>
228-
229- <div>
230- Using this service for the first time? Creating a patch request is simple:
231- </div>
232-
233- <pre>git format-patch main --stdout | ssh {{.MetaData.URL}} pr create {repo}</pre>
234-
235- <div>When running that command we will automatically create a user and a repo if one doesn't exist.</div>
236-
237- <div>Want to submit a v2 of the patch request?</div>
238-
239- <pre>git format-patch main --stdout | ssh {{.MetaData.URL}} pr add {prID}</pre>
240- </details>
24122 </header>
24223
24324 <main>
......@@ -255,4 +36,4 @@ git push origin main
25536 <footer class="mt">
25637 <a href="/rss">rss</a>
25738 </footer>
258-{{end}}
39+{{end}}
+16 -0 web.go #
......@@ -339,6 +339,21 @@ func indexHandler(w http.ResponseWriter, r *http.Request) {
339339 }
340340 }
341341
342+func docsHandler(w http.ResponseWriter, r *http.Request) {
343+ web, err := getWebCtx(r)
344+ if err != nil {
345+ w.WriteHeader(http.StatusInternalServerError)
346+ return
347+ }
348+
349+ w.Header().Set("content-type", "text/html")
350+ tmpl := getTemplate("docs.html")
351+ err = tmpl.ExecuteTemplate(w, "docs.html", nil)
352+ if err != nil {
353+ web.Backend.Logger.Error("cannot execute template", "err", err)
354+ }
355+}
356+
342357 type UserData struct {
343358 UserID int64
344359 Name string
......@@ -1130,6 +1145,7 @@ func GitWebServer(cfg *GitCfg) http.Handler {
11301145 mux.HandleFunc("GET /r/{user}", ctxMdw(ctx, userDetailHandler))
11311146 mux.HandleFunc("GET /rss/{user}", ctxMdw(ctx, rssHandler))
11321147 mux.HandleFunc("GET /rss", ctxMdw(ctx, rssHandler))
1148+ mux.HandleFunc("GET /docs", ctxMdw(ctx, docsHandler))
11331149 mux.HandleFunc("GET /", ctxMdw(ctx, indexHandler))
11341150 mux.HandleFunc("GET /syntax.css", ctxMdw(ctx, chromaStyleHandler))
11351151 embedFS, err := getEmbedFS(embedStaticFS, "static")
Back to top