- commit
- 3770873
- parent
- 0c5b23a
- author
- Eric Bower
- date
- 2025-03-13 12:16:06 -0400 EDT
refactor: add docs page
4 files changed,
+374,
-263
M
cli.go
+89,
-63
1@@ -1,6 +1,7 @@
2 package git
3
4 import (
5+ "errors"
6 "fmt"
7 "io"
8 "strconv"
9@@ -627,101 +628,126 @@ Here's how it works:
10 Name: "accept",
11 Usage: "Accept a PR",
12 Args: true,
13- ArgsUsage: "[prID]",
14+ ArgsUsage: "[prID], [prID]...",
15 Action: func(cCtx *cli.Context) error {
16 args := cCtx.Args()
17 if !args.Present() {
18- return fmt.Errorf("must provide a patch request ID")
19+ return fmt.Errorf("must provide at least one patch request ID")
20 }
21
22- prID, err := strToInt(args.First())
23- if err != nil {
24- return err
25- }
26+ prIDs := args.Tail()
27+ prIDs = append(prIDs, args.First())
28
29- prq, err := pr.GetPatchRequestByID(prID)
30- if err != nil {
31- return err
32- }
33+ var errs error
34+ for _, prIDStr := range prIDs {
35+ prID, err := strToInt(prIDStr)
36+ if err != nil {
37+ wish.Errorln(sesh, err)
38+ continue
39+ }
40
41- user, err := pr.UpsertUser(pubkey, userName)
42- if err != nil {
43- return err
44- }
45+ prq, err := pr.GetPatchRequestByID(prID)
46+ if err != nil {
47+ return err
48+ }
49
50- repo, err := pr.GetRepoByID(prq.RepoID)
51- if err != nil {
52- return err
53- }
54+ user, err := pr.UpsertUser(pubkey, userName)
55+ if err != nil {
56+ return err
57+ }
58
59- acl := be.GetPatchRequestAcl(repo, prq, user)
60- if !acl.CanReview {
61- return fmt.Errorf("you are not authorized to accept a PR")
62- }
63+ repo, err := pr.GetRepoByID(prq.RepoID)
64+ if err != nil {
65+ return err
66+ }
67
68- if prq.Status == "accepted" {
69- return fmt.Errorf("PR has already been accepted")
70- }
71+ acl := be.GetPatchRequestAcl(repo, prq, user)
72+ if !acl.CanReview {
73+ return fmt.Errorf("you are not authorized to accept a PR")
74+ }
75
76- err = pr.UpdatePatchRequestStatus(prID, user.ID, "accepted")
77- if err != nil {
78- return err
79+ if prq.Status == "accepted" {
80+ return fmt.Errorf("PR has already been accepted")
81+ }
82+
83+ err = pr.UpdatePatchRequestStatus(prID, user.ID, "accepted")
84+ if err != nil {
85+ return err
86+ }
87+ wish.Printf(sesh, "Accepted PR %s (#%d)\n", prq.Name, prq.ID)
88+ err = prSummary(be, pr, sesh, prID)
89+ if err != nil {
90+ errs = errors.Join(errs, err)
91+ }
92+ wish.Printf(sesh, "\n\n")
93 }
94- wish.Printf(sesh, "Accepted PR %s (#%d)\n", prq.Name, prq.ID)
95- return prSummary(be, pr, sesh, prID)
96+
97+ return errs
98 },
99 },
100 {
101 Name: "close",
102 Usage: "Close a PR",
103 Args: true,
104- ArgsUsage: "[prID]",
105+ ArgsUsage: "[prID], [prID]...",
106 Action: func(cCtx *cli.Context) error {
107 args := cCtx.Args()
108 if !args.Present() {
109 return fmt.Errorf("must provide a patch request ID")
110 }
111
112- prID, err := strToInt(args.First())
113- if err != nil {
114- return err
115- }
116+ prIDs := args.Tail()
117+ prIDs = append(prIDs, args.First())
118
119- prq, err := pr.GetPatchRequestByID(prID)
120- if err != nil {
121- return err
122- }
123+ var errs error
124+ for _, prIDStr := range prIDs {
125+ prID, err := strToInt(prIDStr)
126+ if err != nil {
127+ wish.Errorln(sesh, err)
128+ continue
129+ }
130
131- patchUser, err := pr.GetUserByID(prq.UserID)
132- if err != nil {
133- return err
134- }
135+ prq, err := pr.GetPatchRequestByID(prID)
136+ if err != nil {
137+ return err
138+ }
139
140- repo, err := pr.GetRepoByID(prq.RepoID)
141- if err != nil {
142- return err
143- }
144+ patchUser, err := pr.GetUserByID(prq.UserID)
145+ if err != nil {
146+ return err
147+ }
148
149- acl := be.GetPatchRequestAcl(repo, prq, patchUser)
150- if !acl.CanModify {
151- return fmt.Errorf("you are not authorized to change PR status")
152- }
153+ repo, err := pr.GetRepoByID(prq.RepoID)
154+ if err != nil {
155+ return err
156+ }
157
158- if prq.Status == "closed" {
159- return fmt.Errorf("PR has already been closed")
160- }
161+ acl := be.GetPatchRequestAcl(repo, prq, patchUser)
162+ if !acl.CanModify {
163+ return fmt.Errorf("you are not authorized to change PR status")
164+ }
165
166- user, err := pr.UpsertUser(pubkey, userName)
167- if err != nil {
168- return err
169- }
170+ if prq.Status == "closed" {
171+ return fmt.Errorf("PR has already been closed")
172+ }
173
174- err = pr.UpdatePatchRequestStatus(prID, user.ID, "closed")
175- if err != nil {
176- return err
177+ user, err := pr.UpsertUser(pubkey, userName)
178+ if err != nil {
179+ return err
180+ }
181+
182+ err = pr.UpdatePatchRequestStatus(prID, user.ID, "closed")
183+ if err != nil {
184+ return err
185+ }
186+ wish.Printf(sesh, "Closed PR %s (#%d)\n", prq.Name, prq.ID)
187+ err = prSummary(be, pr, sesh, prID)
188+ if err != nil {
189+ errs = errors.Join(errs, err)
190+ }
191+ wish.Printf(sesh, "\n\n")
192 }
193- wish.Printf(sesh, "Closed PR %s (#%d)\n", prq.Name, prq.ID)
194- return prSummary(be, pr, sesh, prID)
195+ return errs
196 },
197 },
198 {
+258,
-0
1@@ -0,0 +1,258 @@
2+{{template "base" .}}
3+
4+{{define "title"}}git-pr{{end}}
5+
6+{{define "meta"}}
7+<link rel="alternate" type="application/atom+xml"
8+ title="RSS feed for git collaboration server"
9+ href="/rss" />
10+{{end}}
11+
12+{{define "body"}}
13+<header class="group">
14+ <h1 class="text-2xl"><a href="/">DASHBOARD</a> / docs</h1>
15+ <div>
16+ <span>A pastebin supercharged for git collaboration</span> ·
17+ <a href="https://github.com/picosh/git-pr">github</a> ·
18+ <a href="https://youtu.be/d28Dih-BBUw">demo video</a>
19+ </div>
20+ <pre class="m-0">ssh {{.MetaData.URL}} help</pre>
21+</header>
22+
23+<main class="group">
24+ <details>
25+ <summary>Intro</summary>
26+
27+ <div>
28+ <p>
29+ We are trying to build the simplest git collaboration tool. The goal is to make
30+ self-hosting as simple as running an SSH server -- all without
31+ sacrificing external collaborators time and energy.
32+ </p>
33+
34+ <blockquote>
35+ <code>git format-patch</code> isn't the problem and pull requests aren't the solution.
36+ </blockquote>
37+
38+ <p>
39+ We are combining mailing list and pull request workflows. In order to build the
40+ simplest collaboration tool, we needed something as simple as generating patches
41+ but the ease-of-use of pull requests.
42+ </p>
43+
44+ <p>
45+ The goal is not to create another code forge here. The goal is to create a very
46+ simple self-hosted git solution with the ability to collaborate with external
47+ contributors. All the code owner needs to setup a running git server:
48+ </p>
49+
50+ <ul><li>A single golang binary</li></ul>
51+
52+ <div>
53+ All an external contributor needs is:
54+ </div>
55+
56+ <ul>
57+ <li>An SSH keypair</li>
58+ <li>An SSH client</li>
59+ </ul>
60+
61+ <h2 class="text-xl">the problem</h2>
62+
63+ <p>
64+ Email is great as a decentralized system to send and receive changes (patchsets)
65+ to a git repo. However, onboarding a new user to a mailing list, properly
66+ setting up their email client, and then finally submitting the code contribution
67+ is enough to make many developers give up. Further, because we are leveraging
68+ the email protocol for collaboration, we are limited by its feature-set. For
69+ example, it is not possible to make edits to emails, everyone has a different
70+ client, those clients have different limitations around plain text email and
71+ downloading patches from it.
72+ </p>
73+
74+ <p>
75+ Github pull requests are easy to use, easy to edit, and easy to manage. The
76+ downside is it forces the user to be inside their website to perform reviews.
77+ For quick changes, this is great, but when you start reading code within a web
78+ browser, there are quite a few downsides. At a certain point, it makes more
79+ sense to review code inside your local development environment, IDE, etc. There
80+ are tools and plugins that allow users to review PRs inside their IDE, but it
81+ requires a herculean effort to make it usable.
82+ </p>
83+
84+ <p>
85+ Further, self-hosted solutions that mimic a pull request require a lot of
86+ infrastructure in order to manage it. A database, a web site connected to git,
87+ admin management, and services to manage it all. Another big point of friction:
88+ before an external user submits a code change, they first need to create an
89+ account and then login. This adds quite a bit of friction for a self-hosted
90+ solution, not only for an external contributor, but also for the code owner who
91+ has to provision the infra. Often times they also have to fork the repo within
92+ the code forge before submitting a PR. Then they never make a contribution ever
93+ again and keep a forked repo around forever. That seems silly.
94+ </p>
95+
96+ <h2 class="text-xl">introducing patch requests (PR)</h2>
97+
98+ <p>
99+ Instead, we want to create a self-hosted git "server" that can handle sending
100+ and receiving patches without the cumbersome nature of setting up email or the
101+ limitations imposed by the email protocol. Further, we want the primary workflow
102+ to surround the local development environment. Github is bringing the IDE to the
103+ browser in order to support their workflow, we want to flip that idea on its
104+ head by making code reviews a first-class citizen inside your local development
105+ environment.
106+ </p>
107+
108+ <p>
109+ We see this as a hybrid between the github workflow of a pull request and
110+ sending and receiving patches over email.
111+ </p>
112+
113+ <p>
114+ The basic idea is to leverage an SSH app to handle most of the interaction
115+ between contributor and owner of a project. Everything can be done completely
116+ within the terminal, in a way that is ergonomic and fully featured.
117+ </p>
118+
119+ <p>
120+ Notifications would happen with RSS and all state mutations would result in the
121+ generation of static web assets so it can all be hosted using a simple file web
122+ server.
123+ </p>
124+
125+ <h3 class="text-lg">format-patch workflow</h3>
126+
127+ <p>
128+ The fundamental collaboration tool here is <code>format-patch</code>. Whether you a
129+ submitting code changes or you are reviewing code changes, it all happens in
130+ code. Both contributor and owner are simply creating new commits and generating
131+ patches on top of each other. This obviates the need to have a web viewer where
132+ the reviewing can "comment" on a line of code block. There's no need, apply the
133+ contributor's patches, write comments or code changes, generate a new patch,
134+ send the patch to the git server as a "review." This flow also works the exact
135+ same if two users are collaborating on a set of changes.
136+ </p>
137+
138+ <p>
139+ This also solves the problem of sending multiple patchsets for the same code
140+ change. There's a single, central Patch Request where all changes and
141+ collaboration happens.
142+ </p>
143+
144+ <p>
145+ We could figure out a way to leverage <code>git notes</code> for reviews / comments, but
146+ honestly, that solution feels brutal and outside the comfort level of most git
147+ users. Just send reviews as code and write comments in the programming language
148+ you are using. It's the job of the contributor to "address" those comments and
149+ then remove them in subsequent patches. This is the forcing function to address
150+ all comments: the patch won't be merged if there are comment unaddressed in
151+ code; they cannot be ignored or else they will be upstreamed erroneously.
152+ </p>
153+ </div>
154+ </details>
155+
156+ <details>
157+ <summary>How do Patch Requests work?</summary>
158+ <div>
159+ Patch requests (PR) are the simplest way to submit, review, and accept changes to your git repository.
160+ Here's how it works:
161+ </div>
162+
163+ <ol>
164+ <li>External contributor clones repo (<code>git-clone</code>)</li>
165+ <li>External contributor makes a code change (<code>git-add</code> & <code>git-commit</code>)</li>
166+ <li>External contributor generates patches (<code>git-format-patch</code>)</li>
167+ <li>External contributor submits a PR to SSH server</li>
168+ <li>Owner receives RSS notification that there's a new PR</li>
169+ <li>Owner applies patches locally (<code>git-am</code>) from SSH server</li>
170+ <li>Owner makes suggestions in code! (<code>git-add</code> & <code>git-commit</code>)</li>
171+ <li>Owner submits review by piping patch to SSH server (<code>git-format-patch</code>)</li>
172+ <li>External contributor receives RSS notification of the PR review</li>
173+ <li>External contributor re-applies patches (<code>git-am</code>)</li>
174+ <li>External contributor reviews and removes comments in code!</li>
175+ <li>External contributor submits another patch (<code>git-format-patch</code>)</li>
176+ <li>Owner applies patches locally (<code>git-am</code>)</li>
177+ <li>Owner marks PR as accepted and pushes code to main (<code>git-push</code>)</li>
178+ </ol>
179+
180+ <div>Example commands</div>
181+
182+ <pre># Owner hosts repo `test.git` using github
183+
184+# Contributor clones repo
185+git clone git@github.com:picosh/test.git
186+
187+# Contributor wants to make a change
188+# Contributor makes changes via commits
189+git add -A && git commit -m "fix: some bugs"
190+
191+# Contributor runs:
192+git format-patch origin/main --stdout | ssh {{.MetaData.URL}} pr create test
193+# > Patch Request has been created (ID: 1)
194+
195+# Owner can checkout patch:
196+ssh {{.MetaData.URL}} pr print 1 | git am -3
197+# Owner can comment (IN CODE), commit, then send another format-patch
198+# on top of the PR:
199+git format-patch origin/main --stdout | ssh {{.MetaData.URL}} pr add --review 1
200+# UI clearly marks patch as a review
201+
202+# Contributor can checkout reviews
203+ssh {{.MetaData.URL}} pr print 1 | git am -3
204+
205+# Owner can reject a pr:
206+ssh {{.MetaData.URL}} pr close 1
207+
208+# Owner can accept a pr:
209+ssh {{.MetaData.URL}} pr accept 1
210+
211+# Owner can prep PR for upstream:
212+git rebase -i origin/main
213+
214+# Then push to upstream
215+git push origin main
216+
217+# Done!
218+</pre>
219+ </details>
220+
221+ <details>
222+ <summary>What's a repo?</summary>
223+
224+ <div>
225+ A repo is designed to mimick a git repo, but it's really just a tag. When
226+ submitting a patch request, if the user does not provide a repo name then
227+ the default "bin" will be selected. When a user creates a repo they become
228+ the repo owner and have special privileges.
229+ </div>
230+ </details>
231+
232+ <details>
233+ <summary>Can anyone use this service?</summary>
234+
235+ <div>
236+ This service is a public space for anyone to freely create "repos" and
237+ collaborate with users. Anyone is able to add patchsets to a patch request
238+ and anyone is able to review any other patch requests, regardless of repo.
239+ </div>
240+ </details>
241+
242+ <details>
243+ <summary>First time user experience</summary>
244+
245+ <div>
246+ Using this service for the first time? Creating a patch request is simple:
247+ </div>
248+
249+ <pre>git format-patch main --stdout | ssh pr.pico.sh pr create {repo}</pre>
250+
251+ <div>When running that command we will automatically create a user and a repo if one doesn't exist.</div>
252+
253+ <div>Want to submit a v2 of the patch request?</div>
254+
255+ <pre>git format-patch main --stdout | ssh pr.pico.sh pr add {prID}</pre>
256+ </details>
257+</main>
258+
259+{{end}}
+3,
-200
1@@ -10,209 +10,12 @@
2
3 {{define "body"}}
4 <header class="group">
5- <h1 class="text-2xl">git-pr</h1>
6+ <h1 class="text-2xl">patchbin</h1>
7 <div>
8- <span>A new git collaboration service</span> ·
9- <a href="https://github.com/picosh/git-pr">github</a> ·
10- <a href="https://youtu.be/d28Dih-BBUw">demo video</a>
11+ <span>A pastebin supercharged for git collaboration</span> ·
12+ <a href="/docs">docs</a>
13 </div>
14 <pre class="m-0">ssh {{.MetaData.URL}} help</pre>
15- <details>
16- <summary>Intro</summary>
17-
18- <div>
19- <p>
20- We are trying to build the simplest git collaboration tool. The goal is to make
21- self-hosting a git server as simple as running an SSH server -- all without
22- sacrificing external collaborators time and energy.
23- </p>
24-
25- <blockquote>
26- <code>git format-patch</code> isn't the problem and pull requests aren't the solution.
27- </blockquote>
28-
29- <p>
30- We are combining mailing list and pull request workflows. In order to build the
31- simplest collaboration tool, we needed something as simple as generating patches
32- but the ease-of-use of pull requests.
33- </p>
34-
35- <p>
36- The goal is not to create another code forge here. The goal is to create a very
37- simple self-hosted git solution with the ability to collaborate with external
38- contributors. All the code owner needs to setup a running git server:
39- </p>
40-
41- <ul><li>A single golang binary</li></ul>
42-
43- <div>
44- All an external contributor needs is:
45- </div>
46-
47- <ul>
48- <li>An SSH keypair</li>
49- <li>An SSH client</li>
50- </ul>
51-
52- <h2 class="text-xl">the problem</h2>
53-
54- <p>
55- Email is great as a decentralized system to send and receive changes (patchsets)
56- to a git repo. However, onboarding a new user to a mailing list, properly
57- setting up their email client, and then finally submitting the code contribution
58- is enough to make many developers give up. Further, because we are leveraging
59- the email protocol for collaboration, we are limited by its feature-set. For
60- example, it is not possible to make edits to emails, everyone has a different
61- client, those clients have different limitations around plain text email and
62- downloading patches from it.
63- </p>
64-
65- <p>
66- Github pull requests are easy to use, easy to edit, and easy to manage. The
67- downside is it forces the user to be inside their website to perform reviews.
68- For quick changes, this is great, but when you start reading code within a web
69- browser, there are quite a few downsides. At a certain point, it makes more
70- sense to review code inside your local development environment, IDE, etc. There
71- are tools and plugins that allow users to review PRs inside their IDE, but it
72- requires a herculean effort to make it usable.
73- </p>
74-
75- <p>
76- Further, self-hosted solutions that mimic a pull request require a lot of
77- infrastructure in order to manage it. A database, a web site connected to git,
78- admin management, and services to manage it all. Another big point of friction:
79- before an external user submits a code change, they first need to create an
80- account and then login. This adds quite a bit of friction for a self-hosted
81- solution, not only for an external contributor, but also for the code owner who
82- has to provision the infra. Often times they also have to fork the repo within
83- the code forge before submitting a PR. Then they never make a contribution ever
84- again and keep a forked repo around forever. That seems silly.
85- </p>
86-
87- <h2 class="text-xl">introducing patch requests (PR)</h2>
88-
89- <p>
90- Instead, we want to create a self-hosted git "server" that can handle sending
91- and receiving patches without the cumbersome nature of setting up email or the
92- limitations imposed by the email protocol. Further, we want the primary workflow
93- to surround the local development environment. Github is bringing the IDE to the
94- browser in order to support their workflow, we want to flip that idea on its
95- head by making code reviews a first-class citizen inside your local development
96- environment.
97- </p>
98-
99- <p>
100- We see this as a hybrid between the github workflow of a pull request and
101- sending and receiving patches over email.
102- </p>
103-
104- <p>
105- The basic idea is to leverage an SSH app to handle most of the interaction
106- between contributor and owner of a project. Everything can be done completely
107- within the terminal, in a way that is ergonomic and fully featured.
108- </p>
109-
110- <p>
111- Notifications would happen with RSS and all state mutations would result in the
112- generation of static web assets so it can all be hosted using a simple file web
113- server.
114- </p>
115-
116- <h3 class="text-lg">format-patch workflow</h3>
117-
118- <p>
119- The fundamental collaboration tool here is <code>format-patch</code>. Whether you a
120- submitting code changes or you are reviewing code changes, it all happens in
121- code. Both contributor and owner are simply creating new commits and generating
122- patches on top of each other. This obviates the need to have a web viewer where
123- the reviewing can "comment" on a line of code block. There's no need, apply the
124- contributor's patches, write comments or code changes, generate a new patch,
125- send the patch to the git server as a "review." This flow also works the exact
126- same if two users are collaborating on a set of changes.
127- </p>
128-
129- <p>
130- This also solves the problem of sending multiple patchsets for the same code
131- change. There's a single, central Patch Request where all changes and
132- collaboration happens.
133- </p>
134-
135- <p>
136- We could figure out a way to leverage <code>git notes</code> for reviews / comments, but
137- honestly, that solution feels brutal and outside the comfort level of most git
138- users. Just send reviews as code and write comments in the programming language
139- you are using. It's the job of the contributor to "address" those comments and
140- then remove them in subsequent patches. This is the forcing function to address
141- all comments: the patch won't be merged if there are comment unaddressed in
142- code; they cannot be ignored or else they will be upstreamed erroneously.
143- </p>
144- </div>
145- </details>
146-
147- <details>
148- <summary>How do Patch Requests work?</summary>
149- <div>
150- Patch requests (PR) are the simplest way to submit, review, and accept changes to your git repository.
151- Here's how it works:
152- </div>
153-
154- <ol>
155- <li>External contributor clones repo (<code>git-clone</code>)</li>
156- <li>External contributor makes a code change (<code>git-add</code> & <code>git-commit</code>)</li>
157- <li>External contributor generates patches (<code>git-format-patch</code>)</li>
158- <li>External contributor submits a PR to SSH server</li>
159- <li>Owner receives RSS notification that there's a new PR</li>
160- <li>Owner applies patches locally (<code>git-am</code>) from SSH server</li>
161- <li>Owner makes suggestions in code! (<code>git-add</code> & <code>git-commit</code>)</li>
162- <li>Owner submits review by piping patch to SSH server (<code>git-format-patch</code>)</li>
163- <li>External contributor receives RSS notification of the PR review</li>
164- <li>External contributor re-applies patches (<code>git-am</code>)</li>
165- <li>External contributor reviews and removes comments in code!</li>
166- <li>External contributor submits another patch (<code>git-format-patch</code>)</li>
167- <li>Owner applies patches locally (<code>git-am</code>)</li>
168- <li>Owner marks PR as accepted and pushes code to main (<code>git-push</code>)</li>
169- </ol>
170-
171- <div>Example commands</div>
172-
173- <pre># Owner hosts repo `test.git` using github
174-
175-# Contributor clones repo
176-git clone git@github.com:picosh/test.git
177-
178-# Contributor wants to make a change
179-# Contributor makes changes via commits
180-git add -A && git commit -m "fix: some bugs"
181-
182-# Contributor runs:
183-git format-patch origin/main --stdout | ssh {{.MetaData.URL}} pr create test
184-# > Patch Request has been created (ID: 1)
185-
186-# Owner can checkout patch:
187-ssh {{.MetaData.URL}} pr print 1 | git am -3
188-# Owner can comment (IN CODE), commit, then send another format-patch
189-# on top of the PR:
190-git format-patch origin/main --stdout | ssh {{.MetaData.URL}} pr add --review 1
191-# UI clearly marks patch as a review
192-
193-# Contributor can checkout reviews
194-ssh {{.MetaData.URL}} pr print 1 | git am -3
195-
196-# Owner can reject a pr:
197-ssh {{.MetaData.URL}} pr close 1
198-
199-# Owner can accept a pr:
200-ssh {{.MetaData.URL}} pr accept 1
201-
202-# Owner can prep PR for upstream:
203-git rebase -i origin/main
204-
205-# Then push to upstream
206-git push origin main
207-
208-# Done!
209-</pre>
210- </details>
211 </header>
212
213 <main>
M
web.go
+24,
-0
1@@ -109,6 +109,10 @@ type LinkData struct {
2 Text string
3 }
4
5+type BasicData struct {
6+ MetaData
7+}
8+
9 type PrTableData struct {
10 Prs []*PrListData
11 NumOpen int
12@@ -268,6 +272,25 @@ func getPrTableData(web *WebCtx, prs []*PatchRequest, query url.Values) ([]*PrLi
13 return prdata, nil
14 }
15
16+func docsHandler(w http.ResponseWriter, r *http.Request) {
17+ web, err := getWebCtx(r)
18+ if err != nil {
19+ w.WriteHeader(http.StatusInternalServerError)
20+ return
21+ }
22+
23+ w.Header().Set("content-type", "text/html")
24+ tmpl := getTemplate("docs.html")
25+ err = tmpl.ExecuteTemplate(w, "docs.html", BasicData{
26+ MetaData: MetaData{
27+ URL: web.Backend.Cfg.Url,
28+ },
29+ })
30+ if err != nil {
31+ web.Backend.Logger.Error("cannot execute template", "err", err)
32+ }
33+}
34+
35 func indexHandler(w http.ResponseWriter, r *http.Request) {
36 web, err := getWebCtx(r)
37 if err != nil {
38@@ -1084,6 +1107,7 @@ func StartWebServer(cfg *GitCfg) {
39 http.HandleFunc("GET /r/{user}", ctxMdw(ctx, userDetailHandler))
40 http.HandleFunc("GET /rss/{user}", ctxMdw(ctx, rssHandler))
41 http.HandleFunc("GET /rss", ctxMdw(ctx, rssHandler))
42+ http.HandleFunc("GET /docs", ctxMdw(ctx, docsHandler))
43 http.HandleFunc("GET /", ctxMdw(ctx, indexHandler))
44 http.HandleFunc("GET /syntax.css", ctxMdw(ctx, chromaStyleHandler))
45 embedFS, err := getEmbedFS(embedStaticFS, "static")