package main import ( "fmt" "log" "os" "os/exec" "regexp" "sort" "strings" ) func main() { rev := "HEAD" if len(os.Args) == 2 { rev = os.Args[1] } files := gitListFiles(rev) authors := gitBlameFiles(rev, files) sortedAuthors, keys := sortAuthors(authors) rank := 1 for _, k := range keys { for i := 0; i < len(sortedAuthors[k]); i++ { fmt.Printf("%3d - %6d %s\n", rank, k, sortedAuthors[k][i]) rank = rank + 1 } } } func gitListFiles(rev string) []string { out, err := exec.Command("git", "ls-tree", "--name-only", "-r", rev).Output() if err != nil { log.Fatal(err) } files := strings.Split(string(out), "\n") return files } func gitBlameFiles(rev string, files []string) map[string]int { authors := make(map[string]int) for i := 0; i < len(files)-1; i++ { gitBlameFile(rev, files[i], authors) } return authors } func gitBlameFile(rev, file string, authors map[string]int) { out, err := exec.Command("git", "blame", "-e", "-w", rev, "--", file).Output() if err != nil { log.Fatal(err) } lines := strings.Split(string(out), "\n") authorRegex := regexp.MustCompile(`^.*?\((.*?)\s*\d{4}-\d{2}-\d{2}.*`) for j := 0; j < len(lines)-1; j++ { if string(lines[j][0]) != "^" { matched := authorRegex.FindStringSubmatch(string(lines[j])) if len(matched) > 0 { authors[matched[1]] = authors[matched[1]] + 1 } } } } func sortAuthors(authors map[string]int) (map[int][]string, []int) { var keys []int sortedAuthors := make(map[int][]string) for k, v := range authors { sortedAuthors[v] = append(sortedAuthors[v], k) if len(sortedAuthors[v]) == 1 { keys = append(keys, v) } } sort.Sort(sort.Reverse(sort.IntSlice(keys))) return sortedAuthors, keys }