1package git
2
3import (
4 "errors"
5 "fmt"
6 "io"
7
8 "github.com/go-git/go-git/v5"
9 "github.com/go-git/go-git/v5/plumbing"
10 "github.com/go-git/go-git/v5/plumbing/object"
11)
12
13var ()
14
15var (
16 MissingRefErr = errors.New("Reference not found")
17 TreeForFileErr = errors.New("Trying to get tree of a file")
18)
19
20type (
21 GitRepository struct {
22 path string
23 repository *git.Repository
24
25 ref plumbing.Hash
26 // this is setRef when ref is setRef
27 setRef bool
28 }
29)
30
31func OpenRepository(dir string) (*GitRepository, error) {
32 g := &GitRepository{
33 path: dir,
34 }
35
36 repo, err := git.PlainOpen(dir)
37 if err != nil {
38 return nil, err
39 }
40 g.repository = repo
41
42 return g, nil
43}
44
45func (g *GitRepository) SetRef(ref string) error {
46 if ref == "" {
47 head, err := g.repository.Head()
48 if err != nil {
49 return errors.Join(MissingRefErr, err)
50 }
51 g.ref = head.Hash()
52 } else {
53 hash, err := g.repository.ResolveRevision(plumbing.Revision(ref))
54 if err != nil {
55 return errors.Join(MissingRefErr, err)
56 }
57 g.ref = *hash
58 }
59 g.setRef = true
60 return nil
61}
62
63func (g *GitRepository) Path() string {
64 return g.path
65}
66
67func (g *GitRepository) LastCommit() (*object.Commit, error) {
68 err := g.validateRef()
69 if err != nil {
70 return nil, err
71 }
72
73 c, err := g.repository.CommitObject(g.ref)
74 if err != nil {
75 return nil, err
76 }
77 return c, nil
78}
79
80func (g *GitRepository) Commits() ([]*object.Commit, error) {
81 err := g.validateRef()
82 if err != nil {
83 return nil, err
84 }
85
86 ci, err := g.repository.Log(&git.LogOptions{From: g.ref})
87 if err != nil {
88 return nil, fmt.Errorf("commits from ref: %w", err)
89 }
90
91 commits := []*object.Commit{}
92 // TODO: for now only load first 1000
93 for x := 0; x < 1000; x++ {
94 c, err := ci.Next()
95 if err != nil && errors.Is(err, io.EOF) {
96 break
97 } else if err != nil {
98 return nil, err
99 }
100 commits = append(commits, c)
101 }
102 if err != nil {
103 return nil, err
104 }
105
106 return commits, nil
107}
108
109func (g *GitRepository) Head() (*plumbing.Reference, error) {
110 return g.repository.Head()
111}
112
113func (g *GitRepository) Tags() ([]*object.Tag, error) {
114 ti, err := g.repository.TagObjects()
115 if err != nil {
116 return nil, err
117 }
118
119 tags := []*object.Tag{}
120 err = ti.ForEach(func(t *object.Tag) error {
121 tags = append(tags, t)
122 return nil
123 })
124 if err != nil {
125 return nil, err
126 }
127
128 return tags, nil
129}
130
131func (g *GitRepository) Branches() ([]*plumbing.Reference, error) {
132 bs, err := g.repository.Branches()
133 if err != nil {
134 return nil, err
135 }
136
137 branches := []*plumbing.Reference{}
138 err = bs.ForEach(func(ref *plumbing.Reference) error {
139 branches = append(branches, ref)
140 return nil
141 })
142 if err != nil {
143 return nil, err
144 }
145
146 return branches, nil
147}
148
149func (g *GitRepository) Tree(path string) (*object.Tree, error) {
150 err := g.validateRef()
151 if err != nil {
152 return nil, err
153 }
154
155 c, err := g.repository.CommitObject(g.ref)
156 if err != nil {
157 return nil, err
158 }
159
160 tree, err := c.Tree()
161 if err != nil {
162 return nil, err
163 }
164
165 if path == "" {
166 return tree, nil
167 } else {
168 o, err := tree.FindEntry(path)
169 if err != nil {
170 return nil, err
171 }
172
173 if !o.Mode.IsFile() {
174 subtree, err := tree.Tree(path)
175 if err != nil {
176 return nil, err
177 }
178 return subtree, nil
179 } else {
180 return nil, TreeForFileErr
181 }
182 }
183}
184
185func (g *GitRepository) validateRef() error {
186 if !g.setRef {
187 return g.SetRef("")
188 }
189 return nil
190}
191
192func (g *GitRepository) FileContent(path string) (string, error) {
193 c, err := g.repository.CommitObject(g.ref)
194 if err != nil {
195 return "", err
196 }
197
198 tree, err := c.Tree()
199 if err != nil {
200 return "", err
201 }
202
203 file, err := tree.File(path)
204 if err != nil {
205 return "", err
206 }
207
208 isbin, err := file.IsBinary()
209 if err != nil {
210 return "", err
211 }
212
213 if !isbin {
214 return file.Contents()
215 } else {
216 return "Binary file", nil
217 }
218}