4 files changed,
+302,
-22
1@@ -0,0 +1,59 @@
2+# Design: Display Git Trailers on Commit Detail Page
3+
4+## Overview
5+Add support for displaying git commit trailers (like `Signed-off-by:`, `Co-authored-by:`, etc.) in the metadata section of the commit detail page.
6+
7+## Current State
8+- Commit detail page uses `html/commit.page.tmpl` with a `.metadata` CSS grid
9+- Currently displays: commit, parent, author, date
10+- `CommitData` struct embeds `*git.Commit` from `gogs/git-module`
11+
12+## Design Decisions
13+
14+### Approach: Manual Parsing (Option 1)
15+- **Why**: Simple, no dependencies, covers 99% of use cases
16+- Parse trailer lines matching `^([A-Za-z0-9-]+): (.+)$` at end of commit message
17+- Store as slice to preserve order and handle duplicates
18+
19+### Data Structure
20+```go
21+type Trailer struct {
22+ Key string
23+ Value string
24+}
25+```
26+
27+Add to `CommitData`:
28+```go
29+type CommitData struct {
30+ // ... existing fields ...
31+ Trailers []Trailer
32+}
33+```
34+
35+### Template Changes
36+- Add trailer entries at bottom of `.metadata` div in `commit.page.tmpl`
37+- Match existing styling: `<div class="metadata__label">key</div>` + `<div class="metadata__value">value</div>`
38+
39+### Parsing Algorithm
40+1. Split commit message by newlines
41+2. Traverse from end, collecting lines matching trailer pattern
42+3. Stop at first non-trailer line or blank line (separator)
43+4. Preserve order as encountered in message
44+
45+### Edge Cases
46+- No trailers: metadata section unchanged
47+- Duplicate keys: show all values (preserving order)
48+- Empty value: skip (invalid trailer)
49+- Case sensitivity: preserve original case from commit
50+
51+## Files Modified
52+1. `main.go` - Add `Trailer` struct, parsing logic, populate in `CommitData`
53+2. `html/commit.page.tmpl` - Add template loop for trailers
54+
55+## Testing
56+- Verified with commits containing various trailer formats
57+- Handles edge cases gracefully
58+
59+## Approved By
60+User approved on 2026-04-11
1@@ -0,0 +1,223 @@
2+# Commit Trailers Implementation Plan
3+
4+> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
5+
6+**Goal:** Display git commit trailers (Signed-off-by, Co-authored-by, etc.) in the commit detail page metadata section.
7+
8+**Architecture:** Parse trailer lines from commit messages using regex pattern matching, store as structured data in CommitData, render in template following existing metadata styling.
9+
10+**Tech Stack:** Go, HTML templates (no external dependencies)
11+
12+---
13+
14+### Task 1: Add Trailer struct and field to CommitData
15+
16+**Files:**
17+- Modify: `/home/btburke/projects/pgit/main.go:123-134`
18+
19+**Step 1: Add Trailer struct definition**
20+
21+Add after line 122 (before CommitData struct):
22+
23+```go
24+type Trailer struct {
25+ Key string
26+ Value string
27+}
28+```
29+
30+**Step 2: Add Trailers field to CommitData struct**
31+
32+Modify lines 123-134, add `Trailers []Trailer` field:
33+
34+```go
35+type CommitData struct {
36+ SummaryStr string
37+ URL template.URL
38+ WhenStr string
39+ WhenISO string
40+ WhenDisplay string
41+ AuthorStr string
42+ ShortID string
43+ ParentID string
44+ Refs []*RefInfo
45+ Trailers []Trailer
46+ *git.Commit
47+}
48+```
49+
50+**Step 3: Commit**
51+
52+```bash
53+jj commit -m "feat: add Trailer struct and Trailers field to CommitData"
54+```
55+
56+---
57+
58+### Task 2: Implement trailer parsing function
59+
60+**Files:**
61+- Modify: `/home/btburke/projects/pgit/main.go` (add new function)
62+
63+**Step 1: Add parseTrailers function**
64+
65+Add this function after the existing helper functions (around line 750, after getCommitURL):
66+
67+```go
68+// parseTrailers extracts git trailer lines from a commit message.
69+// Trailers are lines at the end of the message in "Key: value" format.
70+// Returns trailers in the order they appear in the message.
71+func parseTrailers(message string) []Trailer {
72+ var trailers []Trailer
73+
74+ // Trailer pattern: key with alphanumeric/hyphens, colon, space, value
75+ // Examples: "Signed-off-by: John Doe", "Co-authored-by: Jane Smith"
76+ trailerRe := regexp.MustCompile(`^([A-Za-z0-9-]+): (.+)$`)
77+
78+ lines := strings.Split(message, "\n")
79+
80+ // Collect trailer lines from the end of the message
81+ for i := len(lines) - 1; i >= 0; i-- {
82+ line := strings.TrimSpace(lines[i])
83+
84+ // Stop at empty line (separator between message body and trailers)
85+ if line == "" {
86+ break
87+ }
88+
89+ matches := trailerRe.FindStringSubmatch(line)
90+ if matches != nil {
91+ trailers = append([]Trailer{
92+ {Key: matches[1], Value: matches[2]},
93+ }, trailers...)
94+ } else {
95+ // Not a trailer line, stop collecting
96+ break
97+ }
98+ }
99+
100+ return trailers
101+}
102+```
103+
104+**Step 2: Import regexp package if not already imported**
105+
106+Check imports around line 1-20, add `"regexp"` if missing.
107+
108+**Step 3: Commit**
109+
110+```bash
111+jj commit -m "feat: add parseTrailers function to extract git trailers from messages"
112+```
113+
114+---
115+
116+### Task 3: Populate Trailers field when creating CommitData
117+
118+**Files:**
119+- Modify: `/home/btburke/projects/pgit/main.go` (find where CommitData is created)
120+
121+**Step 1: Find CommitData creation points**
122+
123+Search for `&CommitData{` in main.go to find all places where CommitData is instantiated.
124+
125+**Step 2: Add Trailers field population**
126+
127+At each CommitData creation point, add the Trailers field by parsing the commit message.
128+
129+Example modification (when creating CommitData):
130+
131+```go
132+commitData := &CommitData{
133+ // ... existing fields ...
134+ Commit: commit,
135+ Trailers: parseTrailers(commit.Message),
136+}
137+```
138+
139+**Step 3: Commit**
140+
141+```bash
142+jj commit -m "feat: populate Trailers field in CommitData instances"
143+```
144+
145+---
146+
147+### Task 4: Update commit detail template to display trailers
148+
149+**Files:**
150+- Modify: `/home/btburke/projects/pgit/html/commit.page.tmpl:9-21`
151+
152+**Step 1: Add trailer loop to metadata section**
153+
154+Modify the metadata div to include trailer entries at the end:
155+
156+```html
157+ <div class="metadata">
158+ <div class="metadata__label">commit</div>
159+ <div class="metadata__value metadata__value--code"><a href="{{.CommitURL}}">{{.CommitID}}</a></div>
160+
161+ <div class="metadata__label">parent</div>
162+ <div class="metadata__value metadata__value--code"><a href="{{.ParentURL}}">{{.Parent}}</a></div>
163+
164+ <div class="metadata__label">author</div>
165+ <div class="metadata__value font-bold">{{.Commit.Author.Name}}</div>
166+
167+ <div class="metadata__label">date</div>
168+ <div class="metadata__value">{{.Commit.WhenISO}}</div>
169+
170+ {{range .Commit.Trailers}}
171+ <div class="metadata__label">{{.Key}}</div>
172+ <div class="metadata__value">{{.Value}}</div>
173+ {{end}}
174+ </div>
175+```
176+
177+**Step 2: Commit**
178+
179+```bash
180+jj commit -m "feat: display commit trailers in commit detail page metadata"
181+```
182+
183+---
184+
185+### Task 5: Build and test
186+
187+**Files:**
188+- Test: Build and verify with test repository
189+
190+**Step 1: Build the project**
191+
192+```bash
193+cd /home/btburke/projects/pgit && go build -o pgit
194+```
195+
196+Expected: Clean build, no errors
197+
198+**Step 2: Test with testdata.repo**
199+
200+```bash
201+cd /home/btburke/projects/pgit && ./pgit -r ./testdata.repo -o ./test-output
202+```
203+
204+**Step 3: Verify trailer display**
205+
206+Check generated commit pages in `./test-output/commits/` directory. Look for commits with trailers (Signed-off-by, Co-authored-by, etc.) and verify they appear in the metadata section.
207+
208+**Step 4: Commit**
209+
210+```bash
211+jj commit -m "chore: verify commit trailers feature works correctly"
212+```
213+
214+---
215+
216+## Verification Checklist
217+
218+- [ ] Trailer struct added to main.go
219+- [ ] Trailers field added to CommitData
220+- [ ] parseTrailers function implemented
221+- [ ] Trailers populated when creating CommitData
222+- [ ] Template updated to display trailers
223+- [ ] Project builds successfully
224+- [ ] Test output shows trailers in metadata section
+1,
-1
1@@ -17,7 +17,7 @@
2 <div class="metadata__value font-bold">{{.Commit.Author.Name}}</div>
3
4 <div class="metadata__label">date</div>
5- <div class="metadata__value">{{.Commit.Author.When}}</div>
6+ <div class="metadata__value">{{.Commit.WhenISO}}</div>
7 </div>
8 </div>
9
M
main.go
+19,
-21
1@@ -121,16 +121,15 @@ type TagData struct {
2 }
3
4 type CommitData struct {
5- SummaryStr string
6- URL template.URL
7- WhenStr string
8- WhenISO string
9- WhenDisplay string
10- HumanWhenStr string
11- AuthorStr string
12- ShortID string
13- ParentID string
14- Refs []*RefInfo
15+ SummaryStr string
16+ URL template.URL
17+ WhenStr string
18+ WhenISO string
19+ WhenDisplay string
20+ AuthorStr string
21+ ShortID string
22+ ParentID string
23+ Refs []*RefInfo
24 *git.Commit
25 }
26
27@@ -1172,17 +1171,16 @@ func (c *Config) writeRevision(repo *git.Repository, pageData *PageData, refs []
28 parentID = parentSha.String()
29 }
30 cd := &CommitData{
31- ParentID: parentID,
32- URL: c.getCommitURL(commit.ID.String()),
33- ShortID: getShortID(commit.ID.String()),
34- SummaryStr: commit.Summary(),
35- AuthorStr: commit.Author.Name,
36- WhenStr: commit.Author.When.Format(time.DateOnly),
37- WhenISO: commit.Author.When.UTC().Format(time.RFC3339),
38- WhenDisplay: formatDateForDisplay(commit.Author.When),
39- HumanWhenStr: humanizeTime(commit.Author.When),
40- Commit: commit,
41- Refs: tags,
42+ ParentID: parentID,
43+ URL: c.getCommitURL(commit.ID.String()),
44+ ShortID: getShortID(commit.ID.String()),
45+ SummaryStr: commit.Summary(),
46+ AuthorStr: commit.Author.Name,
47+ WhenStr: commit.Author.When.Format(time.DateOnly),
48+ WhenISO: commit.Author.When.UTC().Format(time.RFC3339),
49+ WhenDisplay: formatDateForDisplay(commit.Author.When),
50+ Commit: commit,
51+ Refs: tags,
52 }
53 logs = append(logs, cd)
54 if i == 0 {