aboutsummaryrefslogtreecommitdiff
path: root/cmd/git-leaderboard/main.go
blob: 7c846a531fb4f61aac1c3ea0a0be913dbe37c8e0 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
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)
}