1package gitweb
2
3import (
4 "errors"
5 "io"
6 "os"
7 "path/filepath"
8 "sort"
9
10 "github.com/go-git/go-git/v5"
11 "github.com/go-git/go-git/v5/plumbing/object"
12)
13
14type RepoPage struct {
15 *Repo
16
17 // Underlying file which is present on this page.
18 CurrentFile RepoFile
19
20 // Tree, if the underlying file is a directory.
21 tree *object.Tree
22}
23
24type CommitInfo struct {
25 Commits []*object.Commit
26 Total uint
27}
28
29var (
30 ExpectedDirectory = errors.New("Expected directory")
31 ExpectedSubmodule = errors.New("Expected submodule")
32 ExpectedRegular = errors.New("Expected regular file")
33)
34
35func (r *RepoPage) Files() ([]RepoFile, error) {
36 if !r.CurrentFile.IsDir() {
37 return nil, ExpectedDirectory
38 }
39
40 var entries []RepoFile
41 basepath := filepath.Base(r.CurrentFile.Path)
42
43 walker := object.NewTreeWalker(r.tree, false, nil)
44 defer walker.Close()
45 for {
46 name, f, err := walker.Next()
47 if err == io.EOF {
48 break
49 } else if err != nil {
50 return nil, err
51 }
52
53 relpath := filepath.Join(basepath, name)
54 file := RepoFile{
55 Path: filepath.ToSlash(relpath),
56 mode: f.Mode,
57 }
58
59 entries = append(entries, file)
60 }
61
62 sort.Sort(byType(entries))
63 return entries, nil
64}
65
66func (r *RepoPage) Commits() (*CommitInfo, error) {
67 var total, numCommits uint
68
69 logOpts := &git.LogOptions{Order: git.LogOrderDFSPost}
70 if r.CurrentFile.Path != "" {
71 logOpts.PathFilter = func(fp string) bool {
72 return fp == r.CurrentFile.Path
73 }
74 }
75 iter, err := r.git.Log(logOpts)
76 if err != nil {
77 return nil, err
78 }
79
80 commits := make([]*object.Commit, r.maxCommits)
81 err = iter.ForEach(func(c *object.Commit) error {
82 if numCommits < r.maxCommits {
83 commits[numCommits] = c
84 numCommits++
85 }
86
87 total++
88 return nil
89 })
90 if err != nil {
91 return nil, err
92 }
93
94 commits = commits[0:numCommits] // Shrink to appropriate size
95 return &CommitInfo{commits, total}, nil
96}
97
98func (r *RepoPage) Blob() (*object.File, error) {
99 if r.CurrentFile.IsDir() || r.CurrentFile.IsSubmodule() {
100 return nil, ExpectedRegular
101 }
102
103 commit, err := r.Tip()
104 if err != nil {
105 return nil, err
106 }
107 return commit.File(r.CurrentFile.Path)
108}
109
110func (r *RepoPage) Submodule(file *RepoFile) (*object.File, error) {
111 if !file.IsSubmodule() {
112 return nil, ExpectedSubmodule
113 }
114
115 // git-go only seems to have very limited support for submodules
116 // in bare repositories. Hence, just display .gitmodules for now.
117 commit, err := r.Tip()
118 if err != nil {
119 return nil, err
120 }
121 return commit.File(".gitmodules")
122}
123
124func (r *RepoPage) findReadme() (string, error) {
125 var result string
126
127 walker := object.NewTreeWalker(r.tree, false, nil)
128 defer walker.Close()
129 for {
130 _, f, err := walker.Next()
131 if err == io.EOF {
132 break
133 } else if err != nil {
134 return "", err
135 }
136
137 if isReadme(f.Name) {
138 result = f.Name
139 break
140 }
141 }
142
143 if result == "" {
144 return "", os.ErrNotExist
145 }
146
147 return result, nil
148}
149
150func (r *RepoPage) Readme() (string, error) {
151 if !r.CurrentFile.IsDir() {
152 return "", ExpectedDirectory
153 }
154
155 fp, err := r.findReadme()
156 if err != nil {
157 return "", err
158 }
159 file, err := r.tree.File(fp)
160 if err != nil {
161 return "", err
162 }
163
164 return file.Contents()
165}