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