repos / pgit

static site generator for git
git clone https://github.com/picosh/pgit.git

commit
3700715
parent
e51bb05
author
Eric Bower
date
2023-08-06 03:40:43 +0000 UTC
looking good
6 files changed,  +141, -77
M go.mod
M go.sum
M go.mod
+1, -0
1@@ -4,6 +4,7 @@ go 1.18
2 
3 require (
4 	github.com/gogs/git-module v1.6.0
5+	github.com/mergestat/timediff v0.0.3
6 	github.com/picosh/pico v1.1.6
7 	github.com/spf13/viper v1.12.0
8 )
M go.sum
+2, -0
1@@ -213,6 +213,8 @@ github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
2 github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
3 github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75 h1:Pijfgr7ZuvX7QIQiEwLdRVr3RoMG+i0SbBO1Qu+7yVk=
4 github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo=
5+github.com/mergestat/timediff v0.0.3 h1:ucCNh4/ZrTPjFZ081PccNbhx9spymCJkFxSzgVuPU+Y=
6+github.com/mergestat/timediff v0.0.3/go.mod h1:yvMUaRu2oetc+9IbPLYBJviz6sA7xz8OXMDfhBl7YSI=
7 github.com/microcosm-cc/bluemonday v1.0.21 h1:dNH3e4PSyE4vNX+KlRGHT5KrSvjeUkoNPwEORjffHJg=
8 github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM=
9 github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
M html/commit.page.tmpl
+11, -11
 1@@ -5,17 +5,17 @@
 2   {{template "header" .}}
 3 
 4   <dl>
 5-      <dt>commit</dt>
 6-      <dd><a href="{{.Data.CommitURL}}">{{.Data.Commit.ID}}</a></dd>
 7+    <dt>commit</dt>
 8+    <dd><a href="{{.Data.CommitURL}}">{{.Data.CommitID}}</a></dd>
 9 
10-      <dt>parent</dt>
11-      <dd><a href="{{.Data.ParentURL}}">{{.Data.Parent}}</a></dd>
12+    <dt>parent</dt>
13+    <dd><a href="{{.Data.ParentURL}}">{{.Data.Parent}}</a></dd>
14 
15-      <dt>author</dt>
16-      <dd>{{.Data.Commit.Author.Name}}</dd>
17+    <dt>author</dt>
18+    <dd>{{.Data.Commit.Author.Name}}</dd>
19 
20-      <dt>date</dt>
21-      <dd>{{.Data.Commit.Author.When}}</dd>
22+    <dt>date</dt>
23+    <dd>{{.Data.Commit.Author.When}}</dd>
24   </dl>
25 
26   <pre>{{.Data.Commit.Message}}</pre>
27@@ -31,15 +31,15 @@
28     {{range .Data.Diff.Files}}
29       <div>
30         <span>{{.FileType}}</span>
31-        <span>{{.Name}}</span>
32+        <a href="#diff-{{.Name}}">{{.Name}}</a>
33       </div>
34     {{end}}
35     </div>
36   </div>
37 
38   {{range .Data.Diff.Files}}
39-    <div class="flex justify-between mono">
40-      <span>{{.FileType}} {{.OldName}} => {{.Name}}</span>
41+    <div id="diff-{{.Name}}" class="flex justify-between mono">
42+      <span>{{.FileType}} {{if eq .FileType "R"}}{{.OldName}} => {{end}}{{.Name}}</span>
43       <div>
44         <span class="color-green">+{{.NumAdditions}}</span>,
45         <span class="color-red">-{{.NumDeletions}}</span>
M html/index.page.tmpl
+13, -6
 1@@ -3,10 +3,17 @@
 2 {{define "title"}}index{{end}}
 3 
 4 {{define "content"}}
 5-  <h1>repos</h1>
 6-  {{range .RepoList}}
 7-    <div>
 8-      <a href="{{.URL}}">{{.Name}}</a>
 9-    </div>
10-  {{end}}
11+  <h1 class="text-xl">repos</h1>
12+  <div>
13+    {{range .RepoList}}
14+      <div class="my box">
15+        <div class="flex justify-between items-center">
16+          <div><a class="text-lg" href="{{.URL}}">{{.Name}}</a></div>
17+          <div class="text-sm">{{.LastCommit.Author.When}}</div>
18+        </div>
19+
20+        <div class="my">{{.Desc}}</div>
21+      </div>
22+    {{end}}
23+  </div>
24 {{end}}
M html/tree.page.tmpl
+20, -12
 1@@ -5,16 +5,24 @@
 2 {{define "content"}}
 3   {{template "header" .}}
 4 
 5-  <div>
 6-  {{range .Data.Tree}}
 7-    <div class="flex justify-between mt">
 8-      <div class="text-md">
 9-        <a href="{{.URL}}">{{.Path}}</a>
10-      </div>
11-      <div class="mono">
12-        <span>lines: {{.NumLines}}</span>
13-      </div>
14-    </div>
15-  {{end}}
16-  </div>
17+  <table>
18+    <tbody>
19+    {{range .Data.Tree}}
20+      <tr>
21+        <td>
22+          <a href="{{.URL}}">{{.Path}}</a>
23+        </td>
24+        <td>
25+          <a href="{{.CommitURL}}">{{.Desc}}</a>
26+        </td>
27+        <td>
28+          {{.When}}
29+        </td>
30+        <td class="mono font-bold">
31+          L{{.NumLines}}
32+        </td>
33+      </tr>
34+    {{end}}
35+    </tbody>
36+  </table>
37 {{end}}
M main.go
+94, -48
  1@@ -6,9 +6,11 @@ import (
  2 	html "html/template"
  3 	"os"
  4 	"path/filepath"
  5+	"sort"
  6 	"strings"
  7 
  8 	git "github.com/gogs/git-module"
  9+	"github.com/mergestat/timediff"
 10 	"github.com/picosh/pico/pastes"
 11 	"github.com/spf13/viper"
 12 )
 13@@ -16,8 +18,10 @@ import (
 14 var defaultBranches = []string{"main", "master"}
 15 
 16 type RepoItemData struct {
 17-	URL  string
 18-	Name string
 19+	URL        string
 20+	Name       string
 21+	Desc string
 22+	LastCommit *git.Commit
 23 }
 24 
 25 type IndexPage struct {
 26@@ -32,6 +36,7 @@ type RepoData struct {
 27 	LogURL     string
 28 	RefsURL    string
 29 	CloneURL   string
 30+	MaxCommits int
 31 }
 32 
 33 type CommitData struct {
 34@@ -44,6 +49,9 @@ type TreeItem struct {
 35 	URL      string
 36 	Path     string
 37 	Entry    *git.TreeEntry
 38+	CommitURL string
 39+	Desc string
 40+	When string
 41 }
 42 
 43 type PageData struct {
 44@@ -59,6 +67,7 @@ type PageData struct {
 45 
 46 type CommitPageData struct {
 47 	CommitMsg template.HTML
 48+	CommitID string
 49 	Commit    *CommitData
 50 	Diff      *DiffRender
 51 	Repo      *RepoData
 52@@ -114,7 +123,7 @@ func bail(err error) {
 53 	}
 54 }
 55 
 56-func CommitURL(repo string, commitID string) string {
 57+func commitURL(repo string, commitID string) string {
 58 	return fmt.Sprintf("/%s/commits/%s.html", repo, commitID)
 59 }
 60 
 61@@ -283,14 +292,15 @@ func writeHTMLTreeFiles(data *PageData) string {
 62 	return readme
 63 }
 64 
 65-func writeLogDiffs(project string, repo *git.Repository, data *PageData, cache map[string]bool) {
 66-	for _, commit := range data.Log {
 67+func (c *Config) writeLogDiffs(repo *git.Repository, pageData *PageData) {
 68+	project := pageData.Repo.Name
 69+	for _, commit := range pageData.Log {
 70 		commitID := commit.ID.String()
 71 
 72-		if cache[commitID] {
 73+		if c.Cache[commitID] {
 74 			continue
 75 		} else {
 76-			cache[commitID] = true
 77+			c.Cache[commitID] = true
 78 		}
 79 
 80 		ancestors, err := commit.Ancestors()
 81@@ -302,7 +312,7 @@ func writeLogDiffs(project string, repo *git.Repository, data *PageData, cache m
 82 			pt := ancestors[0]
 83 			parent = &CommitData{
 84 				Commit: pt,
 85-				URL:    CommitURL(project, pt.ID.String()),
 86+				URL:    commitURL(project, pt.ID.String()),
 87 			}
 88 		}
 89 		parentID := parent.ID.String()
 90@@ -347,30 +357,30 @@ func writeLogDiffs(project string, repo *git.Repository, data *PageData, cache m
 91 
 92 		commitData := &CommitPageData{
 93 			Commit:    commit,
 94+			CommitID: commit.ID.String()[:7],
 95 			Diff:      rnd,
 96-			Repo:      data.Repo,
 97-			Parent:    parentID,
 98-			CommitURL: CommitURL(project, commitID),
 99-			ParentURL: CommitURL(project, parentID),
100+			Repo:      pageData.Repo,
101+			Parent:    parentID[:7],
102+			CommitURL: commitURL(project, commitID),
103+			ParentURL: commitURL(project, parentID),
104 		}
105 
106 		writeHtml(&WriteData{
107 			Name:     fmt.Sprintf("%s.html", commitID),
108 			Template: "./html/commit.page.tmpl",
109 			Data:     commitData,
110-			RepoName: data.Repo.Name,
111+			RepoName: pageData.Repo.Name,
112 			Subdir:   "commits",
113-			Repo:     data.Repo,
114+			Repo:     pageData.Repo,
115 		})
116 	}
117 }
118 
119-func writeRepo(config *RepoConfig) {
120+func (c *Config) writeRepo(config *RepoConfig) *BranchOutput {
121 	repo, err := git.Open(config.Path)
122 	bail(err)
123 
124 	name := repoName(config.Path)
125-	desc := config.Desc
126 
127 	heads, err := repo.ShowRef(git.ShowRefOptions{Heads: true, Tags: false})
128 	bail(err)
129@@ -383,19 +393,19 @@ func writeRepo(config *RepoConfig) {
130 
131 	repoData := &RepoData{
132 		Name:       name,
133-		Desc:       desc,
134+		Desc:       config.Desc,
135+		MaxCommits: config.MaxCommits,
136 		SummaryURL: fmt.Sprintf("/%s/index.html", name),
137 		TreeURL:    fmt.Sprintf("/%s/tree/%s/index.html", name, revName),
138 		LogURL:     fmt.Sprintf("/%s/logs/%s/index.html", name, revName),
139 		RefsURL:    fmt.Sprintf("/%s/refs.html", name),
140-		CloneURL:   fmt.Sprintf("/%s.git", name),
141+		CloneURL:   fmt.Sprintf("https://%s/%s.git", c.URL, name),
142 	}
143 
144 	tags, _ := repo.ShowRef(git.ShowRefOptions{Heads: false, Tags: true})
145 
146-	cache := make(map[string]bool)
147-
148-	readme := ""
149+	var mainOutput *BranchOutput
150+	claimed := false
151 	for _, revn := range config.Refs {
152 		for _, head := range heads {
153 			_, headName := filepath.Split(head.Refspec)
154@@ -410,9 +420,10 @@ func writeRepo(config *RepoConfig) {
155 				Repo:     repoData,
156 			}
157 
158-			branchReadme := writeBranch(repo, data, cache)
159-			if readme == "" {
160-				readme = branchReadme
161+			branchOutput := c.writeBranch(repo, data)
162+			if !claimed {
163+				mainOutput = branchOutput
164+				claimed = true
165 			}
166 		}
167 	}
168@@ -423,20 +434,36 @@ func writeRepo(config *RepoConfig) {
169 		Rev:      rev,
170 		RevName:  revName,
171 		Repo:     repoData,
172-		Readme:   template.HTML(readme),
173+		Readme:   template.HTML(mainOutput.Readme),
174 	}
175 	writeRefs(data)
176 	writeRootSummary(data)
177+	return mainOutput
178+}
179+
180+type BranchOutput struct {
181+	Readme     string
182+	LastCommit *git.Commit
183 }
184 
185-func writeBranch(repo *git.Repository, pageData *PageData, cache map[string]bool) string {
186-	commits, err := repo.CommitsByPage(pageData.Rev.ID, 0, 100)
187+func (c *Config) writeBranch(repo *git.Repository, pageData *PageData) *BranchOutput {
188+	output := &BranchOutput{}
189+	pageSize := pageData.Repo.MaxCommits
190+	if pageSize == 0 {
191+		pageSize = 5000
192+	}
193+
194+	commits, err := repo.CommitsByPage(pageData.Rev.ID, 0, pageSize)
195 	bail(err)
196 
197 	logs := []*CommitData{}
198-	for _, commit := range commits {
199+	for i, commit := range commits {
200+		if i == 0 {
201+			output.LastCommit = commit
202+		}
203+
204 		logs = append(logs, &CommitData{
205-			URL:    CommitURL(pageData.Repo.Name, commit.ID.String()),
206+			URL:    commitURL(pageData.Repo.Name, commit.ID.String()),
207 			Commit: commit,
208 		})
209 	}
210@@ -447,6 +474,19 @@ func writeBranch(repo *git.Repository, pageData *PageData, cache map[string]bool
211 	treeEntries := walkTree(tree, pageData.RevName, "", entries)
212 	for _, entry := range treeEntries {
213 		entry.Path = strings.TrimPrefix(entry.Path, "/")
214+
215+		lastCommits, err := repo.RevList([]string{pageData.Rev.Refspec}, git.RevListOptions{
216+			Path: entry.Path,
217+		})
218+		bail(err)
219+
220+		var lc *git.Commit
221+		if (len(lastCommits) > 0) {
222+			lc = lastCommits[0]
223+		}
224+		entry.CommitURL = commitURL(pageData.Repo.Name, lc.ID.String())
225+		entry.Desc = lc.Summary()
226+		entry.When = timediff.TimeDiff(lc.Author.When)
227 		entry.URL = filepath.Join(
228 			"/",
229 			pageData.Repo.Name,
230@@ -462,25 +502,24 @@ func writeBranch(repo *git.Repository, pageData *PageData, cache map[string]bool
231 
232 	writeLog(pageData)
233 	readme := writeHTMLTreeFiles(pageData)
234-	writeLogDiffs(pageData.Repo.Name, repo, pageData, cache)
235+	c.writeLogDiffs(repo, pageData)
236 
237-	for _, def := range defaultBranches {
238-		if def == pageData.RevName {
239-			writeTree(pageData)
240-		}
241-	}
242+	writeTree(pageData)
243 
244-	return readme
245+	output.Readme = readme
246+	return output
247 }
248 
249 type RepoConfig struct {
250-	Path string   `mapstructure:"path"`
251-	Refs []string `mapstructure:"refs"`
252-	Desc string   `mapstructure:"desc"`
253+	Path       string   `mapstructure:"path"`
254+	Refs       []string `mapstructure:"refs"`
255+	Desc       string   `mapstructure:"desc"`
256+	MaxCommits int      `mapstructure:"max_commits"`
257 }
258 type Config struct {
259 	Repos []*RepoConfig `mapstructure:"repos"`
260 	URL   string        `mapstructure:"url"`
261+	Cache map[string]bool
262 }
263 
264 func main() {
265@@ -492,23 +531,30 @@ func main() {
266 	bail(err)
267 
268 	var config Config
269-	if err := viper.Unmarshal(&config); err != nil {
270-		fmt.Println(err)
271-		return
272-	}
273+	err = viper.Unmarshal(&config)
274+	bail(err)
275+
276+	config.Cache = make(map[string]bool)
277+
278 	repoList := []*RepoItemData{}
279 	for _, r := range config.Repos {
280+		mainOutput := config.writeRepo(r)
281 		name := repoName(r.Path)
282 		url := filepath.Join("/", name, "index.html")
283 		repoList = append(repoList, &RepoItemData{
284-			URL:  url,
285-			Name: name,
286+			URL:        url,
287+			Name:       name,
288+			Desc: r.Desc,
289+			LastCommit: mainOutput.LastCommit,
290 		})
291 	}
292+	sort.Slice(repoList, func(i, j int) bool {
293+		first := repoList[i].LastCommit.Author.When
294+		second := repoList[j].LastCommit.Author.When
295+		return first.After(second)
296+	})
297+
298 	writeIndex(&IndexPage{
299 		RepoList: repoList,
300 	})
301-	for _, r := range config.Repos {
302-		writeRepo(r)
303-	}
304 }