vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
Show an issue's linked changes and commits in the sidebar
fd9aa8ed41eb04a21fe73fbc0c6bd14762abd735 · Keenan Nemetz · 1d ago · parent c980fe53
server/web/issues.go +50−3
| @@ -7,6 +7,7 @@ import ( | |||
| 7 | 7 | "sort" | |
| 8 | 8 | "strconv" | |
| 9 | 9 | "strings" | |
| 10 | "time" | ||
| 10 | 11 | ||
| 11 | 12 | "github.com/rodent-software/vole/core/git" | |
| 12 | 13 | voleid "github.com/rodent-software/vole/core/id" | |
| @@ -164,12 +165,25 @@ type issueSidebarView struct { | |||
| 164 | 165 | Error string | |
| 165 | 166 | } | |
| 166 | 167 | ||
| 168 | // issueChange is one change request linked to an issue. It carries what the | ||
| 169 | // sidebar row shows: the number that names it, the title that identifies it, and | ||
| 170 | // the state so a merged or closed change is not read as pending work. | ||
| 171 | type issueChange struct { | ||
| 172 | ID string | ||
| 173 | Number int | ||
| 174 | Title string | ||
| 175 | State string | ||
| 176 | Author string | ||
| 177 | Created time.Time | ||
| 178 | } | ||
| 179 | |||
| 167 | 180 | // issuePageView is the full issue page: the sidebar plus the conversation. | |
| 168 | 181 | type issuePageView struct { | |
| 169 | 182 | issueSidebarView | |
| 170 | Description template.HTML | ||
| 171 | Thread threadView | ||
| 172 | Backlinks []git.Commit | ||
| 183 | Description template.HTML | ||
| 184 | Thread threadView | ||
| 185 | Backlinks []git.Commit | ||
| 186 | LinkedChanges []issueChange | ||
| 173 | 187 | } | |
| 174 | 188 | ||
| 175 | 189 | // issueSidebar is the data the issue page and its HTMX fragments share. The | |
| @@ -237,9 +251,42 @@ func (w *Web) IssueView(rw http.ResponseWriter, r *http.Request) { | |||
| 237 | 251 | Description: w.renderMarkdown(m.Name, iss.Description), | |
| 238 | 252 | Thread: w.issueThread(r, m, iss, ""), | |
| 239 | 253 | Backlinks: w.backlinks(m, iss.ID), | |
| 254 | LinkedChanges: w.issueLinkedChanges(memoOf(r), m, iss.ID), | ||
| 240 | 255 | }, 0) | |
| 241 | 256 | } | |
| 242 | 257 | ||
| 258 | // issueLinkedChanges finds the change requests whose closes list names the | ||
| 259 | // issue. The index stores the link in both directions, so this is one lookup | ||
| 260 | // rather than a read of every request on the metadata branch. | ||
| 261 | func (w *Web) issueLinkedChanges(memo *requestMemo, m *repo.Meta, id string) []issueChange { | ||
| 262 | if w.Index == nil { | ||
| 263 | return nil | ||
| 264 | } | ||
| 265 | if err := w.ensureIndex(memo, m); err != nil { | ||
| 266 | return nil | ||
| 267 | } | ||
| 268 | ids, err := w.Index.Closes(m.ID, id) | ||
| 269 | if err != nil || len(ids) == 0 { | ||
| 270 | return nil | ||
| 271 | } | ||
| 272 | rows, err := w.Index.RequestsByID(m.ID, ids) | ||
| 273 | if err != nil { | ||
| 274 | return nil | ||
| 275 | } | ||
| 276 | out := make([]issueChange, 0, len(ids)) | ||
| 277 | for _, id := range ids { | ||
| 278 | row, ok := rows[id] | ||
| 279 | if !ok { | ||
| 280 | continue | ||
| 281 | } | ||
| 282 | out = append(out, issueChange{ | ||
| 283 | ID: row.ID, Number: row.Number, Title: row.Title, | ||
| 284 | State: row.State, Author: row.Author, Created: row.Created, | ||
| 285 | }) | ||
| 286 | } | ||
| 287 | return out | ||
| 288 | } | ||
| 289 | |||
| 243 | 290 | // backlinks finds commits whose message mentions the issue. git does the search, | |
| 244 | 291 | // so there is no second index to keep in sync. | |
| 245 | 292 | func (w *Web) backlinks(m *repo.Meta, id string) []git.Commit { | |