| Fixing ordering of commits on details page, refactor pass. - staticgit - A git … | |
| Log | |
| Files | |
| Refs | |
| README | |
| --- | |
| commit e3b9e35e373c00b591b8907ce7d7c1d3d7d2ce71 | |
| parent 2c17ce4ce5c99e093bcc742c8815e7ad64e3b233 | |
| Author: Jay Scott <[email protected]> | |
| Date: Sun, 14 Jul 2024 20:58:56 +0100 | |
| Fixing ordering of commits on details page, refactor pass. | |
| Diffstat: | |
| M main.go | 280 ++++++++++++++++-------------… | |
| 1 file changed, 140 insertions(+), 140 deletions(-) | |
| --- | |
| diff --git a/main.go b/main.go | |
| @@ -16,23 +16,23 @@ import ( | |
| "github.com/go-git/go-git/v5/plumbing/object" | |
| ) | |
| -type CommitInfo struct { | |
| +type Commit struct { | |
| Hash string | |
| Author string | |
| Date string | |
| - Message string | |
| + Msg string | |
| Added int | |
| Removed int | |
| } | |
| -type RepoInfo struct { | |
| - Name string | |
| - Description string | |
| - LastCommitTime time.Time | |
| +type Repo struct { | |
| + Name string | |
| + Desc string | |
| + LastMod time.Time | |
| } | |
| const ( | |
| - base = ` | |
| + baseHtml = ` | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| @@ -72,7 +72,7 @@ const ( | |
| </html> | |
| ` | |
| - details = ` | |
| + detailHtml = ` | |
| {{define "content"}} | |
| <pre>{{.ReadmeContent}}</pre> | |
| @@ -92,7 +92,7 @@ const ( | |
| {{range .Commits}} | |
| <tr> | |
| <td>{{.Date}}</td> | |
| - <td>{{.Message}}</td> | |
| + <td>{{.Msg}}</td> | |
| <td>{{.Author}}</td> | |
| <td>{{.Hash}}</td> | |
| <td>+{{.Added}}</td> | |
| @@ -111,7 +111,7 @@ const ( | |
| {{end}} | |
| ` | |
| - index = ` | |
| + indexHtml = ` | |
| {{define "content"}} | |
| <table> | |
| <thead> | |
| @@ -125,8 +125,8 @@ const ( | |
| {{range .Repos}} | |
| <tr> | |
| <td><a href="{{.Name}}/index.html">{{.Name}}</a></td> | |
| - <td>{{.Description}}</td> | |
| - <td>{{.LastCommitTime.Format "2006-01-02 15:04:05"}}</td> | |
| + <td>{{.Desc}}</td> | |
| + <td>{{.LastMod.Format "2006-01-02 15:04:05"}}</td> | |
| </tr> | |
| {{end}} | |
| </tbody> | |
| @@ -137,64 +137,64 @@ const ( | |
| var ( | |
| templates = map[string]*template.Template{ | |
| - "index": template.Must(template.New("base").Parse(base + ind… | |
| - "details": template.Must(template.New("base").Parse(base + det… | |
| + "index": template.Must(template.New("base").Parse(baseHtml +… | |
| + "details": template.Must(template.New("base").Parse(baseHtml +… | |
| } | |
| - reposPath string | |
| - ignoreDirs map[string]bool | |
| - outputRoot string | |
| - commitLimit int | |
| + repoDir string | |
| + ignoreDirs map[string]bool | |
| + outDir string | |
| + maxCommits int | |
| ) | |
| -func generateIndex(repoInfos []RepoInfo) error { | |
| - sort.Slice(repoInfos, func(i, j int) bool { | |
| - return repoInfos[i].LastCommitTime.After(repoInfos[j].LastComm… | |
| +func genIndex(repos []Repo) error { | |
| + sort.Slice(repos, func(i, j int) bool { | |
| + return repos[i].LastMod.After(repos[j].LastMod) | |
| }) | |
| - indexOutputPath := filepath.Join(outputRoot, "index.html") | |
| + path := filepath.Join(outDir, "index.html") | |
| - indexFile, err := os.Create(indexOutputPath) | |
| + f, err := os.Create(path) | |
| if err != nil { | |
| - return fmt.Errorf("failed to create index HTML file: %w", err) | |
| + return fmt.Errorf("create index HTML: %w", err) | |
| } | |
| - defer indexFile.Close() | |
| + defer f.Close() | |
| - return templates["index"].Execute(indexFile, struct { | |
| + return templates["index"].Execute(f, struct { | |
| Title string | |
| - Repos []RepoInfo | |
| + Repos []Repo | |
| }{ | |
| Title: "Repos for days!", | |
| - Repos: repoInfos, | |
| + Repos: repos, | |
| }) | |
| } | |
| -func generateRepo(repoName, repoPath, outputDir string) error { | |
| - repo, err := git.PlainOpen(repoPath) | |
| +func genRepo(name, path, out string) error { | |
| + repo, err := git.PlainOpen(path) | |
| if err != nil { | |
| - return fmt.Errorf("failed to open git repository: %w", err) | |
| + return fmt.Errorf("open git repo: %w", err) | |
| } | |
| - readme, err := getReadme(repoPath) | |
| + readme, err := readme(path) | |
| if err != nil { | |
| - return fmt.Errorf("failed to get README: %w", err) | |
| + return fmt.Errorf("get README: %w", err) | |
| } | |
| - commits, err := getCommits(repo) | |
| + cs, err := commits(repo) | |
| if err != nil { | |
| - return fmt.Errorf("failed to get commit history: %w", err) | |
| + return fmt.Errorf("get commits: %w", err) | |
| } | |
| - files, err := getFiles(repo) | |
| + fs, err := files(repo) | |
| if err != nil { | |
| - return fmt.Errorf("failed to get repository files: %w", err) | |
| + return fmt.Errorf("get files: %w", err) | |
| } | |
| - outputPath := filepath.Join(outputDir, "index.html") | |
| + path = filepath.Join(out, "index.html") | |
| - f, err := os.Create(outputPath) | |
| + f, err := os.Create(path) | |
| if err != nil { | |
| - return fmt.Errorf("failed to create details HTML file: %w", er… | |
| + return fmt.Errorf("create details HTML: %w", err) | |
| } | |
| defer f.Close() | |
| @@ -202,130 +202,130 @@ func generateRepo(repoName, repoPath, outputDir string)… | |
| Title string | |
| ReadmeContent string | |
| Files []string | |
| - Commits []CommitInfo | |
| + Commits []Commit | |
| }{ | |
| - Title: "git clone [email protected]:" + repoName, | |
| + Title: "git clone [email protected]:" + name, | |
| ReadmeContent: readme, | |
| - Files: files, | |
| - Commits: commits, | |
| + Files: fs, | |
| + Commits: cs, | |
| }) | |
| } | |
| -func getCommits(repo *git.Repository) ([]CommitInfo, error) { | |
| - commitIter, err := repo.CommitObjects() | |
| +func commits(repo *git.Repository) ([]Commit, error) { | |
| + iter, err := repo.CommitObjects() | |
| if err != nil { | |
| - return nil, fmt.Errorf("failed to get commit objects: %w", err) | |
| + return nil, fmt.Errorf("get commit objects: %w", err) | |
| } | |
| - var commitHistory []CommitInfo | |
| + var cs []Commit | |
| count := 0 | |
| - reachedLimit := false | |
| + limit := false | |
| - err = commitIter.ForEach(func(c *object.Commit) error { | |
| - if reachedLimit { | |
| + err = iter.ForEach(func(c *object.Commit) error { | |
| + if limit { | |
| return nil | |
| } | |
| stats, err := c.Stats() | |
| if err != nil { | |
| - return fmt.Errorf("failed to get commit stats: %w", er… | |
| + return fmt.Errorf("get commit stats: %w", err) | |
| } | |
| - added, removed := 0, 0 | |
| + add, del := 0, 0 | |
| for _, stat := range stats { | |
| - added += stat.Addition | |
| - removed += stat.Deletion | |
| + add += stat.Addition | |
| + del += stat.Deletion | |
| } | |
| - commitHistory = append(commitHistory, CommitInfo{ | |
| + cs = append(cs, Commit{ | |
| 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], | |
| - Added: added, | |
| - Removed: removed, | |
| + Msg: strings.Split(c.Message, "\n")[0], | |
| + Added: add, | |
| + Removed: del, | |
| }) | |
| count++ | |
| - if count >= commitLimit { | |
| - reachedLimit = true | |
| + if count >= maxCommits { | |
| + limit = true | |
| } | |
| return nil | |
| }) | |
| if err != nil { | |
| - return nil, fmt.Errorf("failed to iterate over commits: %w", e… | |
| + return nil, fmt.Errorf("iterate commits: %w", err) | |
| } | |
| - return commitHistory, nil | |
| -} | |
| + sort.Slice(cs, func(i, j int) bool { | |
| + timeI, _ := time.Parse("02 Jan 2006 15:04:05", cs[i].Date) | |
| + timeJ, _ := time.Parse("02 Jan 2006 15:04:05", cs[j].Date) | |
| + return timeI.After(timeJ) | |
| + }) | |
| -func getDescription(repoPath string) string { | |
| - descPath := filepath.Join(repoPath, "description") | |
| - description, _ := os.ReadFile(descPath) | |
| - return strings.TrimSpace(string(description)) | |
| + return cs, nil | |
| } | |
| -func getFiles(repo *git.Repository) ([]string, error) { | |
| +func files(repo *git.Repository) ([]string, error) { | |
| ref, err := repo.Head() | |
| if err != nil { | |
| - return nil, fmt.Errorf("failed to get HEAD reference: %w", err) | |
| + return nil, fmt.Errorf("get HEAD: %w", err) | |
| } | |
| commit, err := repo.CommitObject(ref.Hash()) | |
| if err != nil { | |
| - return nil, fmt.Errorf("failed to get commit object: %w", err) | |
| + return nil, fmt.Errorf("get commit object: %w", err) | |
| } | |
| tree, err := commit.Tree() | |
| if err != nil { | |
| - return nil, fmt.Errorf("failed to get tree: %w", err) | |
| + return nil, fmt.Errorf("get tree: %w", err) | |
| } | |
| - var files []string | |
| + var fs []string | |
| err = tree.Files().ForEach(func(f *object.File) error { | |
| - files = append(files, f.Name) | |
| + fs = append(fs, f.Name) | |
| return nil | |
| }) | |
| if err != nil { | |
| - return nil, fmt.Errorf("failed to iterate over files: %w", err) | |
| + return nil, fmt.Errorf("iterate files: %w", err) | |
| } | |
| - sort.Strings(files) | |
| - return files, nil | |
| + sort.Strings(fs) | |
| + return fs, nil | |
| } | |
| -func getReadme(repoPath string) (string, error) { | |
| - readmeFiles := []string{"README.md", "README.txt", "README"} | |
| - repo, err := git.PlainOpen(repoPath) | |
| +func readme(path string) (string, error) { | |
| + names := []string{"README.md", "README.txt", "README"} | |
| + repo, err := git.PlainOpen(path) | |
| if err != nil { | |
| - return "", fmt.Errorf("failed to open git repository: %w", err) | |
| + return "", fmt.Errorf("open git repo: %w", err) | |
| } | |
| - headRef, err := repo.Head() | |
| + ref, err := repo.Head() | |
| if err != nil { | |
| - return "", fmt.Errorf("failed to get HEAD reference: %w", err) | |
| + return "", fmt.Errorf("get HEAD: %w", err) | |
| } | |
| - commit, err := repo.CommitObject(headRef.Hash()) | |
| + commit, err := repo.CommitObject(ref.Hash()) | |
| if err != nil { | |
| - return "", fmt.Errorf("failed to get commit object: %w", err) | |
| + return "", fmt.Errorf("get commit object: %w", err) | |
| } | |
| tree, err := commit.Tree() | |
| if err != nil { | |
| - return "", fmt.Errorf("failed to get tree: %w", err) | |
| + return "", fmt.Errorf("get tree: %w", err) | |
| } | |
| - for _, fileName := range readmeFiles { | |
| - file, err := tree.File(fileName) | |
| + for _, name := range names { | |
| + file, err := tree.File(name) | |
| if err != nil { | |
| continue | |
| } | |
| content, err := file.Contents() | |
| if err != nil { | |
| - return "", fmt.Errorf("failed to read file contents: %… | |
| + return "", fmt.Errorf("read file contents: %w", err) | |
| } | |
| return content, nil | |
| @@ -334,100 +334,100 @@ func getReadme(repoPath string) (string, error) { | |
| return "No README found!", nil | |
| } | |
| -func getRepo(repoPath string) (RepoInfo, error) { | |
| - repo, err := git.PlainOpen(repoPath) | |
| +func repoInfo(path string) (Repo, error) { | |
| + repo, err := git.PlainOpen(path) | |
| if err != nil { | |
| - return RepoInfo{}, fmt.Errorf("failed to open repository: %w",… | |
| + return Repo{}, fmt.Errorf("open repo: %w", err) | |
| } | |
| - headRef, err := repo.Head() | |
| + ref, err := repo.Head() | |
| if err != nil { | |
| - return RepoInfo{}, fmt.Errorf("failed to get HEAD reference: %… | |
| + return Repo{}, fmt.Errorf("get HEAD: %w", err) | |
| } | |
| - commit, err := repo.CommitObject(headRef.Hash()) | |
| + commit, err := repo.CommitObject(ref.Hash()) | |
| if err != nil { | |
| - return RepoInfo{}, fmt.Errorf("failed to get commit object: %w… | |
| + return Repo{}, fmt.Errorf("get commit object: %w", err) | |
| } | |
| - description := getDescription(repoPath) | |
| + content, _ := os.ReadFile(filepath.Join(path, "description")) | |
| - return RepoInfo{ | |
| - Name: filepath.Base(repoPath), | |
| - Description: description, | |
| - LastCommitTime: commit.Committer.When, | |
| + return Repo{ | |
| + Name: filepath.Base(path), | |
| + Desc: strings.TrimSpace(string(content)), | |
| + LastMod: commit.Committer.When, | |
| }, nil | |
| } | |
| -func parseIgnored(ignoreDirs string) map[string]bool { | |
| - ignoreMap := make(map[string]bool) | |
| - for _, dir := range strings.Split(ignoreDirs, ",") { | |
| - if trimmedDir := strings.TrimSpace(dir); trimmedDir != "" { | |
| - ignoreMap[trimmedDir] = true | |
| +func ignoreList(dirs string) map[string]bool { | |
| + ignore := make(map[string]bool) | |
| + for _, dir := range strings.Split(dirs, ",") { | |
| + if d := strings.TrimSpace(dir); d != "" { | |
| + ignore[d] = true | |
| } | |
| } | |
| - return ignoreMap | |
| + return ignore | |
| } | |
| -func processRepos() error { | |
| - repos, err := os.ReadDir(reposPath) | |
| +func build() error { | |
| + dirs, err := os.ReadDir(repoDir) | |
| if err != nil { | |
| - return fmt.Errorf("failed to read repos directory: %w", err) | |
| + return fmt.Errorf("read repos dir: %w", err) | |
| } | |
| var wg sync.WaitGroup | |
| - repoInfosChan := make(chan RepoInfo, len(repos)) | |
| - errorsChan := make(chan error, len(repos)) | |
| + repoChan := make(chan Repo, len(dirs)) | |
| + errChan := make(chan error, len(dirs)) | |
| - for _, r := range repos { | |
| - if r.IsDir() && !ignoreDirs[r.Name()] { | |
| + for _, d := range dirs { | |
| + if d.IsDir() && !ignoreDirs[d.Name()] { | |
| wg.Add(1) | |
| - go func(r os.DirEntry) { | |
| + go func(d os.DirEntry) { | |
| defer wg.Done() | |
| - repoPath := filepath.Join(reposPath, r.Name()) | |
| - repoInfo, err := getRepo(repoPath) | |
| + path := filepath.Join(repoDir, d.Name()) | |
| + repo, err := repoInfo(path) | |
| if err != nil { | |
| - errorsChan <- fmt.Errorf("failed to ge… | |
| + errChan <- fmt.Errorf("get info for %s… | |
| return | |
| } | |
| - repoInfosChan <- repoInfo | |
| + repoChan <- repo | |
| - outputDir := filepath.Join(outputRoot, r.Name(… | |
| - if err := os.MkdirAll(outputDir, 0755); err !=… | |
| - errorsChan <- fmt.Errorf("failed to cr… | |
| + out := filepath.Join(outDir, d.Name()) | |
| + if err := os.MkdirAll(out, 0755); err != nil { | |
| + errChan <- fmt.Errorf("create dir for … | |
| return | |
| } | |
| - if err := generateRepo(r.Name(), repoPath, out… | |
| - errorsChan <- fmt.Errorf("failed to pr… | |
| + if err := genRepo(d.Name(), path, out); err !=… | |
| + errChan <- fmt.Errorf("process %s: %w"… | |
| } | |
| - }(r) | |
| + }(d) | |
| } | |
| } | |
| go func() { | |
| wg.Wait() | |
| - close(repoInfosChan) | |
| - close(errorsChan) | |
| + close(repoChan) | |
| + close(errChan) | |
| }() | |
| - var repoInfos []RepoInfo | |
| - for repoInfo := range repoInfosChan { | |
| - repoInfos = append(repoInfos, repoInfo) | |
| + var repos []Repo | |
| + for repo := range repoChan { | |
| + repos = append(repos, repo) | |
| } | |
| - for err := range errorsChan { | |
| + for err := range errChan { | |
| fmt.Println(err) | |
| } | |
| - return generateIndex(repoInfos) | |
| + return genIndex(repos) | |
| } | |
| func main() { | |
| - flag.StringVar(&reposPath, "p", "", "Path to the git repositories (req… | |
| - flag.StringVar(&outputRoot, "o", ".", "Root path where output director… | |
| - flag.IntVar(&commitLimit, "c", 100, "Limit for the number of commits t… | |
| - ignoreFlag := flag.String("i", "", "Directories to ignore (comma-separ… | |
| + flag.StringVar(&repoDir, "p", "", "Path to git repos (required)") | |
| + flag.StringVar(&outDir, "o", ".", "Root path for output") | |
| + flag.IntVar(&maxCommits, "c", 100, "Max commits to display (default 10… | |
| + ignore := flag.String("i", "", "Dirs to ignore (comma-separated)") | |
| flag.Usage = func() { | |
| fmt.Fprintf(os.Stderr, "Usage: %s [options]\n\n", os.Args[0]) | |
| @@ -439,14 +439,14 @@ func main() { | |
| flag.Parse() | |
| - if reposPath == "" { | |
| + if repoDir == "" { | |
| flag.Usage() | |
| os.Exit(1) | |
| } | |
| - ignoreDirs = parseIgnored(*ignoreFlag) | |
| + ignoreDirs = ignoreList(*ignore) | |
| - if err := processRepos(); err != nil { | |
| - log.Fatalf("Error processing repositories: %v", err) | |
| + if err := build(); err != nil { | |
| + log.Fatalf("Error building site: %v", err) | |
| } | |
| } |