diff options
| author | Franck Cuny <franck@fcuny.net> | 2025-08-25 07:46:47 -0700 |
|---|---|---|
| committer | Franck Cuny <franck@fcuny.net> | 2025-08-25 07:46:47 -0700 |
| commit | 977190e31f91339dae91253f53af89c38cbe04fc (patch) | |
| tree | bd942f1ebffc955be2caa724abc55e6703993639 /cmd | |
| parent | add seq-stat: terminal histogram generator for number sequences (diff) | |
| download | x-977190e31f91339dae91253f53af89c38cbe04fc.tar.gz | |
add `git-leaderboard`
Diffstat (limited to 'cmd')
| -rw-r--r-- | cmd/git-leaderboard/main.go | 128 |
1 files changed, 128 insertions, 0 deletions
diff --git a/cmd/git-leaderboard/main.go b/cmd/git-leaderboard/main.go new file mode 100644 index 0000000..7c846a5 --- /dev/null +++ b/cmd/git-leaderboard/main.go @@ -0,0 +1,128 @@ +package main + +import ( + "flag" + "fmt" + "os" + "os/exec" + "sort" + "strings" +) + +type Author struct { + Name string + Count int +} + +func main() { + lineFlag := flag.Bool("line", false, "Count lines changed instead of commits") + flag.Parse() + + if !isGitRepository() { + fmt.Println("Error: Not in a git repository") + os.Exit(1) + } + + authors, err := getAuthors(*lineFlag) + if err != nil { + fmt.Printf("Error getting authors: %v\n", err) + os.Exit(1) + } + + if len(authors) == 0 { + fmt.Println("No authors found. The repository might be empty or have no commits.") + os.Exit(0) + } + + sort.Slice(authors, func(i, j int) bool { + return authors[i].Count > authors[j].Count + }) + + maxCount := authors[0].Count + + fmt.Printf("%-30s %-10s %s\n", "Author", "Count", "Contribution") + fmt.Println(strings.Repeat("-", 80)) + + for _, author := range authors { + bar := generateBar(author.Count, maxCount) + fmt.Printf("%-30s %-10d %s\n", author.Name, author.Count, bar) + } +} + +func isGitRepository() bool { + cmd := exec.Command("git", "rev-parse", "--is-inside-work-tree") + err := cmd.Run() + return err == nil +} + +func getAuthors(countLines bool) ([]Author, error) { + var cmd *exec.Cmd + + if countLines { + cmd = exec.Command("git", "log", "--pretty=format:%aN", "--numstat") + } else { + cmd = exec.Command("git", "shortlog", "-sn", "--no-merges", "HEAD") + } + + output, err := cmd.Output() + if err != nil { + if exitErr, ok := err.(*exec.ExitError); ok { + return nil, fmt.Errorf("git command failed: %s. Error output: %s", err, exitErr.Stderr) + } + return nil, fmt.Errorf("error executing git command: %v", err) + } + + lines := strings.Split(string(output), "\n") + authors := make(map[string]int) + + if countLines { + currentAuthor := "" + for _, line := range lines { + if line == "" { + currentAuthor = "" + continue + } + if currentAuthor == "" { + currentAuthor = line + continue + } else { + fields := strings.Fields(line) + if len(fields) >= 2 { + added, _ := parseInt(fields[0]) + deleted, _ := parseInt(fields[1]) + authors[currentAuthor] += added + deleted + } + } + } + } else { + for _, line := range lines { + fields := strings.Fields(line) + if len(fields) >= 2 { + count, _ := parseInt(fields[0]) + name := strings.Join(fields[1:], " ") + authors[name] = count + } + } + } + + var result []Author + for name, count := range authors { + result = append(result, Author{Name: name, Count: count}) + } + return result, nil +} + +func parseInt(s string) (int, error) { + if s == "-" { + return 0, nil + } + var result int + _, err := fmt.Sscanf(s, "%d", &result) + return result, err +} + +func generateBar(count, maxCount int) string { + maxWidth := 38 + width := int(float64(count) / float64(maxCount) * float64(maxWidth)) + return strings.Repeat("▆", width) +} |
