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