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.
Show what a merged change request landed
2841d922c15ce32fb600d992f9f5dc2d22c48f90 · Keenan Nemetz · 14d 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".
forge/landing/land.go +10−5
| @@ -96,7 +96,10 @@ func Land(g Gate, id, actor string, now time.Time) (*Plan, error) { | |||
| 96 | 96 | if plan.Request.State == "merged" && plan.Request.MergedHead != "" { | |
| 97 | 97 | return plan, nil | |
| 98 | 98 | } | |
| 99 | closed, err := marked(g, id, plan.Head, actor, now) | ||
| 99 | // The base already contains the head, so there is no "before" left to | ||
| 100 | // name: plan.OldTip is the moved tip, not the one the landing started | ||
| 101 | // from. Marking without a base keeps the record honest. | ||
| 102 | closed, err := marked(g, id, "", plan.Head, actor, now) | ||
| 100 | 103 | if err != nil { | |
| 101 | 104 | return plan, &ErrLandedUnmarked{NewTip: plan.OldTip, Err: err} | |
| 102 | 105 | } | |
| @@ -120,7 +123,7 @@ func Land(g Gate, id, actor string, now time.Time) (*Plan, error) { | |||
| 120 | 123 | g.Audit(fmt.Sprintf("merge %s by %s (head %s)", g.Ref(plan.Base), actor, format.SHA(plan.NewTip))) | |
| 121 | 124 | } | |
| 122 | 125 | } | |
| 123 | closed, err := marked(g, id, plan.NewTip, actor, now) | ||
| 126 | closed, err := marked(g, id, plan.OldTip, plan.NewTip, actor, now) | ||
| 124 | 127 | if err != nil { | |
| 125 | 128 | return plan, &ErrLandedUnmarked{NewTip: plan.NewTip, Err: err} | |
| 126 | 129 | } | |
| @@ -212,9 +215,11 @@ func Prepare(g Gate, id, actor string, now time.Time) (*Plan, error) { | |||
| 212 | 215 | } | |
| 213 | 216 | ||
| 214 | 217 | // marked writes the merged state and turns a validation problem into an error, so a | |
| 215 | // landing never leaves a request file the metadata reader would refuse. | ||
| 216 | func marked(g Gate, id, head, actor string, now time.Time) ([]string, error) { | ||
| 217 | _, closed, err := metadata.MarkMerged(g.Repo, id, head, actor, now) | ||
| 218 | // landing never leaves a request file the metadata reader would refuse. The base tip | ||
| 219 | // is what the branch pointed at before it moved: it is the only record of what the | ||
| 220 | // merge changed once the head is in the base. | ||
| 221 | func marked(g Gate, id, baseTip, head, actor string, now time.Time) ([]string, error) { | ||
| 222 | _, closed, err := metadata.MarkMerged(g.Repo, id, baseTip, head, actor, now) | ||
| 218 | 223 | if err != nil { | |
| 219 | 224 | return nil, err | |
| 220 | 225 | } | |