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.

Code

392 commit(s) on main

Merge change request 01M70WES5ZZDJ6RP226WB11BBZ: Let people rename and remove their own passkeys keenan · 54m ago 4b8c9e1d
history blame raw 4.0 KiB
1package metadata
2
3// A pushed merge commit is accepted only when its tree is the merge of the branch
4// tip and the reviewed head. The two parents alone are a shape, not a landing.
5
6import (
7 "errors"
8 "strings"
9 "testing"
10
11 "rodent.software/vole/core/git"
12)
13
14func commitFile(t *testing.T, g *git.Repo, parent, name, content, msg string) string {
15 t.Helper()
16 blob, err := g.HashObject([]byte(content))
17 if err != nil {
18 t.Fatal(err)
19 }
20 tree, err := g.MkTree([]git.Entry{{Mode: "100644", Type: "blob", SHA: blob, Name: name}})
21 if err != nil {
22 t.Fatal(err)
23 }
24 commit, err := g.CommitTree(tree, parent, msg)
25 if err != nil {
26 t.Fatal(err)
27 }
28 return commit
29}
30
31func TestFindApprovedMatchChecksTheMergeTree(t *testing.T) {
32 t.Parallel()
33 g := newRequestRepo(t)
34 base := commitFile(t, g, "", "a.md", "one\n", "base")
35 head := commitFile(t, g, base, "c.md", "two\n", "the reviewed head")
36 moved := commitFile(t, g, base, "b.md", "other\n", "the base moved")
37 if err := g.UpdateRef("refs/heads/main", moved); err != nil {
38 t.Fatal(err)
39 }
40
41 // The merge the branch would make, and a merge with the right parents but an
42 // extra file that nobody reviewed.
43 tree, conflicts, err := g.MergeTree(moved, head)
44 if err != nil {
45 t.Fatal(err)
46 }
47 if len(conflicts) > 0 {
48 t.Fatalf("fixture conflict: %v", conflicts)
49 }
50 good, err := g.CommitMergeTree(tree, []string{moved, head}, "merge", "")
51 if err != nil {
52 t.Fatal(err)
53 }
54 blobC, _ := g.HashObject([]byte("two\n"))
55 blobB, _ := g.HashObject([]byte("other\n"))
56 blobX, _ := g.HashObject([]byte("smuggled\n"))
57 badTree, err := g.MkTree([]git.Entry{
58 {Mode: "100644", Type: "blob", SHA: blobC, Name: "c.md"},
59 {Mode: "100644", Type: "blob", SHA: blobB, Name: "b.md"},
60 {Mode: "100644", Type: "blob", SHA: blobX, Name: "x.md"},
61 })
62 if err != nil {
63 t.Fatal(err)
64 }
65 bad, err := g.CommitMergeTree(badTree, []string{moved, head}, "merge", "")
66 if err != nil {
67 t.Fatal(err)
68 }
69
70 req := &Request{
71 ID: reqIDA, Title: "Land it", Author: "keenan", Base: "main",
72 Head: head, State: "open",
73 Approvals: []Approval{{Principal: "wilbur", Base: "main", Head: head}},
74 }
75 reqs := []*Request{req}
76
77 m, err := FindApprovedMatch(g, reqs, ApprovalRequirement{
78 Branch: "main", NewTip: good, OldTip: moved, Required: 1,
79 })
80 if err != nil || m == nil {
81 t.Fatalf("the exact merge was refused: %+v, %v", m, err)
82 }
83
84 m, err = FindApprovedMatch(g, reqs, ApprovalRequirement{
85 Branch: "main", NewTip: bad, OldTip: moved, Required: 1,
86 })
87 if m != nil {
88 t.Fatalf("a merge carrying an unapproved file matched: %+v", m)
89 }
90 var mismatch *TreeMismatchError
91 if !errors.As(err, &mismatch) {
92 t.Fatalf("refusal = %v, want a TreeMismatchError", err)
93 }
94 if !strings.Contains(err.Error(), "does not carry the result") || !strings.Contains(err.Error(), reqIDA) {
95 t.Fatalf("refusal does not explain the mismatch: %v", err)
96 }
97}
98
99// A merge that resolved a conflict is not a clean merge of the two sides, so the
100// approval cannot cover it.
101func TestFindApprovedMatchRefusesAResolvedConflict(t *testing.T) {
102 t.Parallel()
103 g := newRequestRepo(t)
104 base := commitFile(t, g, "", "a.md", "one\n", "base")
105 head := commitFile(t, g, base, "a.md", "branch\n", "branch edit")
106 moved := commitFile(t, g, base, "a.md", "base edit\n", "base edit")
107 if err := g.UpdateRef("refs/heads/main", moved); err != nil {
108 t.Fatal(err)
109 }
110 blob, _ := g.HashObject([]byte("resolved\n"))
111 tree, _ := g.MkTree([]git.Entry{{Mode: "100644", Type: "blob", SHA: blob, Name: "a.md"}})
112 resolved, err := g.CommitMergeTree(tree, []string{moved, head}, "merge", "")
113 if err != nil {
114 t.Fatal(err)
115 }
116 reqs := []*Request{{
117 ID: reqIDA, Title: "Land it", Author: "keenan", Base: "main", Head: head, State: "open",
118 Approvals: []Approval{{Principal: "wilbur", Base: "main", Head: head}},
119 }}
120 _, err = FindApprovedMatch(g, reqs, ApprovalRequirement{
121 Branch: "main", NewTip: resolved, OldTip: moved, Required: 1,
122 })
123 var mismatch *TreeMismatchError
124 if !errors.As(err, &mismatch) || len(mismatch.Conflicts) == 0 {
125 t.Fatalf("a resolved conflict was accepted or not explained: %v", err)
126 }
127}
128