aboutsummaryrefslogtreecommitdiff
path: root/tools/git-blame-stats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--tools/git-blame-stats/default.nix15
-rw-r--r--tools/git-blame-stats/go.mod3
-rw-r--r--tools/git-blame-stats/main.go86
3 files changed, 104 insertions, 0 deletions
diff --git a/tools/git-blame-stats/default.nix b/tools/git-blame-stats/default.nix
new file mode 100644
index 0000000..8897b67
--- /dev/null
+++ b/tools/git-blame-stats/default.nix
@@ -0,0 +1,15 @@
+{ pkgs, ... }:
+
+pkgs.buildGoModule rec {
+ name = "git-blame-stats";
+ src = ./.;
+ vendorSha256 = "sha256-pQpattmS9VmO3ZIQUFn66az8GSmB4IvYhTTCFn6SUmo=";
+ nativeBuildInputs = with pkgs; [ go ];
+
+ meta = with pkgs.lib; {
+ description = "CLI to reports git blame statistics per author.";
+ license = licenses.mit;
+ platforms = platforms.linux;
+ maintainers = [ ];
+ };
+}
diff --git a/tools/git-blame-stats/go.mod b/tools/git-blame-stats/go.mod
new file mode 100644
index 0000000..4738ac4
--- /dev/null
+++ b/tools/git-blame-stats/go.mod
@@ -0,0 +1,3 @@
+module golang.fcuny.net/git-blame-stats
+
+go 1.17
diff --git a/tools/git-blame-stats/main.go b/tools/git-blame-stats/main.go
new file mode 100644
index 0000000..8b1bc9a
--- /dev/null
+++ b/tools/git-blame-stats/main.go
@@ -0,0 +1,86 @@
+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
+}