all repos — legit @ 86b2bf47ff930778bd73cce1cda916ffad41518b

web frontend for git, written in go

git: Add function to generate tar file from repo
Gabriel A. Giovanini mail@gabrielgio.me
Sun, 23 Jun 2024 15:19:15 +0200
commit

86b2bf47ff930778bd73cce1cda916ffad41518b

parent

7e3307efe8a1af7b0da6a3ccfcee40ef5f5d98d8

1 files changed, 136 insertions(+), 0 deletions(-)

jump to
M git/git.gogit/git.go

@@ -1,8 +1,13 @@

package git import ( + "archive/tar" "fmt" + "io" + "io/fs" + "path" "sort" + "time" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing"

@@ -15,6 +20,16 @@ h plumbing.Hash

} type TagList []*object.Tag + +// infoWrapper wraps the property of a TreeEntry so it can export fs.FileInfo +// to tar WriteHeader +type infoWrapper struct { + name string + size int64 + mode fs.FileMode + modTime time.Time + isDir bool +} func (self TagList) Len() int { return len(self)

@@ -154,3 +169,124 @@ }

} return "", fmt.Errorf("unable to find main branch") } + +// WriteTar writes itself from a tree into a binary tar file format. +// prefix is root folder to be appended. +func (g *GitRepo) WriteTar(w io.Writer, prefix string) error { + tw := tar.NewWriter(w) + defer tw.Close() + + c, err := g.r.CommitObject(g.h) + if err != nil { + return fmt.Errorf("commit object: %w", err) + } + + tree, err := c.Tree() + if err != nil { + return err + } + + walker := object.NewTreeWalker(tree, true, nil) + defer walker.Close() + + name, entry, err := walker.Next() + for ; err == nil; name, entry, err = walker.Next() { + info, err := newInfoWrapper(name, prefix, &entry, tree) + if err != nil { + return err + } + + header, err := tar.FileInfoHeader(info, "") + if err != nil { + return err + } + + err = tw.WriteHeader(header) + if err != nil { + return err + } + + if !info.IsDir() { + file, err := tree.File(name) + if err != nil { + return err + } + + reader, err := file.Blob.Reader() + if err != nil { + return err + } + + _, err = io.Copy(tw, reader) + if err != nil { + reader.Close() + return err + } + reader.Close() + } + } + + return nil +} + +func newInfoWrapper( + name string, + prefix string, + entry *object.TreeEntry, + tree *object.Tree, +) (*infoWrapper, error) { + var ( + size int64 + mode fs.FileMode + isDir bool + ) + + if entry.Mode.IsFile() { + file, err := tree.TreeEntryFile(entry) + if err != nil { + return nil, err + } + mode = fs.FileMode(file.Mode) + + size, err = tree.Size(name) + if err != nil { + return nil, err + } + } else { + isDir = true + mode = fs.ModeDir | fs.ModePerm + } + + fullname := path.Join(prefix, name) + return &infoWrapper{ + name: fullname, + size: size, + mode: mode, + modTime: time.Unix(0, 0), + isDir: isDir, + }, nil +} + +func (i *infoWrapper) Name() string { + return i.name +} + +func (i *infoWrapper) Size() int64 { + return i.size +} + +func (i *infoWrapper) Mode() fs.FileMode { + return i.mode +} + +func (i *infoWrapper) ModTime() time.Time { + return i.modTime +} + +func (i *infoWrapper) IsDir() bool { + return i.isDir +} + +func (i *infoWrapper) Sys() any { + return nil +}