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.

Show what a merged change request landed

2841d922c15ce32fb600d992f9f5dc2d22c48f90 · Keenan Nemetz · 13d ago · parent c765ae2e

Show what a merged change request landed

Landing moves the base to include the head, so the three-dot diff the
Files tab reviews an open request with becomes empty by definition: the
merge base of the base and the head is the head itself. A merged request
therefore looked like it had never changed anything.

The landing mark now records `merged_base`, the tip the base pointed at
before it moved, and the Files tab diffs that against what landed: the
head for a fast-forward, the merge commit otherwise, so conflict
resolution is part of the review too. The record is written in the same
commit as the mark, and a retry that finds the head already landed keeps
the base it has instead of naming the moved tip.

`merged_base` is optional on read because landings made before the field
existed have none. Such a record falls back to the merge commit's first
parent when the landing was a merge, and claims nothing when it was a
fast-forward and the starting point is gone.

The request page shows the file count and the review link for a merged
request as well, with the merge named beside it, instead of replacing
them with "Already merged".

server/web/request_view.go +46−8

@@ -11,6 +11,7 @@ import (
11 11 "strings"
12 12 "time"
13 13
14 "github.com/rodent-software/vole/core/git"
14 15 "github.com/rodent-software/vole/forge/access"
15 16 "github.com/rodent-software/vole/forge/landing"
16 17 "github.com/rodent-software/vole/forge/repo"
@@ -269,24 +270,61 @@ func (w *Web) renderRequestPage(rw http.ResponseWriter, r *http.Request, errMsg
269 270 }, status)
270 271 }
271 272
272 // requestDiff computes the three-dot patch for a change and answers whether it
273 // is already merged. The diff is cheap for one branch and always matches what
274 // the approver is being asked to approve.
273 // requestDiff computes the diff a change request is reviewed as and answers whether
274 // it is already merged. The diff is cheap for one branch and always matches what the
275 // approver is being asked to approve.
276 //
277 // While the request is open that is the three-dot diff against the branch it targets.
278 // Once the head is in the base, though, that diff is empty by definition — the merge
279 // base of the two is the head itself — so the landing record is used instead:
280 // merged_base is the tip the request landed on, which is the "before" side of the
281 // change that landed.
275 282 func (w *Web) requestDiff(m *repo.Meta, req *metadata.Request) (files []diffFile, truncated, alreadyMerged bool) {
276 283 if req.Head == "" {
277 284 return nil, false, false
278 285 }
279 if patch, cut, err := m.Git().Diff(req.Base+"..."+req.Head, "", diffCap); err == nil {
280 files = parseDiff(patch)
281 truncated = cut
286 from, to := req.Base+"..."+req.Head, ""
287 landed := req.State == "merged"
288 if !landed {
289 landed, _ = m.Git().IsAncestor(req.Head, req.Base)
282 290 }
283 // An empty diff means one of two very different things, so say which.
284 if in, err := m.Git().IsAncestor(req.Head, req.Base); err == nil && in {
291 if landed {
285 292 alreadyMerged = true
293 from, to = mergedDiffRange(m.Git(), req)
294 }
295 if from == "" {
296 return nil, false, alreadyMerged
297 }
298 if patch, cut, err := m.Git().Diff(from, to, diffCap); err == nil {
299 files = parseDiff(patch)
300 truncated = cut
286 301 }
287 302 return files, truncated, alreadyMerged
288 303 }
289 304
305 // mergedDiffRange is the "before" and "after" of a landing, or two empty strings
306 // when the record cannot say. merged_base is exactly the old side of the change. A
307 // request merged before that field existed names no base, but a landing that was a
308 // merge commit still has one in its first parent; a fast-forward landing's head is
309 // the branch's own commit, so its first parent is the branch history and not the
310 // base.
311 func mergedDiffRange(g *git.Repo, req *metadata.Request) (from, to string) {
312 landing := req.Head
313 if req.MergedHead != "" {
314 landing = req.MergedHead
315 }
316 if req.MergedBase != "" {
317 return req.MergedBase, landing
318 }
319 if landing == req.Head {
320 return "", ""
321 }
322 if parents, err := g.CommitParents(landing); err == nil && len(parents) > 1 {
323 return parents[0], landing
324 }
325 return "", ""
326 }
327
290 328 // reviewPathFor maps an anchor's path to the file row a review URL should open.
291 329 // A comment on the old side of a rename names the old path, while the Files tree
292 330 // keys the change by its new path.