M
Makefile
+7,
-5
1@@ -10,7 +10,7 @@ clean:
2 .PHONY: clean
3
4 build:
5- go build -o pgit ./main.go
6+ go build -o pgit .
7 .PHONY: build
8
9 img:
10@@ -31,15 +31,17 @@ test:
11
12 test-site:
13 mkdir -p testdata.site
14- go run main.go \
15+ rm -rf testdata.site/*
16+ go run . \
17 --repo ./testdata \
18 --out testdata.site \
19 --clone-url "https://test.com/test/test2" \
20 --home-url "https://test.com/test" \
21 --label testdata \
22- --desc "pgit testing site" \
23- --theme "dracula" \
24- --revs "main,branch-c"
25+ --desc "pgit testing site - [link](https://yourmom.com)" \
26+ --theme "kanagawa-dragon" \
27+ --revs "main,branch-c" \
28+ --issues
29 .PHONY: test-site
30
31 static: build clean
+2,
-1
1@@ -8,7 +8,8 @@
2 "ripgrep@latest",
3 "nodejs@latest",
4 "yarn@latest",
5- "git@latest"
6+ "git@latest",
7+ "git-bug@latest"
8 ],
9 "shell": {
10 "init_hook": [
+48,
-0
1@@ -197,6 +197,54 @@
2 }
3 }
4 },
5+ "git-bug@latest": {
6+ "last_modified": "2026-03-21T07:29:51Z",
7+ "resolved": "github:NixOS/nixpkgs/09061f748ee21f68a089cd5d91ec1859cd93d0be#git-bug",
8+ "source": "devbox-search",
9+ "version": "0.10.1",
10+ "systems": {
11+ "aarch64-darwin": {
12+ "outputs": [
13+ {
14+ "name": "out",
15+ "path": "/nix/store/cs52jih5d6zjp73z5aw73jmrgz8kk8nh-git-bug-0.10.1",
16+ "default": true
17+ }
18+ ],
19+ "store_path": "/nix/store/cs52jih5d6zjp73z5aw73jmrgz8kk8nh-git-bug-0.10.1"
20+ },
21+ "aarch64-linux": {
22+ "outputs": [
23+ {
24+ "name": "out",
25+ "path": "/nix/store/h06gd6mgm5jhb2ny43ql7xbxrrd76cxw-git-bug-0.10.1",
26+ "default": true
27+ }
28+ ],
29+ "store_path": "/nix/store/h06gd6mgm5jhb2ny43ql7xbxrrd76cxw-git-bug-0.10.1"
30+ },
31+ "x86_64-darwin": {
32+ "outputs": [
33+ {
34+ "name": "out",
35+ "path": "/nix/store/7kr8ypm571jkqacl9bk5cvfasj031h1l-git-bug-0.10.1",
36+ "default": true
37+ }
38+ ],
39+ "store_path": "/nix/store/7kr8ypm571jkqacl9bk5cvfasj031h1l-git-bug-0.10.1"
40+ },
41+ "x86_64-linux": {
42+ "outputs": [
43+ {
44+ "name": "out",
45+ "path": "/nix/store/gqs0rch1z1gpl2qr52rjgfaxjzqrxr7n-git-bug-0.10.1",
46+ "default": true
47+ }
48+ ],
49+ "store_path": "/nix/store/gqs0rch1z1gpl2qr52rjgfaxjzqrxr7n-git-bug-0.10.1"
50+ }
51+ }
52+ },
53 "git@latest": {
54 "last_modified": "2026-03-21T07:29:51Z",
55 "resolved": "github:NixOS/nixpkgs/09061f748ee21f68a089cd5d91ec1859cd93d0be#git",
+939,
-0
1@@ -0,0 +1,939 @@
2+# git-bug Integration Implementation Plan
3+
4+> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
5+
6+**Goal:** Integrate git-bug issue tracking into pgit static site generator, creating issue list pages and individual issue detail pages.
7+
8+**Architecture:** Use git-bug's Go library (cache and entities/bug packages) to read issues from the repository at build time, filter and categorize them, then generate static HTML pages using Go templates.
9+
10+**Tech Stack:** Go, git-bug library, Go templates (existing pgit template system)
11+
12+---
13+
14+## Task 1: Add git-bug Dependency
15+
16+**Files:**
17+- Modify: `go.mod`
18+- Run: `go mod tidy`
19+
20+**Step 1: Add git-bug dependency**
21+
22+Run: `go get github.com/git-bug/[email protected]`
23+
24+**Step 2: Verify go.mod updated**
25+
26+Check that `github.com/git-bug/git-bug v0.10.1` appears in go.mod
27+
28+**Step 3: Commit**
29+
30+```bash
31+jj commit -m "deps: add git-bug library for issue tracking"
32+```
33+
34+---
35+
36+## Task 2: Create Issue Data Structures
37+
38+**Files:**
39+- Create: `issues.go` (new file)
40+
41+**Step 1: Create issue data types**
42+
43+```go
44+package main
45+
46+import (
47+ "html/template"
48+ "time"
49+)
50+
51+// IssueData represents a git-bug issue for templates
52+type IssueData struct {
53+ ID string
54+ FullID string
55+ Title string
56+ Status string // "open" or "closed"
57+ Author string
58+ CreatedAt time.Time
59+ HumanDate string
60+ Labels []string
61+ CommentCount int // excludes original description
62+ Description template.HTML
63+ Comments []CommentData
64+ URL template.URL
65+}
66+
67+// CommentData represents a comment on an issue
68+type CommentData struct {
69+ Author string
70+ CreatedAt time.Time
71+ HumanDate string
72+ Body template.HTML
73+}
74+
75+// IssuesListPageData for list pages (open, closed, by label)
76+type IssuesListPageData struct {
77+ *PageData
78+ Filter string // "open", "closed", or "label"
79+ OpenCount int
80+ ClosedCount int
81+ Issues []*IssueData
82+ Label string // set when Filter == "label"
83+ AllLabels []string // all unique labels from open issues
84+}
85+
86+// IssueDetailPageData for individual issue pages
87+type IssueDetailPageData struct {
88+ *PageData
89+ Issue *IssueData
90+}
91+
92+// Active returns the active navigation item
93+func (i *IssuesListPageData) Active() string { return "issues" }
94+func (i *IssueDetailPageData) Active() string { return "issues" }
95+```
96+
97+**Step 2: Commit**
98+
99+```bash
100+jj commit -m "feat: add issue data structures"
101+```
102+
103+---
104+
105+## Task 3: Create Issue Loading Function
106+
107+**Files:**
108+- Modify: `issues.go` (add import and functions)
109+
110+**Step 1: Add imports and loading function**
111+
112+```go
113+import (
114+ "fmt"
115+ "net/url"
116+ "path/filepath"
117+ "strings"
118+
119+ "github.com/git-bug/git-bug/cache"
120+ "github.com/git-bug/git-bug/entities/bug"
121+ "github.com/git-bug/git-bug/repository"
122+)
123+
124+// loadIssues reads all issues from git-bug in the repository
125+func (c *Config) loadIssues() ([]*IssueData, error) {
126+ // Open the repository with git-bug's repo opener
127+ repo, err := repository.OpenGoGitRepo(c.RepoPath, nil, nil)
128+ if err != nil {
129+ return nil, fmt.Errorf("failed to open repository: %w", err)
130+ }
131+
132+ // Create cache for efficient querying
133+ repoCache, err := cache.NewRepoCacheNoEvents(repo)
134+ if err != nil {
135+ return nil, fmt.Errorf("failed to create repo cache: %w", err)
136+ }
137+ defer repoCache.Close()
138+
139+ // Read all bugs
140+ var issues []*IssueData
141+ bugCache := repoCache.Bugs()
142+
143+ for streamedBug := range bugCache.ReadAll() {
144+ if streamedBug.Err != nil {
145+ c.Logger.Warn("failed to read bug", "error", streamedBug.Err)
146+ continue
147+ }
148+
149+ b := streamedBug.Entity
150+ snap := b.Compile()
151+
152+ // Count comments (excluding the original description)
153+ commentCount := 0
154+ for _, op := range snap.Operations {
155+ if _, ok := op.(*bug.AddCommentOp); ok {
156+ commentCount++
157+ }
158+ }
159+
160+ // Build labels slice
161+ labels := make([]string, len(snap.Labels))
162+ copy(labels, snap.Labels)
163+
164+ // Build comments
165+ var comments []CommentData
166+ for _, op := range snap.Operations {
167+ if addCommentOp, ok := op.(*bug.AddCommentOp); ok {
168+ comments = append(comments, CommentData{
169+ Author: addCommentOp.Author.Name,
170+ CreatedAt: addCommentOp.Timestamp,
171+ HumanDate: humanizeTime(addCommentOp.Timestamp),
172+ Body: renderMarkdown(addCommentOp.Message),
173+ })
174+ }
175+ }
176+
177+ fullID := b.Id().String()
178+ issue := &IssueData{
179+ ID: getShortID(fullID),
180+ FullID: fullID,
181+ Title: snap.Title,
182+ Status: string(snap.Status),
183+ Author: snap.Author.Name,
184+ CreatedAt: snap.CreateTime,
185+ HumanDate: humanizeTime(snap.CreateTime),
186+ Labels: labels,
187+ CommentCount: commentCount,
188+ Description: renderMarkdown(snap.Description),
189+ Comments: comments,
190+ URL: c.getIssueURL(fullID),
191+ }
192+ issues = append(issues, issue)
193+ }
194+
195+ return issues, nil
196+}
197+
198+// getIssueURL generates the URL for an issue detail page
199+func (c *Config) getIssueURL(issueID string) template.URL {
200+ url := fmt.Sprintf("%sissues/%s.html", c.RootRelative, issueID)
201+ return template.URL(url)
202+}
203+
204+// getIssuesListURL generates URL for issues list pages
205+func (c *Config) getIssuesListURL(filter string, label string) template.URL {
206+ var path string
207+ switch filter {
208+ case "open", "closed":
209+ path = filepath.Join("issues", filter, "index.html")
210+ case "label":
211+ // URL-encode the label
212+ encodedLabel := url.PathEscape(label)
213+ path = filepath.Join("issues", "label", encodedLabel, "index.html")
214+ default:
215+ path = filepath.Join("issues", "open", "index.html")
216+ }
217+ return c.compileURL("/", path)
218+}
219+```
220+
221+**Step 2: Commit**
222+
223+```bash
224+jj commit -m "feat: add issue loading from git-bug"
225+```
226+
227+---
228+
229+## Task 4: Create Issue List Template
230+
231+**Files:**
232+- Create: `html/issues_list.page.tmpl`
233+
234+**Step 1: Create the list template**
235+
236+```html
237+{{template "base" .}}
238+
239+{{define "title"}}{{if eq .Filter "label"}}Issues labeled "{{.Label}}"{{else if eq .Filter "closed"}}Closed Issues{{else}}Open Issues{{end}} - {{.Repo.RepoName}}{{end}}
240+
241+{{define "content"}}
242+<div class="issues-page">
243+ <div class="issues-header">
244+ <h1>
245+ {{if eq .Filter "label"}}
246+ Issues labeled "{{.Label}}"
247+ {{else if eq .Filter "closed"}}
248+ Closed Issues
249+ {{else}}
250+ Open Issues
251+ {{end}}
252+ </h1>
253+
254+ <div class="issues-stats">
255+ {{if eq .Filter "open"}}
256+ <span class="stat active">{{.OpenCount}} Open</span>
257+ <a href="{{getIssuesListURL "closed" ""}}" class="stat">{{.ClosedCount}} Closed</a>
258+ {{else if eq .Filter "closed"}}
259+ <a href="{{getIssuesListURL "open" ""}}" class="stat">{{.OpenCount}} Open</a>
260+ <span class="stat active">{{.ClosedCount}} Closed</span>
261+ {{else}}
262+ <a href="{{getIssuesListURL "open" ""}}" class="stat">{{.OpenCount}} Open</a>
263+ <a href="{{getIssuesListURL "closed" ""}}" class="stat">{{.ClosedCount}} Closed</a>
264+ <span class="label-indicator">Label: {{.Label}}</span>
265+ {{end}}
266+ </div>
267+ </div>
268+
269+ {{if eq .Filter "open"}}
270+ <div class="issues-labels">
271+ <span>Filter by label:</span>
272+ {{range .AllLabels}}
273+ <a href="{{$.Repo.getIssuesListURL "label" .}}" class="label-link">{{.}}</a>
274+ {{end}}
275+ </div>
276+ {{end}}
277+
278+ <div class="issues-list">
279+ {{range .Issues}}
280+ <div class="issue-item">
281+ <div class="issue-item__main">
282+ <a href="{{.URL}}" class="issue-item__title">{{.Title}}</a>
283+ <div class="issue-item__meta">
284+ <span class="issue-id">#{{.ID}}</span>
285+ <span class="issue-status issue-status--{{.Status}}">{{.Status}}</span>
286+ {{if .Labels}}
287+ <span class="issue-labels">
288+ {{range .Labels}}<span class="issue-label">{{.}}</span>{{end}}
289+ </span>
290+ {{end}}
291+ </div>
292+ </div>
293+ <div class="issue-item__stats">
294+ <span class="issue-date" title="{{.CreatedAt}}">{{.HumanDate}}</span>
295+ {{if gt .CommentCount 0}}
296+ <span class="issue-comments">{{.CommentCount}} comment{{if ne .CommentCount 1}}s{{end}}</span>
297+ {{end}}
298+ </div>
299+ </div>
300+ {{else}}
301+ <p class="no-issues">No issues found.</p>
302+ {{end}}
303+ </div>
304+</div>
305+{{end}}
306+```
307+
308+**Step 2: Commit**
309+
310+```bash
311+jj commit -m "feat: add issues list template"
312+```
313+
314+---
315+
316+## Task 5: Create Issue Detail Template
317+
318+**Files:**
319+- Create: `html/issue_detail.page.tmpl`
320+
321+**Step 1: Create the detail template**
322+
323+```html
324+{{template "base" .}}
325+
326+{{define "title"}}{{.Issue.Title}} - Issue #{{.Issue.ID}} - {{.Repo.RepoName}}{{end}}
327+
328+{{define "content"}}
329+<div class="issue-detail">
330+ <div class="issue-detail__header">
331+ <h1>
332+ <span class="issue-status issue-status--{{.Issue.Status}}">{{.Issue.Status}}</span>
333+ {{.Issue.Title}}
334+ </h1>
335+ <div class="issue-detail__meta">
336+ <span class="issue-id">#{{.Issue.ID}}</span>
337+ <span class="issue-author">opened by {{.Issue.Author}}</span>
338+ <span class="issue-date" title="{{.Issue.CreatedAt}}">{{.Issue.HumanDate}}</span>
339+ </div>
340+ {{if .Issue.Labels}}
341+ <div class="issue-detail__labels">
342+ <span>Labels:</span>
343+ {{range .Issue.Labels}}<span class="issue-label">{{.}}</span>{{end}}
344+ </div>
345+ {{end}}
346+ </div>
347+
348+ <div class="issue-description">
349+ <div class="issue-comment">
350+ <div class="issue-comment__header">
351+ <span class="issue-comment__author">{{.Issue.Author}}</span>
352+ <span class="issue-comment__date" title="{{.Issue.CreatedAt}}">{{.Issue.HumanDate}}</span>
353+ </div>
354+ <div class="issue-comment__body">{{.Issue.Description}}</div>
355+ </div>
356+ </div>
357+
358+ {{if .Issue.Comments}}
359+ <div class="issue-comments">
360+ <h3>{{len .Issue.Comments}} Comment{{if ne (len .Issue.Comments) 1}}s{{end}}</h3>
361+ {{range .Issue.Comments}}
362+ <div class="issue-comment">
363+ <div class="issue-comment__header">
364+ <span class="issue-comment__author">{{.Author}}</span>
365+ <span class="issue-comment__date" title="{{.CreatedAt}}">{{.HumanDate}}</span>
366+ </div>
367+ <div class="issue-comment__body">{{.Body}}</div>
368+ </div>
369+ {{end}}
370+ </div>
371+ {{end}}
372+</div>
373+{{end}}
374+```
375+
376+**Step 2: Commit**
377+
378+```bash
379+jj commit -m "feat: add issue detail template"
380+```
381+
382+---
383+
384+## Task 6: Create Issue Page Generation Functions
385+
386+**Files:**
387+- Modify: `issues.go` (add generation functions)
388+
389+**Step 1: Add generation functions**
390+
391+```go
392+// writeIssueListPage generates an issues list page
393+func (c *Config) writeIssueListPage(data *PageData, filter string, label string, issues []*IssueData, openCount, closedCount int, allLabels []string) {
394+ c.Logger.Info("writing issues list", "filter", filter, "label", label, "count", len(issues))
395+
396+ pageData := &IssuesListPageData{
397+ PageData: data,
398+ Filter: filter,
399+ OpenCount: openCount,
400+ ClosedCount: closedCount,
401+ Issues: issues,
402+ Label: label,
403+ AllLabels: allLabels,
404+ }
405+
406+ var subdir string
407+ switch filter {
408+ case "open":
409+ subdir = "issues/open"
410+ case "closed":
411+ subdir = "issues/closed"
412+ case "label":
413+ encodedLabel := url.PathEscape(label)
414+ subdir = filepath.Join("issues/label", encodedLabel)
415+ }
416+
417+ c.writeHtml(&WriteData{
418+ Filename: "index.html",
419+ Subdir: subdir,
420+ Template: "html/issues_list.page.tmpl",
421+ Data: pageData,
422+ })
423+}
424+
425+// writeIssueDetailPage generates an individual issue page
426+func (c *Config) writeIssueDetailPage(data *PageData, issue *IssueData) {
427+ c.Logger.Info("writing issue detail", "id", issue.ID, "title", issue.Title)
428+
429+ pageData := &IssueDetailPageData{
430+ PageData: data,
431+ Issue: issue,
432+ }
433+
434+ c.writeHtml(&WriteData{
435+ Filename: fmt.Sprintf("%s.html", issue.FullID),
436+ Subdir: "issues",
437+ Template: "html/issue_detail.page.tmpl",
438+ Data: pageData,
439+ })
440+}
441+
442+// writeIssues generates all issue-related pages
443+func (c *Config) writeIssues(pageData *PageData) error {
444+ // Load all issues from git-bug
445+ issues, err := c.loadIssues()
446+ if err != nil {
447+ return fmt.Errorf("failed to load issues: %w", err)
448+ }
449+
450+ // If no issues, skip generation
451+ if len(issues) == 0 {
452+ c.Logger.Info("no git-bug issues found, skipping issue generation")
453+ return nil
454+ }
455+
456+ c.Logger.Info("loaded issues", "count", len(issues))
457+
458+ // Categorize issues
459+ var openIssues, closedIssues []*IssueData
460+ labelIssues := make(map[string][]*IssueData)
461+ allLabels := make(map[string]bool)
462+
463+ for _, issue := range issues {
464+ if issue.Status == "open" {
465+ openIssues = append(openIssues, issue)
466+ // Collect labels from open issues
467+ for _, label := range issue.Labels {
468+ allLabels[label] = true
469+ labelIssues[label] = append(labelIssues[label], issue)
470+ }
471+ } else {
472+ closedIssues = append(closedIssues, issue)
473+ }
474+ }
475+
476+ openCount := len(openIssues)
477+ closedCount := len(closedIssues)
478+
479+ // Build sorted label list
480+ var sortedLabels []string
481+ for label := range allLabels {
482+ sortedLabels = append(sortedLabels, label)
483+ }
484+ sort.Strings(sortedLabels)
485+
486+ // Generate individual issue pages for all issues
487+ for _, issue := range issues {
488+ c.writeIssueDetailPage(pageData, issue)
489+ }
490+
491+ // Generate list pages
492+ c.writeIssueListPage(pageData, "open", "", openIssues, openCount, closedCount, sortedLabels)
493+ c.writeIssueListPage(pageData, "closed", "", closedIssues, openCount, closedCount, sortedLabels)
494+
495+ // Generate label filter pages (only for open issues)
496+ for label, issues := range labelIssues {
497+ c.writeIssueListPage(pageData, "label", label, issues, openCount, closedCount, sortedLabels)
498+ }
499+
500+ return nil
501+}
502+```
503+
504+**Step 2: Add import for sort**
505+
506+Add `sort` to imports in `issues.go`
507+
508+**Step 3: Commit**
509+
510+```bash
511+jj commit -m "feat: add issue page generation functions"
512+```
513+
514+---
515+
516+## Task 7: Update Header Template for Issues Navigation
517+
518+**Files:**
519+- Modify: `html/header.partial.tmpl`
520+
521+**Step 1: Add Issues link to navigation**
522+
523+Add after the commits link (around line 16):
524+
525+```html
526+{{if .SiteURLs.IssuesURL}}{{if eq .Active "issues"}}<span class="active">issues</span>{{else}}<a href="{{.SiteURLs.IssuesURL}}">issues</a>{{end}}{{end}}
527+```
528+
529+**Step 2: Commit**
530+
531+```bash
532+jj commit -m "feat: add issues navigation link to header"
533+```
534+
535+---
536+
537+## Task 8: Add IssuesURL to SiteURLs
538+
539+**Files:**
540+- Modify: `main.go` (SiteURLs struct and getURLs method)
541+
542+**Step 1: Add IssuesURL to SiteURLs struct**
543+
544+Around line 167, add:
545+
546+```go
547+type SiteURLs struct {
548+ HomeURL template.URL
549+ CloneURL template.URL
550+ SummaryURL template.URL
551+ RefsURL template.URL
552+ IssuesURL template.URL // new
553+}
554+```
555+
556+**Step 2: Add IssuesURL generation to getURLs method**
557+
558+Around line 609, add:
559+
560+```go
561+func (c *Config) getURLs() *SiteURLs {
562+ return &SiteURLs{
563+ HomeURL: c.HomeURL,
564+ CloneURL: c.CloneURL,
565+ RefsURL: c.getRefsURL(),
566+ SummaryURL: c.getSummaryURL(),
567+ IssuesURL: c.getIssuesURL(),
568+ }
569+}
570+
571+// getIssuesURL returns the URL for the issues page
572+func (c *Config) getIssuesURL() template.URL {
573+ url := c.RootRelative + "issues/open/index.html"
574+ return template.URL(url)
575+}
576+```
577+
578+**Step 3: Commit**
579+
580+```bash
581+jj commit -m "feat: add IssuesURL to site URLs"
582+```
583+
584+---
585+
586+## Task 9: Add --issues CLI Flag and Integration
587+
588+**Files:**
589+- Modify: `main.go` (Config struct, flags, and writeRepo)
590+
591+**Step 1: Add Issues field to Config struct**
592+
593+Around line 34, add:
594+
595+```go
596+type Config struct {
597+ // ... existing fields ...
598+ Issues bool // enable git-bug issue generation
599+ // ... rest of fields ...
600+}
601+```
602+
603+**Step 2: Add --issues flag**
604+
605+Around line 1117, add:
606+
607+```go
608+var issuesFlag = flag.Bool("issues", false, "enable git-bug issue generation")
609+```
610+
611+**Step 3: Add Issues to config initialization**
612+
613+Around line 1146, add:
614+
615+```go
616+config := &Config{
617+ // ... existing fields ...
618+ Issues: *issuesFlag,
619+ // ... rest ...
620+}
621+```
622+
623+**Step 4: Integrate issue generation into writeRepo**
624+
625+Around line 740 (before `c.writeRefs`), add:
626+
627+```go
628+// Generate issue pages if enabled
629+if c.Issues {
630+ err := c.writeIssues(data)
631+ if err != nil {
632+ c.Logger.Warn("failed to write issues", "error", err)
633+ }
634+}
635+```
636+
637+**Step 5: Commit**
638+
639+```bash
640+jj commit -m "feat: add --issues flag and integrate issue generation"
641+```
642+
643+---
644+
645+## Task 10: Add CSS Styling for Issues
646+
647+**Files:**
648+- Modify: `html/header.partial.tmpl` (add style block) or create separate CSS
649+
650+**Step 1: Add styles to header template**
651+
652+Add inside the `<style>` block or in a style tag in header.partial.tmpl:
653+
654+```html
655+<style>
656+/* Issues styles */
657+.issues-page {
658+ max-width: 1200px;
659+ margin: 0 auto;
660+ padding: 20px;
661+}
662+
663+.issues-header {
664+ margin-bottom: 20px;
665+ border-bottom: 1px solid var(--border);
666+ padding-bottom: 15px;
667+}
668+
669+.issues-header h1 {
670+ margin-bottom: 10px;
671+}
672+
673+.issues-stats {
674+ display: flex;
675+ gap: 15px;
676+ align-items: center;
677+}
678+
679+.issues-stats .stat {
680+ padding: 5px 10px;
681+ border-radius: 4px;
682+ text-decoration: none;
683+}
684+
685+.issues-stats .stat.active {
686+ background: var(--link-color);
687+ color: var(--bg-color);
688+}
689+
690+.issues-labels {
691+ margin: 15px 0;
692+ padding: 10px;
693+ background: rgba(255,255,255,0.05);
694+ border-radius: 4px;
695+}
696+
697+.issues-labels .label-link {
698+ margin-left: 10px;
699+ padding: 2px 8px;
700+ background: var(--border);
701+ border-radius: 3px;
702+ font-size: 0.9em;
703+}
704+
705+.issue-item {
706+ display: flex;
707+ justify-content: space-between;
708+ align-items: flex-start;
709+ padding: 15px;
710+ border-bottom: 1px solid var(--border);
711+}
712+
713+.issue-item:hover {
714+ background: rgba(255,255,255,0.02);
715+}
716+
717+.issue-item__title {
718+ font-size: 1.1em;
719+ font-weight: bold;
720+ margin-bottom: 5px;
721+ display: block;
722+}
723+
724+.issue-item__meta {
725+ display: flex;
726+ gap: 10px;
727+ align-items: center;
728+ font-size: 0.85em;
729+ color: var(--text-color);
730+ opacity: 0.7;
731+}
732+
733+.issue-status {
734+ padding: 2px 8px;
735+ border-radius: 3px;
736+ font-size: 0.85em;
737+ font-weight: bold;
738+}
739+
740+.issue-status--open {
741+ background: #28a745;
742+ color: white;
743+}
744+
745+.issue-status--closed {
746+ background: #6c757d;
747+ color: white;
748+}
749+
750+.issue-labels {
751+ display: flex;
752+ gap: 5px;
753+}
754+
755+.issue-label {
756+ padding: 2px 6px;
757+ background: var(--border);
758+ border-radius: 3px;
759+ font-size: 0.8em;
760+}
761+
762+.issue-item__stats {
763+ text-align: right;
764+ font-size: 0.85em;
765+ opacity: 0.7;
766+}
767+
768+.issue-comments {
769+ margin-left: 10px;
770+}
771+
772+/* Issue detail */
773+.issue-detail {
774+ max-width: 1000px;
775+ margin: 0 auto;
776+ padding: 20px;
777+}
778+
779+.issue-detail__header {
780+ margin-bottom: 20px;
781+ padding-bottom: 15px;
782+ border-bottom: 1px solid var(--border);
783+}
784+
785+.issue-detail__header h1 {
786+ margin-bottom: 10px;
787+}
788+
789+.issue-detail__meta {
790+ display: flex;
791+ gap: 10px;
792+ align-items: center;
793+ margin-bottom: 10px;
794+}
795+
796+.issue-detail__labels {
797+ margin-top: 10px;
798+}
799+
800+.issue-description {
801+ margin-bottom: 30px;
802+}
803+
804+.issue-comment {
805+ padding: 15px;
806+ border: 1px solid var(--border);
807+ border-radius: 6px;
808+ margin-bottom: 15px;
809+}
810+
811+.issue-comment__header {
812+ display: flex;
813+ justify-content: space-between;
814+ margin-bottom: 10px;
815+ padding-bottom: 10px;
816+ border-bottom: 1px solid var(--border);
817+}
818+
819+.issue-comment__author {
820+ font-weight: bold;
821+}
822+
823+.issue-comment__date {
824+ opacity: 0.7;
825+ font-size: 0.9em;
826+}
827+
828+.issue-comment__body {
829+ line-height: 1.6;
830+}
831+
832+.issue-comments h3 {
833+ margin-bottom: 15px;
834+}
835+</style>
836+```
837+
838+**Step 2: Commit**
839+
840+```bash
841+jj commit -m "feat: add CSS styling for issues"
842+```
843+
844+---
845+
846+## Task 11: Build and Test
847+
848+**Files:**
849+- Run: Build and test commands
850+
851+**Step 1: Build the project**
852+
853+```bash
854+go build -o pgit
855+```
856+
857+**Step 2: Test without --issues (should work as before)**
858+
859+```bash
860+./pgit --revs HEAD --out ./test-output
861+```
862+
863+**Step 3: Test with --issues on repo without git-bug (should skip gracefully)**
864+
865+```bash
866+./pgit --revs HEAD --out ./test-output --issues
867+```
868+
869+Expected: Should see "no git-bug issues found, skipping issue generation" in logs
870+
871+**Step 4: Test with --issues on repo with git-bug (if available)**
872+
873+If you have a repo with git-bug:
874+```bash
875+./pgit --repo /path/to/repo-with-issues --revs HEAD --out ./test-output --issues
876+```
877+
878+Expected: Should see issues pages generated
879+
880+**Step 5: Commit**
881+
882+```bash
883+jj commit -m "build: verify issues feature compiles and runs"
884+```
885+
886+---
887+
888+## Task 12: Add Template Function for IssuesListURL
889+
890+**Files:**
891+- Modify: `main.go` (writeHtml method to add template functions)
892+
893+**Step 1: Add template function**
894+
895+In `writeHtml` method, modify the template parsing to include a function map:
896+
897+```go
898+func (c *Config) writeHtml(writeData *WriteData) {
899+ // Create function map
900+ funcMap := template.FuncMap{
901+ "getIssuesListURL": func(filter string, label string) template.URL {
902+ return c.getIssuesListURL(filter, label)
903+ },
904+ }
905+
906+ ts, err := template.New("").Funcs(funcMap).ParseFS(
907+ embedFS,
908+ writeData.Template,
909+ "html/header.partial.tmpl",
910+ "html/footer.partial.tmpl",
911+ "html/base.layout.tmpl",
912+ )
913+ bail(err)
914+
915+ // ... rest of method ...
916+}
917+```
918+
919+**Step 2: Commit**
920+
921+```bash
922+jj commit -m "feat: add getIssuesListURL template function"
923+```
924+
925+---
926+
927+## Summary
928+
929+After completing all tasks, the pgit static site generator will:
930+
931+1. Support `--issues` flag to enable git-bug integration
932+2. Generate `/issues/open/index.html` with all open issues
933+3. Generate `/issues/closed/index.html` with all closed issues
934+4. Generate `/issues/label/{label}/index.html` for each label (URL-encoded)
935+5. Generate `/issues/{short-hash}.html` for each issue
936+6. Show issue counts and navigation links on list pages
937+7. Display individual issues with title, status, labels, description, and comments
938+8. Add "Issues" link to site navigation
939+
940+All pages will match the existing pgit styling and use the same template system.
1@@ -0,0 +1,129 @@
2+# CSS Build Pipeline Improvement - Design Document
3+
4+**Date:** 2026-04-08
5+
6+## Goal
7+
8+Improve the CSS build pipeline by concatenating all CSS files in the proper precedence order, minimizing the result, appending a content-based hash to the filename, and inserting the correct link in the header.
9+
10+## Architecture
11+
12+Build-time CSS processing integrated into the existing Go build flow. CSS files are concatenated in precedence order, minified using a Go library, hashed for cache-busting, and the resulting filename is passed to templates.
13+
14+**Tech Stack:**
15+- Go 1.21+
16+- `github.com/tdewolff/minify/v2` - CSS minification library
17+- SHA256 for content hashing
18+
19+---
20+
21+## Current State
22+
23+**Current CSS Setup:**
24+- `static/pgit.css` - Main stylesheet (1380 lines, ~23KB)
25+- `vars.css` - Dynamically generated from Chroma theme colors
26+- `syntax.css` - Dynamically generated for syntax highlighting
27+- CSS links are hardcoded in `html/base.layout.tmpl`
28+
29+**Current Build Flow:**
30+1. `copyStatic()` copies static files as-is
31+2. `vars.css` and `syntax.css` are generated in main.go
32+3. No minification or concatenation happens
33+
34+---
35+
36+## Design Decisions
37+
38+### CSS Precedence Order
39+
40+1. **`pgit.css`** - Base styles (default variables, layout, components)
41+2. **`vars.css`** - Theme overrides (CSS variables from Chroma theme)
42+3. **`syntax.css`** - Syntax highlighting (`.chroma*` selectors)
43+
44+This order ensures base styles load first, theme variables override defaults, and syntax highlighting has its specific selectors applied last.
45+
46+### CSS Minification Library
47+
48+**Selected:** `github.com/tdewolff/minify/v2`
49+
50+**Rationale:**
51+- Pure Go implementation (no external dependencies)
52+- Very fast (~50-70MB/s for CSS processing)
53+- Safe minifications (removes comments/whitespace, optimizes colors/numbers, but won't restructure rules)
54+- Well maintained (4.1k stars, active development)
55+- Simple API for integration
56+- Can also handle HTML minification if needed in future
57+
58+**Typical compression:** 10-15% reduction for CSS files.
59+
60+### Build Flow
61+
62+**New steps in `main()`:**
63+
64+1. **Collect CSS content in order:**
65+ - Read `static/pgit.css` from embedded FS
66+ - Generate `vars.css` content (from existing `style()` function)
67+ - Generate `syntax.css` content (from existing `formatter.WriteCSS()`)
68+
69+2. **Concatenate:** `pgit.css` + `vars.css` + `syntax.css`
70+
71+3. **Minify** using `tdewolff/minify/v2/css`
72+
73+4. **Generate hash** (SHA256, first 8 chars of hex encoding)
74+
75+5. **Write file:** `styles.{hash}.css`
76+
77+6. **Update Config** with the CSS filename for templates
78+
79+7. **Update template** `html/base.layout.tmpl` to use single CSS link
80+
81+### Output Structure
82+
83+```
84+public/
85+ styles.a3f7b2c1.css # Minified, hashed bundle (replaces pgit.css, vars.css, syntax.css)
86+ index.html # References styles.a3f7b2c1.css
87+ ...
88+```
89+
90+---
91+
92+## Files to Modify
93+
94+1. **`main.go`** - Add CSS bundling logic
95+ - Add `CSSFile` field to `Config` struct
96+ - Create `bundleCSS()` function
97+ - Call it in `main()` after theme setup, before `writeRepo()`
98+
99+2. **`html/base.layout.tmpl`** - Update CSS link
100+ - Replace two `<link>` tags with single `{{.Repo.CSSFile}}` reference
101+
102+3. **`go.mod`** - Add tdewolff/minify dependency
103+
104+---
105+
106+## Out of Scope
107+
108+- HTML minification (pages are already lightweight)
109+- Source maps for CSS (not needed)
110+- External CSS processing tools (keeping it pure Go)
111+- Additional CSS files (none planned)
112+
113+---
114+
115+## Success Criteria
116+
117+- [ ] All three CSS files concatenated in correct order
118+- [ ] Result is minified (smaller than sum of individual files)
119+- [ ] Filename includes content-based hash (e.g., `styles.a3f7b2c1.css`)
120+- [ ] HTML templates reference the hashed CSS file correctly
121+- [ ] Site renders identically to before (visual parity)
122+- [ ] Build completes without errors
123+
124+## Benefits
125+
126+- ✅ **Single HTTP request** for all CSS (vs. 3 separate files)
127+- ✅ **Cache-busting** via content hash in filename
128+- ✅ **Smaller file size** (~10-15% reduction from minification)
129+- ✅ **No external tools** - pure Go implementation
130+- ✅ **Deterministic** - same content = same hash
1@@ -0,0 +1,402 @@
2+# CSS Build Pipeline Implementation Plan
3+
4+> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
5+
6+**Goal:** Concatenate all CSS files in proper precedence order, minimize, add content hash to filename, and update templates to reference the bundled CSS.
7+
8+**Architecture:** Build-time CSS processing integrated into existing Go build flow using tdewolff/minify library.
9+
10+**Tech Stack:** Go 1.21+, github.com/tdewolff/minify/v2, SHA256 hashing
11+
12+---
13+
14+## Task 1: Add CSS minification dependency
15+
16+**Files:**
17+- Modify: `go.mod`
18+
19+**Step 1: Add tdewolff/minify dependency**
20+
21+Run: `go get github.com/tdewolff/minify/v2/css`
22+
23+Expected: Module added to go.mod and go.sum updated
24+
25+**Step 2: Commit**
26+
27+```bash
28+jj commit -m "deps: add tdewolff/minify for CSS processing"
29+```
30+
31+---
32+
33+## Task 2: Add CSSFile field to Config struct
34+
35+**Files:**
36+- Modify: `main.go:39-81` (Config struct)
37+
38+**Step 1: Add CSSFile field**
39+
40+Add to Config struct after line 80:
41+
42+```go
43+ // CSS bundle filename (with content hash)
44+ CSSFile string
45+```
46+
47+**Step 2: Verify struct compiles**
48+
49+Run: `go build -o /dev/null .`
50+
51+Expected: Build succeeds
52+
53+**Step 3: Commit**
54+
55+```bash
56+jj commit -m "feat: add CSSFile field to Config struct"
57+```
58+
59+---
60+
61+## Task 3: Create CSS bundling function
62+
63+**Files:**
64+- Modify: `main.go` (add new function before main())
65+
66+**Step 1: Add imports for minification and hashing**
67+
68+Add to imports section:
69+
70+```go
71+ "crypto/sha256"
72+ "encoding/hex"
73+
74+ "github.com/tdewolff/minify/v2"
75+ "github.com/tdewolff/minify/v2/css"
76+```
77+
78+**Step 2: Create bundleCSS function**
79+
80+Add before `main()` function:
81+
82+```go
83+// bundleCSS concatenates, minifies, and hashes all CSS files
84+// Returns the filename of the bundled CSS (e.g., "styles.a3f7b2c1.css")
85+func (c *Config) bundleCSS() (string, error) {
86+ c.Logger.Info("bundling CSS files")
87+
88+ // Initialize minifier
89+ m := minify.New()
90+ m.AddFunc("text/css", css.Minify)
91+
92+ var buf bytes.Buffer
93+
94+ // 1. Read pgit.css from embedded static FS
95+ pgitCSS, err := staticFS.ReadFile("static/pgit.css")
96+ if err != nil {
97+ return "", fmt.Errorf("failed to read pgit.css: %w", err)
98+ }
99+ buf.Write(pgitCSS)
100+ buf.WriteString("\n")
101+
102+ // 2. Generate vars.css content
103+ varsCSS := style(*c.Theme)
104+ buf.WriteString(varsCSS)
105+ buf.WriteString("\n")
106+
107+ // 3. Generate syntax.css content
108+ var syntaxBuf bytes.Buffer
109+ err = c.Formatter.WriteCSS(&syntaxBuf, c.Theme)
110+ if err != nil {
111+ return "", fmt.Errorf("failed to generate syntax.css: %w", err)
112+ }
113+ buf.Write(syntaxBuf.Bytes())
114+
115+ // 4. Minify the concatenated CSS
116+ minified, err := m.Bytes("text/css", buf.Bytes())
117+ if err != nil {
118+ return "", fmt.Errorf("failed to minify CSS: %w", err)
119+ }
120+
121+ // 5. Generate content hash (first 8 chars of SHA256)
122+ hash := sha256.Sum256(minified)
123+ hashStr := hex.EncodeToString(hash[:8])
124+
125+ // 6. Create filename with hash
126+ filename := fmt.Sprintf("styles.%s.css", hashStr)
127+
128+ // 7. Write to output directory
129+ outPath := filepath.Join(c.Outdir, filename)
130+ err = os.WriteFile(outPath, minified, 0644)
131+ if err != nil {
132+ return "", fmt.Errorf("failed to write CSS bundle: %w", err)
133+ }
134+
135+ c.Logger.Info("CSS bundle created", "filename", filename, "size", len(minified))
136+
137+ return filename, nil
138+}
139+```
140+
141+**Step 3: Verify function compiles**
142+
143+Run: `go build -o /dev/null .`
144+
145+Expected: Build succeeds
146+
147+**Step 4: Commit**
148+
149+```bash
150+jj commit -m "feat: add CSS bundling function"
151+```
152+
153+---
154+
155+## Task 4: Integrate CSS bundling into main build flow
156+
157+**Files:**
158+- Modify: `main.go:1349-1367` (main function end)
159+
160+**Step 1: Call bundleCSS before writeRepo**
161+
162+Locate this section in main():
163+```go
164+ config.writeRepo()
165+ err = config.copyStatic("static")
166+ bail(err)
167+```
168+
169+Replace with:
170+```go
171+ // Bundle CSS files before generating site
172+ cssFile, err := config.bundleCSS()
173+ bail(err)
174+ config.CSSFile = cssFile
175+
176+ config.writeRepo()
177+
178+ // Note: copyStatic is no longer needed for CSS files since they're bundled
179+ // but we keep it for any other static assets
180+ err = config.copyStatic("static")
181+ bail(err)
182+```
183+
184+**Step 2: Remove old CSS generation code**
185+
186+Remove this section at the end of main():
187+```go
188+ styles := style(*theme)
189+ err = os.WriteFile(filepath.Join(out, "vars.css"), []byte(styles), 0644)
190+ if err != nil {
191+ panic(err)
192+ }
193+
194+ fp := filepath.Join(out, "syntax.css")
195+ w, err := os.OpenFile(fp, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
196+ if err != nil {
197+ bail(err)
198+ }
199+ err = formatter.WriteCSS(w, theme)
200+ if err != nil {
201+ bail(err)
202+ }
203+```
204+
205+**Step 3: Verify build succeeds**
206+
207+Run: `go build -o pgit .`
208+
209+Expected: Build succeeds
210+
211+**Step 4: Commit**
212+
213+```bash
214+jj commit -m "feat: integrate CSS bundling into build flow"
215+```
216+
217+---
218+
219+## Task 5: Update base layout template
220+
221+**Files:**
222+- Modify: `html/base.layout.tmpl:13-15`
223+
224+**Step 1: Replace CSS link tags**
225+
226+Current:
227+```html
228+ <link rel="stylesheet" href="{{.Repo.RootRelative}}pgit.css" />
229+ <link rel="stylesheet" href="{{.Repo.RootRelative}}vars.css" />
230+```
231+
232+Replace with:
233+```html
234+ <link rel="stylesheet" href="{{.Repo.RootRelative}}{{.Repo.CSSFile}}" />
235+```
236+
237+**Step 2: Verify template syntax**
238+
239+No build step for templates - will be validated at runtime
240+
241+**Step 3: Commit**
242+
243+```bash
244+jj commit -m "feat: update template to use bundled CSS"
245+```
246+
247+---
248+
249+## Task 6: Test the implementation
250+
251+**Files:**
252+- Test: Run build and verify output
253+
254+**Step 1: Build the binary**
255+
256+Run: `go build -o pgit .`
257+
258+Expected: Clean build
259+
260+**Step 2: Run test-site target**
261+
262+Run: `make test-site`
263+
264+Expected: Build completes successfully
265+
266+**Step 3: Verify CSS bundle was created**
267+
268+Run: `ls -la testdata.site/*.css`
269+
270+Expected: Single file `styles.{hash}.css` exists (no pgit.css, vars.css, syntax.css)
271+
272+**Step 4: Verify file content**
273+
274+Run: `head -5 testdata.site/styles.*.css`
275+
276+Expected: Minified CSS (no whitespace, all on few lines)
277+
278+**Step 5: Verify HTML references correct file**
279+
280+Run: `grep -o 'styles\.[a-f0-9]*\.css' testdata.site/index.html`
281+
282+Expected: Matches the filename from step 3
283+
284+**Step 6: Check file size reduction**
285+
286+Run: `wc -c static/pgit.css` and compare with bundled file
287+
288+Expected: Bundled file should be smaller than original pgit.css alone (due to minification)
289+
290+**Step 7: Commit**
291+
292+```bash
293+jj commit -m "test: verify CSS bundling works correctly"
294+```
295+
296+---
297+
298+## Task 7: Verify visual parity
299+
300+**Files:**
301+- Test: Compare rendered output
302+
303+**Step 1: Start a local server**
304+
305+Run: `cd testdata.site && python3 -m http.server 8080 &`
306+
307+**Step 2: Check a page renders correctly**
308+
309+Open browser or use curl: `curl -s http://localhost:8080/index.html | head -20`
310+
311+Expected: HTML loads, CSS is referenced correctly
312+
313+**Step 3: Verify CSS loads**
314+
315+Run: `curl -s http://localhost:8080/styles.*.css | head -5`
316+
317+Expected: Returns minified CSS content
318+
319+**Step 4: Stop test server**
320+
321+Run: `pkill -f "http.server 8080"`
322+
323+**Step 5: Commit**
324+
325+```bash
326+jj commit -m "test: verify visual parity and CSS loading"
327+```
328+
329+---
330+
331+## Task 8: Clean up (optional)
332+
333+**Files:**
334+- Modify: `main.go` (remove unused code if desired)
335+
336+**Step 1: Review if copyStatic is still needed**
337+
338+If `static/` only contained CSS files, we can remove the `copyStatic` call entirely.
339+
340+Check: `ls static/`
341+
342+If only CSS files, remove:
343+```go
344+ err = config.copyStatic("static")
345+ bail(err)
346+```
347+
348+**Step 2: Commit if changes made**
349+
350+```bash
351+jj commit -m "cleanup: remove unused copyStatic call"
352+```
353+
354+---
355+
356+## Task 9: Final verification
357+
358+**Files:**
359+- Test: Full build pipeline
360+
361+**Step 1: Clean and rebuild**
362+
363+Run:
364+```bash
365+make clean
366+make build
367+make test-site
368+```
369+
370+Expected: All steps succeed
371+
372+**Step 2: Verify final output**
373+
374+Run: `ls -la testdata.site/ | grep -E '\.(css|html)$'`
375+
376+Expected:
377+- One CSS file: `styles.{hash}.css`
378+- Multiple HTML files
379+- No `pgit.css`, `vars.css`, or `syntax.css`
380+
381+**Step 3: Final commit**
382+
383+```bash
384+jj commit -m "feat: complete CSS build pipeline improvement"
385+```
386+
387+---
388+
389+## Summary
390+
391+After completing all tasks:
392+
393+1. ✅ All three CSS files concatenated in correct order (pgit.css → vars.css → syntax.css)
394+2. ✅ Result is minified using tdewolff/minify
395+3. ✅ Filename includes 8-char content hash (e.g., `styles.a3f7b2c1.css`)
396+4. ✅ HTML templates reference the hashed CSS file via `{{.Repo.CSSFile}}`
397+5. ✅ Site renders identically to before
398+6. ✅ Build completes without errors
399+
400+**Files changed:**
401+- `go.mod` - Added tdewolff/minify dependency
402+- `main.go` - Added bundleCSS function and integrated into build flow
403+- `html/base.layout.tmpl` - Updated to use bundled CSS
M
go.mod
+104,
-4
1@@ -1,17 +1,117 @@
2 module github.com/picosh/pgit
3
4-go 1.24
5+go 1.25.0
6
7 require (
8 github.com/alecthomas/chroma/v2 v2.13.0
9- github.com/dustin/go-humanize v1.0.0
10+ github.com/dustin/go-humanize v1.0.1
11 github.com/gogs/git-module v1.6.0
12 github.com/gomarkdown/markdown v0.0.0-20260217112301-37c66b85d6ab
13 )
14
15 require (
16+ dario.cat/mergo v1.0.2 // indirect
17+ github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
18+ github.com/99designs/gqlgen v0.17.89 // indirect
19+ github.com/99designs/keyring v1.2.2 // indirect
20+ github.com/MichaelMure/go-term-text v0.3.1 // indirect
21+ github.com/Microsoft/go-winio v0.6.2 // indirect
22+ github.com/ProtonMail/go-crypto v1.4.1 // indirect
23+ github.com/RoaringBitmap/roaring v1.9.4 // indirect
24+ github.com/VividCortex/ewma v1.2.0 // indirect
25+ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
26+ github.com/agnivade/levenshtein v1.2.1 // indirect
27+ github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect
28+ github.com/awesome-gocui/gocui v1.1.0 // indirect
29+ github.com/bits-and-blooms/bitset v1.24.4 // indirect
30+ github.com/blevesearch/bleve v1.0.14 // indirect
31+ github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
32+ github.com/blevesearch/mmap-go v1.2.0 // indirect
33+ github.com/blevesearch/segment v0.9.1 // indirect
34+ github.com/blevesearch/snowballstem v0.9.0 // indirect
35+ github.com/blevesearch/zap/v11 v11.0.14 // indirect
36+ github.com/blevesearch/zap/v12 v12.0.14 // indirect
37+ github.com/blevesearch/zap/v13 v13.0.6 // indirect
38+ github.com/blevesearch/zap/v14 v14.0.5 // indirect
39+ github.com/blevesearch/zap/v15 v15.0.3 // indirect
40+ github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
41+ github.com/cloudflare/circl v1.6.3 // indirect
42+ github.com/couchbase/vellum v1.0.2 // indirect
43+ github.com/cyphar/filepath-securejoin v0.6.1 // indirect
44+ github.com/danieljoos/wincred v1.2.3 // indirect
45+ github.com/davecgh/go-spew v1.1.1 // indirect
46 github.com/dlclark/regexp2 v1.11.0 // indirect
47+ github.com/dvsekhvalnov/jose2go v1.8.0 // indirect
48+ github.com/emirpasic/gods v1.18.1 // indirect
49+ github.com/fatih/color v1.19.0 // indirect
50+ github.com/gdamore/encoding v1.0.1 // indirect
51+ github.com/gdamore/tcell/v2 v2.13.8 // indirect
52+ github.com/git-bug/git-bug v0.10.1 // indirect
53+ github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
54+ github.com/go-git/go-billy/v5 v5.8.0 // indirect
55+ github.com/go-git/go-git/v5 v5.17.2 // indirect
56+ github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
57+ github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
58+ github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
59+ github.com/golang/protobuf v1.5.4 // indirect
60+ github.com/golang/snappy v1.0.0 // indirect
61+ github.com/google/go-querystring v1.2.0 // indirect
62+ github.com/google/uuid v1.6.0 // indirect
63+ github.com/gorilla/mux v1.8.1 // indirect
64+ github.com/gorilla/websocket v1.5.3 // indirect
65+ github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
66+ github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
67+ github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
68+ github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
69+ github.com/inconshreveable/mousetrap v1.1.0 // indirect
70+ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
71+ github.com/kevinburke/ssh_config v1.6.0 // indirect
72+ github.com/klauspost/cpuid/v2 v2.3.0 // indirect
73+ github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
74+ github.com/mattn/go-colorable v0.1.14 // indirect
75+ github.com/mattn/go-isatty v0.0.20 // indirect
76+ github.com/mattn/go-runewidth v0.0.22 // indirect
77 github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75 // indirect
78- github.com/stretchr/testify v1.8.1 // indirect
79- golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
80+ github.com/mschoch/smat v0.2.0 // indirect
81+ github.com/mtibben/percent v0.2.1 // indirect
82+ github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 // indirect
83+ github.com/pjbgf/sha1cd v0.5.0 // indirect
84+ github.com/pkg/errors v0.9.1 // indirect
85+ github.com/pmezard/go-difflib v1.0.0 // indirect
86+ github.com/rivo/uniseg v0.4.7 // indirect
87+ github.com/sergi/go-diff v1.4.0 // indirect
88+ github.com/shurcooL/githubv4 v0.0.0-20260209031235-2402fdf4a9ed // indirect
89+ github.com/shurcooL/graphql v0.0.0-20240915155400-7ee5256398cf // indirect
90+ github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect
91+ github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92 // indirect
92+ github.com/skeema/knownhosts v1.3.2 // indirect
93+ github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
94+ github.com/sosodev/duration v1.4.0 // indirect
95+ github.com/spf13/cobra v1.10.2 // indirect
96+ github.com/spf13/pflag v1.0.10 // indirect
97+ github.com/steveyen/gtreap v0.1.0 // indirect
98+ github.com/stretchr/testify v1.11.1 // indirect
99+ github.com/tdewolff/minify/v2 v2.24.12 // indirect
100+ github.com/tdewolff/parse/v2 v2.8.11 // indirect
101+ github.com/vbauerster/mpb/v8 v8.12.0 // indirect
102+ github.com/vektah/gqlparser/v2 v2.5.32 // indirect
103+ github.com/willf/bitset v1.1.11 // indirect
104+ github.com/xanzy/ssh-agent v0.3.3 // indirect
105+ gitlab.com/gitlab-org/api/client-go v1.46.0 // indirect
106+ go.etcd.io/bbolt v1.4.3 // indirect
107+ golang.org/x/crypto v0.49.0 // indirect
108+ golang.org/x/mod v0.34.0 // indirect
109+ golang.org/x/net v0.52.0 // indirect
110+ golang.org/x/oauth2 v0.36.0 // indirect
111+ golang.org/x/sync v0.20.0 // indirect
112+ golang.org/x/sys v0.42.0 // indirect
113+ golang.org/x/telemetry v0.0.0-20260316223853-b6b0c46d1ccd // indirect
114+ golang.org/x/term v0.41.0 // indirect
115+ golang.org/x/text v0.35.0 // indirect
116+ golang.org/x/time v0.15.0 // indirect
117+ golang.org/x/tools v0.43.0 // indirect
118+ golang.org/x/vuln v1.1.4 // indirect
119+ google.golang.org/protobuf v1.36.11 // indirect
120+ gopkg.in/warnings.v0 v0.1.2 // indirect
121+ gopkg.in/yaml.v3 v3.0.1 // indirect
122 )
M
go.sum
+335,
-0
1@@ -1,9 +1,86 @@
2+dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
3+dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
4+github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=
5+github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
6+github.com/99designs/gqlgen v0.17.89 h1:KzEcxPiMgQoMw3m/E85atUEHyZyt0PbAflMia5Kw8z8=
7+github.com/99designs/gqlgen v0.17.89/go.mod h1:GFqruTVGB7ZTdrf1uzOagpXbY7DrEt1pIxnTdhIbWvQ=
8+github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0=
9+github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk=
10+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
11+github.com/MichaelMure/go-term-text v0.3.1 h1:Kw9kZanyZWiCHOYu9v/8pWEgDQ6UVN9/ix2Vd2zzWf0=
12+github.com/MichaelMure/go-term-text v0.3.1/go.mod h1:QgVjAEDUnRMlzpS6ky5CGblux7ebeiLnuy9dAaFZu8o=
13+github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
14+github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
15+github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
16+github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM=
17+github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
18+github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
19+github.com/RoaringBitmap/roaring v1.9.4 h1:yhEIoH4YezLYT04s1nHehNO64EKFTop/wBhxv2QzDdQ=
20+github.com/RoaringBitmap/roaring v1.9.4/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90=
21+github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
22+github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
23+github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
24+github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
25+github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM=
26+github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU=
27 github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU=
28 github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
29 github.com/alecthomas/chroma/v2 v2.13.0 h1:VP72+99Fb2zEcYM0MeaWJmV+xQvz5v5cxRHd+ooU1lI=
30 github.com/alecthomas/chroma/v2 v2.13.0/go.mod h1:BUGjjsD+ndS6eX37YgTchSEG+Jg9Jv1GiZs9sqPqztk=
31 github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
32 github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
33+github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
34+github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
35+github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
36+github.com/awesome-gocui/gocui v1.1.0 h1:db2j7yFEoHZjpQFeE2xqiatS8bm1lO3THeLwE6MzOII=
37+github.com/awesome-gocui/gocui v1.1.0/go.mod h1:M2BXkrp7PR97CKnPRT7Rk0+rtswChPtksw/vRAESGpg=
38+github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
39+github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE=
40+github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
41+github.com/blevesearch/bleve v1.0.14 h1:Q8r+fHTt35jtGXJUM0ULwM3Tzg+MRfyai4ZkWDy2xO4=
42+github.com/blevesearch/bleve v1.0.14/go.mod h1:e/LJTr+E7EaoVdkQZTfoz7dt4KoDNvDbLb8MSKuNTLQ=
43+github.com/blevesearch/blevex v1.0.0/go.mod h1:2rNVqoG2BZI8t1/P1awgTKnGlx5MP9ZbtEciQaNhswc=
44+github.com/blevesearch/cld2 v0.0.0-20200327141045-8b5f551d37f5/go.mod h1:PN0QNTLs9+j1bKy3d/GB/59wsNBFC4sWLWG3k69lWbc=
45+github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
46+github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
47+github.com/blevesearch/mmap-go v1.0.2/go.mod h1:ol2qBqYaOUsGdm7aRMRrYGgPvnwLe6Y+7LMvAB5IbSA=
48+github.com/blevesearch/mmap-go v1.2.0 h1:l33nNKPFcBjJUMwem6sAYJPUzhUCABoK9FxZDGiFNBI=
49+github.com/blevesearch/mmap-go v1.2.0/go.mod h1:Vd6+20GBhEdwJnU1Xohgt88XCD/CTWcqbCNxkZpyBo0=
50+github.com/blevesearch/segment v0.9.0/go.mod h1:9PfHYUdQCgHktBgvtUOF4x+pc4/l8rdH0u5spnW85UQ=
51+github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU=
52+github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw=
53+github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
54+github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs=
55+github.com/blevesearch/zap/v11 v11.0.14 h1:IrDAvtlzDylh6H2QCmS0OGcN9Hpf6mISJlfKjcwJs7k=
56+github.com/blevesearch/zap/v11 v11.0.14/go.mod h1:MUEZh6VHGXv1PKx3WnCbdP404LGG2IZVa/L66pyFwnY=
57+github.com/blevesearch/zap/v12 v12.0.14 h1:2o9iRtl1xaRjsJ1xcqTyLX414qPAwykHNV7wNVmbp3w=
58+github.com/blevesearch/zap/v12 v12.0.14/go.mod h1:rOnuZOiMKPQj18AEKEHJxuI14236tTQ1ZJz4PAnWlUg=
59+github.com/blevesearch/zap/v13 v13.0.6 h1:r+VNSVImi9cBhTNNR+Kfl5uiGy8kIbb0JMz/h8r6+O4=
60+github.com/blevesearch/zap/v13 v13.0.6/go.mod h1:L89gsjdRKGyGrRN6nCpIScCvvkyxvmeDCwZRcjjPCrw=
61+github.com/blevesearch/zap/v14 v14.0.5 h1:NdcT+81Nvmp2zL+NhwSvGSLh7xNgGL8QRVZ67njR0NU=
62+github.com/blevesearch/zap/v14 v14.0.5/go.mod h1:bWe8S7tRrSBTIaZ6cLRbgNH4TUDaC9LZSpRGs85AsGY=
63+github.com/blevesearch/zap/v15 v15.0.3 h1:Ylj8Oe+mo0P25tr9iLPp33lN6d4qcztGjaIsP51UxaY=
64+github.com/blevesearch/zap/v15 v15.0.3/go.mod h1:iuwQrImsh1WjWJ0Ue2kBqY83a0rFtJTqfa9fp1rbVVU=
65+github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
66+github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
67+github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
68+github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
69+github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
70+github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
71+github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
72+github.com/couchbase/ghistogram v0.1.0/go.mod h1:s1Jhy76zqfEecpNWJfWUiKZookAFaiGOEoyzgHt9i7k=
73+github.com/couchbase/moss v0.1.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs=
74+github.com/couchbase/vellum v1.0.2 h1:BrbP0NKiyDdndMPec8Jjhy0U47CZ0Lgx3xUC2r9rZqw=
75+github.com/couchbase/vellum v1.0.2/go.mod h1:FcwrEivFpNi24R3jLOs3n+fs5RnuQnQqCLBJ1uAg1W4=
76+github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
77+github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
78+github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
79+github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
80+github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8=
81+github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM=
82+github.com/cznic/strutil v0.0.0-20181122101858-275e90344537/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc=
83+github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ=
84+github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs=
85 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
86 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
87 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
88@@ -11,29 +88,287 @@ github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxK
89 github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
90 github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
91 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
92+github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
93+github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
94+github.com/dvsekhvalnov/jose2go v1.8.0 h1:LqkkVKAlHFfH9LOEl5fe4p/zL02OhWE7pCufMBG2jLA=
95+github.com/dvsekhvalnov/jose2go v1.8.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=
96+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
97+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
98+github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64=
99+github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
100+github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
101+github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
102+github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
103+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
104+github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
105+github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
106+github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
107+github.com/gdamore/tcell/v2 v2.4.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU=
108+github.com/gdamore/tcell/v2 v2.13.8 h1:Mys/Kl5wfC/GcC5Cx4C2BIQH9dbnhnkPgS9/wF3RlfU=
109+github.com/gdamore/tcell/v2 v2.13.8/go.mod h1:+Wfe208WDdB7INEtCsNrAN6O2m+wsTPk1RAovjaILlo=
110+github.com/git-bug/git-bug v0.10.1 h1:mjJK/wrfKWUze5EkVpJj+3N3LzSfeujqqH1qq8x4aco=
111+github.com/git-bug/git-bug v0.10.1/go.mod h1:43BRtb/Nr6QEJlNLkLY/64vyopxA0y5nvjSNsktnan8=
112+github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
113+github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
114+github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
115+github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
116+github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0=
117+github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY=
118+github.com/go-git/go-git/v5 v5.17.2 h1:B+nkdlxdYrvyFK4GPXVU8w1U+YkbsgciIR7f2sZJ104=
119+github.com/go-git/go-git/v5 v5.17.2/go.mod h1:pW/VmeqkanRFqR6AljLcs7EA7FbZaN5MQqO7oZADXpo=
120+github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
121+github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
122+github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0=
123+github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
124 github.com/gogs/git-module v1.6.0 h1:71GdRM9/pFxGgSUz8t2DKmm3RYuHUnTjsOuFInJXnkM=
125 github.com/gogs/git-module v1.6.0/go.mod h1:8jFYhDxLUwEOhM2709l2CJXmoIIslobU1xszpT0NcAI=
126+github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
127+github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
128+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
129+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
130+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
131+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
132+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
133+github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
134+github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
135+github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
136 github.com/gomarkdown/markdown v0.0.0-20260217112301-37c66b85d6ab h1:VYNivV7P8IRHUam2swVUNkhIdp0LRRFKe4hXNnoZKTc=
137 github.com/gomarkdown/markdown v0.0.0-20260217112301-37c66b85d6ab/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
138+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
139+github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0=
140+github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU=
141+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
142+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
143+github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
144+github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
145+github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
146+github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
147+github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
148+github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
149+github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
150+github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
151+github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
152+github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
153+github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
154+github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
155+github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
156+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
157 github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
158 github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
159+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
160+github.com/ikawaha/kagome.ipadic v1.1.2/go.mod h1:DPSBbU0czaJhAb/5uKQZHMc9MTVRpDugJfX+HddPHHg=
161+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
162+github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
163+github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
164+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
165+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
166+github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ=
167+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
168+github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY=
169+github.com/kevinburke/ssh_config v1.6.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
170+github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
171+github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
172+github.com/kljensen/snowball v0.6.0/go.mod h1:27N7E8fVU5H68RlUmnWwZCfxgt4POBJfENGMvNRhldw=
173+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
174+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
175+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
176+github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
177+github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
178+github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
179+github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
180+github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
181+github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
182+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
183+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
184+github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
185+github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
186+github.com/mattn/go-runewidth v0.0.22 h1:76lXsPn6FyHtTY+jt2fTTvsMUCZq1k0qwRsAMuxzKAk=
187+github.com/mattn/go-runewidth v0.0.22/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
188 github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75 h1:Pijfgr7ZuvX7QIQiEwLdRVr3RoMG+i0SbBO1Qu+7yVk=
189 github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo=
190+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
191+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
192+github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
193+github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
194+github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
195+github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
196+github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
197+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
198+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
199+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
200+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
201+github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
202+github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI=
203+github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
204+github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
205+github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
206+github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
207+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
208+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
209 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
210 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
211+github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
212+github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
213+github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
214+github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
215+github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
216+github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
217+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
218+github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
219+github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
220+github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
221+github.com/shurcooL/githubv4 v0.0.0-20260209031235-2402fdf4a9ed h1:KT7hI8vYXgU0s2qaMkrfq9tCA1w/iEPgfredVP+4Tzw=
222+github.com/shurcooL/githubv4 v0.0.0-20260209031235-2402fdf4a9ed/go.mod h1:zqMwyHmnN/eDOZOdiTohqIUKUrTFX62PNlu7IJdu0q8=
223+github.com/shurcooL/graphql v0.0.0-20240915155400-7ee5256398cf h1:o1uxfymjZ7jZ4MsgCErcwWGtVKSiNAXtS59Lhs6uI/g=
224+github.com/shurcooL/graphql v0.0.0-20240915155400-7ee5256398cf/go.mod h1:9dIRpgIY7hVhoqfe0/FcYp0bpInZaT7dc3BYOprrIUE=
225+github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs=
226+github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1qZoYLZzLnBw+QkPP9WZnjlSWihhxAJC1+/M=
227+github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92 h1:OfRzdxCzDhp+rsKWXuOO2I/quKMJ/+TQwVbIP/gltZg=
228+github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92/go.mod h1:7/OT02F6S6I7v6WXb+IjhMuZEYfH/RJ5RwEWnEo5BMg=
229+github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
230+github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg=
231+github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow=
232+github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
233+github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
234+github.com/sosodev/duration v1.4.0 h1:35ed0KiVFriGHHzZZJaZLgmTEEICIyt8Sx0RQfj9IjE=
235+github.com/sosodev/duration v1.4.0/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
236+github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
237+github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
238+github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
239+github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
240+github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
241+github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
242+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
243+github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
244+github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
245+github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
246+github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
247+github.com/steveyen/gtreap v0.1.0 h1:CjhzTa274PyJLJuMZwIzCO1PfC00oRa8d1Kc78bFXJM=
248+github.com/steveyen/gtreap v0.1.0/go.mod h1:kl/5J7XbrOmlIbYIXdRHDDE5QxHqpk0cmkT7Z4dM9/Y=
249 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
250 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
251 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
252+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
253 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
254+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
255 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
256 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
257 github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
258 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
259+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
260+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
261+github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
262+github.com/tdewolff/minify/v2 v2.24.12 h1:YXJxVJmz7vxgnEv1v8J/EI4x+Uw4MMohcRFK7TFOjmk=
263+github.com/tdewolff/minify/v2 v2.24.12/go.mod h1:exq1pjdrh9uAICdfVKQwqz6MsJmWmQahZuTC6pTO6ro=
264+github.com/tdewolff/parse/v2 v2.8.11 h1:SGyjEy3xEqd+W9WVzTlTQ5GkP/en4a1AZNZVJ1cvgm0=
265+github.com/tdewolff/parse/v2 v2.8.11/go.mod h1:Hwlni2tiVNKyzR1o6nUs4FOF07URA+JLBLd6dlIXYqo=
266+github.com/tdewolff/test v1.0.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
267+github.com/tebeka/snowball v0.4.2/go.mod h1:4IfL14h1lvwZcp1sfXuuc7/7yCsvVffTWxWxCLfFpYg=
268+github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8=
269+github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
270+github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
271+github.com/vbauerster/mpb/v8 v8.12.0 h1:+gneY3ifzc88tKDzOtfG8k8gfngCx615S2ZmFM4liWg=
272+github.com/vbauerster/mpb/v8 v8.12.0/go.mod h1:V02YIuMVo301Y1VE9VtZlD8s84OMsk+EKN6mwvf/588=
273+github.com/vektah/gqlparser/v2 v2.5.32 h1:k9QPJd4sEDTL+qB4ncPLflqTJ3MmjB9SrVzJrawpFSc=
274+github.com/vektah/gqlparser/v2 v2.5.32/go.mod h1:c1I28gSOVNzlfc4WuDlqU7voQnsqI6OG2amkBAFmgts=
275+github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
276+github.com/willf/bitset v1.1.11 h1:N7Z7E9UvjW+sGsEl7k/SJrvY2reP1A07MrGuCjIOjRE=
277+github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI=
278+github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
279+github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
280+github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
281+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
282+gitlab.com/gitlab-org/api/client-go v1.46.0 h1:YxBWFZIFYKcGESCb9fpkwzouo+apyB9pr/XTWzNoL24=
283+gitlab.com/gitlab-org/api/client-go v1.46.0/go.mod h1:FtgyU6g2HS5+fMhw6nLK96GBEEBx5MzntOiJWfIaiN8=
284+go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
285+go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
286+go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
287+go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
288+golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
289+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
290+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
291+golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
292+golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
293+golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
294+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
295+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
296+golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
297+golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
298+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
299+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
300+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
301+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
302+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
303+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
304+golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
305+golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
306+golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
307+golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
308+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
309+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
310 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
311 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
312 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
313+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
314+golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
315+golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
316+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
317+golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
318+golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
319+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
320+golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
321+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
322+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
323+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
324+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
325+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
326+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
327+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
328+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
329+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
330+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
331+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
332+golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
333+golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
334+golang.org/x/telemetry v0.0.0-20260316223853-b6b0c46d1ccd h1:QbR6Giw8AyR6v6Vff72jiZRUdZnetfgYRndQuKa806k=
335+golang.org/x/telemetry v0.0.0-20260316223853-b6b0c46d1ccd/go.mod h1:TpUTTEp9frx7rTdLpC9gFG9kdI7zVLFTFFlqaH2Cncw=
336+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
337+golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
338+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
339+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
340+golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
341+golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
342+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
343+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
344+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
345+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
346+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
347+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
348+golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
349+golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
350+golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
351+golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
352+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
353+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
354+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
355+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
356+golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
357+golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
358+golang.org/x/vuln v1.1.4 h1:Ju8QsuyhX3Hk8ma3CesTbO8vfJD9EvUBgHvkxHBzj0I=
359+golang.org/x/vuln v1.1.4/go.mod h1:F+45wmU18ym/ca5PLTPLsSzr2KppzswxPP603ldA67s=
360+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
361+google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
362+google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
363 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
364+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
365+gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
366+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
367+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
368+gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
369+gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
370+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
371 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
372+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
373 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
374 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
375 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+1,
-2
1@@ -10,8 +10,7 @@
2
3 {{template "meta" .}}
4
5- <link rel="stylesheet" href="{{.Repo.RootRelative}}vars.css" />
6- <link rel="stylesheet" href="{{.Repo.RootRelative}}pgit.css" />
7+ <link rel="stylesheet" href="{{.Repo.RootRelative}}{{.Repo.CSSFile}}" />
8 </head>
9 <body>
10 <header>{{template "header" .}}</header>
+1,
-0
1@@ -14,6 +14,7 @@
2 {{if eq .Active "refs"}}<span class="active">refs</span>{{else}}<a href="{{.SiteURLs.RefsURL}}">refs</a>{{end}}
3 {{if eq .Active "code"}}<span class="active">code</span>{{else}}<a href="{{.RevData.TreeURL}}">code</a>{{end}}
4 {{if eq .Active "commits"}}<span class="active">commits</span>{{else}}<a href="{{.RevData.LogURL}}">commits</a>{{end}}
5+ {{if .SiteURLs.IssuesURL}}{{if eq .Active "issues"}}<span class="active">issues</span>{{else}}<a href="{{.SiteURLs.IssuesURL}}">issues</a>{{end}}{{end}}
6 <details class="nav-menu__ref-selector">
7 <summary>{{.RevData.Name}}</summary>
8 <div class="ref-selector__dropdown">
+51,
-0
1@@ -0,0 +1,51 @@
2+{{template "base" .}}
3+
4+{{define "title"}}{{.Issue.Title}} - Issue #{{.Issue.ID}} - {{.Repo.RepoName}}{{end}}
5+{{define "meta"}}{{end}}
6+
7+{{define "content"}}
8+<div class="issue-detail">
9+ <div class="issue-detail__header">
10+ <div class="issue-detail__title-row">
11+ <h1 class="issue-detail__title">{{.Issue.Title}}</h1>
12+ <span class="issue-detail__status issue-detail__status--{{.Issue.Status}}">{{.Issue.Status}}</span>
13+ </div>
14+ <div class="issue-detail__meta">
15+ <span class="issue-id">#{{.Issue.ID}}</span>
16+ <span class="issue-author">opened by {{.Issue.Author}}</span>
17+ <span class="issue-date" title="{{.Issue.CreatedAt}}">{{.Issue.HumanDate}}</span>
18+ </div>
19+ {{if .Issue.Labels}}
20+ <div class="issue-detail__labels">
21+ <span>Labels:</span>
22+ {{range .Issue.Labels}}<span class="issue-label">{{.}}</span>{{end}}
23+ </div>
24+ {{end}}
25+ </div>
26+
27+ <div class="issue-description">
28+ <div class="issue-comment">
29+ <div class="issue-comment__header">
30+ <span class="issue-comment__author">{{.Issue.Author}}</span>
31+ <span class="issue-comment__date" title="{{.Issue.CreatedAt}}">{{.Issue.HumanDate}}</span>
32+ </div>
33+ <div class="issue-comment__body markdown">{{.Issue.Description}}</div>
34+ </div>
35+ </div>
36+
37+ {{if .Issue.Comments}}
38+ <div class="issue-comments">
39+ <h3>{{len .Issue.Comments}} Comment{{if ne (len .Issue.Comments) 1}}s{{end}}</h3>
40+ {{range .Issue.Comments}}
41+ <div class="issue-comment">
42+ <div class="issue-comment__header">
43+ <span class="issue-comment__author">{{.Author}}</span>
44+ <span class="issue-comment__date" title="{{.CreatedAt}}">{{.HumanDate}}</span>
45+ </div>
46+ <div class="issue-comment__body markdown">{{.Body}}</div>
47+ </div>
48+ {{end}}
49+ </div>
50+ {{end}}
51+</div>
52+{{end}}
+63,
-0
1@@ -0,0 +1,63 @@
2+{{template "base" .}}
3+
4+{{define "title"}}{{if eq .Filter "label"}}Issues labeled "{{.Label}}"{{else if eq .Filter "closed"}}Closed Issues{{else}}Open Issues{{end}} - {{.Repo.RepoName}}{{end}}
5+{{define "meta"}}{{end}}
6+
7+{{define "content"}}
8+<div class="issues-page">
9+ <div class="issues-header">
10+ <h1>
11+ {{if eq .Filter "label"}}
12+ Issues labeled "{{.Label}}"
13+ {{else if eq .Filter "closed"}}
14+ Closed Issues
15+ {{else}}
16+ Open Issues
17+ {{end}}
18+ </h1>
19+
20+ <nav class="issues-nav">
21+ {{if eq .Filter "open"}}
22+ <span class="active">{{.OpenCount}} Open</span>
23+ <a href="{{.ClosedIssuesURL}}">{{.ClosedCount}} Closed</a>
24+ {{else if eq .Filter "closed"}}
25+ <a href="{{.OpenIssuesURL}}">{{.OpenCount}} Open</a>
26+ <span class="active">{{.ClosedCount}} Closed</span>
27+ {{else}}
28+ <a href="{{.OpenIssuesURL}}">{{.OpenCount}} Open</a>
29+ <a href="{{.ClosedIssuesURL}}">{{.ClosedCount}} Closed</a>
30+ {{end}}
31+
32+ {{if .AllLabels}}
33+ <details class="issues-nav__label-selector">
34+ <summary>Labels</summary>
35+ <div class="label-selector__dropdown">
36+ {{range .AllLabels}}
37+ <a href="{{$.Repo.RootRelative}}issues/label/{{.}}/index.html">{{.}}</a>
38+ {{end}}
39+ </div>
40+ </details>
41+ {{end}}
42+ </nav>
43+ </div>
44+
45+ <div class="issues-list">
46+ {{range .Issues}}
47+ <div class="issue-item">
48+ <div class="issue-item__main">
49+ <a href="{{.URL}}" class="issue-item__title">{{.Title}}</a>
50+ <span class="issue-meta">#{{.ID}}{{if gt .CommentCount 0}} · {{.CommentCount}} comment{{if ne .CommentCount 1}}s{{end}}{{end}}</span>
51+ </div>
52+ <div class="issue-item__stats">
53+ <span class="issue-date" title="{{.CreatedAt}}">{{.HumanDate}}</span>
54+ {{if .Labels}}
55+ <span class="issue-labels-text">{{range $i, $label := .Labels}}{{if $i}}, {{end}}{{$label}}{{end}}</span>
56+ {{end}}
57+ </div>
58+ </div>
59+ {{else}}
60+ <p class="no-issues">No issues found.</p>
61+ {{end}}
62+ </div>
63+</div>
64+{{end}}
+1,
-1
1@@ -1,6 +1,6 @@
2 {{template "base" .}}
3
4-{{define "title"}}{{.Repo.RepoName}}{{if .Repo.Desc}}- {{.Repo.Desc}}{{end}}{{end}}
5+{{define "title"}}{{.Repo.RepoName}}{{end}}
6 {{define "meta"}}
7 <link rel="stylesheet" href="{{.Repo.RootRelative}}syntax.css" />
8 {{end}}
+268,
-0
1@@ -0,0 +1,268 @@
2+package main
3+
4+import (
5+ "fmt"
6+ "html/template"
7+ "net/url"
8+ "path/filepath"
9+
10+ "github.com/git-bug/git-bug/entities/bug"
11+ "github.com/git-bug/git-bug/repository"
12+)
13+
14+// IssueData represents a git-bug issue for templates
15+type IssueData struct {
16+ ID string
17+ FullID string
18+ Title string
19+ Status string // "open" or "closed"
20+ Author string
21+ CreatedAt string
22+ HumanDate string
23+ Labels []string
24+ CommentCount int // excludes original description
25+ Description template.HTML
26+ Comments []CommentData
27+ URL template.URL
28+}
29+
30+// CommentData represents a comment on an issue
31+type CommentData struct {
32+ Author string
33+ CreatedAt string
34+ HumanDate string
35+ Body template.HTML
36+}
37+
38+// IssuesListPageData for list pages (open, closed, by label)
39+type IssuesListPageData struct {
40+ *PageData
41+ Filter string // "open", "closed", or "label"
42+ OpenCount int
43+ ClosedCount int
44+ Issues []*IssueData
45+ Label string // set when Filter == "label"
46+ AllLabels []string // all unique labels from open issues
47+ OpenIssuesURL template.URL // URL to open issues list
48+ ClosedIssuesURL template.URL // URL to closed issues list
49+}
50+
51+// IssueDetailPageData for individual issue pages
52+type IssueDetailPageData struct {
53+ *PageData
54+ Issue *IssueData
55+}
56+
57+// Active returns the active navigation item
58+func (i *IssuesListPageData) Active() string { return "issues" }
59+func (i *IssueDetailPageData) Active() string { return "issues" }
60+
61+// loadIssues reads all issues from git-bug in the repository
62+func (c *Config) loadIssues() ([]*IssueData, error) {
63+ c.Logger.Info("loading issues from git-bug", "repoPath", c.RepoPath)
64+
65+ // Open the repository with git-bug's repo opener
66+ repo, err := repository.OpenGoGitRepo(c.RepoPath, "", nil)
67+ if err != nil {
68+ return nil, fmt.Errorf("failed to open repository: %w", err)
69+ }
70+
71+ // Read all bugs directly from the repository (without cache)
72+ var issues []*IssueData
73+
74+ for streamedBug := range bug.ReadAll(repo) {
75+ if streamedBug.Err != nil {
76+ c.Logger.Warn("failed to read bug", "error", streamedBug.Err)
77+ continue
78+ }
79+
80+ b := streamedBug.Entity
81+ snap := b.Compile()
82+
83+ // Count comments (excluding the original description)
84+ commentCount := len(snap.Comments) - 1
85+ if commentCount < 0 {
86+ commentCount = 0
87+ }
88+
89+ // Build labels slice
90+ labels := make([]string, len(snap.Labels))
91+ for i, label := range snap.Labels {
92+ labels[i] = string(label)
93+ }
94+
95+ // Build comments (skip first comment which is the description)
96+ var comments []CommentData
97+ for i, comment := range snap.Comments {
98+ if i == 0 {
99+ // Skip the original description
100+ continue
101+ }
102+ comments = append(comments, CommentData{
103+ Author: comment.Author.Name(),
104+ CreatedAt: comment.FormatTime(),
105+ HumanDate: comment.FormatTimeRel(),
106+ Body: c.renderMarkdown(comment.Message),
107+ })
108+ }
109+
110+ // Get description from first comment
111+ var description template.HTML
112+ if len(snap.Comments) > 0 {
113+ description = c.renderMarkdown(snap.Comments[0].Message)
114+ }
115+
116+ fullID := b.Id().String()
117+ issue := &IssueData{
118+ ID: getShortID(fullID),
119+ FullID: fullID,
120+ Title: snap.Title,
121+ Status: snap.Status.String(),
122+ Author: snap.Author.Name(),
123+ CreatedAt: snap.CreateTime.Format("Mon Jan 2 15:04:05 2006 -0700"),
124+ HumanDate: humanizeTime(snap.CreateTime),
125+ Labels: labels,
126+ CommentCount: commentCount,
127+ Description: description,
128+ Comments: comments,
129+ URL: c.getIssueURL(fullID),
130+ }
131+ issues = append(issues, issue)
132+ }
133+
134+ return issues, nil
135+}
136+
137+// getIssueURL generates the URL for an issue detail page
138+func (c *Config) getIssueURL(issueID string) template.URL {
139+ url := fmt.Sprintf("%sissues/%s.html", c.RootRelative, issueID)
140+ return template.URL(url)
141+}
142+
143+// getIssuesListURL generates URL for issues list pages
144+func (c *Config) getIssuesListURL(filter string, label string) template.URL {
145+ var path string
146+ switch filter {
147+ case "open", "closed":
148+ path = filepath.Join("issues", filter, "index.html")
149+ case "label":
150+ // URL-encode the label
151+ encodedLabel := url.PathEscape(label)
152+ path = filepath.Join("issues", "label", encodedLabel, "index.html")
153+ default:
154+ path = filepath.Join("issues", "open", "index.html")
155+ }
156+ return c.compileURL("/", path)
157+}
158+
159+// writeIssueListPage generates an issues list page
160+func (c *Config) writeIssueListPage(data *PageData, filter string, label string, issues []*IssueData, openCount, closedCount int, allLabels []string) {
161+ c.Logger.Info("writing issues list", "filter", filter, "label", label, "count", len(issues))
162+
163+ pageData := &IssuesListPageData{
164+ PageData: data,
165+ Filter: filter,
166+ OpenCount: openCount,
167+ ClosedCount: closedCount,
168+ Issues: issues,
169+ Label: label,
170+ AllLabels: allLabels,
171+ OpenIssuesURL: c.getIssuesListURL("open", ""),
172+ ClosedIssuesURL: c.getIssuesListURL("closed", ""),
173+ }
174+
175+ var subdir string
176+ switch filter {
177+ case "open":
178+ subdir = "issues/open"
179+ case "closed":
180+ subdir = "issues/closed"
181+ case "label":
182+ encodedLabel := url.PathEscape(label)
183+ subdir = filepath.Join("issues/label", encodedLabel)
184+ }
185+
186+ c.writeHtml(&WriteData{
187+ Filename: "index.html",
188+ Subdir: subdir,
189+ Template: "html/issues_list.page.tmpl",
190+ Data: pageData,
191+ })
192+}
193+
194+// writeIssueDetailPage generates an individual issue page
195+func (c *Config) writeIssueDetailPage(data *PageData, issue *IssueData) {
196+ c.Logger.Info("writing issue detail", "id", issue.ID, "title", issue.Title)
197+
198+ pageData := &IssueDetailPageData{
199+ PageData: data,
200+ Issue: issue,
201+ }
202+
203+ c.writeHtml(&WriteData{
204+ Filename: fmt.Sprintf("%s.html", issue.FullID),
205+ Subdir: "issues",
206+ Template: "html/issue_detail.page.tmpl",
207+ Data: pageData,
208+ })
209+}
210+
211+// writeIssues generates all issue-related pages
212+func (c *Config) writeIssues(pageData *PageData) error {
213+ // Load all issues from git-bug
214+ issues, err := c.loadIssues()
215+ if err != nil {
216+ return fmt.Errorf("failed to load issues: %w", err)
217+ }
218+
219+ // If no issues, skip generation
220+ if len(issues) == 0 {
221+ c.Logger.Info("no git-bug issues found, skipping issue generation")
222+ return nil
223+ }
224+
225+ c.Logger.Info("loaded issues", "count", len(issues))
226+
227+ // Categorize issues
228+ var openIssues, closedIssues []*IssueData
229+ labelIssues := make(map[string][]*IssueData)
230+ allLabels := make(map[string]bool)
231+
232+ for _, issue := range issues {
233+ if issue.Status == "open" {
234+ openIssues = append(openIssues, issue)
235+ // Collect labels from open issues
236+ for _, label := range issue.Labels {
237+ allLabels[label] = true
238+ labelIssues[label] = append(labelIssues[label], issue)
239+ }
240+ } else {
241+ closedIssues = append(closedIssues, issue)
242+ }
243+ }
244+
245+ openCount := len(openIssues)
246+ closedCount := len(closedIssues)
247+
248+ // Build sorted label list
249+ var sortedLabels []string
250+ for label := range allLabels {
251+ sortedLabels = append(sortedLabels, label)
252+ }
253+
254+ // Generate individual issue pages for all issues
255+ for _, issue := range issues {
256+ c.writeIssueDetailPage(pageData, issue)
257+ }
258+
259+ // Generate list pages
260+ c.writeIssueListPage(pageData, "open", "", openIssues, openCount, closedCount, sortedLabels)
261+ c.writeIssueListPage(pageData, "closed", "", closedIssues, openCount, closedCount, sortedLabels)
262+
263+ // Generate label filter pages (only for open issues)
264+ for label, issues := range labelIssues {
265+ c.writeIssueListPage(pageData, "label", label, issues, openCount, closedCount, sortedLabels)
266+ }
267+
268+ return nil
269+}
M
main.go
+258,
-28
1@@ -2,10 +2,13 @@ package main
2
3 import (
4 "bytes"
5+ "crypto/sha256"
6 "embed"
7+ "encoding/hex"
8 "flag"
9 "fmt"
10 "html/template"
11+ "io"
12 "log/slog"
13 "math"
14 "os"
15@@ -17,12 +20,18 @@ import (
16 "unicode/utf8"
17
18 "github.com/alecthomas/chroma/v2"
19+ "github.com/alecthomas/chroma/v2/formatters"
20 formatterHtml "github.com/alecthomas/chroma/v2/formatters/html"
21 "github.com/alecthomas/chroma/v2/lexers"
22 "github.com/alecthomas/chroma/v2/styles"
23 "github.com/dustin/go-humanize"
24 git "github.com/gogs/git-module"
25 "github.com/gomarkdown/markdown"
26+ "github.com/gomarkdown/markdown/ast"
27+ "github.com/gomarkdown/markdown/html"
28+ "github.com/gomarkdown/markdown/parser"
29+ "github.com/tdewolff/minify/v2"
30+ "github.com/tdewolff/minify/v2/css"
31 )
32
33 //go:embed html/*.tmpl
34@@ -40,8 +49,8 @@ type Config struct {
35 // optional params
36 // generate logs anad tree based on the git revisions provided
37 Revs []string
38- // description of repo used in the header of site
39- Desc string
40+ // description of repo used in the header of site (HTML)
41+ Desc template.HTML
42 // maximum number of commits that we will process in descending order
43 MaxCommits int
44 // name of the readme file
45@@ -51,6 +60,8 @@ type Config struct {
46 // We offer a way to disable showing the latest commit in the output
47 // for those who want a faster build time
48 HideTreeLastCommit bool
49+ // enable git-bug issue generation
50+ Issues bool
51
52 // user-defined urls
53 HomeURL template.URL
54@@ -71,6 +82,8 @@ type Config struct {
55 // chroma style
56 Theme *chroma.Style
57 Formatter *formatterHtml.Formatter
58+ // CSS bundle filename (with content hash)
59+ CSSFile string
60 }
61
62 type RevInfo interface {
63@@ -169,6 +182,7 @@ type SiteURLs struct {
64 CloneURL template.URL
65 SummaryURL template.URL
66 RefsURL template.URL
67+ IssuesURL template.URL
68 }
69
70 type PageData struct {
71@@ -308,10 +322,121 @@ func isMarkdownFile(filename string) bool {
72 return ext == ".md"
73 }
74
75-// renderMarkdown converts markdown text to HTML.
76-func renderMarkdown(mdText string) template.HTML {
77- md := []byte(mdText)
78- htmlBytes := markdown.ToHTML(md, nil, nil)
79+// chromaMarkdownRenderer is a custom HTML renderer that applies Chroma syntax highlighting to code blocks
80+type chromaMarkdownRenderer struct {
81+ defaultRenderer *html.Renderer
82+ theme *chroma.Style
83+}
84+
85+func (r *chromaMarkdownRenderer) RenderNode(w io.Writer, node ast.Node, entering bool) ast.WalkStatus {
86+ // Only process code blocks when entering (not when exiting)
87+ if entering {
88+ if codeBlock, ok := node.(*ast.CodeBlock); ok {
89+ // Extract language from Info field (e.g., "go" from "```go")
90+ lang := extractLanguage(codeBlock.Info)
91+
92+ // Get the code content from Literal
93+ code := string(codeBlock.Literal)
94+
95+ // Use Chroma to highlight
96+ highlighted, err := r.highlightCode(lang, code)
97+ if err == nil {
98+ w.Write([]byte(highlighted))
99+ return ast.SkipChildren
100+ }
101+ // Fall back to default rendering if highlighting fails
102+ }
103+ }
104+
105+ // Use default renderer for all other nodes
106+ return r.defaultRenderer.RenderNode(w, node, entering)
107+}
108+
109+func (r *chromaMarkdownRenderer) RenderHeader(w io.Writer, ast ast.Node) {
110+ r.defaultRenderer.RenderHeader(w, ast)
111+}
112+
113+func (r *chromaMarkdownRenderer) RenderFooter(w io.Writer, ast ast.Node) {
114+ r.defaultRenderer.RenderFooter(w, ast)
115+}
116+
117+func (r *chromaMarkdownRenderer) highlightCode(lang, code string) (string, error) {
118+ // Get appropriate lexer
119+ var lexer chroma.Lexer
120+ if lang != "" {
121+ lexer = lexers.Get(lang)
122+ }
123+ if lexer == nil {
124+ lexer = lexers.Analyse(code)
125+ }
126+ if lexer == nil {
127+ lexer = lexers.Get("plaintext")
128+ }
129+
130+ // Tokenize
131+ iterator, err := lexer.Tokenise(nil, code)
132+ if err != nil {
133+ return "", err
134+ }
135+
136+ // Create formatter WITHOUT line numbers for markdown code blocks
137+ formatter := formatters.Get("html")
138+ if formatter == nil {
139+ return "", fmt.Errorf("failed to get HTML formatter")
140+ }
141+
142+ // Format with Chroma
143+ var buf bytes.Buffer
144+ err = formatter.Format(&buf, r.theme, iterator)
145+ if err != nil {
146+ return "", err
147+ }
148+
149+ // Chroma's HTML formatter wraps output in <pre class="chroma"><code>...</code></pre>
150+ // We need to strip the outer <pre> wrapper since gomarkdown already adds it,
151+ // but keep the <code class="chroma"> tag for styling
152+ highlighted := buf.String()
153+
154+ // Remove Chroma's <pre class="chroma"> prefix and </pre> suffix
155+ // This leaves us with <code class="chroma">...</code> which is what we want
156+ highlighted = strings.TrimPrefix(highlighted, "<pre class=\"chroma\">")
157+ highlighted = strings.TrimSuffix(highlighted, "</pre>")
158+
159+ return highlighted, nil
160+}
161+
162+func extractLanguage(info []byte) string {
163+ if len(info) == 0 {
164+ return "" // No language specified
165+ }
166+ // Info may contain additional text after the language (e.g., "go filename.go")
167+ parts := strings.Fields(string(info))
168+ if len(parts) > 0 {
169+ return parts[0] // First part is the language
170+ }
171+ return ""
172+}
173+
174+// renderMarkdown converts markdown text to HTML with Chroma syntax highlighting for code blocks.
175+func (c *Config) renderMarkdown(mdText string) template.HTML {
176+ // Parse markdown with fenced code block support
177+ extensions := parser.CommonExtensions | parser.FencedCode
178+ p := parser.NewWithExtensions(extensions)
179+ doc := p.Parse([]byte(mdText))
180+
181+ // Create default HTML renderer
182+ htmlFlags := html.CommonFlags
183+ opts := html.RendererOptions{Flags: htmlFlags}
184+ defaultRenderer := html.NewRenderer(opts)
185+
186+ // Create custom renderer with Chroma support
187+ customRenderer := &chromaMarkdownRenderer{
188+ defaultRenderer: defaultRenderer,
189+ theme: c.Theme,
190+ }
191+
192+ // Render to HTML
193+ htmlBytes := markdown.Render(doc, customRenderer)
194 return template.HTML(htmlBytes)
195 }
196
197@@ -339,6 +464,31 @@ func readmeFile(repo *Config) string {
198 return strings.ToLower(repo.Readme)
199 }
200
201+// readDescription reads the repository description from .git/description (cloned repo)
202+// or ./description (bare repo). Returns empty string if not found.
203+func readDescription(repoPath string) string {
204+ // Try .git/description first (cloned repo)
205+ descPath := filepath.Join(repoPath, ".git", "description")
206+ data, err := os.ReadFile(descPath)
207+ if err != nil {
208+ // Try ./description (bare repo)
209+ descPath = filepath.Join(repoPath, "description")
210+ data, err = os.ReadFile(descPath)
211+ if err != nil {
212+ return ""
213+ }
214+ }
215+
216+ desc := strings.TrimSpace(string(data))
217+
218+ // Filter out the default git description
219+ if desc == "Unnamed repository; edit this file 'description' to name the repository." {
220+ return ""
221+ }
222+
223+ return desc
224+}
225+
226 func (c *Config) writeHtml(writeData *WriteData) {
227 ts, err := template.ParseFS(
228 embedFS,
229@@ -449,7 +599,7 @@ func (c *Config) writeHTMLTreeFile(pageData *PageData, treeItem *TreeItem) strin
230 treeItem.NumLines = len(strings.Split(str, "\n"))
231 // Use markdown rendering for .md files, syntax highlighting for others
232 if isMarkdownFile(treeItem.Entry.Name()) {
233- contents = string(renderMarkdown(str))
234+ contents = string(c.renderMarkdown(str))
235 } else {
236 contents, err = c.parseText(treeItem.Entry.Name(), string(b))
237 bail(err)
238@@ -612,9 +762,15 @@ func (c *Config) getURLs() *SiteURLs {
239 CloneURL: c.CloneURL,
240 RefsURL: c.getRefsURL(),
241 SummaryURL: c.getSummaryURL(),
242+ IssuesURL: c.getIssuesURL(),
243 }
244 }
245
246+func (c *Config) getIssuesURL() template.URL {
247+ url := c.RootRelative + "issues/open/index.html"
248+ return template.URL(url)
249+}
250+
251 func getShortID(id string) string {
252 return id[:7]
253 }
254@@ -738,11 +894,20 @@ func (c *Config) writeRepo() *BranchOutput {
255 SiteURLs: c.getURLs(),
256 Refs: refInfoList,
257 }
258+
259+ // Generate issue pages if enabled
260+ if c.Issues {
261+ err := c.writeIssues(data)
262+ if err != nil {
263+ c.Logger.Warn("failed to write issues", "error", err)
264+ }
265+ }
266+
267 c.writeRefs(data, refInfoList)
268 // Convert README markdown to HTML for summary page
269 var readmeHTML template.HTML
270 if isMarkdownFile(readmeFile(c)) {
271- readmeHTML = renderMarkdown(mainOutput.Readme)
272+ readmeHTML = c.renderMarkdown(mainOutput.Readme)
273 } else {
274 readmeHTML = template.HTML(mainOutput.Readme)
275 }
276@@ -1085,7 +1250,6 @@ func style(theme chroma.Style) string {
277 kw := theme.Get(chroma.Keyword)
278 nv := theme.Get(chroma.NameVariable)
279 cm := theme.Get(chroma.Comment)
280- ln := theme.Get(chroma.LiteralNumber)
281 return fmt.Sprintf(`:root {
282 --bg-color: %s;
283 --text-color: %s;
284@@ -1099,10 +1263,67 @@ func style(theme chroma.Style) string {
285 cm.Colour.String(),
286 nv.Colour.String(),
287 kw.Colour.String(),
288- ln.Colour.String(),
289+ nv.Colour.String(),
290 )
291 }
292
293+// bundleCSS concatenates, minifies, and hashes all CSS files
294+// Returns the filename of the bundled CSS (e.g., "styles.a3f7b2c1.css")
295+func (c *Config) bundleCSS() (string, error) {
296+ c.Logger.Info("bundling CSS files")
297+
298+ // Initialize minifier
299+ m := minify.New()
300+ m.AddFunc("text/css", css.Minify)
301+
302+ var buf bytes.Buffer
303+
304+ // 1. Read pgit.css from embedded static FS
305+ pgitCSS, err := staticFS.ReadFile("static/pgit.css")
306+ if err != nil {
307+ return "", fmt.Errorf("failed to read pgit.css: %w", err)
308+ }
309+ buf.Write(pgitCSS)
310+ buf.WriteString("\n")
311+
312+ // 2. Generate vars.css content
313+ varsCSS := style(*c.Theme)
314+ buf.WriteString(varsCSS)
315+ buf.WriteString("\n")
316+
317+ // 3. Generate syntax.css content
318+ var syntaxBuf bytes.Buffer
319+ err = c.Formatter.WriteCSS(&syntaxBuf, c.Theme)
320+ if err != nil {
321+ return "", fmt.Errorf("failed to generate syntax.css: %w", err)
322+ }
323+ buf.Write(syntaxBuf.Bytes())
324+
325+ // 4. Minify the concatenated CSS
326+ minified, err := m.Bytes("text/css", buf.Bytes())
327+ if err != nil {
328+ return "", fmt.Errorf("failed to minify CSS: %w", err)
329+ }
330+
331+ // 5. Generate content hash (first 8 chars of SHA256)
332+ hash := sha256.Sum256(minified)
333+ hashStr := hex.EncodeToString(hash[:8])
334+
335+ // 6. Create filename with hash
336+ filename := fmt.Sprintf("styles.%s.css", hashStr)
337+
338+ // 7. Write to output directory
339+ outPath := filepath.Join(c.Outdir, filename)
340+ err = os.WriteFile(outPath, minified, 0644)
341+ if err != nil {
342+ return "", fmt.Errorf("failed to write CSS bundle: %w", err)
343+ }
344+
345+ c.Logger.Info("CSS bundle created", "filename", filename, "size", len(minified))
346+
347+ return filename, nil
348+}
349+
350 func main() {
351 var outdir = flag.String("out", "./public", "output directory")
352 var rpath = flag.String("repo", ".", "path to git repo")
353@@ -1115,6 +1336,7 @@ func main() {
354 var rootRelativeFlag = flag.String("root-relative", "/", "html root relative")
355 var maxCommitsFlag = flag.Int("max-commits", 0, "maximum number of commits to generate")
356 var hideTreeLastCommitFlag = flag.Bool("hide-tree-last-commit", false, "dont calculate last commit for each file in the tree")
357+ var issuesFlag = flag.Bool("issues", false, "enable git-bug issue generation")
358
359 flag.Parse()
360
361@@ -1143,6 +1365,26 @@ func main() {
362 formatterHtml.WithClasses(true),
363 )
364
365+ // Create a temporary config to use for rendering markdown
366+ tempConfig := &Config{
367+ Theme: theme,
368+ Logger: logger,
369+ RepoPath: repoPath,
370+ }
371+
372+ // Determine description: --desc flag overrides git description
373+ var descHTML template.HTML
374+ if *descFlag != "" {
375+ // Use --desc flag value, process as markdown
376+ descHTML = tempConfig.renderMarkdown(*descFlag)
377+ } else {
378+ // Try to read from git description file
379+ gitDesc := readDescription(repoPath)
380+ if gitDesc != "" {
381+ descHTML = tempConfig.renderMarkdown(gitDesc)
382+ }
383+ }
384+
385 config := &Config{
386 Outdir: out,
387 RepoPath: repoPath,
388@@ -1153,9 +1395,10 @@ func main() {
389 Logger: logger,
390 CloneURL: template.URL(*cloneFlag),
391 HomeURL: template.URL(*homeFlag),
392- Desc: *descFlag,
393+ Desc: descHTML,
394 MaxCommits: *maxCommitsFlag,
395 HideTreeLastCommit: *hideTreeLastCommitFlag,
396+ Issues: *issuesFlag,
397 RootRelative: *rootRelativeFlag,
398 Formatter: formatter,
399 }
400@@ -1165,25 +1408,12 @@ func main() {
401 bail(fmt.Errorf("you must provide --revs"))
402 }
403
404- config.writeRepo()
405- err = config.copyStatic("static")
406+ // Bundle CSS files before generating site
407+ cssFile, err := config.bundleCSS()
408 bail(err)
409+ config.CSSFile = cssFile
410
411- styles := style(*theme)
412- err = os.WriteFile(filepath.Join(out, "vars.css"), []byte(styles), 0644)
413- if err != nil {
414- panic(err)
415- }
416-
417- fp := filepath.Join(out, "syntax.css")
418- w, err := os.OpenFile(fp, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
419- if err != nil {
420- bail(err)
421- }
422- err = formatter.WriteCSS(w, theme)
423- if err != nil {
424- bail(err)
425- }
426+ config.writeRepo()
427
428 url := filepath.Join("/", "index.html")
429 config.Logger.Info("root url", "url", url)
+312,
-1
1@@ -9,7 +9,7 @@
2 --border: #6272a4;
3 --link-color: #8be9fd;
4 --hover: #ff79c6;
5- --visited: #bd93f9;
6+ --visited: #8be9fd;
7 --white: #f2f2f2;
8 --white-light: #f2f2f2;
9 --white-dark: #e8e8e8;
10@@ -19,6 +19,8 @@
11 --pre: #252525;
12 --blockquote: #bd93f9;
13 --blockquote-bg: #353548;
14+ --text-red: #ff5555;
15+ --text-green: #50fa7b;
16 }
17
18 /* ==== BASE RESET ==== */
19@@ -316,6 +318,11 @@ sup {
20 margin-top: var(--grid-height);
21 }
22
23+.site-header__desc > p {
24+ padding: 0;
25+ margin: 0;
26+}
27+
28 /* Navigation */
29 .nav-menu {
30 display: flex;
31@@ -1029,6 +1036,15 @@ sup {
32 .last-commit-bar__info {
33 white-space: normal;
34 }
35+
36+ /* Issue detail title row becomes column on mobile */
37+ .issue-detail__title-row {
38+ flex-direction: column;
39+ }
40+
41+ .issue-detail__status {
42+ align-self: flex-start;
43+ }
44 }
45
46 @media only screen and (max-width: 900px) {
47@@ -1067,3 +1083,298 @@ sup {
48 .file-list-summary__item {
49 margin: var(--grid-height) 0;
50 }
51+
52+/* ==== ISSUES PAGES ==== */
53+
54+/* Issues List Page */
55+.issues-page {
56+ margin-top: var(--line-height);
57+}
58+
59+.issues-header {
60+ border-bottom: 1px solid var(--border);
61+}
62+
63+.issues-header h1 {
64+ font-size: 1rem;
65+ font-weight: bold;
66+ text-transform: uppercase;
67+ margin-bottom: var(--grid-height);
68+}
69+
70+/* Issues Navigation - matches top-level nav-menu */
71+.issues-nav {
72+ display: flex;
73+ align-items: center;
74+ gap: 1.5rem;
75+ margin: 1rem 0;
76+ flex-wrap: wrap;
77+}
78+
79+.issues-nav a,
80+.issues-nav span.active {
81+ color: var(--grey-light);
82+ font-weight: 600;
83+ text-transform: uppercase;
84+ font-size: 0.9rem;
85+ text-decoration: none;
86+ padding-bottom: 0.4rem;
87+ border-bottom: 2px solid transparent;
88+ transition: all 0.2s ease;
89+}
90+
91+.issues-nav a:hover,
92+.issues-nav span.active {
93+ color: var(--text-color);
94+ border-bottom: 2px solid var(--link-color);
95+}
96+
97+/* Label Selector Dropdown */
98+.issues-nav__label-selector {
99+ margin-left: auto;
100+ position: relative;
101+}
102+
103+.issues-nav__label-selector summary {
104+ color: var(--grey-light);
105+ font-weight: 600;
106+ text-transform: uppercase;
107+ font-size: 0.9rem;
108+ padding-bottom: 0.4rem;
109+ border-bottom: 2px solid transparent;
110+ cursor: pointer;
111+ list-style: none;
112+ display: flex;
113+ align-items: center;
114+ gap: 0.25rem;
115+}
116+
117+.issues-nav__label-selector summary:hover {
118+ color: var(--text-color);
119+}
120+
121+.issues-nav__label-selector summary::-webkit-details-marker {
122+ display: none;
123+}
124+
125+.issues-nav__label-selector summary::after {
126+ content: "▼";
127+ font-size: 0.7rem;
128+}
129+
130+.issues-nav__label-selector[open] summary::after {
131+ content: "▲";
132+}
133+
134+.label-selector__dropdown {
135+ position: absolute;
136+ top: 100%;
137+ right: 0;
138+ min-width: 150px;
139+ background-color: var(--bg-color);
140+ border: 1px solid var(--border);
141+ border-radius: 4px;
142+ padding: 0.5rem 0;
143+ margin-top: 0.25rem;
144+ z-index: 100;
145+ max-height: 300px;
146+ overflow-y: auto;
147+}
148+
149+.label-selector__dropdown a {
150+ display: block;
151+ padding: 0.25rem 1rem;
152+ color: var(--text-color);
153+ font-weight: 600;
154+ text-transform: uppercase;
155+ font-size: 0.9rem;
156+ text-decoration: none;
157+ border-bottom: none;
158+ padding-bottom: 0.25rem;
159+}
160+
161+.label-selector__dropdown a:hover {
162+ background-color: var(--pre);
163+ color: var(--link-color);
164+ text-decoration: none;
165+}
166+
167+.issues-list {
168+ display: flex;
169+ flex-direction: column;
170+}
171+
172+.issue-item {
173+ display: flex;
174+ justify-content: space-between;
175+ align-items: flex-start;
176+ padding: 0.25rem 0.5rem;
177+ border-bottom: 1px solid var(--grey);
178+ transition: background-color 0.2s ease;
179+ margin: 0 -0.5rem;
180+}
181+
182+.issue-item:hover {
183+ background-color: var(--pre);
184+}
185+
186+.issue-item__main {
187+ flex: 1;
188+ margin-right: 1rem;
189+ display: flex;
190+ flex-direction: column;
191+ gap: 0.1rem;
192+}
193+
194+.issue-item__title {
195+ font-size: 1rem;
196+ font-weight: bold;
197+ text-decoration: none;
198+ display: block;
199+ line-height: var(--line-height);
200+}
201+
202+.issue-item__title:hover {
203+ text-decoration: underline;
204+}
205+
206+.issue-meta {
207+ color: var(--grey-light);
208+ font-family: monospace;
209+ font-size: 0.8rem;
210+ line-height: var(--line-height);
211+}
212+
213+.issue-item__stats {
214+ display: flex;
215+ flex-direction: column;
216+ align-items: flex-end;
217+ text-align: right;
218+ font-size: 0.8rem;
219+ color: var(--grey-light);
220+ line-height: var(--line-height);
221+}
222+
223+.issue-date {
224+ white-space: nowrap;
225+}
226+
227+.issue-labels-text {
228+ color: var(--grey-light);
229+}
230+
231+.no-issues {
232+ color: var(--grey-light);
233+ font-style: italic;
234+ padding: var(--line-height) 0;
235+}
236+
237+/* Issue Detail Page */
238+.issue-detail {}
239+
240+.issue-detail__header {
241+ margin-bottom: var(--line-height);
242+ padding-bottom: var(--grid-height);
243+ border-bottom: 1px solid var(--border);
244+}
245+
246+.issue-detail__title-row {
247+ display: flex;
248+ justify-content: space-between;
249+ align-items: flex-start;
250+ margin-bottom: var(--grid-height);
251+}
252+
253+.issue-detail__title {
254+ font-size: 1rem;
255+ font-weight: bold;
256+ line-height: var(--line-height);
257+ margin: 0 1rem 0 0;
258+ flex: 1;
259+ word-wrap: break-word;
260+ overflow-wrap: break-word;
261+}
262+
263+.issue-detail__status {
264+ font-size: 0.9rem;
265+ font-weight: 600;
266+ text-transform: uppercase;
267+ white-space: nowrap;
268+ flex-shrink: 0;
269+}
270+
271+.issue-detail__status--open {
272+ color: var(--text-red);
273+}
274+
275+.issue-detail__status--closed {
276+ color: var(--text-green);
277+}
278+
279+.issue-detail__meta {
280+ display: flex;
281+ gap: 0.5rem;
282+ align-items: center;
283+ font-size: 0.8rem;
284+ color: var(--grey-light);
285+ flex-wrap: wrap;
286+}
287+
288+.issue-detail__labels {
289+ font-size: 0.9rem;
290+}
291+
292+.issue-detail__labels>span:first-child {
293+ color: var(--grey-light);
294+ margin-right: 0.5rem;
295+}
296+
297+.issue-label:not(:last-child)::after {
298+ content: "•";
299+ margin: 0 0.4rem;
300+ color: var(--grey-light);
301+}
302+
303+.issue-description {
304+ margin-bottom: var(--line-height);
305+}
306+
307+.issue-comment {
308+ padding: var(--grid-height);
309+ border: 1px solid var(--border);
310+ border-radius: 4px;
311+ margin-bottom: var(--grid-height);
312+ background: var(--pre);
313+}
314+
315+.issue-comment__header {
316+ display: flex;
317+ justify-content: space-between;
318+ align-items: center;
319+ margin-bottom: var(--grid-height);
320+ padding-bottom: var(--grid-height);
321+ border-bottom: 1px solid var(--grey);
322+ font-size: 0.9rem;
323+}
324+
325+.issue-comment__author {
326+ font-weight: bold;
327+ color: var(--text-color);
328+}
329+
330+.issue-comment__date {
331+ color: var(--grey-light);
332+ font-size: 0.8rem;
333+}
334+
335+.issue-comment__body {
336+ line-height: var(--line-height);
337+}
338+
339+.issue-comments h3 {
340+ font-size: 1rem;
341+ font-weight: bold;
342+ text-transform: uppercase;
343+ margin: var(--line-height) 0 var(--grid-height);
344+ color: var(--white-dark);
345+}
+1,
-1
1@@ -1 +1 @@
2-8
3+11
+0,
-0
+0,
-0
+1,
-0
1@@ -0,0 +1 @@
2+3
+1,
-0
1@@ -0,0 +1 @@
2+8
1@@ -0,0 +1 @@
2+{"storage":"boltdb","index_type":"upside_down"}
+0,
-0
1@@ -0,0 +1 @@
2+{"storage":"boltdb","index_type":"upside_down"}
1@@ -1 +1 @@
2-3a2e74a39caacee6610381c6de17a3a8ebada53c
3+62216754e378ef45e78db0213a1b1957996747f5