v0.33.0 cf26365

signature.go RawHistoryFolder

1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package web
4
5import (
6	"bnema.dev/ermite/internal/domain"
7)
8
9// SignatureBadge is the pill of a signed commit. Label is the verdict, Proto
10// the protocol ("SSH", "GPG", "X.509", empty when unknown), Class the style
11// (sig-ok, sig-warn, sig-bad, sig-none) and Title the tooltip that says why.
12// Reason says why without naming the key, for a page that shows the key next
13// to it.
14type SignatureBadge struct {
15	Label, Proto, Class, Title, Reason string
16}
17
18// signatureProto is the display name of a signature format.
19func signatureProto(f domain.SignatureFormat) string {
20	switch f {
21	case domain.FormatSSH:
22		return "SSH"
23	case domain.FormatGPG:
24		return "GPG"
25	case domain.FormatX509:
26		return "X.509"
27	}
28	return ""
29}
30
31// signatureBadge is the badge of a verification, nil when there is nothing to
32// show: the commit was not checked, or (unless long, the commit page) it is
33// not signed.
34func signatureBadge(v domain.CommitVerification, long bool) *SignatureBadge {
35	switch v.Status {
36	case domain.StatusUnchecked:
37		return nil
38	case domain.StatusUnsigned:
39		if !long {
40			return nil
41		}
42		return &SignatureBadge{Label: "unsigned", Class: "sig-none", Title: "This commit is not signed", Reason: "Not signed"}
43	}
44	b := &SignatureBadge{Proto: signatureProto(v.Format)}
45	key := keyPhrase(v)
46	switch v.Status {
47	case domain.StatusVerified:
48		b.Label, b.Class = "Verified", "sig-ok"
49		b.Title = "Signed with " + key + " — verified for the committer email"
50		b.Reason = "Verified for the committer email"
51	case domain.StatusUnknownKey:
52		b.Label, b.Class = "Unverified", "sig-warn"
53		b.Title = unknownKeyTitle(v)
54		b.Reason = "The key is unknown"
55	case domain.StatusEmailMismatch:
56		b.Label, b.Class = "Unverified", "sig-warn"
57		b.Title = "Signed with " + key + " — this key is not trusted for the committer email"
58		b.Reason = "This key is not trusted for the committer email"
59	case domain.StatusExpired:
60		b.Label, b.Class = "Unverified", "sig-warn"
61		b.Title = "Signed with " + key + " — the key or the signature expired"
62		b.Reason = "The key or the signature expired"
63	case domain.StatusRevoked:
64		b.Label, b.Class = "Unverified", "sig-warn"
65		b.Title = "Signed with " + key + " — the key is revoked"
66		b.Reason = "The key is revoked"
67	case domain.StatusBadSignature:
68		b.Label, b.Class = "Bad signature", "sig-bad"
69		b.Reason = "The signature does not match the commit"
70	case domain.StatusUnsupported:
71		b.Label, b.Class = "Unverified", "sig-warn"
72		if proto := signatureProto(v.Format); proto != "" {
73			b.Reason = proto + " signatures are not checked"
74		} else {
75			b.Reason = "This kind of signature is not checked"
76		}
77	default:
78		return nil
79	}
80	if b.Title == "" {
81		b.Title = b.Reason
82	}
83	return b
84}
85
86// keyPhrase names the signing key for a tooltip: "SSH key SHA256:… (title)".
87func keyPhrase(v domain.CommitVerification) string {
88	s := "a key"
89	if p := signatureProto(v.Format); p != "" {
90		s = p + " key"
91	}
92	if v.Fingerprint != "" {
93		s += " " + v.Fingerprint
94	}
95	if v.KeyTitle != "" {
96		s += " (" + visibleText(v.KeyTitle) + ")"
97	}
98	return s
99}
100
101// unknownKeyTitle is the tooltip of a signature by a key that is not trusted.
102func unknownKeyTitle(v domain.CommitVerification) string {
103	s := "Signed with an unknown "
104	if p := signatureProto(v.Format); p != "" {
105		s += p + " "
106	}
107	s += "key"
108	if v.Fingerprint != "" {
109		s += " " + v.Fingerprint
110	}
111	return s
112}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 45

↑↓ moveenter openesc close