1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
package loose_test
import (
"os"
"strings"
"testing"
"lindenii.org/go/furgit/internal/testgit"
"lindenii.org/go/furgit/object/header"
"lindenii.org/go/furgit/object/id"
"lindenii.org/go/furgit/object/store/loose"
"lindenii.org/go/furgit/object/typ"
)
// gitOracleObject is one object created by git,
// paired with its expected content body and full serialized form.
type gitOracleObject struct {
name string
ty typ.Type
id id.ObjectID
body []byte
raw []byte
}
// openLooseStore opens a loose store over the repository's objects directory.
func openLooseStore(t *testing.T, repo *testgit.Repo) *loose.Loose {
t.Helper()
repoRoot := repo.Root(t)
objectsRoot, err := repoRoot.OpenRoot(".git/objects")
if err != nil {
_ = repoRoot.Close()
t.Fatalf("OpenRoot(.git/objects): %v", err)
}
_ = repoRoot.Close()
t.Cleanup(func() { _ = objectsRoot.Close() })
looseStore, err := loose.New(objectsRoot, repo.ObjectFormat(t))
if err != nil {
t.Fatalf("loose.New: %v", err)
}
return looseStore
}
// gitOracleObjects builds one object of each kind with git
// and precomputes each one's expected content body and full serialized form.
func gitOracleObjects(t *testing.T, repo *testgit.Repo) []gitOracleObject {
t.Helper()
blobID, err := repo.HashObject(t, typ.TypeBlob, strings.NewReader("blob body\n"))
if err != nil {
t.Fatalf("HashObject(blob): %v", err)
}
treeID, err := repo.MkTree(t, []testgit.TreeEntry{
{Mode: "100644", Type: typ.TypeBlob, OID: blobID, Name: "file"},
})
if err != nil {
t.Fatalf("MkTree: %v", err)
}
commitID, err := repo.CommitTree(t, treeID, testgit.CommitTreeOptions{Message: "subject\n\nbody"})
if err != nil {
t.Fatalf("CommitTree: %v", err)
}
tagID, err := repo.TagAnnotated(t, "v1", commitID, testgit.TagAnnotatedOptions{Message: "tag message"})
if err != nil {
t.Fatalf("TagAnnotated: %v", err)
}
kinds := []struct {
name string
ty typ.Type
id id.ObjectID
}{
{name: "blob", ty: typ.TypeBlob, id: blobID},
{name: "tree", ty: typ.TypeTree, id: treeID},
{name: "commit", ty: typ.TypeCommit, id: commitID},
{name: "tag", ty: typ.TypeTag, id: tagID},
}
objects := make([]gitOracleObject, 0, len(kinds))
for _, kind := range kinds {
body, err := repo.CatFile(t, kind.ty, kind.id)
if err != nil {
t.Fatalf("CatFile(%s): %v", kind.name, err)
}
raw := header.Append(nil, kind.ty, uint64(len(body)))
raw = append(raw, body...)
objects = append(objects, gitOracleObject{
name: kind.name,
ty: kind.ty,
id: kind.id,
body: body,
raw: raw,
})
}
return objects
}
// corruptLooseObjectTrailer flips the final byte of a loose object file,
// damaging the zlib Adler-32 trailer.
func corruptLooseObjectTrailer(t *testing.T, repo *testgit.Repo, objectID id.ObjectID) {
t.Helper()
root := repo.Root(t)
defer func() { _ = root.Close() }()
hex := objectID.String()
relPath := ".git/objects/" + hex[:2] + "/" + hex[2:]
file, err := root.OpenFile(relPath, os.O_RDWR, 0)
if err != nil {
t.Fatalf("OpenFile(%q): %v", relPath, err)
}
defer func() { _ = file.Close() }()
info, err := file.Stat()
if err != nil {
t.Fatalf("Stat(%q): %v", relPath, err)
}
if info.Size() == 0 {
t.Fatalf("corrupt trailer on empty file %q", relPath)
}
last := make([]byte, 1)
_, err = file.ReadAt(last, info.Size()-1)
if err != nil {
t.Fatalf("ReadAt(%q): %v", relPath, err)
}
last[0] ^= 0xff
_, err = file.WriteAt(last, info.Size()-1)
if err != nil {
t.Fatalf("WriteAt(%q): %v", relPath, err)
}
}
|