this repo has no description
1package git 2 3import ( 4 "archive/tar" 5 "fmt" 6 "io" 7 "io/fs" 8 "path" 9 "sort" 10 "time" 11 12 "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 GitRepo struct { 18 r *git.Repository 19 h plumbing.Hash 20} 21 22type TagList struct { 23 refs []*TagReference 24 r *git.Repository 25} 26 27// TagReference is used to list both tag and non-annotated tags. 28// Non-annotated tags should only contains a reference. 29// Annotated tags should contain its reference and its tag information. 30type TagReference struct { 31 ref *plumbing.Reference 32 tag *object.Tag 33} 34 35// infoWrapper wraps the property of a TreeEntry so it can export fs.FileInfo 36// to tar WriteHeader 37type infoWrapper struct { 38 name string 39 size int64 40 mode fs.FileMode 41 modTime time.Time 42 isDir bool 43} 44 45func (self *TagList) Len() int { 46 return len(self.refs) 47} 48 49func (self *TagList) Swap(i, j int) { 50 self.refs[i], self.refs[j] = self.refs[j], self.refs[i] 51} 52 53// sorting tags in reverse chronological order 54func (self *TagList) Less(i, j int) bool { 55 var dateI time.Time 56 var dateJ time.Time 57 58 if self.refs[i].tag != nil { 59 dateI = self.refs[i].tag.Tagger.When 60 } else { 61 c, err := self.r.CommitObject(self.refs[i].ref.Hash()) 62 if err != nil { 63 dateI = time.Now() 64 } else { 65 dateI = c.Committer.When 66 } 67 } 68 69 if self.refs[j].tag != nil { 70 dateJ = self.refs[j].tag.Tagger.When 71 } else { 72 c, err := self.r.CommitObject(self.refs[j].ref.Hash()) 73 if err != nil { 74 dateJ = time.Now() 75 } else { 76 dateJ = c.Committer.When 77 } 78 } 79 80 return dateI.After(dateJ) 81} 82 83func Open(path string, ref string) (*GitRepo, error) { 84 var err error 85 g := GitRepo{} 86 g.r, err = git.PlainOpen(path) 87 if err != nil { 88 return nil, fmt.Errorf("opening %s: %w", path, err) 89 } 90 91 if ref == "" { 92 head, err := g.r.Head() 93 if err != nil { 94 return nil, fmt.Errorf("getting head of %s: %w", path, err) 95 } 96 g.h = head.Hash() 97 } else { 98 hash, err := g.r.ResolveRevision(plumbing.Revision(ref)) 99 if err != nil { 100 return nil, fmt.Errorf("resolving rev %s for %s: %w", ref, path, err) 101 } 102 g.h = *hash 103 } 104 return &g, nil 105} 106 107func (g *GitRepo) Commits() ([]*object.Commit, error) { 108 ci, err := g.r.Log(&git.LogOptions{From: g.h}) 109 if err != nil { 110 return nil, fmt.Errorf("commits from ref: %w", err) 111 } 112 113 commits := []*object.Commit{} 114 ci.ForEach(func(c *object.Commit) error { 115 commits = append(commits, c) 116 return nil 117 }) 118 119 return commits, nil 120} 121 122func (g *GitRepo) LastCommit() (*object.Commit, error) { 123 c, err := g.r.CommitObject(g.h) 124 if err != nil { 125 return nil, fmt.Errorf("last commit: %w", err) 126 } 127 return c, nil 128} 129 130func (g *GitRepo) FileContent(path string) (string, error) { 131 c, err := g.r.CommitObject(g.h) 132 if err != nil { 133 return "", fmt.Errorf("commit object: %w", err) 134 } 135 136 tree, err := c.Tree() 137 if err != nil { 138 return "", fmt.Errorf("file tree: %w", err) 139 } 140 141 file, err := tree.File(path) 142 if err != nil { 143 return "", err 144 } 145 146 isbin, _ := file.IsBinary() 147 148 if !isbin { 149 return file.Contents() 150 } else { 151 return "Not displaying binary file", nil 152 } 153} 154 155func (g *GitRepo) Tags() ([]*TagReference, error) { 156 iter, err := g.r.Tags() 157 if err != nil { 158 return nil, fmt.Errorf("tag objects: %w", err) 159 } 160 161 tags := make([]*TagReference, 0) 162 163 if err := iter.ForEach(func(ref *plumbing.Reference) error { 164 obj, err := g.r.TagObject(ref.Hash()) 165 switch err { 166 case nil: 167 tags = append(tags, &TagReference{ 168 ref: ref, 169 tag: obj, 170 }) 171 case plumbing.ErrObjectNotFound: 172 tags = append(tags, &TagReference{ 173 ref: ref, 174 }) 175 default: 176 return err 177 } 178 return nil 179 }); err != nil { 180 return nil, err 181 } 182 183 tagList := &TagList{r: g.r, refs: tags} 184 sort.Sort(tagList) 185 return tags, nil 186} 187 188func (g *GitRepo) Branches() ([]*plumbing.Reference, error) { 189 bi, err := g.r.Branches() 190 if err != nil { 191 return nil, fmt.Errorf("branchs: %w", err) 192 } 193 194 branches := []*plumbing.Reference{} 195 196 _ = bi.ForEach(func(ref *plumbing.Reference) error { 197 branches = append(branches, ref) 198 return nil 199 }) 200 201 return branches, nil 202} 203 204func (g *GitRepo) FindMainBranch(branches []string) (string, error) { 205 206 for _, b := range branches { 207 _, err := g.r.ResolveRevision(plumbing.Revision(b)) 208 if err == nil { 209 return b, nil 210 } 211 } 212 return "", fmt.Errorf("unable to find main branch") 213} 214 215// WriteTar writes itself from a tree into a binary tar file format. 216// prefix is root folder to be appended. 217func (g *GitRepo) WriteTar(w io.Writer, prefix string) error { 218 tw := tar.NewWriter(w) 219 defer tw.Close() 220 221 c, err := g.r.CommitObject(g.h) 222 if err != nil { 223 return fmt.Errorf("commit object: %w", err) 224 } 225 226 tree, err := c.Tree() 227 if err != nil { 228 return err 229 } 230 231 walker := object.NewTreeWalker(tree, true, nil) 232 defer walker.Close() 233 234 name, entry, err := walker.Next() 235 for ; err == nil; name, entry, err = walker.Next() { 236 info, err := newInfoWrapper(name, prefix, &entry, tree) 237 if err != nil { 238 return err 239 } 240 241 header, err := tar.FileInfoHeader(info, "") 242 if err != nil { 243 return err 244 } 245 246 err = tw.WriteHeader(header) 247 if err != nil { 248 return err 249 } 250 251 if !info.IsDir() { 252 file, err := tree.File(name) 253 if err != nil { 254 return err 255 } 256 257 reader, err := file.Blob.Reader() 258 if err != nil { 259 return err 260 } 261 262 _, err = io.Copy(tw, reader) 263 if err != nil { 264 reader.Close() 265 return err 266 } 267 reader.Close() 268 } 269 } 270 271 return nil 272} 273 274func newInfoWrapper( 275 name string, 276 prefix string, 277 entry *object.TreeEntry, 278 tree *object.Tree, 279) (*infoWrapper, error) { 280 var ( 281 size int64 282 mode fs.FileMode 283 isDir bool 284 ) 285 286 if entry.Mode.IsFile() { 287 file, err := tree.TreeEntryFile(entry) 288 if err != nil { 289 return nil, err 290 } 291 mode = fs.FileMode(file.Mode) 292 293 size, err = tree.Size(name) 294 if err != nil { 295 return nil, err 296 } 297 } else { 298 isDir = true 299 mode = fs.ModeDir | fs.ModePerm 300 } 301 302 fullname := path.Join(prefix, name) 303 return &infoWrapper{ 304 name: fullname, 305 size: size, 306 mode: mode, 307 modTime: time.Unix(0, 0), 308 isDir: isDir, 309 }, nil 310} 311 312func (i *infoWrapper) Name() string { 313 return i.name 314} 315 316func (i *infoWrapper) Size() int64 { 317 return i.size 318} 319 320func (i *infoWrapper) Mode() fs.FileMode { 321 return i.mode 322} 323 324func (i *infoWrapper) ModTime() time.Time { 325 return i.modTime 326} 327 328func (i *infoWrapper) IsDir() bool { 329 return i.isDir 330} 331 332func (i *infoWrapper) Sys() any { 333 return nil 334} 335 336func (t *TagReference) Name() string { 337 return t.ref.Name().Short() 338} 339 340func (t *TagReference) Message() string { 341 if t.tag != nil { 342 return t.tag.Message 343 } 344 return "" 345}