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.

Say what each change is waiting for in the list

7cc36ba0f4ba96d3f4938b13758b2cc20c22375a · Keenan Nemetz · 5d ago · parent 946d2fd4

Say what each change is waiting for in the list

A change spends most of its life in states "open" cannot describe: held for a
maintainer to allow CI, running its checks, failed, waiting for a reviewer,
conflicting with its base. The list now says which of those it is, in one
column, with a tooltip that explains the label.

The answer comes from facts a list can afford: the approvals it already counts,
one batched run-queue query for every head on the page, the contributor
allowance (one ref listing, not one per row), and — only for a change that would
otherwise read as ready — whether it still merges into its base. The full
landing plan stays on the detail page.

The rest of the row follows the issue list: the author and opening moved under
the title, the comment count sits at the end of that line, and the last column
is "Updated", the newest commit over the request's directory. The list sorts by
it. The separate Author, Approvals and Age columns are gone.

Closes #194

forge/index/refresh.go +20−7

@@ -98,12 +98,17 @@ func (s *Store) IndexRepo(m *repo.Meta) error {
98 98 if err != nil {
99 99 return err
100 100 }
101 // One `git log` dates every issue's last activity. The branch has no updated
102 // field to read, and a process per issue would make a rebuild cost the queue.
101 // One `git log` dates every issue's and request's last activity. The branch has
102 // no updated field to read, and a process per entity would make a rebuild cost
103 // the queue.
103 104 touchTimes, err := issueTouchTimes(g, commit)
104 105 if err != nil {
105 106 return err
106 107 }
108 requestTimes, err := requestTouchTimes(g, commit)
109 if err != nil {
110 return err
111 }
107 112
108 113 tx, err := s.db.Begin()
109 114 if err != nil {
@@ -138,7 +143,7 @@ func (s *Store) IndexRepo(m *repo.Meta) error {
138 143 }
139 144 }
140 145 for _, req := range requests {
141 if err := insertRequest(tx, m.ID, req); err != nil {
146 if err := insertRequest(tx, m.ID, req, requestTimes[req.ID]); err != nil {
142 147 return err
143 148 }
144 149 if err := replaceComments(tx, m.ID, commentKindRequest, req.ID, req.Title, req.Comments); err != nil {
@@ -220,9 +225,10 @@ func (s *Store) indexFrom(m *repo.Meta, from, to string) error {
220 225 }
221 226 }
222 227
223 // The commits also date each issue's last activity, which is what the list
224 // sorts by. They name every issue the range touched, including one edited and
225 // then edited back: the tree diff cannot see those, but the issue still moved.
228 // The commits also date each entity's last activity, which is what the lists
229 // sort by. They name every issue and request the range touched, including one
230 // edited and then edited back: the tree diff cannot see those, but the entity
231 // still moved.
226 232 touchTimes, err := issueTouchTimes(g, from+".."+to)
227 233 if err != nil {
228 234 return err
@@ -230,6 +236,13 @@ func (s *Store) indexFrom(m *repo.Meta, from, to string) error {
230 236 for id := range touchTimes {
231 237 issueIDs[id] = true
232 238 }
239 requestTimes, err := requestTouchTimes(g, from+".."+to)
240 if err != nil {
241 return err
242 }
243 for id := range requestTimes {
244 requestIDs[id] = true
245 }
233 246
234 247 // Read every changed entity before opening the write transaction. `to` is a
235 248 // commit, so a concurrent push cannot make the rows describe two tree states.
@@ -324,7 +337,7 @@ func (s *Store) indexFrom(m *repo.Meta, from, to string) error {
324 337 if err := deleteRequest(tx, m.ID, req.ID); err != nil {
325 338 return err
326 339 }
327 if err := insertRequest(tx, m.ID, req); err != nil {
340 if err := insertRequest(tx, m.ID, req, requestTimes[req.ID]); err != nil {
328 341 return err
329 342 }
330 343 if err := replaceComments(tx, m.ID, commentKindRequest, req.ID, req.Title, req.Comments); err != nil {