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_test.go +13
| @@ -166,6 +166,11 @@ func TestLandFastForwardsAnApprovedRequest(t *testing.T) { | |||
| 166 | 166 | t.Fatalf("merged record = %q by %q at %v, want head %s by reviewer", | |
| 167 | 167 | got.MergedHead, got.MergedBy, got.MergedAt, head) | |
| 168 | 168 | } | |
| 169 | // The base tip before the landing is the "before" side of what the merge | ||
| 170 | // changed: once the head is in main, it is the only record of the starting point. | ||
| 171 | if got.MergedBase != base { | ||
| 172 | t.Fatalf("merged_base = %s, want the base tip %s", got.MergedBase, base) | ||
| 173 | } | ||
| 169 | 174 | } | |
| 170 | 175 | ||
| 171 | 176 | func TestLandRefusesWithoutAnApproval(t *testing.T) { | |
| @@ -230,6 +235,9 @@ func TestLandWritesAMergeCommitWhenTheBaseMoved(t *testing.T) { | |||
| 230 | 235 | if got.MergedHead != res.NewTip { | |
| 231 | 236 | t.Fatalf("merged_head = %s, want the merge commit %s", got.MergedHead, res.NewTip) | |
| 232 | 237 | } | |
| 238 | if got.MergedBase != moved { | ||
| 239 | t.Fatalf("merged_base = %s, want the base tip %s the merge started from", got.MergedBase, moved) | ||
| 240 | } | ||
| 233 | 241 | } | |
| 234 | 242 | ||
| 235 | 243 | func TestLandRefusesAConflict(t *testing.T) { | |
| @@ -284,6 +292,11 @@ func TestLandTwiceMarksOnceAndKeepsTheHead(t *testing.T) { | |||
| 284 | 292 | if got.State != "merged" || got.MergedHead != head { | |
| 285 | 293 | t.Fatalf("record = %q/%q, want merged at %s", got.State, got.MergedHead, head) | |
| 286 | 294 | } | |
| 295 | // A retry has no base tip of its own to name, so it keeps the one the first | ||
| 296 | // landing recorded instead of overwriting it with the moved tip. | ||
| 297 | if got.MergedBase != base { | ||
| 298 | t.Fatalf("merged_base = %s, want the retained %s", got.MergedBase, base) | ||
| 299 | } | ||
| 287 | 300 | } | |
| 288 | 301 | ||
| 289 | 302 | func TestLandRefusesAClosedRequest(t *testing.T) { | |