1package service
2
3import (
4 "compress/gzip"
5 "context"
6 "errors"
7 "io"
8 "log/slog"
9
10 "git.gabrielgio.me/cerrado/pkg/config"
11 "git.gabrielgio.me/cerrado/pkg/git"
12 gogit "github.com/go-git/go-git/v5"
13 "github.com/go-git/go-git/v5/plumbing"
14 "github.com/go-git/go-git/v5/plumbing/object"
15)
16
17type (
18 Repository struct {
19 Name string
20 Description string
21 Public bool
22 LastCommit *git.CommitReference
23 Ref string
24 }
25
26 GitService struct {
27 configRepo configurationRepository
28 }
29
30 configurationRepository interface {
31 List() []*config.GitRepositoryConfiguration
32 GetByName(name string) *config.GitRepositoryConfiguration
33 }
34)
35
36var ErrRepositoryNotFound = errors.New("Repository not found")
37
38// TODO: make it configurable
39const timeFormat = "2006.01.02 15:04:05"
40
41func NewGitService(configRepo configurationRepository) *GitService {
42 return &GitService{
43 configRepo: configRepo,
44 }
45}
46
47func (g *GitService) ListRepositories() ([]*Repository, error) {
48 rs := g.configRepo.List()
49
50 repos := make([]*Repository, 0, len(rs))
51 for _, r := range rs {
52 repo, err := git.OpenRepository(r.Path)
53 if err != nil {
54 if errors.Is(err, gogit.ErrRepositoryNotExists) {
55 slog.Info("Path does not contain a repository", "path", r.Path)
56 continue
57 }
58 return nil, err
59 }
60
61 obj, err := repo.LastCommit()
62 if err != nil {
63 slog.Error("Error fetching last commit", "repository", r.Path, "error", err)
64 continue
65 }
66
67 head, err := repo.Head()
68 if err != nil {
69 slog.Error("Error fetching head", "repository", r.Path, "error", err)
70 continue
71 }
72
73 repos = append(repos, &Repository{
74 Name: r.Name,
75 Description: r.Description,
76 Public: r.Public,
77 LastCommit: obj,
78 Ref: head.Name().Short(),
79 })
80 }
81
82 return repos, nil
83}
84
85func (g *GitService) ListCommits(name, ref, from string, count int) ([]*git.CommitReference, *object.Commit, error) {
86 r := g.configRepo.GetByName(name)
87 if r == nil {
88 return nil, nil, ErrRepositoryNotFound
89 }
90
91 repo, err := git.OpenRepository(r.Path)
92 if err != nil {
93 return nil, nil, err
94 }
95
96 err = repo.SetRef(ref)
97 if err != nil {
98 return nil, nil, err
99 }
100 return repo.Commits(count, from)
101}
102
103func (g *GitService) LastCommit(name, ref string) (*git.CommitReference, error) {
104 r := g.configRepo.GetByName(name)
105 if r == nil {
106 return nil, ErrRepositoryNotFound
107 }
108
109 repo, err := git.OpenRepository(r.Path)
110 if err != nil {
111 return nil, err
112 }
113
114 err = repo.SetRef(ref)
115 if err != nil {
116 return nil, err
117 }
118
119 return repo.LastCommit()
120}
121
122func (g *GitService) WriteTarGZip(w io.Writer, name, ref string, prefix string) error {
123 r := g.configRepo.GetByName(name)
124 if r == nil {
125 return ErrRepositoryNotFound
126 }
127
128 repo, err := git.OpenRepository(r.Path)
129 if err != nil {
130 return err
131 }
132
133 err = repo.SetRef(ref)
134 if err != nil {
135 return err
136 }
137
138 gw := gzip.NewWriter(w)
139 defer gw.Close()
140
141 err = repo.WriteTar(gw, prefix)
142 if err != nil {
143 return err
144 }
145
146 return nil
147}
148
149func (g *GitService) Diff(name, ref string) (string, error) {
150 r := g.configRepo.GetByName(name)
151 if r == nil {
152 return "", ErrRepositoryNotFound
153 }
154
155 repo, err := git.OpenRepository(r.Path)
156 if err != nil {
157 return "", err
158 }
159
160 err = repo.SetRef(ref)
161 if err != nil {
162 return "", err
163 }
164
165 return repo.Diff()
166}
167
168func (g *GitService) GetTree(name, ref, path string) (*object.Tree, error) {
169 r := g.configRepo.GetByName(name)
170 if r == nil {
171 return nil, ErrRepositoryNotFound
172 }
173
174 repo, err := git.OpenRepository(r.Path)
175 if err != nil {
176 return nil, err
177 }
178 err = repo.SetRef(ref)
179 if err != nil {
180 return nil, err
181 }
182
183 return repo.Tree(path)
184}
185
186func (g *GitService) IsBinary(name, ref, path string) (bool, error) {
187 r := g.configRepo.GetByName(name)
188 if r == nil {
189 return false, ErrRepositoryNotFound
190 }
191
192 repo, err := git.OpenRepository(r.Path)
193 if err != nil {
194 return false, err
195 }
196 err = repo.SetRef(ref)
197 if err != nil {
198 return false, err
199 }
200
201 return repo.IsBinary(path)
202}
203
204func (g *GitService) GetFileContent(name, ref, path string) ([]byte, error) {
205 r := g.configRepo.GetByName(name)
206 if r == nil {
207 return nil, ErrRepositoryNotFound
208 }
209
210 repo, err := git.OpenRepository(r.Path)
211 if err != nil {
212 return nil, err
213 }
214 err = repo.SetRef(ref)
215 if err != nil {
216 return nil, err
217 }
218
219 return repo.FileContent(path)
220}
221
222func (g *GitService) GetAbout(name string) ([]byte, error) {
223 r := g.configRepo.GetByName(name)
224 if r == nil {
225 return nil, ErrRepositoryNotFound
226 }
227
228 repo, err := git.OpenRepository(r.Path)
229 if err != nil {
230 return nil, err
231 }
232 err = repo.SetRef("")
233 if err != nil {
234 return nil, err
235 }
236
237 file, err := repo.FileContent(r.About)
238 if err != nil {
239 return nil, err
240 }
241
242 return file, nil
243}
244
245func (g *GitService) GetTag(ref, name string) (*object.Commit, *git.TagReference, error) {
246 r := g.configRepo.GetByName(name)
247 if r == nil {
248 return nil, nil, ErrRepositoryNotFound
249 }
250
251 repo, err := git.OpenRepository(r.Path)
252 if err != nil {
253 return nil, nil, err
254 }
255
256 err = repo.SetRef(ref)
257 if err != nil {
258 return nil, nil, err
259 }
260
261 return repo.Tag()
262}
263
264func (g *GitService) ListTags(name string) ([]*git.TagReference, error) {
265 r := g.configRepo.GetByName(name)
266 if r == nil {
267 return nil, ErrRepositoryNotFound
268 }
269
270 repo, err := git.OpenRepository(r.Path)
271 if err != nil {
272 return nil, err
273 }
274 return repo.Tags()
275}
276
277func (g *GitService) ListBranches(name string) ([]*plumbing.Reference, error) {
278 r := g.configRepo.GetByName(name)
279 if r == nil {
280 return nil, ErrRepositoryNotFound
281 }
282
283 repo, err := git.OpenRepository(r.Path)
284 if err != nil {
285 return nil, err
286 }
287 return repo.Branches()
288}
289
290func (g *GitService) GetHead(name string) (*plumbing.Reference, error) {
291 r := g.configRepo.GetByName(name)
292 if r == nil {
293 return nil, ErrRepositoryNotFound
294 }
295
296 repo, err := git.OpenRepository(r.Path)
297 if err != nil {
298 return nil, err
299 }
300
301 return repo.Head()
302}
303
304func (g *GitService) WriteInfoRefs(ctx context.Context, name string, w io.Writer) error {
305 r := g.configRepo.GetByName(name)
306 if r == nil {
307 return ErrRepositoryNotFound
308 }
309
310 repo, err := git.OpenRepository(r.Path)
311 if err != nil {
312 return err
313 }
314
315 return repo.WriteInfoRefs(ctx, w)
316}
317
318func (g *GitService) WriteUploadPack(ctx context.Context, name string, re io.Reader, w io.Writer) error {
319 r := g.configRepo.GetByName(name)
320 if r == nil {
321 return ErrRepositoryNotFound
322 }
323
324 repo, err := git.OpenRepository(r.Path)
325 if err != nil {
326 return err
327 }
328
329 return repo.WriteUploadPack(ctx, re, w)
330}