Fast implementation of Git in pure Go
1package furgit
2
3import (
4 "bytes"
5 "fmt"
6 "os"
7 "path/filepath"
8 "testing"
9 "time"
10)
11
12func TestCommitWrite(t *testing.T) {
13 repoPath, cleanup := setupTestRepo(t)
14 defer cleanup()
15
16 blobHash := gitHashObject(t, repoPath, "blob", []byte("content"))
17
18 repo, err := OpenRepository(repoPath)
19 if err != nil {
20 t.Fatalf("OpenRepository failed: %v", err)
21 }
22 defer func() {
23 _ = repo.Close()
24 }()
25
26 blobHashObj, _ := repo.ParseHash(blobHash)
27 tree := &Tree{
28 Entries: []TreeEntry{
29 {Mode: 0o100644, Name: []byte("file.txt"), ID: blobHashObj},
30 },
31 }
32 treeHash, _ := repo.WriteLooseObject(tree)
33
34 whenUnix := time.Date(2023, 11, 16, 12, 0, 0, 0, time.UTC).Unix()
35 commit := &Commit{
36 Tree: treeHash,
37 Author: Ident{
38 Name: []byte("Test Author"),
39 Email: []byte("test@example.org"),
40 WhenUnix: whenUnix,
41 OffsetMinutes: 0,
42 },
43 Committer: Ident{
44 Name: []byte("Test Committer"),
45 Email: []byte("committer@example.org"),
46 WhenUnix: whenUnix,
47 OffsetMinutes: 0,
48 },
49 Message: []byte("Initial commit\n"),
50 }
51
52 commitHash, err := repo.WriteLooseObject(commit)
53 if err != nil {
54 t.Fatalf("WriteLooseObject failed: %v", err)
55 }
56
57 gitType := string(gitCatFile(t, repoPath, "-t", commitHash.String()))
58 if gitType != "commit" {
59 t.Errorf("git type: got %q, want %q", gitType, "commit")
60 }
61
62 readObj, err := repo.ReadObject(commitHash)
63 if err != nil {
64 t.Fatalf("ReadObject failed after write: %v", err)
65 }
66 readCommit, ok := readObj.(*StoredCommit)
67 if !ok {
68 t.Fatalf("expected *StoredCommit, got %T", readObj)
69 }
70
71 if !bytes.HasPrefix(readCommit.Author.Name, []byte("Test Author")) {
72 t.Errorf("author name: got %q, want prefix %q", readCommit.Author.Name, "Test Author")
73 }
74 if !bytes.Equal(readCommit.Message, []byte("Initial commit\n")) {
75 t.Errorf("message: got %q, want %q", readCommit.Message, "Initial commit\n")
76 }
77}
78
79func TestCommitRead(t *testing.T) {
80 repoPath, cleanup := setupTestRepo(t)
81 defer cleanup()
82
83 workDir, cleanupWork := setupWorkDir(t)
84 defer cleanupWork()
85
86 err := os.WriteFile(filepath.Join(workDir, "file.txt"), []byte("content"), 0o644)
87 if err != nil {
88 t.Fatalf("failed to write file.txt: %v", err)
89 }
90 gitCmd(t, repoPath, nil, "--work-tree="+workDir, "add", ".")
91 gitCmd(t, repoPath, nil, "--work-tree="+workDir, "commit", "-m", "Test commit")
92 commitHash := gitCmd(t, repoPath, nil, "rev-parse", "HEAD")
93
94 repo, err := OpenRepository(repoPath)
95 if err != nil {
96 t.Fatalf("OpenRepository failed: %v", err)
97 }
98 defer func() {
99 _ = repo.Close()
100 }()
101
102 hash, _ := repo.ParseHash(commitHash)
103 obj, err := repo.ReadObject(hash)
104 if err != nil {
105 t.Fatalf("ReadObject failed: %v", err)
106 }
107
108 commit, ok := obj.(*StoredCommit)
109 if !ok {
110 t.Fatalf("expected *StoredCommit, got %T", obj)
111 }
112
113 if !bytes.HasPrefix(commit.Author.Name, []byte("Test Author")) {
114 t.Errorf("author name: got %q", commit.Author.Name)
115 }
116 if !bytes.Equal(commit.Author.Email, []byte("test@example.org")) {
117 t.Errorf("author email: got %q", commit.Author.Email)
118 }
119 if !bytes.Equal(commit.Message, []byte("Test commit\n")) {
120 t.Errorf("message: got %q", commit.Message)
121 }
122 if commit.ObjectType() != ObjectTypeCommit {
123 t.Errorf("ObjectType(): got %d, want %d", commit.ObjectType(), ObjectTypeCommit)
124 }
125}
126
127func TestCommitWithParents(t *testing.T) {
128 repoPath, cleanup := setupTestRepo(t)
129 defer cleanup()
130
131 workDir, cleanupWork := setupWorkDir(t)
132 defer cleanupWork()
133
134 err := os.WriteFile(filepath.Join(workDir, "file1.txt"), []byte("content1"), 0o644)
135 if err != nil {
136 t.Fatalf("failed to write file1.txt: %v", err)
137 }
138 gitCmd(t, repoPath, nil, "--work-tree="+workDir, "add", ".")
139 gitCmd(t, repoPath, nil, "--work-tree="+workDir, "commit", "-m", "First commit")
140 parent1Hash := gitCmd(t, repoPath, nil, "rev-parse", "HEAD")
141
142 err = os.WriteFile(filepath.Join(workDir, "file2.txt"), []byte("content2"), 0o644)
143 if err != nil {
144 t.Fatalf("failed to write file2.txt: %v", err)
145 }
146 gitCmd(t, repoPath, nil, "--work-tree="+workDir, "add", ".")
147 gitCmd(t, repoPath, nil, "--work-tree="+workDir, "commit", "-m", "Second commit")
148 parent2Hash := gitCmd(t, repoPath, nil, "rev-parse", "HEAD")
149
150 err = os.WriteFile(filepath.Join(workDir, "file3.txt"), []byte("content3"), 0o644)
151 if err != nil {
152 t.Fatalf("failed to write file3.txt: %v", err)
153 }
154 gitCmd(t, repoPath, nil, "--work-tree="+workDir, "add", ".")
155 treeHash := gitCmd(t, repoPath, nil, "--work-tree="+workDir, "write-tree")
156
157 mergeCommitData := fmt.Sprintf("tree %s\nparent %s\nparent %s\nauthor Test Author <test@example.org> 1234567890 +0000\ncommitter Test Committer <committer@example.org> 1234567890 +0000\n\nMerge commit\n",
158 treeHash, parent1Hash, parent2Hash)
159
160 cmd := gitHashObject(t, repoPath, "commit", []byte(mergeCommitData))
161 mergeHash := cmd
162
163 repo, err := OpenRepository(repoPath)
164 if err != nil {
165 t.Fatalf("OpenRepository failed: %v", err)
166 }
167 defer func() {
168 _ = repo.Close()
169 }()
170
171 hash, _ := repo.ParseHash(mergeHash)
172 obj, _ := repo.ReadObject(hash)
173 commit := obj.(*StoredCommit)
174
175 if len(commit.Parents) != 2 {
176 t.Fatalf("parents count: got %d, want 2", len(commit.Parents))
177 }
178
179 p1, _ := repo.ParseHash(parent1Hash)
180 p2, _ := repo.ParseHash(parent2Hash)
181
182 if commit.Parents[0] != p1 {
183 t.Errorf("parent[0]: got %s, want %s", commit.Parents[0], parent1Hash)
184 }
185 if commit.Parents[1] != p2 {
186 t.Errorf("parent[1]: got %s, want %s", commit.Parents[1], parent2Hash)
187 }
188}