Commit f0187c7c7e
f0187c7c7ed3ca9ed68c8f8d3f0321562aec3d22
parent: 934524888e
Verified · cmc
cmc <hello@cleberg.net> · 2026-09-17 16:26 UTC
web: a merge request without a merge or close stamp makes no byline claim
Ref #218
internal/httpd/mrpage_test.go
+15 −3
| @@ -63,7 +63,8 @@ func testMR(state string) store.MR { |
| 63 | 63 | } |
| 64 | 64 | |
| 65 | 65 | // The header states what happened to the MR: who opened, merged, or closed |
| 66 | | // it, and when. |
| 66 | // it, and when. A merge or close with no stamp (imports, pre-0029 merges) |
| 67 | // names the state without claiming a byline it cannot back up. |
| 67 | 68 | func TestMRHeaderByState(t *testing.T) { |
| 68 | 69 | open := renderMR(t, testMR("open"), nil, nil) |
| 69 | 70 | if !strings.Contains(open, "opened by") { |
| @@ -90,11 +91,22 @@ func TestMRHeaderByState(t *testing.T) { |
| 90 | 91 | } |
| 91 | 92 | |
| 92 | 93 | // Imports and pre-0029 merges carry no stamp; the wording drops the |
| 93 | | // claim rather than inventing a time. |
| 94 | // byline claim rather than naming nobody or inventing a time. |
| 94 | 95 | out = renderMR(t, testMR("merged"), nil, nil) |
| 95 | | if strings.Contains(out, "opened by") || strings.Contains(out, " on 20") { |
| 96 | if strings.Contains(out, "opened by") || strings.Contains(out, " on 20") || strings.Contains(out, "merged by") { |
| 96 | 97 | t.Errorf("unstamped merged header:\n%s", out) |
| 97 | 98 | } |
| 99 | if !strings.Contains(out, "merged") { |
| 100 | t.Errorf("unstamped merged header drops the state entirely:\n%s", out) |
| 101 | } |
| 102 | |
| 103 | out = renderMR(t, testMR("closed"), nil, nil) |
| 104 | if strings.Contains(out, "opened by") || strings.Contains(out, " on 20") || strings.Contains(out, "closed without merging by") { |
| 105 | t.Errorf("unstamped closed header:\n%s", out) |
| 106 | } |
| 107 | if !strings.Contains(out, "without merging") { |
| 108 | t.Errorf("unstamped closed header drops the state entirely:\n%s", out) |
| 109 | } |
| 98 | 110 | } |
| 99 | 111 | |
| 100 | 112 | // Approvals and checks carry their times in the aside, so reading the MR |
internal/web/templates/mr.html
+4 −1
| @@ -3,7 +3,10 @@ |
| 3 | 3 | {{$base := printf "/%s/%s/mrs/%d" .Repo.OwnerName .Repo.Name .MR.Number}} |
| 4 | 4 | <h1 class="issuetitle">{{.MR.Title}} <span class="issuenumber">!{{.MR.Number}}</span></h1> |
| 5 | 5 | <p class="issuemeta"><span class="chip chip-{{.MR.State}}">{{if eq .MR.State "source_gone"}}source gone{{else}}{{.MR.State}}{{end}}</span>{{if .MR.Draft}} <span class="chip chip-neutral">draft</span>{{end}} |
| 6 | | {{if eq .MR.State "merged"}}merged by <a href="/{{.MR.MergedBy}}">{{.MR.MergedBy}}</a> on {{when .MR.MergedAt}}{{else if eq .MR.State "closed"}}closed without merging by <a href="/{{.MR.ClosedBy}}">{{.MR.ClosedBy}}</a> on {{when .MR.ClosedAt}}{{else}}opened by <a href="/{{.MR.Author}}">{{.MR.Author}}</a> on {{when .MR.CreatedAt}}{{end}} |
| 6 | {{if and (eq .MR.State "merged") .MR.MergedBy}}merged by <a href="/{{.MR.MergedBy}}">{{.MR.MergedBy}}</a>{{with .MR.MergedAt}} on {{when .}}{{end}} |
| 7 | {{else if and (eq .MR.State "closed") .MR.ClosedBy}}closed without merging by <a href="/{{.MR.ClosedBy}}">{{.MR.ClosedBy}}</a>{{with .MR.ClosedAt}} on {{when .}}{{end}} |
| 8 | {{else if or (eq .MR.State "merged") (eq .MR.State "closed")}}{{if eq .MR.State "merged"}}merged{{else}}closed without merging{{end}} |
| 9 | {{else}}opened by <a href="/{{.MR.Author}}">{{.MR.Author}}</a> on {{when .MR.CreatedAt}}{{end}} |
| 7 | 10 | · <code>{{.MR.SourceRef}}</code> into <code>{{.MR.TargetRef}}</code></p> |
| 8 | 11 | {{with field . "StackedOn"}}<p class="meta">Stacked on <a href="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/mrs/{{.Number}}">!{{.Number}} {{.Title}}</a>: merges into its branch until that lands, then onto its target.</p>{{end}} |
| 9 | 12 | {{with field . "Stacked"}}<p class="meta">Builds on this: {{range $i, $k := .}}{{if $i}}, {{end}}<a href="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/mrs/{{$k.Number}}">!{{$k.Number}} {{$k.Title}}</a>{{end}}. Merging with squash or rebase is refused while they are open.</p>{{end}} |