Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

Give every account a profile page and a directory of accounts

a6b42433c087ab0c936e34a1d05cf488db7a92d6 · Keenan Nemetz · 8d ago · parent 727df386

Give every account a profile page and a directory of accounts

Clicking a name now opens /u/<name>: the issues and changes somebody
opened, the reviews they gave, the comments they left, and the
repositories that work is in. The numbers and the feed are grouped reads
of the same event rows the activity feed pages through, so a profile
cannot disagree with the feed, and every read is limited to the
repositories the reader may open. A private repository's work is not a
number an outsider gets to learn, so two readers can see different
totals and the page says what its numbers cover.

/u lists the roster. Account names are public on this instance; their
activity is not. Both pages ask crawlers to leave them out of an index.

@name in an issue, a comment, a change request, or a release note links
to the account it addresses, using the mention scanner CI already
evaluates: what reads as an address on the page is what a workflow sees.
A name that is not an account stays text, and code samples are skipped.

Git author names are not linked and commit counts are not shown. A
commit author is a name somebody typed, not the credential that proves
an account, and the index only keeps the top eight authors of each
default branch anyway.

metadata/mentions.go +28−10

@@ -57,10 +57,16 @@ func fenceMarker(line string) string {
57 57 return ""
58 58 }
59 59
60 // collectMentions scans one line that is not inside a fenced block. Inline code is
61 // skipped by counting a backtick run and jumping to the matching run. An unmatched
62 // backtick is literal Markdown, so the rest of the line is still scanned.
63 func collectMentions(line string, seen map[string]bool, out *[]string) {
60 // EachMention calls fn once for every mention on one line of prose: start is the
61 // byte offset of the '@', end the offset just past the account name, and name the
62 // lowercased account the text addresses. fn returns false to stop the scan.
63 //
64 // It is the one scanner: Mentions collects names for an event payload with it, and
65 // the web renderer links the same spans, so a name a workflow responds to is the
66 // name the page turns into a link. Scanning a line rather than a rendered
67 // document is deliberate — the offline form of escape and inline code is still
68 // visible there.
69 func EachMention(line string, fn func(start, end int, name string) bool) {
64 70 for i := 0; i < len(line); {
65 71 switch line[i] {
66 72 case '`':
@@ -96,17 +102,29 @@ func collectMentions(line string, seen map[string]bool, out *[]string) {
96 102 i = end
97 103 continue
98 104 }
99 if !seen[name] {
100 if len(*out) >= MaxMentions {
101 return
102 }
103 seen[name] = true
104 *out = append(*out, name)
105 if !fn(i, end, name) {
106 return
105 107 }
106 108 i = end
107 109 }
108 110 }
109 111
112 // collectMentions scans one line that is not inside a fenced block, keeping the
113 // first MaxMentions distinct names.
114 func collectMentions(line string, seen map[string]bool, out *[]string) {
115 EachMention(line, func(_, _ int, name string) bool {
116 if seen[name] {
117 return true
118 }
119 if len(*out) >= MaxMentions {
120 return false
121 }
122 seen[name] = true
123 *out = append(*out, name)
124 return true
125 })
126 }
127
110 128 func backtickRun(s string) int {
111 129 n := 0
112 130 for n < len(s) && s[n] == '`' {