Home

ugit @main - refs - log -
-
https://git.jolheiser.com/ugit.git
The code powering this h*ckin' site
tree log patch
feat: commit page Signed-off-by: jolheiser <john.olheiser@gmail.com>
Signature
-----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEgqEQpE3xoo1QwJO/uFOtpdp7v3oFAmWnS3UACgkQuFOtpdp7 v3ojzg//ZxX8po5dXI00mAFxogFjS5NwJCn53acCV4QORv8eWw57/sIdsFGvjOsh 8JX2rvCOI4O8x9yAoEzayAkT8wSG/VmIchjiBpDfA27KtMmFbsGzvhGABNrZkSoq 56c610GnxY+5D5N344xpxgOmqwiTxVarUieq3NR0zUcrgRgBfK/7Idc4nGXcsTTJ 3HRbDEcMC+cxj7Ma2AIGeIVn9InT2I0hn2dR3F+EAqaBhR4BgVTPPFOZsKaEz/As wsFMoVVIwBHwyGJ0K1jZKFl9Z8XT33fBGeJbIPv2IuikAzyXqZFFxTBXJCmkyEW+ 1LN7jXDe1UwdOHYaY81l2UrQlZ1y5FayykUD5Zh6Aw4h9BTBBNPIUpFASloeRikv 0QrFxUEvnaGQ21bgL9ty3mzytPbHit+3Vqy4rVT/kD6AZaVaV83tH0IK5DfHqTjW L1+tvS9YOvY3eobVEntIuR70EfBoyK6ooRKqF8wPDCYAcUUkYvtYs4r43eEec3zK QdRov3YVHgHidM0ggkYAMOjbb2ecEKsPjF6yJ7i1vYaWmC/Sx9HZ2LgnNDCmvZJz u/Bl41cVEJW0oymrwG4YJevrWMaM+0/F7yooG6JTybkX0esdFuL7nPe0WKTZA0vC nK1jHPQ/pHfFFdoR2ymS32BQWp4yF0KCP/mTztWOYCxZN5CiwGA= =Qm9e -----END PGP SIGNATURE-----
jolheiser <john.olheiser@gmail.com>
1 year ago
12 changed files, 645 additions(+), 46 deletions(-)
internal/git/repo.gointernal/html/chroma.gointernal/html/generate.cssinternal/html/repo.templinternal/html/repo_commit.templinternal/html/repo_commit_templ.gointernal/html/repo_log.templinternal/html/repo_log_templ.gointernal/html/repo_templ.gointernal/html/tailwind.gointernal/http/http.gointernal/http/repo.go
M internal/git/repo.gointernal/git/repo.go
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
diff --git a/internal/git/repo.go b/internal/git/repo.go
index ace821a91a86430d28803d25de14841770b7317f..67af17c205c3d6e21f9eae4517d17fa0bb3bb1cb 100644
--- a/internal/git/repo.go
+++ b/internal/git/repo.go
@@ -1,6 +1,7 @@
 package git
 
 import (
+	"bytes"
 	"encoding/json"
 	"errors"
 	"os"
@@ -97,6 +98,31 @@ 	Signature string
 	Author    string
 	Email     string
 	When      time.Time
+	// Extra
+	Stats CommitStats
+	Patch string
+	Files []CommitFile
+}
+
+// CommitStats is the stats of a commit
+type CommitStats struct {
+	Changed   int
+	Additions int
+	Deletions int
+}
+
+// CommitFile is a file contained in a commit
+type CommitFile struct {
+	From   CommitFileEntry
+	To     CommitFileEntry
+	Action string
+	Patch  string
+}
+
+// CommitFileEntry is a from/to in a file commit
+type CommitFileEntry struct {
+	Path   string
+	Commit string
 }
 
 func (c Commit) Short() string {
@@ -118,7 +144,7 @@ 	if err != nil {
 		return Commit{}, err
 	}
 
-	return commit(repo, sha)
+	return commit(repo, sha, true)
 }
 
 // LastCommit returns the last commit of the repo
@@ -133,15 +159,88 @@ 	if err != nil {
 		return Commit{}, err
 	}
 
-	return commit(repo, head.Hash().String())
+	return commit(repo, head.Hash().String(), false)
 }
 
-func commit(repo *git.Repository, sha string) (Commit, error) {
+func commit(repo *git.Repository, sha string, extra bool) (Commit, error) {
 	obj, err := repo.CommitObject(plumbing.NewHash(sha))
 	if err != nil {
 		return Commit{}, err
 	}
 
+	var c, a, d int
+	var p string
+	var f []CommitFile
+	if extra {
+		stats, err := obj.Stats()
+		if err != nil {
+			return Commit{}, err
+		}
+
+		c = len(stats)
+		for _, stat := range stats {
+			a += stat.Addition
+			d += stat.Deletion
+		}
+
+		parent, err := obj.Parent(0)
+		if err != nil {
+			return Commit{}, err
+		}
+
+		patch, err := obj.Patch(parent)
+		if err != nil {
+			return Commit{}, err
+		}
+
+		var buf bytes.Buffer
+		if err := patch.Encode(&buf); err != nil {
+			return Commit{}, err
+		}
+		p = buf.String()
+
+		objTree, err := obj.Tree()
+		if err != nil {
+			return Commit{}, err
+		}
+		parentTree, err := parent.Tree()
+		if err != nil {
+			return Commit{}, err
+		}
+
+		changes, err := parentTree.Diff(objTree)
+		if err != nil {
+			return Commit{}, err
+		}
+
+		for _, change := range changes {
+			action, err := change.Action()
+			if err != nil {
+				return Commit{}, err
+			}
+			patch, err := change.Patch()
+			if err != nil {
+				return Commit{}, err
+			}
+			var buf bytes.Buffer
+			if err := patch.Encode(&buf); err != nil {
+				return Commit{}, err
+			}
+			f = append(f, CommitFile{
+				From: CommitFileEntry{
+					Path:   change.From.Name,
+					Commit: parent.Hash.String(),
+				},
+				To: CommitFileEntry{
+					Path:   change.To.Name,
+					Commit: obj.Hash.String(),
+				},
+				Action: action.String(),
+				Patch:  buf.String(),
+			})
+		}
+	}
+
 	return Commit{
 		SHA:       obj.Hash.String(),
 		Message:   obj.Message,
@@ -149,6 +248,13 @@ 		Signature: obj.PGPSignature,
 		Author:    obj.Author.Name,
 		Email:     obj.Author.Email,
 		When:      obj.Author.When,
+		Stats: CommitStats{
+			Changed:   c,
+			Additions: a,
+			Deletions: d,
+		},
+		Patch: p,
+		Files: f,
 	}, nil
 }
 
M internal/html/chroma.gointernal/html/chroma.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
diff --git a/internal/html/chroma.go b/internal/html/chroma.go
index 1ea0601d93977cad0f9414c5d3794465b620f191..a551028728d886bf961df2bd069bb71c6f593ced 100644
--- a/internal/html/chroma.go
+++ b/internal/html/chroma.go
@@ -16,12 +16,15 @@ 		html.WithLinkableLineNumbers(true, "L"),
 		html.WithClasses(true),
 		html.LineNumbersInTable(true),
 	)
+	basicFormatter = html.New(
+		html.WithClasses(true),
+	)
 	Code = code{}
 )
 
 type code struct{}
 
-func (c code) Convert(source []byte, fileName string, writer io.Writer) error {
+func (c code) setup(source []byte, fileName string) (chroma.Iterator, *chroma.Style, error) {
 	lexer := lexers.Match(fileName)
 	if lexer == nil {
 		lexer = lexers.Fallback
@@ -35,8 +38,24 @@ 	}
 
 	iter, err := lexer.Tokenise(nil, string(source))
 	if err != nil {
+		return nil, nil, err
+	}
+
+	return iter, style, nil
+}
+
+func (c code) Basic(source []byte, fileName string, writer io.Writer) error {
+	iter, style, err := c.setup(source, fileName)
+	if err != nil {
 		return err
 	}
+	return basicFormatter.Format(writer, style, iter)
+}
 
+func (c code) Convert(source []byte, fileName string, writer io.Writer) error {
+	iter, style, err := c.setup(source, fileName)
+	if err != nil {
+		return err
+	}
 	return Formatter.Format(writer, style, iter)
 }
M internal/html/generate.cssinternal/html/generate.css
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
diff --git a/internal/html/generate.css b/internal/html/generate.css
index 34d0aefbe66512d4dc681fa85c49fd965d10370f..2ebab2812af856489efbd790b4f22817c9fd5b54 100644
--- a/internal/html/generate.css
+++ b/internal/html/generate.css
@@ -41,4 +41,9 @@
 .chroma .line.active,
 .chroma .line.active * {
   background: rgb(var(--ctp-surface0)) !important;
+}
+
+.commit .chroma {
+  border-radius: .25rem;
+  padding: 1em;
 }
\ No newline at end of file
M internal/html/repo.templinternal/html/repo.templ
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
diff --git a/internal/html/repo.templ b/internal/html/repo.templ
index a5a09728873d4c35b747288bae91edfd21319045..b1929b9a85c42e64ce02477b8364c1be6647876b 100644
--- a/internal/html/repo.templ
+++ b/internal/html/repo.templ
@@ -16,9 +16,9 @@ 			{ " " }
 			<a class="text-text/70 text-sm underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(fmt.Sprintf("/%s/tree/%s/", rhcc.Name, rhcc.Ref)) }>{ "@" + rhcc.Ref }</a>
 		}
 		{ " - " }
-		<a class="underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(fmt.Sprintf("/%s/refs", rhcc.Name)) }>Refs</a>
+		<a class="underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(fmt.Sprintf("/%s/refs", rhcc.Name)) }>refs</a>
 		{ " - " }
-		<a class="underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(fmt.Sprintf("/%s/log/%s", rhcc.Name, rhcc.Ref)) }>Log</a>
+		<a class="underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(fmt.Sprintf("/%s/log/%s", rhcc.Name, rhcc.Ref)) }>log</a>
 	</div>
 	<div class="text-text/80 mb-1">{ rhcc.Description }</div>
 }
I internal/html/repo_commit.templ
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
diff --git a/internal/html/repo_commit.templ b/internal/html/repo_commit.templ
new file mode 100644
index 0000000000000000000000000000000000000000..7f07f145081601e442a16b27556e1ce276f54949
--- /dev/null
+++ b/internal/html/repo_commit.templ
@@ -0,0 +1,29 @@
+package html
+
+import "fmt"
+import "github.com/dustin/go-humanize"
+import "go.jolheiser.com/ugit/internal/git"
+
+type RepoCommitContext struct{
+  BaseContext
+  RepoHeaderComponentContext
+  Commit git.Commit
+}
+
+templ RepoCommit(rcc RepoCommitContext) {
+	@base(rcc.BaseContext) {
+		@repoHeaderComponent(rcc.RepoHeaderComponentContext)
+		<div class="text-text mt-5"><a class="underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(fmt.Sprintf("/%s/tree/%s/", rcc.RepoHeaderComponentContext.Name, rcc.Commit.SHA)) }>tree</a>{ " " }<a class="underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(fmt.Sprintf("/%s/log/%s", rcc.RepoHeaderComponentContext.Name, rcc.Commit.SHA)) }>log</a>{ " " }<a class="underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(fmt.Sprintf("/%s/commit/%s.patch", rcc.RepoHeaderComponentContext.Name, rcc.Commit.SHA)) }>patch</a></div>
+		<div class="text-text whitespace-pre mt-5">{ rcc.Commit.Message }</div>
+		<div class="text-text mt-3">
+			<div>{ rcc.Commit.Author }{ " " }<a class="underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(fmt.Sprintf("mailto:%s", rcc.Commit.Email)) }>{ fmt.Sprintf("<%s>", rcc.Commit.Email) }</a></div>
+			<div title={ rcc.Commit.When.Format("01/02/2006 03:04:05 PM") }>{ humanize.Time(rcc.Commit.When) }</div>
+		</div>
+		<div class="text-text mt-5">{ fmt.Sprintf("%d changed files, %d additions(+), %d deletions(-)", rcc.Commit.Stats.Changed, rcc.Commit.Stats.Additions, rcc.Commit.Stats.Deletions) }</div>
+		for _, file := range rcc.Commit.Files {
+			<div class="text-text mt-5"><span class="text-text/80">{ string(file.Action[0]) }</span>{ " " }<a class="underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(fmt.Sprintf("/%s/tree/%s/%s", rcc.RepoHeaderComponentContext.Name, file.From.Commit, file.From.Path)) }>{ file.From.Path }</a>{ " -> " }<a class="underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(fmt.Sprintf("/%s/tree/%s/%s", rcc.RepoHeaderComponentContext.Name, file.To.Commit, file.To.Path)) }>{ file.To.Path }</a></div>
+			<div class="whitespace-pre commit">@templ.Raw(file.Patch)</div>
+		}
+	}
+}
+
I internal/html/repo_commit_templ.go
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
diff --git a/internal/html/repo_commit_templ.go b/internal/html/repo_commit_templ.go
new file mode 100644
index 0000000000000000000000000000000000000000..8a48de28e800dc0136b43650eb499947f4eb50cb
--- /dev/null
+++ b/internal/html/repo_commit_templ.go
@@ -0,0 +1,332 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.2.501
+package html
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import "context"
+import "io"
+import "bytes"
+
+import "fmt"
+import "github.com/dustin/go-humanize"
+import "go.jolheiser.com/ugit/internal/git"
+
+type RepoCommitContext struct {
+	BaseContext
+	RepoHeaderComponentContext
+	Commit git.Commit
+}
+
+func RepoCommit(rcc RepoCommitContext) templ.Component {
+	return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
+		templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
+		if !templ_7745c5c3_IsBuffer {
+			templ_7745c5c3_Buffer = templ.GetBuffer()
+			defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+		}
+		ctx = templ.InitializeContext(ctx)
+		templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+		if templ_7745c5c3_Var1 == nil {
+			templ_7745c5c3_Var1 = templ.NopComponent
+		}
+		ctx = templ.ClearChildren(ctx)
+		templ_7745c5c3_Var2 := templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
+			templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
+			if !templ_7745c5c3_IsBuffer {
+				templ_7745c5c3_Buffer = templ.GetBuffer()
+				defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+			}
+			templ_7745c5c3_Err = repoHeaderComponent(rcc.RepoHeaderComponentContext).Render(ctx, templ_7745c5c3_Buffer)
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <div class=\"text-text mt-5\"><a class=\"underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"")
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			var templ_7745c5c3_Var3 templ.SafeURL = templ.SafeURL(fmt.Sprintf("/%s/tree/%s/", rcc.RepoHeaderComponentContext.Name, rcc.Commit.SHA))
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var3)))
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			templ_7745c5c3_Var4 := `tree`
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var4)
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a>")
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			var templ_7745c5c3_Var5 string
+			templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(" ")
+			if templ_7745c5c3_Err != nil {
+				return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 15, Col: 229}
+			}
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a class=\"underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"")
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			var templ_7745c5c3_Var6 templ.SafeURL = templ.SafeURL(fmt.Sprintf("/%s/log/%s", rcc.RepoHeaderComponentContext.Name, rcc.Commit.SHA))
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var6)))
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			templ_7745c5c3_Var7 := `log`
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var7)
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a>")
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			var templ_7745c5c3_Var8 string
+			templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(" ")
+			if templ_7745c5c3_Err != nil {
+				return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 15, Col: 427}
+			}
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a class=\"underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"")
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			var templ_7745c5c3_Var9 templ.SafeURL = templ.SafeURL(fmt.Sprintf("/%s/commit/%s.patch", rcc.RepoHeaderComponentContext.Name, rcc.Commit.SHA))
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var9)))
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			templ_7745c5c3_Var10 := `patch`
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var10)
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></div><div class=\"text-text whitespace-pre mt-5\">")
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			var templ_7745c5c3_Var11 string
+			templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(rcc.Commit.Message)
+			if templ_7745c5c3_Err != nil {
+				return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 16, Col: 65}
+			}
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div class=\"text-text mt-3\"><div>")
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			var templ_7745c5c3_Var12 string
+			templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(rcc.Commit.Author)
+			if templ_7745c5c3_Err != nil {
+				return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 18, Col: 27}
+			}
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			var templ_7745c5c3_Var13 string
+			templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(" ")
+			if templ_7745c5c3_Err != nil {
+				return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 18, Col: 34}
+			}
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a class=\"underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"")
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			var templ_7745c5c3_Var14 templ.SafeURL = templ.SafeURL(fmt.Sprintf("mailto:%s", rcc.Commit.Email))
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var14)))
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			var templ_7745c5c3_Var15 string
+			templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("<%s>", rcc.Commit.Email))
+			if templ_7745c5c3_Err != nil {
+				return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 18, Col: 223}
+			}
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></div><div title=\"")
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(rcc.Commit.When.Format("01/02/2006 03:04:05 PM")))
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			var templ_7745c5c3_Var16 string
+			templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(humanize.Time(rcc.Commit.When))
+			if templ_7745c5c3_Err != nil {
+				return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 19, Col: 99}
+			}
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div><div class=\"text-text mt-5\">")
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			var templ_7745c5c3_Var17 string
+			templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d changed files, %d additions(+), %d deletions(-)", rcc.Commit.Stats.Changed, rcc.Commit.Stats.Additions, rcc.Commit.Stats.Deletions))
+			if templ_7745c5c3_Err != nil {
+				return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 21, Col: 179}
+			}
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			for _, file := range rcc.Commit.Files {
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"text-text mt-5\"><span class=\"text-text/80\">")
+				if templ_7745c5c3_Err != nil {
+					return templ_7745c5c3_Err
+				}
+				var templ_7745c5c3_Var18 string
+				templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(string(file.Action[0]))
+				if templ_7745c5c3_Err != nil {
+					return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 23, Col: 82}
+				}
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
+				if templ_7745c5c3_Err != nil {
+					return templ_7745c5c3_Err
+				}
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span>")
+				if templ_7745c5c3_Err != nil {
+					return templ_7745c5c3_Err
+				}
+				var templ_7745c5c3_Var19 string
+				templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(" ")
+				if templ_7745c5c3_Err != nil {
+					return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 23, Col: 96}
+				}
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
+				if templ_7745c5c3_Err != nil {
+					return templ_7745c5c3_Err
+				}
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a class=\"underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"")
+				if templ_7745c5c3_Err != nil {
+					return templ_7745c5c3_Err
+				}
+				var templ_7745c5c3_Var20 templ.SafeURL = templ.SafeURL(fmt.Sprintf("/%s/tree/%s/%s", rcc.RepoHeaderComponentContext.Name, file.From.Commit, file.From.Path))
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var20)))
+				if templ_7745c5c3_Err != nil {
+					return templ_7745c5c3_Err
+				}
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
+				if templ_7745c5c3_Err != nil {
+					return templ_7745c5c3_Err
+				}
+				var templ_7745c5c3_Var21 string
+				templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(file.From.Path)
+				if templ_7745c5c3_Err != nil {
+					return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 23, Col: 320}
+				}
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
+				if templ_7745c5c3_Err != nil {
+					return templ_7745c5c3_Err
+				}
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a>")
+				if templ_7745c5c3_Err != nil {
+					return templ_7745c5c3_Err
+				}
+				var templ_7745c5c3_Var22 string
+				templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(" -> ")
+				if templ_7745c5c3_Err != nil {
+					return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 23, Col: 334}
+				}
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
+				if templ_7745c5c3_Err != nil {
+					return templ_7745c5c3_Err
+				}
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a class=\"underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"")
+				if templ_7745c5c3_Err != nil {
+					return templ_7745c5c3_Err
+				}
+				var templ_7745c5c3_Var23 templ.SafeURL = templ.SafeURL(fmt.Sprintf("/%s/tree/%s/%s", rcc.RepoHeaderComponentContext.Name, file.To.Commit, file.To.Path))
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var23)))
+				if templ_7745c5c3_Err != nil {
+					return templ_7745c5c3_Err
+				}
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
+				if templ_7745c5c3_Err != nil {
+					return templ_7745c5c3_Err
+				}
+				var templ_7745c5c3_Var24 string
+				templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(file.To.Path)
+				if templ_7745c5c3_Err != nil {
+					return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 23, Col: 552}
+				}
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
+				if templ_7745c5c3_Err != nil {
+					return templ_7745c5c3_Err
+				}
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></div><div class=\"whitespace-pre commit\">")
+				if templ_7745c5c3_Err != nil {
+					return templ_7745c5c3_Err
+				}
+				templ_7745c5c3_Err = templ.Raw(file.Patch).Render(ctx, templ_7745c5c3_Buffer)
+				if templ_7745c5c3_Err != nil {
+					return templ_7745c5c3_Err
+				}
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
+				if templ_7745c5c3_Err != nil {
+					return templ_7745c5c3_Err
+				}
+			}
+			if !templ_7745c5c3_IsBuffer {
+				_, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer)
+			}
+			return templ_7745c5c3_Err
+		})
+		templ_7745c5c3_Err = base(rcc.BaseContext).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
+		if templ_7745c5c3_Err != nil {
+			return templ_7745c5c3_Err
+		}
+		if !templ_7745c5c3_IsBuffer {
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
+		}
+		return templ_7745c5c3_Err
+	})
+}
M internal/html/repo_log.templinternal/html/repo_log.templ
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
diff --git a/internal/html/repo_log.templ b/internal/html/repo_log.templ
index f1517e54ce09efb84951a11a879a216aa022aa50..3a94902fe41f49bd3072555d7279308c1b659589 100644
--- a/internal/html/repo_log.templ
+++ b/internal/html/repo_log.templ
@@ -16,7 +16,10 @@ 		@repoHeaderComponent(rlc.RepoHeaderComponentContext)
 		<div class="grid grid-cols-8 gap-5 text-text mt-5">
 			for _, commit := range rlc.Commits {
 				<div class="col-span-4">
-					<div>{ commit.Short() }</div>
+					<div><a class="underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(fmt.Sprintf("/%s/commit/%s", rlc.RepoHeaderComponentContext.Name, commit.SHA)) }>{ commit.Short() }</a></div>
+					if commit.Signature != "" {
+						<details class="col-span-8 whitespace-pre"><summary class="cursor-pointer">Signature</summary><code>{ commit.Signature }</code></details>
+					}
 					<div class="whitespace-pre">
 						if commit.Details() != "" {
 							<details><summary class="cursor-pointer">{ commit.Summary() }</summary>{ commit.Details() }</details>
M internal/html/repo_log_templ.gointernal/html/repo_log_templ.go
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
diff --git a/internal/html/repo_log_templ.go b/internal/html/repo_log_templ.go
index d7478c29c1b30530e33eba2b25280d9c7c575a92..193fbaf12bed484d8f927e26c5f8313564f468bd 100644
--- a/internal/html/repo_log_templ.go
+++ b/internal/html/repo_log_templ.go
@@ -48,20 +48,61 @@ 			if templ_7745c5c3_Err != nil {
 				return templ_7745c5c3_Err
 			}
 			for _, commit := range rlc.Commits {
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"col-span-4\"><div>")
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"col-span-4\"><div><a class=\"underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"")
+				if templ_7745c5c3_Err != nil {
+					return templ_7745c5c3_Err
+				}
+				var templ_7745c5c3_Var3 templ.SafeURL = templ.SafeURL(fmt.Sprintf("/%s/commit/%s", rlc.RepoHeaderComponentContext.Name, commit.SHA))
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var3)))
+				if templ_7745c5c3_Err != nil {
+					return templ_7745c5c3_Err
+				}
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
-				var templ_7745c5c3_Var3 string
-				templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(commit.Short())
+				var templ_7745c5c3_Var4 string
+				templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(commit.Short())
+				if templ_7745c5c3_Err != nil {
+					return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_log.templ`, Line: 18, Col: 209}
+				}
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
 				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_log.templ`, Line: 18, Col: 26}
+					return templ_7745c5c3_Err
 				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></div>")
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div class=\"whitespace-pre\">")
+				if commit.Signature != "" {
+					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<details class=\"col-span-8 whitespace-pre\"><summary class=\"cursor-pointer\">")
+					if templ_7745c5c3_Err != nil {
+						return templ_7745c5c3_Err
+					}
+					templ_7745c5c3_Var5 := `Signature`
+					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var5)
+					if templ_7745c5c3_Err != nil {
+						return templ_7745c5c3_Err
+					}
+					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</summary><code>")
+					if templ_7745c5c3_Err != nil {
+						return templ_7745c5c3_Err
+					}
+					var templ_7745c5c3_Var6 string
+					templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(commit.Signature)
+					if templ_7745c5c3_Err != nil {
+						return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_log.templ`, Line: 20, Col: 124}
+					}
+					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
+					if templ_7745c5c3_Err != nil {
+						return templ_7745c5c3_Err
+					}
+					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</code></details>")
+					if templ_7745c5c3_Err != nil {
+						return templ_7745c5c3_Err
+					}
+				}
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"whitespace-pre\">")
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
@@ -70,12 +111,12 @@ 					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<details><summary class=\"cursor-pointer\">")
 					if templ_7745c5c3_Err != nil {
 						return templ_7745c5c3_Err
 					}
-					var templ_7745c5c3_Var4 string
-					templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(commit.Summary())
+					var templ_7745c5c3_Var7 string
+					templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(commit.Summary())
 					if templ_7745c5c3_Err != nil {
-						return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_log.templ`, Line: 21, Col: 66}
+						return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_log.templ`, Line: 24, Col: 66}
 					}
-					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
+					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
 					if templ_7745c5c3_Err != nil {
 						return templ_7745c5c3_Err
 					}
@@ -83,12 +124,12 @@ 					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</summary>")
 					if templ_7745c5c3_Err != nil {
 						return templ_7745c5c3_Err
 					}
-					var templ_7745c5c3_Var5 string
-					templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(commit.Details())
+					var templ_7745c5c3_Var8 string
+					templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(commit.Details())
 					if templ_7745c5c3_Err != nil {
-						return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_log.templ`, Line: 21, Col: 96}
+						return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_log.templ`, Line: 24, Col: 96}
 					}
-					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
+					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
 					if templ_7745c5c3_Err != nil {
 						return templ_7745c5c3_Err
 					}
@@ -97,12 +138,12 @@ 					if templ_7745c5c3_Err != nil {
 						return templ_7745c5c3_Err
 					}
 				} else {
-					var templ_7745c5c3_Var6 string
-					templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(commit.Message)
+					var templ_7745c5c3_Var9 string
+					templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(commit.Message)
 					if templ_7745c5c3_Err != nil {
-						return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_log.templ`, Line: 23, Col: 23}
+						return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_log.templ`, Line: 26, Col: 23}
 					}
-					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
+					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
 					if templ_7745c5c3_Err != nil {
 						return templ_7745c5c3_Err
 					}
@@ -111,21 +152,21 @@ 				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div><div class=\"col-span-4\"><div>")
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
-				var templ_7745c5c3_Var7 string
-				templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(commit.Author)
+				var templ_7745c5c3_Var10 string
+				templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(commit.Author)
 				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_log.templ`, Line: 28, Col: 25}
+					return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_log.templ`, Line: 31, Col: 25}
 				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
-				var templ_7745c5c3_Var8 string
-				templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(" ")
+				var templ_7745c5c3_Var11 string
+				templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(" ")
 				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_log.templ`, Line: 28, Col: 32}
+					return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_log.templ`, Line: 31, Col: 32}
 				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
@@ -133,8 +174,8 @@ 				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a class=\"underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"")
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
-				var templ_7745c5c3_Var9 templ.SafeURL = templ.SafeURL(fmt.Sprintf("mailto:%s", commit.Email))
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var9)))
+				var templ_7745c5c3_Var12 templ.SafeURL = templ.SafeURL(fmt.Sprintf("mailto:%s", commit.Email))
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var12)))
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
@@ -142,12 +183,12 @@ 				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
-				var templ_7745c5c3_Var10 string
-				templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("<%s>", commit.Email))
+				var templ_7745c5c3_Var13 string
+				templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("<%s>", commit.Email))
 				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_log.templ`, Line: 28, Col: 213}
+					return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_log.templ`, Line: 31, Col: 213}
 				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
@@ -163,12 +204,12 @@ 				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
-				var templ_7745c5c3_Var11 string
-				templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(humanize.Time(commit.When))
+				var templ_7745c5c3_Var14 string
+				templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(humanize.Time(commit.When))
 				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_log.templ`, Line: 29, Col: 93}
+					return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_log.templ`, Line: 32, Col: 93}
 				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
M internal/html/repo_templ.gointernal/html/repo_templ.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
diff --git a/internal/html/repo_templ.go b/internal/html/repo_templ.go
index 19a9472ecd575acf1742bcb51e2fe291d2e9f06c..abc54ff9a194bb8f2e50de8c3cd3e80f6e1a313d 100644
--- a/internal/html/repo_templ.go
+++ b/internal/html/repo_templ.go
@@ -116,7 +116,7 @@ 		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
 		if templ_7745c5c3_Err != nil {
 			return templ_7745c5c3_Err
 		}
-		templ_7745c5c3_Var9 := `Refs`
+		templ_7745c5c3_Var9 := `refs`
 		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var9)
 		if templ_7745c5c3_Err != nil {
 			return templ_7745c5c3_Err
@@ -147,7 +147,7 @@ 		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
 		if templ_7745c5c3_Err != nil {
 			return templ_7745c5c3_Err
 		}
-		templ_7745c5c3_Var12 := `Log`
+		templ_7745c5c3_Var12 := `log`
 		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var12)
 		if templ_7745c5c3_Err != nil {
 			return templ_7745c5c3_Err
M internal/html/tailwind.gointernal/html/tailwind.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
diff --git a/internal/html/tailwind.go b/internal/html/tailwind.go
index 1511821349c2dd93230c66da3d364e7f79ea0b0e..16113d82f1ed8f475e51d00a1ff13c952831e4a0 100644
--- a/internal/html/tailwind.go
+++ b/internal/html/tailwind.go
@@ -5,5 +5,5 @@ import "net/http"
 
 func TailwindHandler(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "text/css")
-	w.Write([]byte("/*! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:\"\"}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}.latte{--ctp-rosewater:220,138,120;--ctp-flamingo:221,120,120;--ctp-pink:234,118,203;--ctp-mauve:136,57,239;--ctp-red:210,15,57;--ctp-maroon:230,69,83;--ctp-peach:254,100,11;--ctp-yellow:223,142,29;--ctp-green:64,160,43;--ctp-teal:23,146,153;--ctp-sky:4,165,229;--ctp-sapphire:32,159,181;--ctp-blue:30,102,245;--ctp-lavender:114,135,253;--ctp-text:76,79,105;--ctp-subtext1:92,95,119;--ctp-subtext0:108,111,133;--ctp-overlay2:124,127,147;--ctp-overlay1:140,143,161;--ctp-overlay0:156,160,176;--ctp-surface2:172,176,190;--ctp-surface1:188,192,204;--ctp-surface0:204,208,218;--ctp-base:239,241,245;--ctp-mantle:230,233,239;--ctp-crust:220,224,232}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.col-span-1{grid-column:span 1/span 1}.col-span-2{grid-column:span 2/span 2}.col-span-4{grid-column:span 4/span 4}.col-span-5{grid-column:span 5/span 5}.col-span-6{grid-column:span 6/span 6}.col-span-7{grid-column:span 7/span 7}.col-span-8{grid-column:span 8/span 8}.mx-auto{margin-left:auto;margin-right:auto}.my-10{margin-top:2.5rem;margin-bottom:2.5rem}.mb-1{margin-bottom:.25rem}.mb-3{margin-bottom:.75rem}.mr-1{margin-right:.25rem}.mt-2{margin-top:.5rem}.mt-5{margin-top:1.25rem}.inline-block{display:inline-block}.grid{display:grid}.h-5{height:1.25rem}.w-5{width:1.25rem}.max-w-7xl{max-width:80rem}.cursor-pointer{cursor:pointer}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-8{grid-template-columns:repeat(8,minmax(0,1fr))}.gap-1{gap:.25rem}.gap-5{gap:1.25rem}.whitespace-pre{white-space:pre}.rounded{border-radius:.25rem}.bg-base\\/50{background-color:rgba(var(--ctp-base),.5)}.stroke-mauve{stroke:rgb(var(--ctp-mauve))}.px-5{padding-left:1.25rem;padding-right:1.25rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.align-middle{vertical-align:middle}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.font-bold{font-weight:700}.text-blue{--tw-text-opacity:1;color:rgba(var(--ctp-blue),var(--tw-text-opacity))}.text-mauve{--tw-text-opacity:1;color:rgba(var(--ctp-mauve),var(--tw-text-opacity))}.text-subtext0{--tw-text-opacity:1;color:rgba(var(--ctp-subtext0),var(--tw-text-opacity))}.text-subtext1{--tw-text-opacity:1;color:rgba(var(--ctp-subtext1),var(--tw-text-opacity))}.text-text{--tw-text-opacity:1;color:rgba(var(--ctp-text),var(--tw-text-opacity))}.text-text\\/70{color:rgba(var(--ctp-text),.7)}.text-text\\/80{color:rgba(var(--ctp-text),.8)}.underline{text-decoration-line:underline}.decoration-blue\\/50{text-decoration-color:rgba(var(--ctp-blue),.5)}.decoration-mauve\\/50{text-decoration-color:rgba(var(--ctp-mauve),.5)}.decoration-text\\/50{text-decoration-color:rgba(var(--ctp-text),.5)}.decoration-dashed{text-decoration-style:dashed}.markdown *{all:revert;color:rgb(var(--ctp-text))}.markdown a{color:rgb(var(--ctp-blue));text-decoration-line:underline;text-decoration-style:dashed}.markdown a:hover{text-decoration-style:solid}.chroma{font-size:small}.chroma *{background-color:rgb(var(--ctp-base))!important}.chroma table{border-spacing:5px 0!important}.chroma .lnt{color:rgb(var(--ctp-subtext1))!important}.chroma .lnt:focus,.chroma .lnt:target{color:rgb(var(--ctp-subtext0))!important}.chroma .line{white-space:break-spaces}.chroma .line.active,.chroma .line.active *{background:rgb(var(--ctp-surface0))!important}.bg,.chroma{color:#4c4f69;background-color:#eff1f5}.chroma .lntd:last-child{width:100%}.chroma .ln:target,.chroma .lnt:target{color:#bcc0cc;background-color:#eff1f5}.chroma .err{color:#d20f39}.chroma .lnlinks{outline:none;text-decoration:none;color:inherit}.chroma .lntd{vertical-align:top;padding:0;margin:0;border:0}.chroma .lntable{border-spacing:0;padding:0;margin:0;border:0}.chroma .hl{color:#bcc0cc}.chroma .ln,.chroma .lnt{white-space:pre;-webkit-user-select:none;-moz-user-select:none;user-select:none;margin-right:.4em;padding:0 .4em;color:#8c8fa1}.chroma .line{display:flex}.chroma .k{color:#8839ef}.chroma .kc{color:#fe640b}.chroma .kd{color:#d20f39}.chroma .kn{color:#179299}.chroma .kp,.chroma .kr{color:#8839ef}.chroma .kt{color:#d20f39}.chroma .na{color:#1e66f5}.chroma .bp,.chroma .nb{color:#04a5e5}.chroma .nc,.chroma .no{color:#df8e1d}.chroma .nd{color:#1e66f5;font-weight:700}.chroma .ni{color:#179299}.chroma .ne{color:#fe640b}.chroma .fm,.chroma .nf{color:#1e66f5}.chroma .nl{color:#04a5e5}.chroma .nn,.chroma .py{color:#fe640b}.chroma .nt{color:#8839ef}.chroma .nv,.chroma .vc,.chroma .vg,.chroma .vi,.chroma .vm{color:#dc8a78}.chroma .s{color:#40a02b}.chroma .sa{color:#d20f39}.chroma .sb,.chroma .sc{color:#40a02b}.chroma .dl{color:#1e66f5}.chroma .sd{color:#9ca0b0}.chroma .s2{color:#40a02b}.chroma .se{color:#1e66f5}.chroma .sh{color:#9ca0b0}.chroma .si,.chroma .sx{color:#40a02b}.chroma .sr{color:#179299}.chroma .s1,.chroma .ss{color:#40a02b}.chroma .il,.chroma .m,.chroma .mb,.chroma .mf,.chroma .mh,.chroma .mi,.chroma .mo{color:#fe640b}.chroma .o,.chroma .ow{color:#04a5e5;font-weight:700}.chroma .c,.chroma .c1,.chroma .ch,.chroma .cm,.chroma .cp,.chroma .cpf,.chroma .cs{color:#9ca0b0;font-style:italic}.chroma .cpf{font-weight:700}.chroma .gd{color:#d20f39;background-color:#ccd0da}.chroma .ge{font-style:italic}.chroma .gr{color:#d20f39}.chroma .gh{color:#fe640b;font-weight:700}.chroma .gi{color:#40a02b;background-color:#ccd0da}.chroma .gs,.chroma .gu{font-weight:700}.chroma .gu{color:#fe640b}.chroma .gt{color:#d20f39}.chroma .gl{text-decoration:underline}@media (prefers-color-scheme:dark){.bg,.chroma{color:#cdd6f4;background-color:#1e1e2e}.chroma .lntd:last-child{width:100%}.chroma .ln:target,.chroma .lnt:target{color:#45475a;background-color:#1e1e2e}.chroma .err{color:#f38ba8}.chroma .lnlinks{outline:none;text-decoration:none;color:inherit}.chroma .lntd{vertical-align:top;padding:0;margin:0;border:0}.chroma .lntable{border-spacing:0;padding:0;margin:0;border:0}.chroma .hl{color:#45475a}.chroma .ln,.chroma .lnt{white-space:pre;-webkit-user-select:none;-moz-user-select:none;user-select:none;margin-right:.4em;padding:0 .4em;color:#7f849c}.chroma .line{display:flex}.chroma .k{color:#cba6f7}.chroma .kc{color:#fab387}.chroma .kd{color:#f38ba8}.chroma .kn{color:#94e2d5}.chroma .kp,.chroma .kr{color:#cba6f7}.chroma .kt{color:#f38ba8}.chroma .na{color:#89b4fa}.chroma .bp,.chroma .nb{color:#89dceb}.chroma .nc,.chroma .no{color:#f9e2af}.chroma .nd{color:#89b4fa;font-weight:700}.chroma .ni{color:#94e2d5}.chroma .ne{color:#fab387}.chroma .fm,.chroma .nf{color:#89b4fa}.chroma .nl{color:#89dceb}.chroma .nn,.chroma .py{color:#fab387}.chroma .nt{color:#cba6f7}.chroma .nv,.chroma .vc,.chroma .vg,.chroma .vi,.chroma .vm{color:#f5e0dc}.chroma .s{color:#a6e3a1}.chroma .sa{color:#f38ba8}.chroma .sb,.chroma .sc{color:#a6e3a1}.chroma .dl{color:#89b4fa}.chroma .sd{color:#6c7086}.chroma .s2{color:#a6e3a1}.chroma .se{color:#89b4fa}.chroma .sh{color:#6c7086}.chroma .si,.chroma .sx{color:#a6e3a1}.chroma .sr{color:#94e2d5}.chroma .s1,.chroma .ss{color:#a6e3a1}.chroma .il,.chroma .m,.chroma .mb,.chroma .mf,.chroma .mh,.chroma .mi,.chroma .mo{color:#fab387}.chroma .o,.chroma .ow{color:#89dceb;font-weight:700}.chroma .c,.chroma .c1,.chroma .ch,.chroma .cm,.chroma .cp,.chroma .cpf,.chroma .cs{color:#6c7086;font-style:italic}.chroma .cpf{font-weight:700}.chroma .gd{color:#f38ba8;background-color:#313244}.chroma .ge{font-style:italic}.chroma .gr{color:#f38ba8}.chroma .gh{color:#fab387;font-weight:700}.chroma .gi{color:#a6e3a1;background-color:#313244}.chroma .gs,.chroma .gu{font-weight:700}.chroma .gu{color:#fab387}.chroma .gt{color:#f38ba8}.chroma .gl{text-decoration:underline}.dark\\:mocha{--ctp-rosewater:245,224,220;--ctp-flamingo:242,205,205;--ctp-pink:245,194,231;--ctp-mauve:203,166,247;--ctp-red:243,139,168;--ctp-maroon:235,160,172;--ctp-peach:250,179,135;--ctp-yellow:249,226,175;--ctp-green:166,227,161;--ctp-teal:148,226,213;--ctp-sky:137,220,235;--ctp-sapphire:116,199,236;--ctp-blue:137,180,250;--ctp-lavender:180,190,254;--ctp-text:205,214,244;--ctp-subtext1:186,194,222;--ctp-subtext0:166,173,200;--ctp-overlay2:147,153,178;--ctp-overlay1:127,132,156;--ctp-overlay0:108,112,134;--ctp-surface2:88,91,112;--ctp-surface1:69,71,90;--ctp-surface0:49,50,68;--ctp-base:30,30,46;--ctp-mantle:24,24,37;--ctp-crust:17,17,27}}.hover\\:decoration-solid:hover{text-decoration-style:solid}@media (prefers-color-scheme:dark){.dark\\:bg-base\\/95{background-color:rgba(var(--ctp-base),.95)}.dark\\:text-lavender{--tw-text-opacity:1;color:rgba(var(--ctp-lavender),var(--tw-text-opacity))}.dark\\:decoration-lavender\\/50{text-decoration-color:rgba(var(--ctp-lavender),.5)}}@media (min-width:640px){.sm\\:grid-cols-8{grid-template-columns:repeat(8,minmax(0,1fr))}}"))
+	w.Write([]byte("/*! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:\"\"}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}.latte{--ctp-rosewater:220,138,120;--ctp-flamingo:221,120,120;--ctp-pink:234,118,203;--ctp-mauve:136,57,239;--ctp-red:210,15,57;--ctp-maroon:230,69,83;--ctp-peach:254,100,11;--ctp-yellow:223,142,29;--ctp-green:64,160,43;--ctp-teal:23,146,153;--ctp-sky:4,165,229;--ctp-sapphire:32,159,181;--ctp-blue:30,102,245;--ctp-lavender:114,135,253;--ctp-text:76,79,105;--ctp-subtext1:92,95,119;--ctp-subtext0:108,111,133;--ctp-overlay2:124,127,147;--ctp-overlay1:140,143,161;--ctp-overlay0:156,160,176;--ctp-surface2:172,176,190;--ctp-surface1:188,192,204;--ctp-surface0:204,208,218;--ctp-base:239,241,245;--ctp-mantle:230,233,239;--ctp-crust:220,224,232}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.col-span-1{grid-column:span 1/span 1}.col-span-2{grid-column:span 2/span 2}.col-span-4{grid-column:span 4/span 4}.col-span-5{grid-column:span 5/span 5}.col-span-6{grid-column:span 6/span 6}.col-span-7{grid-column:span 7/span 7}.col-span-8{grid-column:span 8/span 8}.mx-auto{margin-left:auto;margin-right:auto}.my-10{margin-top:2.5rem;margin-bottom:2.5rem}.mb-1{margin-bottom:.25rem}.mb-3{margin-bottom:.75rem}.mr-1{margin-right:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-5{margin-top:1.25rem}.inline-block{display:inline-block}.grid{display:grid}.h-5{height:1.25rem}.w-5{width:1.25rem}.max-w-7xl{max-width:80rem}.cursor-pointer{cursor:pointer}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-8{grid-template-columns:repeat(8,minmax(0,1fr))}.gap-1{gap:.25rem}.gap-5{gap:1.25rem}.whitespace-pre{white-space:pre}.rounded{border-radius:.25rem}.bg-base\\/50{background-color:rgba(var(--ctp-base),.5)}.stroke-mauve{stroke:rgb(var(--ctp-mauve))}.px-5{padding-left:1.25rem;padding-right:1.25rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.align-middle{vertical-align:middle}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.font-bold{font-weight:700}.text-blue{--tw-text-opacity:1;color:rgba(var(--ctp-blue),var(--tw-text-opacity))}.text-mauve{--tw-text-opacity:1;color:rgba(var(--ctp-mauve),var(--tw-text-opacity))}.text-subtext0{--tw-text-opacity:1;color:rgba(var(--ctp-subtext0),var(--tw-text-opacity))}.text-subtext1{--tw-text-opacity:1;color:rgba(var(--ctp-subtext1),var(--tw-text-opacity))}.text-text{--tw-text-opacity:1;color:rgba(var(--ctp-text),var(--tw-text-opacity))}.text-text\\/70{color:rgba(var(--ctp-text),.7)}.text-text\\/80{color:rgba(var(--ctp-text),.8)}.underline{text-decoration-line:underline}.decoration-blue\\/50{text-decoration-color:rgba(var(--ctp-blue),.5)}.decoration-mauve\\/50{text-decoration-color:rgba(var(--ctp-mauve),.5)}.decoration-text\\/50{text-decoration-color:rgba(var(--ctp-text),.5)}.decoration-dashed{text-decoration-style:dashed}.markdown *{all:revert;color:rgb(var(--ctp-text))}.markdown a{color:rgb(var(--ctp-blue));text-decoration-line:underline;text-decoration-style:dashed}.markdown a:hover{text-decoration-style:solid}.chroma{font-size:small}.chroma *{background-color:rgb(var(--ctp-base))!important}.chroma table{border-spacing:5px 0!important}.chroma .lnt{color:rgb(var(--ctp-subtext1))!important}.chroma .lnt:focus,.chroma .lnt:target{color:rgb(var(--ctp-subtext0))!important}.chroma .line{white-space:break-spaces}.chroma .line.active,.chroma .line.active *{background:rgb(var(--ctp-surface0))!important}.commit .chroma{border-radius:.25rem;padding:1em}.bg,.chroma{color:#4c4f69;background-color:#eff1f5}.chroma .lntd:last-child{width:100%}.chroma .ln:target,.chroma .lnt:target{color:#bcc0cc;background-color:#eff1f5}.chroma .err{color:#d20f39}.chroma .lnlinks{outline:none;text-decoration:none;color:inherit}.chroma .lntd{vertical-align:top;padding:0;margin:0;border:0}.chroma .lntable{border-spacing:0;padding:0;margin:0;border:0}.chroma .hl{color:#bcc0cc}.chroma .ln,.chroma .lnt{white-space:pre;-webkit-user-select:none;-moz-user-select:none;user-select:none;margin-right:.4em;padding:0 .4em;color:#8c8fa1}.chroma .line{display:flex}.chroma .k{color:#8839ef}.chroma .kc{color:#fe640b}.chroma .kd{color:#d20f39}.chroma .kn{color:#179299}.chroma .kp,.chroma .kr{color:#8839ef}.chroma .kt{color:#d20f39}.chroma .na{color:#1e66f5}.chroma .bp,.chroma .nb{color:#04a5e5}.chroma .nc,.chroma .no{color:#df8e1d}.chroma .nd{color:#1e66f5;font-weight:700}.chroma .ni{color:#179299}.chroma .ne{color:#fe640b}.chroma .fm,.chroma .nf{color:#1e66f5}.chroma .nl{color:#04a5e5}.chroma .nn,.chroma .py{color:#fe640b}.chroma .nt{color:#8839ef}.chroma .nv,.chroma .vc,.chroma .vg,.chroma .vi,.chroma .vm{color:#dc8a78}.chroma .s{color:#40a02b}.chroma .sa{color:#d20f39}.chroma .sb,.chroma .sc{color:#40a02b}.chroma .dl{color:#1e66f5}.chroma .sd{color:#9ca0b0}.chroma .s2{color:#40a02b}.chroma .se{color:#1e66f5}.chroma .sh{color:#9ca0b0}.chroma .si,.chroma .sx{color:#40a02b}.chroma .sr{color:#179299}.chroma .s1,.chroma .ss{color:#40a02b}.chroma .il,.chroma .m,.chroma .mb,.chroma .mf,.chroma .mh,.chroma .mi,.chroma .mo{color:#fe640b}.chroma .o,.chroma .ow{color:#04a5e5;font-weight:700}.chroma .c,.chroma .c1,.chroma .ch,.chroma .cm,.chroma .cp,.chroma .cpf,.chroma .cs{color:#9ca0b0;font-style:italic}.chroma .cpf{font-weight:700}.chroma .gd{color:#d20f39;background-color:#ccd0da}.chroma .ge{font-style:italic}.chroma .gr{color:#d20f39}.chroma .gh{color:#fe640b;font-weight:700}.chroma .gi{color:#40a02b;background-color:#ccd0da}.chroma .gs,.chroma .gu{font-weight:700}.chroma .gu{color:#fe640b}.chroma .gt{color:#d20f39}.chroma .gl{text-decoration:underline}@media (prefers-color-scheme:dark){.bg,.chroma{color:#cdd6f4;background-color:#1e1e2e}.chroma .lntd:last-child{width:100%}.chroma .ln:target,.chroma .lnt:target{color:#45475a;background-color:#1e1e2e}.chroma .err{color:#f38ba8}.chroma .lnlinks{outline:none;text-decoration:none;color:inherit}.chroma .lntd{vertical-align:top;padding:0;margin:0;border:0}.chroma .lntable{border-spacing:0;padding:0;margin:0;border:0}.chroma .hl{color:#45475a}.chroma .ln,.chroma .lnt{white-space:pre;-webkit-user-select:none;-moz-user-select:none;user-select:none;margin-right:.4em;padding:0 .4em;color:#7f849c}.chroma .line{display:flex}.chroma .k{color:#cba6f7}.chroma .kc{color:#fab387}.chroma .kd{color:#f38ba8}.chroma .kn{color:#94e2d5}.chroma .kp,.chroma .kr{color:#cba6f7}.chroma .kt{color:#f38ba8}.chroma .na{color:#89b4fa}.chroma .bp,.chroma .nb{color:#89dceb}.chroma .nc,.chroma .no{color:#f9e2af}.chroma .nd{color:#89b4fa;font-weight:700}.chroma .ni{color:#94e2d5}.chroma .ne{color:#fab387}.chroma .fm,.chroma .nf{color:#89b4fa}.chroma .nl{color:#89dceb}.chroma .nn,.chroma .py{color:#fab387}.chroma .nt{color:#cba6f7}.chroma .nv,.chroma .vc,.chroma .vg,.chroma .vi,.chroma .vm{color:#f5e0dc}.chroma .s{color:#a6e3a1}.chroma .sa{color:#f38ba8}.chroma .sb,.chroma .sc{color:#a6e3a1}.chroma .dl{color:#89b4fa}.chroma .sd{color:#6c7086}.chroma .s2{color:#a6e3a1}.chroma .se{color:#89b4fa}.chroma .sh{color:#6c7086}.chroma .si,.chroma .sx{color:#a6e3a1}.chroma .sr{color:#94e2d5}.chroma .s1,.chroma .ss{color:#a6e3a1}.chroma .il,.chroma .m,.chroma .mb,.chroma .mf,.chroma .mh,.chroma .mi,.chroma .mo{color:#fab387}.chroma .o,.chroma .ow{color:#89dceb;font-weight:700}.chroma .c,.chroma .c1,.chroma .ch,.chroma .cm,.chroma .cp,.chroma .cpf,.chroma .cs{color:#6c7086;font-style:italic}.chroma .cpf{font-weight:700}.chroma .gd{color:#f38ba8;background-color:#313244}.chroma .ge{font-style:italic}.chroma .gr{color:#f38ba8}.chroma .gh{color:#fab387;font-weight:700}.chroma .gi{color:#a6e3a1;background-color:#313244}.chroma .gs,.chroma .gu{font-weight:700}.chroma .gu{color:#fab387}.chroma .gt{color:#f38ba8}.chroma .gl{text-decoration:underline}.dark\\:mocha{--ctp-rosewater:245,224,220;--ctp-flamingo:242,205,205;--ctp-pink:245,194,231;--ctp-mauve:203,166,247;--ctp-red:243,139,168;--ctp-maroon:235,160,172;--ctp-peach:250,179,135;--ctp-yellow:249,226,175;--ctp-green:166,227,161;--ctp-teal:148,226,213;--ctp-sky:137,220,235;--ctp-sapphire:116,199,236;--ctp-blue:137,180,250;--ctp-lavender:180,190,254;--ctp-text:205,214,244;--ctp-subtext1:186,194,222;--ctp-subtext0:166,173,200;--ctp-overlay2:147,153,178;--ctp-overlay1:127,132,156;--ctp-overlay0:108,112,134;--ctp-surface2:88,91,112;--ctp-surface1:69,71,90;--ctp-surface0:49,50,68;--ctp-base:30,30,46;--ctp-mantle:24,24,37;--ctp-crust:17,17,27}}.hover\\:decoration-solid:hover{text-decoration-style:solid}@media (prefers-color-scheme:dark){.dark\\:bg-base\\/95{background-color:rgba(var(--ctp-base),.95)}.dark\\:text-lavender{--tw-text-opacity:1;color:rgba(var(--ctp-lavender),var(--tw-text-opacity))}.dark\\:decoration-lavender\\/50{text-decoration-color:rgba(var(--ctp-lavender),.5)}}@media (min-width:640px){.sm\\:grid-cols-8{grid-template-columns:repeat(8,minmax(0,1fr))}}"))
 }
M internal/http/http.gointernal/http/http.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
diff --git a/internal/http/http.go b/internal/http/http.go
index 0a0a717f1a6b235ae50298deae6e6ed7e08602bc..7e1bf4abc35c5104cab2cc6b237e479532d45f12 100644
--- a/internal/http/http.go
+++ b/internal/http/http.go
@@ -82,6 +82,8 @@ 				rh.repoTree(chi.URLParam(r, "ref"), chi.URLParam(r, "*")).ServeHTTP(w, r)
 			})
 			r.Get("/refs", httperr.Handler(rh.repoRefs))
 			r.Get("/log/{ref}", httperr.Handler(rh.repoLog))
+			r.Get("/commit/{commit}", httperr.Handler(rh.repoCommit))
+			r.Get("/commit/{commit}.patch", httperr.Handler(rh.repoPatch))
 		})
 	})
 
M internal/http/repo.gointernal/http/repo.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
diff --git a/internal/http/repo.go b/internal/http/repo.go
index 83892353a29c3d3804a479980df7f6c0d8a1d686..f1713d7849d56f7ea6f6361f768a52387adcd152 100644
--- a/internal/http/repo.go
+++ b/internal/http/repo.go
@@ -173,3 +173,65 @@ 	}
 
 	return nil
 }
+
+func (rh repoHandler) repoCommit(w http.ResponseWriter, r *http.Request) error {
+	repoName := chi.URLParam(r, "repo")
+	repo, err := git.NewRepo(rh.s.RepoDir, repoName)
+	if err != nil {
+		httpErr := http.StatusInternalServerError
+		if errors.Is(err, fs.ErrNotExist) {
+			httpErr = http.StatusNotFound
+		}
+		return httperr.Status(err, httpErr)
+	}
+	if repo.Meta.Private {
+		return httperr.Status(errors.New("could not get git repo"), http.StatusNotFound)
+	}
+
+	commit, err := repo.Commit(chi.URLParam(r, "commit"))
+	if err != nil {
+		return httperr.Error(err)
+	}
+
+	for idx, p := range commit.Files {
+		var patch bytes.Buffer
+		if err := html.Code.Basic([]byte(p.Patch), "commit.patch", &patch); err != nil {
+			return httperr.Error(err)
+		}
+		commit.Files[idx].Patch = patch.String()
+	}
+
+	if err := html.RepoCommit(html.RepoCommitContext{
+		BaseContext:                rh.baseContext(),
+		RepoHeaderComponentContext: rh.repoHeaderContext(repo, r),
+		Commit:                     commit,
+	}).Render(r.Context(), w); err != nil {
+		return httperr.Error(err)
+	}
+
+	return nil
+}
+
+func (rh repoHandler) repoPatch(w http.ResponseWriter, r *http.Request) error {
+	repoName := chi.URLParam(r, "repo")
+	repo, err := git.NewRepo(rh.s.RepoDir, repoName)
+	if err != nil {
+		httpErr := http.StatusInternalServerError
+		if errors.Is(err, fs.ErrNotExist) {
+			httpErr = http.StatusNotFound
+		}
+		return httperr.Status(err, httpErr)
+	}
+	if repo.Meta.Private {
+		return httperr.Status(errors.New("could not get git repo"), http.StatusNotFound)
+	}
+
+	commit, err := repo.Commit(chi.URLParam(r, "commit"))
+	if err != nil {
+		return httperr.Error(err)
+	}
+
+	w.Write([]byte(commit.Patch))
+
+	return nil
+}