tangled
alpha
login
or
join now
runxiyu.tngl.sh
/
furgit
6
fork
atom
Fast implementation of Git in pure Go
6
fork
atom
overview
issues
pulls
pipelines
repository: Refactor
runxiyu.tngl.sh
1 week ago
120509f0
7a9f76f3
verified
This commit was signed with the committer's
known signature
.
runxiyu.tngl.sh
SSH Key Fingerprint:
SHA256:jdcgZM4f40eqgoEW57poBu5vM9WoGE02ZzhWbfbNzc0=
+149
-268
13 changed files
expand all
collapse all
unified
split
repository
algorithm.go
close.go
config.go
objects.go
open_config.go
refs.go
refs_test.go
repository.go
stored.go
traversal_bench_test.go
traversal_helpers_test.go
traversal_test.go
tree.go
+27
repository/algorithm.go
···
1
1
+
package repository
2
2
+
3
3
+
import (
4
4
+
"fmt"
5
5
+
6
6
+
"codeberg.org/lindenii/furgit/config"
7
7
+
"codeberg.org/lindenii/furgit/objectid"
8
8
+
)
9
9
+
10
10
+
func detectObjectAlgorithm(cfg *config.Config) (objectid.Algorithm, error) {
11
11
+
algoName := cfg.Lookup("extensions", "", "objectformat").Value
12
12
+
if algoName == "" {
13
13
+
algoName = objectid.AlgorithmSHA1.String()
14
14
+
}
15
15
+
16
16
+
algo, ok := objectid.ParseAlgorithm(algoName)
17
17
+
if !ok {
18
18
+
return objectid.AlgorithmUnknown, fmt.Errorf("repository: unsupported object format %q", algoName)
19
19
+
}
20
20
+
21
21
+
return algo, nil
22
22
+
}
23
23
+
24
24
+
// Algorithm returns the repository object ID algorithm.
25
25
+
func (repo *Repository) Algorithm() objectid.Algorithm {
26
26
+
return repo.algo
27
27
+
}
+32
repository/close.go
···
1
1
+
package repository
2
2
+
3
3
+
import "errors"
4
4
+
5
5
+
// Close closes owned stores and filesystem roots.
6
6
+
// The behavior of the repo after Close is undefined.
7
7
+
func (repo *Repository) Close() error {
8
8
+
var errs []error
9
9
+
10
10
+
if repo.refs != nil {
11
11
+
err := repo.refs.Close()
12
12
+
if err != nil {
13
13
+
errs = append(errs, err)
14
14
+
}
15
15
+
}
16
16
+
17
17
+
if repo.objects != nil {
18
18
+
err := repo.objects.Close()
19
19
+
if err != nil {
20
20
+
errs = append(errs, err)
21
21
+
}
22
22
+
}
23
23
+
24
24
+
if repo.objectsLooseForWritingOnly != nil {
25
25
+
err := repo.objectsLooseForWritingOnly.Close()
26
26
+
if err != nil {
27
27
+
errs = append(errs, err)
28
28
+
}
29
29
+
}
30
30
+
31
31
+
return errors.Join(errs...)
32
32
+
}
+32
repository/config.go
···
1
1
+
package repository
2
2
+
3
3
+
import (
4
4
+
"fmt"
5
5
+
"os"
6
6
+
7
7
+
"codeberg.org/lindenii/furgit/config"
8
8
+
)
9
9
+
10
10
+
func parseRepositoryConfig(root *os.Root) (*config.Config, error) {
11
11
+
configFile, err := root.Open("config")
12
12
+
if err != nil {
13
13
+
return nil, fmt.Errorf("repository: open config: %w", err)
14
14
+
}
15
15
+
16
16
+
defer func() { _ = configFile.Close() }()
17
17
+
18
18
+
cfg, err := config.ParseConfig(configFile)
19
19
+
if err != nil {
20
20
+
return nil, fmt.Errorf("repository: parse config: %w", err)
21
21
+
}
22
22
+
23
23
+
return cfg, nil
24
24
+
}
25
25
+
26
26
+
// Config returns the parsed repository configuration snapshot.
27
27
+
//
28
28
+
// The returned pointer is owned by Repository. Callers should treat it as
29
29
+
// read-only.
30
30
+
func (repo *Repository) Config() *config.Config {
31
31
+
return repo.config
32
32
+
}
-39
repository/open_config.go
···
1
1
-
package repository
2
2
-
3
3
-
import (
4
4
-
"fmt"
5
5
-
"os"
6
6
-
7
7
-
"codeberg.org/lindenii/furgit/config"
8
8
-
"codeberg.org/lindenii/furgit/objectid"
9
9
-
)
10
10
-
11
11
-
func parseRepositoryConfig(root *os.Root) (*config.Config, error) {
12
12
-
configFile, err := root.Open("config")
13
13
-
if err != nil {
14
14
-
return nil, fmt.Errorf("repository: open config: %w", err)
15
15
-
}
16
16
-
17
17
-
defer func() { _ = configFile.Close() }()
18
18
-
19
19
-
cfg, err := config.ParseConfig(configFile)
20
20
-
if err != nil {
21
21
-
return nil, fmt.Errorf("repository: parse config: %w", err)
22
22
-
}
23
23
-
24
24
-
return cfg, nil
25
25
-
}
26
26
-
27
27
-
func detectObjectAlgorithm(cfg *config.Config) (objectid.Algorithm, error) {
28
28
-
algoName := cfg.Lookup("extensions", "", "objectformat").Value
29
29
-
if algoName == "" {
30
30
-
algoName = objectid.AlgorithmSHA1.String()
31
31
-
}
32
32
-
33
33
-
algo, ok := objectid.ParseAlgorithm(algoName)
34
34
-
if !ok {
35
35
-
return objectid.AlgorithmUnknown, fmt.Errorf("repository: unsupported object format %q", algoName)
36
36
-
}
37
37
-
38
38
-
return algo, nil
39
39
-
}
+5
repository/open_objects.go
repository/objects.go
···
62
62
63
63
return objectsChain, objectsLooseForWritingOnly, nil
64
64
}
65
65
+
66
66
+
// Objects returns the configured object store.
67
67
+
func (repo *Repository) Objects() objectstore.Store {
68
68
+
return repo.objects
69
69
+
}
+5
repository/open_refs.go
repository/refs.go
···
45
45
46
46
return refchain.New(backends...), nil
47
47
}
48
48
+
49
49
+
// Refs returns the configured ref store.
50
50
+
func (repo *Repository) Refs() refstore.Store {
51
51
+
return repo.refs
52
52
+
}
repository/read_stored.go
repository/stored.go
-54
repository/repository.go
···
2
2
package repository
3
3
4
4
import (
5
5
-
"errors"
6
6
-
7
5
"codeberg.org/lindenii/furgit/config"
8
6
"codeberg.org/lindenii/furgit/objectid"
9
7
"codeberg.org/lindenii/furgit/objectstore"
···
23
21
objectsLooseForWritingOnly *objectloose.Store
24
22
refs refstore.Store
25
23
}
26
26
-
27
27
-
// Algorithm returns the repository object ID algorithm.
28
28
-
func (repo *Repository) Algorithm() objectid.Algorithm {
29
29
-
return repo.algo
30
30
-
}
31
31
-
32
32
-
// Config returns the parsed repository configuration snapshot.
33
33
-
//
34
34
-
// The returned pointer is owned by Repository. Callers should treat it as
35
35
-
// read-only.
36
36
-
func (repo *Repository) Config() *config.Config {
37
37
-
return repo.config
38
38
-
}
39
39
-
40
40
-
// Objects returns the configured object store.
41
41
-
func (repo *Repository) Objects() objectstore.Store {
42
42
-
return repo.objects
43
43
-
}
44
44
-
45
45
-
// Refs returns the configured ref store.
46
46
-
func (repo *Repository) Refs() refstore.Store {
47
47
-
return repo.refs
48
48
-
}
49
49
-
50
50
-
// Close closes owned stores and filesystem roots.
51
51
-
// The behavior of the repo after Close is undefined.
52
52
-
func (repo *Repository) Close() error {
53
53
-
var errs []error
54
54
-
55
55
-
if repo.refs != nil {
56
56
-
err := repo.refs.Close()
57
57
-
if err != nil {
58
58
-
errs = append(errs, err)
59
59
-
}
60
60
-
}
61
61
-
62
62
-
if repo.objects != nil {
63
63
-
err := repo.objects.Close()
64
64
-
if err != nil {
65
65
-
errs = append(errs, err)
66
66
-
}
67
67
-
}
68
68
-
69
69
-
if repo.objectsLooseForWritingOnly != nil {
70
70
-
err := repo.objectsLooseForWritingOnly.Close()
71
71
-
if err != nil {
72
72
-
errs = append(errs, err)
73
73
-
}
74
74
-
}
75
75
-
76
76
-
return errors.Join(errs...)
77
77
-
}
repository/repository_test.go
repository/refs_test.go
-73
repository/traversal_bench_test.go
···
1
1
-
package repository_test
2
2
-
3
3
-
import (
4
4
-
"os"
5
5
-
"strings"
6
6
-
"testing"
7
7
-
8
8
-
"codeberg.org/lindenii/furgit/object"
9
9
-
"codeberg.org/lindenii/furgit/repository"
10
10
-
)
11
11
-
12
12
-
const benchRepoPathEnv = "FURGIT_BENCH_REPO"
13
13
-
14
14
-
// BenchmarkTraverseHeadTree measures iterative traversal of HEAD's root tree.
15
15
-
//
16
16
-
// Set FURGIT_BENCH_REPO to a repository path (typically .git or a bare repo)
17
17
-
// before running this benchmark.
18
18
-
func BenchmarkTraverseHeadTree(b *testing.B) {
19
19
-
repoPath := strings.TrimSpace(os.Getenv(benchRepoPathEnv))
20
20
-
if repoPath == "" {
21
21
-
b.Fatalf("missing %s", benchRepoPathEnv)
22
22
-
}
23
23
-
24
24
-
root, err := os.OpenRoot(repoPath)
25
25
-
if err != nil {
26
26
-
b.Fatalf("os.OpenRoot(%q): %v", repoPath, err)
27
27
-
}
28
28
-
29
29
-
b.Cleanup(func() {
30
30
-
_ = root.Close()
31
31
-
})
32
32
-
33
33
-
repo, err := repository.Open(root)
34
34
-
if err != nil {
35
35
-
b.Fatalf("repository.Open(root for %q): %v", repoPath, err)
36
36
-
}
37
37
-
38
38
-
b.Cleanup(func() {
39
39
-
_ = repo.Close()
40
40
-
})
41
41
-
42
42
-
head, err := repo.Refs().ResolveFully("HEAD")
43
43
-
if err != nil {
44
44
-
b.Fatalf("ResolveRefFully(HEAD): %v", err)
45
45
-
}
46
46
-
47
47
-
stored, err := repo.ReadStored(head.ID)
48
48
-
if err != nil {
49
49
-
b.Fatalf("ReadStored(%s): %v", head.ID, err)
50
50
-
}
51
51
-
52
52
-
commit, ok := stored.Object().(*object.Commit)
53
53
-
if !ok {
54
54
-
b.Fatalf("HEAD object type %T, want *object.Commit", stored.Object())
55
55
-
}
56
56
-
57
57
-
b.ReportAllocs()
58
58
-
b.ResetTimer()
59
59
-
60
60
-
var lastCount int
61
61
-
for b.Loop() {
62
62
-
lastCount, err = traverseTreeIter(repo, commit.Tree)
63
63
-
if err != nil {
64
64
-
b.Fatalf("traverseTreeIter: %v", err)
65
65
-
}
66
66
-
}
67
67
-
68
68
-
b.StopTimer()
69
69
-
70
70
-
if lastCount <= 0 {
71
71
-
b.Fatalf("traverseTreeIter count = %d, want > 0", lastCount)
72
72
-
}
73
73
-
}
-102
repository/traversal_helpers_test.go
···
1
1
-
package repository_test
2
2
-
3
3
-
import (
4
4
-
"codeberg.org/lindenii/furgit/object"
5
5
-
"codeberg.org/lindenii/furgit/objectid"
6
6
-
"codeberg.org/lindenii/furgit/repository"
7
7
-
)
8
8
-
9
9
-
type treeWalkFrame struct {
10
10
-
id objectid.ObjectID
11
11
-
isTree bool
12
12
-
}
13
13
-
14
14
-
func traverseTreeIter(repo *repository.Repository, root objectid.ObjectID) (int, error) {
15
15
-
stack := []treeWalkFrame{{id: root, isTree: true}}
16
16
-
total := 0
17
17
-
18
18
-
for len(stack) > 0 {
19
19
-
frame := stack[len(stack)-1]
20
20
-
stack = stack[:len(stack)-1]
21
21
-
id := frame.id
22
22
-
23
23
-
if !frame.isTree {
24
24
-
_, err := repo.Objects().ReadSize(id)
25
25
-
if err != nil {
26
26
-
return 0, err
27
27
-
}
28
28
-
29
29
-
total++
30
30
-
31
31
-
continue
32
32
-
}
33
33
-
34
34
-
tree, err := repo.ReadStoredTree(id)
35
35
-
if err != nil {
36
36
-
return 0, err
37
37
-
}
38
38
-
39
39
-
total++
40
40
-
41
41
-
for i := len(tree.Tree().Entries) - 1; i >= 0; i-- {
42
42
-
entry := tree.Tree().Entries[i]
43
43
-
if entry.Mode == object.FileModeGitlink {
44
44
-
continue
45
45
-
}
46
46
-
47
47
-
stack = append(stack, treeWalkFrame{
48
48
-
id: entry.ID,
49
49
-
isTree: entry.Mode == object.FileModeDir,
50
50
-
})
51
51
-
}
52
52
-
}
53
53
-
54
54
-
return total, nil
55
55
-
}
56
56
-
57
57
-
func traverseReachableIter(repo *repository.Repository, root objectid.ObjectID) (int, error) {
58
58
-
stack := []objectid.ObjectID{root}
59
59
-
visited := make(map[objectid.ObjectID]struct{})
60
60
-
total := 0
61
61
-
62
62
-
for len(stack) > 0 {
63
63
-
id := stack[len(stack)-1]
64
64
-
stack = stack[:len(stack)-1]
65
65
-
66
66
-
_, ok := visited[id]
67
67
-
if ok {
68
68
-
continue
69
69
-
}
70
70
-
71
71
-
visited[id] = struct{}{}
72
72
-
73
73
-
stored, err := repo.ReadStored(id)
74
74
-
if err != nil {
75
75
-
return 0, err
76
76
-
}
77
77
-
78
78
-
total++
79
79
-
80
80
-
switch obj := stored.Object().(type) {
81
81
-
case *object.Commit:
82
82
-
stack = append(stack, obj.Tree)
83
83
-
stack = append(stack, obj.Parents...)
84
84
-
case *object.Tree:
85
85
-
for i := len(obj.Entries) - 1; i >= 0; i-- {
86
86
-
entry := obj.Entries[i]
87
87
-
if entry.Mode == object.FileModeGitlink {
88
88
-
continue
89
89
-
}
90
90
-
91
91
-
stack = append(stack, entry.ID)
92
92
-
}
93
93
-
case *object.Tag:
94
94
-
stack = append(stack, obj.Target)
95
95
-
case *object.Blob:
96
96
-
default:
97
97
-
// Unknown parsed object variants are treated as leaves.
98
98
-
}
99
99
-
}
100
100
-
101
101
-
return total, nil
102
102
-
}
+48
repository/traversal_test.go
···
8
8
"testing"
9
9
10
10
"codeberg.org/lindenii/furgit/internal/testgit"
11
11
+
"codeberg.org/lindenii/furgit/object"
11
12
"codeberg.org/lindenii/furgit/objectid"
12
13
"codeberg.org/lindenii/furgit/repository"
13
14
)
···
131
132
t.Fatalf("no objects were enumerated from HEAD (%s)", fmt.Sprintf("%q", repoPath))
132
133
}
133
134
}
135
135
+
136
136
+
func traverseReachableIter(repo *repository.Repository, root objectid.ObjectID) (int, error) {
137
137
+
stack := []objectid.ObjectID{root}
138
138
+
visited := make(map[objectid.ObjectID]struct{})
139
139
+
total := 0
140
140
+
141
141
+
for len(stack) > 0 {
142
142
+
id := stack[len(stack)-1]
143
143
+
stack = stack[:len(stack)-1]
144
144
+
145
145
+
_, ok := visited[id]
146
146
+
if ok {
147
147
+
continue
148
148
+
}
149
149
+
150
150
+
visited[id] = struct{}{}
151
151
+
152
152
+
stored, err := repo.ReadStored(id)
153
153
+
if err != nil {
154
154
+
return 0, err
155
155
+
}
156
156
+
157
157
+
total++
158
158
+
159
159
+
switch obj := stored.Object().(type) {
160
160
+
case *object.Commit:
161
161
+
stack = append(stack, obj.Tree)
162
162
+
stack = append(stack, obj.Parents...)
163
163
+
case *object.Tree:
164
164
+
for i := len(obj.Entries) - 1; i >= 0; i-- {
165
165
+
entry := obj.Entries[i]
166
166
+
if entry.Mode == object.FileModeGitlink {
167
167
+
continue
168
168
+
}
169
169
+
170
170
+
stack = append(stack, entry.ID)
171
171
+
}
172
172
+
case *object.Tag:
173
173
+
stack = append(stack, obj.Target)
174
174
+
case *object.Blob:
175
175
+
default:
176
176
+
// Unknown parsed object variants are treated as leaves.
177
177
+
}
178
178
+
}
179
179
+
180
180
+
return total, nil
181
181
+
}
repository/tree_resolve.go
repository/tree.go