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