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
103 return commits, nil
104}
105
106func (g *GitRepository) Head() (*plumbing.Reference, error) {
107 return g.repository.Head()
108}
109
110func (g *GitRepository) Tags() ([]*object.Tag, error) {
111 ti, err := g.repository.TagObjects()
112 if err != nil {
113 return nil, err
114 }
115
116 tags := []*object.Tag{}
117 err = ti.ForEach(func(t *object.Tag) error {
118 tags = append(tags, t)
119 return nil
120 })
121 if err != nil {
122 return nil, err
123 }
124
125 return tags, nil
126}
127
128func (g *GitRepository) Branches() ([]*plumbing.Reference, error) {
129 bs, err := g.repository.Branches()
130 if err != nil {
131 return nil, err
132 }
133
134 branches := []*plumbing.Reference{}
135 err = bs.ForEach(func(ref *plumbing.Reference) error {
136 branches = append(branches, ref)
137 return nil
138 })
139 if err != nil {
140 return nil, err
141 }
142
143 return branches, nil
144}
145
146func (g *GitRepository) Tree(path string) (*object.Tree, error) {
147 err := g.validateRef()
148 if err != nil {
149 return nil, err
150 }
151
152 c, err := g.repository.CommitObject(g.ref)
153 if err != nil {
154 return nil, err
155 }
156
157 tree, err := c.Tree()
158 if err != nil {
159 return nil, err
160 }
161
162 if path == "" {
163 return tree, nil
164 } else {
165 o, err := tree.FindEntry(path)
166 if err != nil {
167 return nil, err
168 }
169
170 if !o.Mode.IsFile() {
171 subtree, err := tree.Tree(path)
172 if err != nil {
173 return nil, err
174 }
175 return subtree, nil
176 } else {
177 return nil, TreeForFileErr
178 }
179 }
180}
181
182func (g *GitRepository) validateRef() error {
183 if !g.setRef {
184 return g.SetRef("")
185 }
186 return nil
187}
188
189func (g *GitRepository) FileContent(path string) (string, error) {
190 c, err := g.repository.CommitObject(g.ref)
191 if err != nil {
192 return "", err
193 }
194
195 tree, err := c.Tree()
196 if err != nil {
197 return "", err
198 }
199
200 file, err := tree.File(path)
201 if err != nil {
202 return "", err
203 }
204
205 isbin, err := file.IsBinary()
206 if err != nil {
207 return "", err
208 }
209
210 if !isbin {
211 return file.Contents()
212 } else {
213 return "Binary file", nil
214 }
215}