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/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.

Search for an issue when linking it to a change

838f282eebf7e68f61318eae93d03263df1341df · Keenan Nemetz · 10d ago · parent 267feec2

Search for an issue when linking it to a change

The link field only accepted an id that the author already knew. It is now a search box over the issue index that matches a title, a #number, or a ULID prefix. A matching issue is a button: picking one posts the same link write and replaces the Issues section with the new list. Issues already linked are left out of the results.

forge/index/issues.go +52−3

@@ -2,6 +2,7 @@ package index
2 2
3 3 import (
4 4 "database/sql"
5 "strconv"
5 6 "strings"
6 7 "time"
7 8
@@ -266,11 +267,15 @@ type Filter struct {
266 267 Query string
267 268 }
268 269
269 func (s *Store) Issues(repoID string, f Filter) ([]IssueRow, error) {
270 q := `SELECT i.id, i.title, i.state, i.author, i.created, i.labels, i.assignees, i.comments,
270 // issueSelect is the row every issue list scans: the indexed issue with its
271 // `#42` alias from the numbering table.
272 const issueSelect = `SELECT i.id, i.title, i.state, i.author, i.created, i.labels, i.assignees, i.comments,
271 273 COALESCE(n.number, 0)
272 274 FROM issues i
273 LEFT JOIN entity_numbers n ON n.repo_id = i.repo_id AND n.id = i.id
275 LEFT JOIN entity_numbers n ON n.repo_id = i.repo_id AND n.id = i.id`
276
277 func (s *Store) Issues(repoID string, f Filter) ([]IssueRow, error) {
278 q := issueSelect + `
274 279 WHERE i.repo_id=?`
275 280 args := []any{repoID}
276 281 if f.State != "" && f.State != "all" {
@@ -297,6 +302,50 @@ func (s *Store) Issues(repoID string, f Filter) ([]IssueRow, error) {
297 302 return nil, err
298 303 }
299 304 defer rows.Close()
305 return scanIssueRows(rows)
306 }
307
308 // SearchIssues finds the issues a link picker should offer. A term matches a
309 // title as a substring, and a number or ULID as a typed reference: `42` and
310 // `#42` find issue 42, and a ULID or its prefix matches from the start. Exact
311 // references sort ahead of title matches so the issue someone named is first.
312 func (s *Store) SearchIssues(repoID, term string, limit int) ([]IssueRow, error) {
313 term = strings.TrimSpace(term)
314 if term == "" {
315 return nil, nil
316 }
317 if limit <= 0 {
318 limit = 8
319 }
320 ref := strings.TrimPrefix(term, "#")
321 number := 0
322 if n, err := strconv.Atoi(ref); err == nil && n > 0 {
323 number = n
324 }
325 q := issueSelect + `
326 WHERE i.repo_id=? AND (i.title LIKE ? OR i.id LIKE ?`
327 args := []any{repoID, "%" + term + "%", ref + "%"}
328 if number > 0 {
329 q += ` OR n.number=?`
330 args = append(args, number)
331 }
332 q += `)
333 ORDER BY CASE
334 WHEN i.id=? THEN 0
335 WHEN n.number=? THEN 1
336 WHEN i.id LIKE ? THEN 2
337 ELSE 3 END, i.id DESC
338 LIMIT ?`
339 args = append(args, ref, number, ref+"%", limit)
340 rows, err := s.db.Query(q, args...)
341 if err != nil {
342 return nil, err
343 }
344 defer rows.Close()
345 return scanIssueRows(rows)
346 }
347
348 func scanIssueRows(rows *sql.Rows) ([]IssueRow, error) {
300 349 var out []IssueRow
301 350 for rows.Next() {
302 351 var r IssueRow