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) }