all repos — elgit @ 86b2bf47ff930778bd73cce1cda916ffad41518b

fork of legit: 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
+}