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_files_test.go +67−1

@@ -1,6 +1,11 @@
1 1 package web
2 2
3 import "testing"
3 import (
4 "testing"
5
6 "github.com/rodent-software/vole/core/git"
7 "github.com/rodent-software/vole/metadata"
8 )
4 9
5 10 func findTreeNode(nodes []*treeNode, path string) *treeNode {
6 11 for _, n := range nodes {
@@ -50,3 +55,64 @@ func TestBuildReviewTreeStatusesAndActiveAncestors(t *testing.T) {
50 55 t.Fatal("the renamed file is still keyed by its old path")
51 56 }
52 57 }
58
59 // commitWith writes one file over parent and answers the commit id, so these tests
60 // can build the two landing shapes without the review flow.
61 func commitWith(t *testing.T, g *git.Repo, parent, name, content, msg string) string {
62 t.Helper()
63 blob, err := g.HashObject([]byte(content))
64 if err != nil {
65 t.Fatal(err)
66 }
67 tree, err := g.MkTree([]git.Entry{{Mode: "100644", Type: "blob", SHA: blob, Name: name}})
68 if err != nil {
69 t.Fatal(err)
70 }
71 c, err := g.CommitTree(tree, parent, msg)
72 if err != nil {
73 t.Fatal(err)
74 }
75 return c
76 }
77
78 // A merged request is reviewed against where it landed from, not against the base as
79 // it is now: after the landing the base contains the head, so a diff of the two is
80 // empty. The recorded base is what answers that; a record written before it existed
81 // falls back to the merge commit's first parent, because that is what the base was.
82 func TestMergedDiffRangeNamesTheBaseTheChangeStartedFrom(t *testing.T) {
83 t.Parallel()
84 root := t.TempDir()
85 if err := git.InitBare(root, "main"); err != nil {
86 t.Fatal(err)
87 }
88 g := git.Open(root)
89 base := commitWith(t, g, "", "readme.md", "v1\n", "base")
90 head := commitWith(t, g, base, "readme.md", "v2\n", "change")
91 tree, err := g.MkTree(nil)
92 if err != nil {
93 t.Fatal(err)
94 }
95 landing, err := g.CommitMergeTree(tree, []string{base, head}, "merge the change", "keenan")
96 if err != nil {
97 t.Fatal(err)
98 }
99
100 recorded := &metadata.Request{Base: "main", Head: head, State: "merged", MergedBase: base, MergedHead: landing}
101 if from, to := mergedDiffRange(g, recorded); from != base || to != landing {
102 t.Fatalf("recorded range = %s..%s, want %s..%s", from, to, base, landing)
103 }
104
105 // No recorded base, but the landing itself was a merge: its first parent is the
106 // base tip the merge started from.
107 legacy := &metadata.Request{Base: "main", Head: head, State: "merged", MergedHead: landing}
108 if from, to := mergedDiffRange(g, legacy); from != base || to != landing {
109 t.Fatalf("legacy merge range = %s..%s, want %s..%s", from, to, base, landing)
110 }
111
112 // A fast-forward landing's head is not a merge commit, so nothing is claimed: an
113 // empty range is better than a diff of the branch's own history.
114 fastForward := &metadata.Request{Base: "main", Head: head, State: "merged", MergedHead: head}
115 if from, to := mergedDiffRange(g, fastForward); from != "" || to != "" {
116 t.Fatalf("fast-forward range = %s..%s, want none", from, to)
117 }
118 }