Introduction
Introduction Statistics Contact Development Disclaimer Help
Optimising by adding threading, combining HTML output to one file. - staticgit …
Log
Files
Refs
README
---
commit 89f13ce268930a8a931f4a82312653c96ac8d8ca
parent 884ed087159dcf350421c692b8c530b7db260c28
Author: Jay Scott <[email protected]>
Date: Sat, 13 Jul 2024 19:37:19 +0100
Optimising by adding threading, combining HTML output to one file.
Diffstat:
M Makefile | 2 +-
M main.go | 452 ++++++++++++++---------------…
2 files changed, 210 insertions(+), 244 deletions(-)
---
diff --git a/Makefile b/Makefile
@@ -8,7 +8,7 @@ all: run
run:
@echo "Running $(APP_NAME)..."
- @go run . -g -p ./git -o tmp -i .ssh,dotfiles
+ @go run . -g -p ./git -o tmp -i .ssh,jay.scot,internal-docs
build:
@echo "Building $(APP_NAME) for local architecture..."
diff --git a/main.go b/main.go
@@ -10,23 +10,19 @@ import (
"regexp"
"sort"
"strings"
+ "sync"
"github.com/go-git/go-git/v5"
- "github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
)
-type BranchInfo struct {
- Name string
- LastCommit string
- LastCommitDate string
-}
-
type CommitInfo struct {
Hash string
Author string
Date string
Message string
+ Added int
+ Removed int
}
type RepoInfo struct {
@@ -36,109 +32,124 @@ type RepoInfo struct {
}
const (
- baseTemplate = `
+ base = `
<!DOCTYPE html>
<html>
<head>
-<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
-<meta name="viewport" content="width=device-width, initial-scale=1" />
-<title>{{.Title}}</title>
-<link rel="icon" type="image/png" href="{{.IconPath}}favicon.png" />
-<link rel="stylesheet" type="text/css" href="{{.IconPath}}style.css" />
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <title>{{.Title}}</title>
+ <link rel="icon" type="image/png" href="{{.IconPath}}favicon.png" />
+ <style>
+ body{color:#000;background-color:#FFF;font-family:monospace}
+ table{padding-left:20px}
+ h1{font-size:2em;margin:10}
+ h2{font-size:1.5em;margin:10}
+ table td{padding:0 .4em}
+ a{color:#000;text-decoration:none}
+ a:hover{color:#333;font-weight:700}
+ a:target{background-color:#CCC}
+ .desc{color:#555;margin-bottom:1em}
+ hr{border:0;border-top:1px solid #AAA;height:2px}
+ table tr:hover{background-color:#EEE}
+ </style>
</head>
<body>
-<table>
-<tr><td><img src="{{.IconPath}}logo.png" alt="" width="32" height="32" /></td>
-<td><span class="desc">{{.Title}}</span></td></tr><tr><td></td><td>
-</td></tr>
-</table>
-<hr/>
-<div id="content">
-{{template "content" .}}
-</div>
+ <table>
+ <tr>
+ <td>
+ <img src="{{.IconPath}}logo.png" alt="" width="32" height="3…
+ </td>
+ <td>
+ <span class="desc">{{.Title}}</span>
+ </td>
+ </tr>
+ </table>
+
+ <hr/>
+
+ <div id="content">
+ {{template "content" .}}
+ </div>
</body>
</html>
`
-
- branchesContent = `
-{{define "content"}}
-<h1>{{.RepoName}} - Branches</h1>
-<table>
-<thead>
-<tr><td><b>Branch Name</b></td><td><b>Last Commit</b></td><td><b>Last Commit D…
-</thead>
-<tbody>
-{{range .Branches}}
-<tr><td>{{.Name}}</td><td>{{.LastCommit}}</td><td>{{.LastCommitDate}}</td></tr>
-{{end}}
-</tbody>
-</table>
-{{end}}
-`
-
- commitHistoryContent = `
+ details = `
{{define "content"}}
-<h1>{{.RepoName}} - Commit History</h1>
-<table>
-<thead>
-<tr><td><b>Hash</b></td><td><b>Author</b></td><td><b>Date</b></td><td><b>Messa…
-</thead>
-<tbody>
-{{range .Commits}}
-<tr><td>{{.Hash}}</td><td>{{.Author}}</td><td>{{.Date}}</td><td>{{.Message}}</…
-{{end}}
-</tbody>
-</table>
+ <pre>{{.ReadmeContent}}</pre>
+
+ <h2>Commit History</h2>
+ <hr>
+ <table>
+ <thead>
+ <tr>
+ <td><b>Date</b></td>
+ <td><b>Message</b></td>
+ <td><b>Author</b></td>
+ <td><b>Hash</b></td>
+ <td><b>Added</b></td>
+ <td><b>Deleted</b></td>
+ </tr>
+ </thead>
+ <tbody>
+ {{range .Commits}}
+ <tr>
+ <td>{{.Date}}</td>
+ <td>{{.Message}}</td>
+ <td>{{.Author}}</td>
+ <td>{{.Hash}}</td>
+ <td style="color: green;">+{{.Added}}</td>
+ <td style="color: red;">-{{.Removed}}</td>
+ </tr>
+ {{end}}
+ </tbody>
+ </table>
{{end}}
`
- indexContent = `
-{{define "content"}}
-<table id="index">
-<thead>
-<tr><td><b>Name</b></td><td><b>Description</b></td><td><b>Last commit</b></td>…
-</thead>
-<tbody>
-{{range $group, $repos := .Repos}}
-<tr><td colspan="4"><b>{{if eq $group ""}} {{else}}<hr>{{end}}</b></td></tr>
- {{range $repos}}
- <tr>
- <td>{{.Name}}</td>
- <td>{{.Description}}</td>
- <td>| {{.LastCommit}} | </td>
- <td>
- <a href="{{.Name}}/README.html">readme</a> |
- <a href="{{.Name}}/commits.html">commits</a> |
- <a href="{{.Name}}/branches.html">branches</a>
- </td>
- </tr>
- {{end}}
-{{end}}
-</tbody>
-</table>
-{{end}}
-`
- readmeContent = `
+ index = `
{{define "content"}}
-<h1>{{.RepoName}}</h1>
-<pre>{{.ReadmeContent}}</pre>
+ <table id="index">
+ <thead>
+ <tr>
+ <td><b>Name</b></td>
+ <td><b>Description</b></td>
+ <td><b>Last commit</b></td>
+ </tr>
+ </thead>
+ <tbody>
+ {{range $group, $repos := .Repos}}
+ <tr>
+ <td colspan="4">{{if eq $group ""}}{{else}}<hr>{{end}}</td>
+ </tr>
+ {{range $repos}}
+ <tr>
+ <td><a href="{{.Name}}/index.html">{{.Name}}</a></td>
+ <td>{{.Description}}</td>
+ <td>{{.LastCommit}}</td>
+ </tr>
+ {{end}}
+ {{end}}
+ </tbody>
+ </table>
{{end}}
`
)
var (
- reposPath string
- groupFlag bool
- ignoreDirs map[string]bool
- outputRoot string
-
- branchesTmpl = template.Must(template.New("base").Parse(baseTempl…
- commitHistoryTmpl = template.Must(template.New("base").Parse(baseTempl…
- indexTmpl = template.Must(template.New("base").Parse(baseTempl…
- readmeTmpl = template.Must(template.New("base").Parse(baseTempl…
+ templates = map[string]*template.Template{
+ "index": template.Must(template.New("base").Parse(base + ind…
+ "details": template.Must(template.New("base").Parse(base + det…
+ }
+
+ reposPath string
+ groupFlag bool
+ ignoreDirs map[string]bool
+ outputRoot string
+ commitLimit int
)
-func generateIndexHTML(repoInfos []RepoInfo) error {
+func generateIndex(repoInfos []RepoInfo) error {
groupedRepos := groupRepos(repoInfos)
indexOutputPath := filepath.Join(outputRoot, "index.html")
@@ -148,64 +159,95 @@ func generateIndexHTML(repoInfos []RepoInfo) error {
}
defer indexFile.Close()
- return indexTmpl.Execute(indexFile, struct {
+ return templates["index"].Execute(indexFile, struct {
Title string
IconPath string
Repos map[string][]RepoInfo
}{
- Title: "git clone [email protected]:<reponame>",
+ Title: "repos for days!",
IconPath: "./",
Repos: groupedRepos,
})
}
-func getBranchInfo(repo *git.Repository) ([]BranchInfo, error) {
- branches, err := repo.Branches()
+func generateRepo(repoName, repoPath, outputDir string) error {
+ repo, err := git.PlainOpen(repoPath)
if err != nil {
- return nil, fmt.Errorf("failed to get branches: %w", err)
+ return fmt.Errorf("failed to open git repository: %w", err)
}
- var branchInfos []BranchInfo
- err = branches.ForEach(func(branch *plumbing.Reference) error {
- commit, err := repo.CommitObject(branch.Hash())
- if err != nil {
- return fmt.Errorf("failed to get commit for branch %s:…
- }
-
- branchInfos = append(branchInfos, BranchInfo{
- Name: branch.Name().Short(),
- LastCommit: commit.Hash.String()[:7],
- LastCommitDate: commit.Author.When.Format("02 Jan 2006…
- })
- return nil
- })
+ readme, err := getReadme(repoPath)
+ if err != nil {
+ return fmt.Errorf("failed to get README: %w", err)
+ }
+ commits, err := getCommits(repo)
if err != nil {
- return nil, fmt.Errorf("failed to iterate over branches: %w", …
+ return fmt.Errorf("failed to get commit history: %w", err)
}
- return branchInfos, nil
-}
+ outputPath := filepath.Join(outputDir, "index.html")
-func getCommitHistory(repo *git.Repository) ([]CommitInfo, error) {
- ref, err := repo.Head()
+ f, err := os.Create(outputPath)
if err != nil {
- return nil, fmt.Errorf("failed to get HEAD reference: %w", err)
+ return fmt.Errorf("failed to create details HTML file: %w", er…
}
+ defer f.Close()
- commits, err := repo.Log(&git.LogOptions{From: ref.Hash()})
+ return templates["details"].Execute(f, struct {
+ Title string
+ IconPath string
+ RepoName string
+ ReadmeContent string
+ Commits []CommitInfo
+ }{
+ Title: "git clone [email protected]:" + repoName,
+ IconPath: "../",
+ RepoName: repoName,
+ ReadmeContent: readme,
+ Commits: commits,
+ })
+}
+
+func getCommits(repo *git.Repository) ([]CommitInfo, error) {
+ commitIter, err := repo.CommitObjects()
if err != nil {
- return nil, fmt.Errorf("failed to get commit log: %w", err)
+ return nil, fmt.Errorf("failed to get commit objects: %w", err)
}
var commitHistory []CommitInfo
- err = commits.ForEach(func(c *object.Commit) error {
+ count := 0
+ reachedLimit := false
+
+ err = commitIter.ForEach(func(c *object.Commit) error {
+ if reachedLimit {
+ return nil
+ }
+
+ stats, err := c.Stats()
+ if err != nil {
+ return fmt.Errorf("failed to get commit stats: %w", er…
+ }
+
+ added, removed := 0, 0
+ for _, stat := range stats {
+ added += stat.Addition
+ removed += stat.Deletion
+ }
+
commitHistory = append(commitHistory, CommitInfo{
Hash: c.Hash.String()[:7],
Author: c.Author.Name,
Date: c.Author.When.Format("02 Jan 2006 15:04:05"),
- Message: strings.Split(c.Message, "\n")[0], // Get onl…
+ Message: strings.Split(c.Message, "\n")[0],
+ Added: added,
+ Removed: removed,
})
+
+ count++
+ if count >= commitLimit {
+ reachedLimit = true
+ }
return nil
})
@@ -261,7 +303,7 @@ func getReadme(repoPath string) (string, error) {
return "No README found!", nil
}
-func getRepoInfo(repoPath string) (RepoInfo, error) {
+func getRepo(repoPath string) (RepoInfo, error) {
repo, err := git.PlainOpen(repoPath)
if err != nil {
return RepoInfo{}, fmt.Errorf("failed to open repository: %w",…
@@ -313,7 +355,7 @@ func getGroup(description string) string {
return ""
}
-func parseIgnoreDirs(ignoreDirs string) map[string]bool {
+func parseIgnored(ignoreDirs string) map[string]bool {
ignoreMap := make(map[string]bool)
for _, dir := range strings.Split(ignoreDirs, ",") {
if trimmedDir := strings.TrimSpace(dir); trimmedDir != "" {
@@ -323,149 +365,73 @@ func parseIgnoreDirs(ignoreDirs string) map[string]bool {
return ignoreMap
}
-func processBranches(repoName, repoPath, outputDir string) error {
- repo, err := git.PlainOpen(repoPath)
- if err != nil {
- return fmt.Errorf("failed to open git repository: %w", err)
- }
-
- branches, err := getBranchInfo(repo)
- if err != nil {
- return fmt.Errorf("failed to get branch information: %w", err)
- }
-
- outputPath := filepath.Join(outputDir, "branches.html")
-
- f, err := os.Create(outputPath)
- if err != nil {
- return fmt.Errorf("failed to create branches HTML file: %w", e…
- }
- defer f.Close()
-
- return branchesTmpl.Execute(f, struct {
- Title string
- IconPath string
- RepoName string
- Branches []BranchInfo
- }{
- Title: repoName + " - Branches",
- IconPath: "../",
- RepoName: repoName,
- Branches: branches,
- })
-}
-
-func processCommitHistory(repoName, repoPath, outputDir string) error {
- repo, err := git.PlainOpen(repoPath)
- if err != nil {
- return fmt.Errorf("failed to open git repository: %w", err)
- }
-
- commits, err := getCommitHistory(repo)
- if err != nil {
- return fmt.Errorf("failed to get commit history: %w", err)
- }
-
- outputPath := filepath.Join(outputDir, "commits.html")
-
- f, err := os.Create(outputPath)
- if err != nil {
- return fmt.Errorf("failed to create commit history HTML file: …
- }
- defer f.Close()
-
- return commitHistoryTmpl.Execute(f, struct {
- Title string
- IconPath string
- RepoName string
- Commits []CommitInfo
- }{
- Title: repoName + " - History",
- IconPath: "../",
- RepoName: repoName,
- Commits: commits,
- })
-}
-
-func processReadme(repoName, repoPath, outputDir string) error {
- readme, err := getReadme(repoPath)
+func processRepos() error {
+ repos, err := os.ReadDir(reposPath)
if err != nil {
- return fmt.Errorf("failed to get README: %w", err)
+ return fmt.Errorf("failed to read repos directory: %w", err)
}
- outputPath := filepath.Join(outputDir, "README.html")
+ var wg sync.WaitGroup
+ repoInfosChan := make(chan RepoInfo, len(repos))
+ errorsChan := make(chan error, len(repos))
- f, err := os.Create(outputPath)
- if err != nil {
- return fmt.Errorf("failed to create README HTML file: %w", err)
+ for _, r := range repos {
+ if r.IsDir() && !ignoreDirs[r.Name()] {
+ wg.Add(1)
+ go func(r os.DirEntry) {
+ defer wg.Done()
+ repoPath := filepath.Join(reposPath, r.Name())
+ repoInfo, err := getRepo(repoPath)
+ if err != nil {
+ errorsChan <- fmt.Errorf("failed to ge…
+ return
+ }
+ repoInfosChan <- repoInfo
+
+ outputDir := filepath.Join(outputRoot, r.Name(…
+ if err := os.MkdirAll(outputDir, 0755); err !=…
+ errorsChan <- fmt.Errorf("failed to cr…
+ return
+ }
+
+ if err := generateRepo(r.Name(), repoPath, out…
+ errorsChan <- fmt.Errorf("failed to pr…
+ }
+ }(r)
+ }
}
- defer f.Close()
- return readmeTmpl.Execute(f, struct {
- Title string
- IconPath string
- RepoName string
- ReadmeContent string
- }{
- Title: repoName + " - Readme!",
- IconPath: "../",
- RepoName: repoName,
- ReadmeContent: readme,
- })
-}
+ go func() {
+ wg.Wait()
+ close(repoInfosChan)
+ close(errorsChan)
+ }()
-func processRepositories() error {
- repos, err := os.ReadDir(reposPath)
- if err != nil {
- return fmt.Errorf("failed to read repos directory: %w", err)
+ var repoInfos []RepoInfo
+ for repoInfo := range repoInfosChan {
+ repoInfos = append(repoInfos, repoInfo)
}
- var repoInfos []RepoInfo
- for _, r := range repos {
- if r.IsDir() && !ignoreDirs[r.Name()] {
- repoPath := filepath.Join(reposPath, r.Name())
- repoInfo, err := getRepoInfo(repoPath)
- if err != nil {
- fmt.Printf("Failed to get info for repo %s: %v…
- continue
- }
- repoInfos = append(repoInfos, repoInfo)
-
- outputDir := filepath.Join(outputRoot, r.Name())
- if err := os.MkdirAll(outputDir, 0755); err != nil {
- fmt.Printf("Failed to create output directory …
- continue
- }
-
- if err := processReadme(r.Name(), repoPath, outputDir)…
- fmt.Printf("Failed to process README for repo …
- }
-
- if err := processCommitHistory(r.Name(), repoPath, out…
- fmt.Printf("Failed to process commit history f…
- }
-
- if err := processBranches(r.Name(), repoPath, outputDi…
- fmt.Printf("Failed to process branches for rep…
- }
- }
+ for err := range errorsChan {
+ fmt.Println(err)
}
- return generateIndexHTML(repoInfos)
+ return generateIndex(repoInfos)
}
func main() {
flag.StringVar(&reposPath, "p", "", "Path to the git repositories (req…
+ flag.StringVar(&outputRoot, "o", ".", "Root path where output director…
flag.BoolVar(&groupFlag, "g", false, "Group repositories based on desc…
+ flag.IntVar(&commitLimit, "c", 100, "Limit for the number of commits t…
ignoreFlag := flag.String("i", "", "Directories to ignore (comma-separ…
- flag.StringVar(&outputRoot, "o", ".", "Root path where output director…
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: %s [options]\n\n", os.Args[0])
fmt.Fprintf(os.Stderr, "Options:\n")
flag.PrintDefaults()
fmt.Fprintf(os.Stderr, "\nExample:\n")
- fmt.Fprintf(os.Stderr, " %s -p /path/to/repos -g -i dir1,dir2…
+ fmt.Fprintf(os.Stderr, " %s -p /path/to/repos -g -i dir1,dir2…
}
flag.Parse()
@@ -475,9 +441,9 @@ func main() {
os.Exit(1)
}
- ignoreDirs = parseIgnoreDirs(*ignoreFlag)
+ ignoreDirs = parseIgnored(*ignoreFlag)
- if err := processRepositories(); err != nil {
+ if err := processRepos(); err != nil {
log.Fatalf("Error processing repositories: %v", err)
}
}
You are viewing proxied material from jay.scot. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.