Fast implementation of Git in pure Go

repository: Refactor

runxiyu.tngl.sh 120509f0 7a9f76f3

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