all repos

mugit @ 96929ec

🐮 git server that your cow will love

mugit/internal/git/tree.go (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
ui: show file name, last commit message, and when it was updated, 3 months ago
1
package git
2
3
import (
4
	"errors"
5
	"fmt"
6
	"io"
7
	"mime"
8
	"path"
9
	"path/filepath"
10
	"strings"
11
12
	"github.com/go-git/go-git/v5/plumbing/object"
13
)
14
15
type NiceTree struct {
16
	IsFile bool
17
	Name   string
18
	Commit *Commit
19
	Mode   string
20
	Size   int64
21
}
22
23
func (g *Repo) makeNiceTree(t *object.Tree, parent string) []NiceTree {
24
	var nts []NiceTree
25
	for _, e := range t.Entries {
26
		mode, _ := e.Mode.ToOSFileMode()
27
		sz, _ := t.Size(e.Name)
28
29
		// TODO: this should be cached, its pretty expensive
30
		lc, _ := g.lastCommitForFile(path.Join(parent, e.Name))
31
		nts = append(nts, NiceTree{
32
			Name:   e.Name,
33
			Mode:   mode.String(),
34
			IsFile: e.Mode.IsFile(),
35
			Commit: lc,
36
			Size:   sz,
37
		})
38
	}
39
	return nts
40
}
41
42
func (g *Repo) FileTree(path string) ([]NiceTree, error) {
43
	c, err := g.r.CommitObject(g.h)
44
	if err != nil {
45
		return nil, fmt.Errorf("commit object: %w", err)
46
	}
47
48
	tree, err := c.Tree()
49
	if err != nil {
50
		return nil, fmt.Errorf("file tree: %w", err)
51
	}
52
53
	var files []NiceTree
54
	if path == "" {
55
		files = g.makeNiceTree(tree, path)
56
	} else {
57
		o, err := tree.FindEntry(path)
58
		if err != nil {
59
			return nil, err
60
		}
61
62
		if !o.Mode.IsFile() {
63
			subtree, err := tree.Tree(path)
64
			if err != nil {
65
				return nil, err
66
			}
67
			files = g.makeNiceTree(subtree, path)
68
		}
69
	}
70
71
	return files, nil
72
}
73
74
type FileContent struct {
75
	IsBinary bool
76
	Content  []byte
77
	Mime     string
78
	Size     int64
79
}
80
81
func (fc FileContent) IsImage() bool {
82
	return strings.HasPrefix(fc.Mime, "image/")
83
}
84
85
func (fc *FileContent) String() string {
86
	if fc.IsBinary {
87
		return ""
88
	}
89
	return string(fc.Content)
90
}
91
92
func (g *Repo) FileContent(path string) (*FileContent, error) {
93
	c, err := g.r.CommitObject(g.h)
94
	if err != nil {
95
		return &FileContent{}, fmt.Errorf("commit object: %w", err)
96
	}
97
98
	tree, err := c.Tree()
99
	if err != nil {
100
		return &FileContent{}, fmt.Errorf("file tree: %w", err)
101
	}
102
103
	file, err := tree.File(path)
104
	if err != nil {
105
		if errors.Is(err, object.ErrFileNotFound) {
106
			return &FileContent{}, ErrFileNotFound
107
		}
108
		return &FileContent{}, err
109
	}
110
111
	reader, err := file.Reader()
112
	if err != nil {
113
		return nil, fmt.Errorf("file reader: %w", err)
114
	}
115
	defer reader.Close()
116
117
	content, err := io.ReadAll(reader)
118
	if err != nil {
119
		return nil, fmt.Errorf("read file: %w", err)
120
	}
121
122
	isBin, _ := file.IsBinary()
123
	mimeType := mime.TypeByExtension(filepath.Ext(path))
124
	if mimeType == "" {
125
		mimeType = "text/plain"
126
		if isBin {
127
			mimeType = "application/octet-stream"
128
		}
129
	}
130
131
	return &FileContent{
132
		IsBinary: isBin,
133
		Content:  content,
134
		Mime:     mimeType,
135
		Size:     file.Size,
136
	}, nil
137
}