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 files provides one Git files ref store with loose-over-packed reads
// and transaction-coordinated updates.
package files
import (
"errors"
"io"
"math/rand"
"os"
"path"
"path/filepath"
"strings"
"time"
"codeberg.org/lindenii/furgit/config"
"codeberg.org/lindenii/furgit/objectid"
"codeberg.org/lindenii/furgit/ref/refname"
"codeberg.org/lindenii/furgit/refstore"
)
// Store reads and writes one Git files ref namespace rooted at one repository
// gitdir plus its commondir.
//
// Store owns both roots and closes them in Close.
type Store struct {
gitRoot *os.Root
commonRoot *os.Root
algo objectid.Algorithm
lockRand *rand.Rand
packedRefsTimeout time.Duration
}
var (
_ refstore.ReadingStore = (*Store)(nil)
_ refstore.TransactionalStore = (*Store)(nil)
)
type rootKind uint8
const (
rootGit rootKind = iota
rootCommon
)
type refPath struct {
root rootKind
path string
}
// New creates one files ref store rooted at one repository gitdir.
func New(root *os.Root, algo objectid.Algorithm) (*Store, error) {
if algo.Size() == 0 {
return nil, objectid.ErrInvalidAlgorithm
}
commonRoot, err := openCommonRoot(root)
if err != nil {
return nil, err
}
return &Store{
gitRoot: root,
commonRoot: commonRoot,
algo: algo,
lockRand: rand.New(rand.NewSource(time.Now().UnixNano())), //nolint:gosec
packedRefsTimeout: detectPackedRefsTimeout(commonRoot),
}, nil
}
// Close releases resources associated with the store.
func (store *Store) Close() error {
err := store.gitRoot.Close()
commonErr := store.commonRoot.Close()
if err != nil {
return err
}
return commonErr
}
func openCommonRoot(gitRoot *os.Root) (*os.Root, error) {
content, err := gitRoot.ReadFile("commondir")
if err != nil {
if errorsIsNotExist(err) {
return gitRoot.OpenRoot(".")
}
return nil, err
}
commonDir := strings.TrimSpace(string(content))
if commonDir == "" {
return nil, os.ErrNotExist
}
if filepath.IsAbs(commonDir) {
return os.OpenRoot(commonDir)
}
// This is okay because that's how Git defines it anyway.
return os.OpenRoot(filepath.Join(gitRoot.Name(), commonDir))
}
func (store *Store) rootFor(kind rootKind) *os.Root {
if kind == rootCommon {
return store.commonRoot
}
return store.gitRoot
}
func (store *Store) loosePath(name string) refPath {
parsed := refname.ParseWorktree(name)
switch parsed.Type {
case refname.WorktreeCurrent:
return refPath{root: rootGit, path: parsed.BareRefName}
case refname.WorktreeMain, refname.WorktreeShared:
return refPath{root: rootCommon, path: parsed.BareRefName}
case refname.WorktreeOther:
return refPath{
root: rootCommon,
path: path.Join("worktrees", parsed.WorktreeName, parsed.BareRefName),
}
default:
return refPath{root: rootCommon, path: name}
}
}
func detectPackedRefsTimeout(commonRoot *os.Root) time.Duration {
const defaultTimeout = time.Second
file, err := commonRoot.Open("config")
if err != nil {
return defaultTimeout
}
defer func() { _ = file.Close() }()
cfg, err := config.ParseConfig(file)
if err != nil && !errors.Is(err, io.EOF) {
return defaultTimeout
}
timeoutValue, err := cfg.Lookup("core", "", "packedrefstimeout").Int()
if err != nil {
return defaultTimeout
}
return time.Duration(timeoutValue) * time.Millisecond
}
|