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.

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

metadata/requests_test.go +43−19

@@ -207,12 +207,25 @@ func TestMergedStateNamesTheCommitThatLanded(t *testing.T) {
207 207 return m
208 208 }
209 209
210 if _, err := ParseRequests(merged("merged_head: " + sideSHA + "\nmerged_by: wilbur\nmerged_at: 2026-10-23T09:00:00Z\n")); err != nil {
210 verified := "merged_head: " + sideSHA + "\nmerged_by: wilbur\nmerged_at: 2026-10-23T09:00:00Z\n"
211 if _, err := ParseRequests(merged(verified)); err != nil {
211 212 t.Fatalf("a merged request with a commit was refused: %v", err)
212 213 }
214 // merged_base records what the base pointed at before the landing, which is what
215 // a merged request is diffed against. It is optional on read so records written
216 // before it existed still load, but the value has to be a commit.
217 withBase := merged(verified + "merged_base: " + headSHA + "\n")
218 list, err := ParseRequests(withBase)
219 if err != nil {
220 t.Fatalf("a merged request naming its base was refused: %v", err)
221 }
222 if list[0].MergedBase != headSHA {
223 t.Fatalf("merged_base = %q, want %q", list[0].MergedBase, headSHA)
224 }
213 225 for name, extra := range map[string]string{
214 226 "no merged_head": "merged_by: wilbur\nmerged_at: 2026-10-23T09:00:00Z\n",
215 227 "short merged_head": "merged_head: " + headSHA[:12] + "\nmerged_by: wilbur\nmerged_at: 2026-10-23T09:00:00Z\n",
228 "short merged_base": verified + "merged_base: " + headSHA[:12] + "\n",
216 229 "no merged_by": "merged_head: " + sideSHA + "\nmerged_at: 2026-10-23T09:00:00Z\n",
217 230 "no merged_at": "merged_head: " + sideSHA + "\nmerged_by: wilbur\n",
218 231 "merged_at is not TS": "merged_head: " + sideSHA + "\nmerged_by: wilbur\nmerged_at: yesterday\n",
@@ -222,19 +235,24 @@ func TestMergedStateNamesTheCommitThatLanded(t *testing.T) {
222 235 }
223 236 }
224 237 // A request that is not merged may not carry the record either.
225 pending := map[string]string{
226 RequestsDir + "/" + reqIDA + "/request.md": "---\n" +
227 `title: "Land the retry"` + "\n" +
228 "state: open\n" +
229 "author: keenan\n" +
230 "base: main\n" +
231 "head: " + headSHA + "\n" +
232 "created: 2026-10-22T22:00:00Z\n" +
233 "merged_head: " + sideSHA + "\n" +
234 "---\n",
235 }
236 if _, err := ParseRequests(pending); err == nil {
237 t.Error("an open request with a merged_head was accepted")
238 for field, line := range map[string]string{
239 "merged_head": "merged_head: " + sideSHA + "\n",
240 "merged_base": "merged_base: " + sideSHA + "\n",
241 } {
242 pending := map[string]string{
243 RequestsDir + "/" + reqIDA + "/request.md": "---\n" +
244 `title: "Land the retry"` + "\n" +
245 "state: open\n" +
246 "author: keenan\n" +
247 "base: main\n" +
248 "head: " + headSHA + "\n" +
249 "created: 2026-10-22T22:00:00Z\n" +
250 line +
251 "---\n",
252 }
253 if _, err := ParseRequests(pending); err == nil {
254 t.Errorf("an open request with a %s was accepted", field)
255 }
238 256 }
239 257 }
240 258
@@ -340,12 +358,12 @@ func TestMarkMergedClosesLinkedIssues(t *testing.T) {
340 358 t.Fatal("a request closing a non-existent issue was written")
341 359 }
342 360
343 got, closed, err := MarkMerged(g, req.ID, headSHA, "reviewer", now)
361 got, closed, err := MarkMerged(g, req.ID, sideSHA, headSHA, "reviewer", now)
344 362 if err != nil {
345 363 t.Fatalf("mark: %v", err)
346 364 }
347 if got.State != "merged" || got.MergedHead != headSHA {
348 t.Fatalf("request = %q/%q", got.State, got.MergedHead)
365 if got.State != "merged" || got.MergedHead != headSHA || got.MergedBase != sideSHA {
366 t.Fatalf("request = %q/%q/%q", got.State, got.MergedBase, got.MergedHead)
349 367 }
350 368 if len(closed) != 1 || closed[0] != linked.ID {
351 369 t.Fatalf("closed = %v, want [%s] — the already-closed issue is not rewritten", closed, linked.ID)
@@ -360,9 +378,15 @@ func TestMarkMergedClosesLinkedIssues(t *testing.T) {
360 378 }
361 379 }
362 380 // Marking again is a no-op, so a retried landing does not re-close anything.
363 if _, again, err := MarkMerged(g, req.ID, headSHA, "reviewer", now); err != nil || len(again) != 0 {
381 // A retry that cannot name the base keeps the one the record already has.
382 if _, again, err := MarkMerged(g, req.ID, "", headSHA, "reviewer", now); err != nil || len(again) != 0 {
364 383 t.Fatalf("second mark = %v, %v", again, err)
365 384 }
385 if again, err := GetRequest(g, req.ID); err != nil {
386 t.Fatal(err)
387 } else if again.MergedBase != sideSHA {
388 t.Fatalf("a retry without a base dropped it: merged_base = %q, want %q", again.MergedBase, sideSHA)
389 }
366 390 }
367 391
368 392 // A link is often discovered after a request is opened, so it can be set later. The
@@ -415,7 +439,7 @@ func TestSetRequestClosesEditsAnOpenRequest(t *testing.T) {
415 439 }
416 440 }
417 441 // Once merged, the list is a record of what happened, not a setting.
418 if _, _, err := MarkMerged(g, req.ID, headSHA, "reviewer", now); err != nil {
442 if _, _, err := MarkMerged(g, req.ID, sideSHA, headSHA, "reviewer", now); err != nil {
419 443 t.Fatal(err)
420 444 }
421 445 if _, err := SetRequestCloses(g, req.ID, nil, "keenan"); err == nil {