6 ms·
Show HN: "Git who" – A new CLI tool for industrial-scale Git blaming
I've always wanted a better way to explore the authorship data embedded in a Git commit log. I'm having fun building a CLI tool to do this.
It's a bit like the "Contributors" tab on Github that shows you how many commits each contributor has made but much faster and with many more options.
If you get a chance to try it out, please let me know. I'd love to hear feedback and suggestions. Thank you!
- andrewfromx 2y agoI like it. A problem I had right away is some people commit using two different emails. Like one from home computer and one from work computer. Would be nice to be able to define them as the same thing.
- dolmen 2y agogit help mailmap
- jtwaleson 2y agoThat's what the mailmap is for.
- sebastianlay 2y agoYou might be able to do that with built-in git functionality called gitmailmap. It is basically a file where you can map multiple names and emails to the same one.
- Cadwhisker 2y agoIt's great when you read further down the comments and come across a gem like this. I had no idea this was possible; thanks.
- Noumenon72 2y agoI set this up and tested with `git log` and then found that PyCharm's git client apparently doesn't support this. Disappointing.
- weebst 2y agoLike other commenters have said, mailmap does this and git who will respect your mailmap file.
- max23_ 2y agoLike the other comment, this is "I came looking for copper but found gold" moment for me. Thanks!
- lionkor 2y agoBy the way, git blaming is really misunderstood by a lot of people; its NOT about who did it, its about which commit is to blame -- that's different.
- kemitchell 2y agoI have seen `git blame` used to blame specific people. I've seen it work. Some of those people deserved some blame. The manpage explains what the command does. How and why it's used is up to the user.
- markerz 2y agoIdeally, you find the context for why a change was made
- jtwaleson 2y agoVery cool, I'm working on something similar as part of a bigger project (not TUI related). I'm interested in how you did blame caching, will take a look at the implementation. I am trying to do a "forward blame" so that the blame of new commits can be created very quickly. Happy to exchange some thoughts around this!
- nextts 2y agoDon't tell the higher ups about this stack ranking tool.
- deleted 2y ago[deleted]
- natemwilson 2y agoCool, but how do I increase the number of rows? Is it always just the top ten?
- weebst 2y agoThe -n flag does this. Use -n 0 to show all rows
- ttyyzz 2y ago"This requires that you have Go, Ruby, and the rake Ruby gem installed." - sticking to the binaries then :) Cool little project, will try it tomorrow!
- numbers 2y agoI love TUI tools but I'm not too familiar with Golang, now I am thinking I should start looking into using go for TUIs this is a great tool!
- qudat 2y agoAt pico.sh we have been experimenting with TUIs and remote clis successfully for a few years, you can see how we build our ssh tui app here: https://github.com/picosh/pico/tree/main/pkg/tui https://github.com/picosh/pico/tree/main/pkg/tui
- mmcclimon 2y ago> You can invoke git-who as git who by setting up an alias in your global Git config This works even without the alias, by the way: by default `git whatever` will search your path for `git-whatever` and execute it.
- weebst 2y agoWow! I had no idea. Will need to update the README. Thanks for the tip!
- jeff_carr 2y agoYes, that is awesome. I wonder if "go" works like that also?
- chatmasta 2y agoHas this behavior been the source of exploits in the past? Something about it feels dangerously presumptuous to me.
- mdaniel 2y agoI am guessing it only resorts to that expansion if it dosesn't _already_ know about the command, because $(printf '#!/bin/sh\necho pwned\n' > /bin/git-status; chmod 755 /bin/git-status; git status) results in the thing happening that you'd expect, not a mysterious message FWIW, both brew and kubectl also have adopted this behavior (of $(basename)-plugin style verb extensions) so I find it unlikely they'd all do it if it was a straight-up facepalm
- igorbark 2y agoprobably adding a confirmation message the first time the alias is used for each command would be good, it would be nice to know when i'm invoking git and when i'm invoking a third party binary regardless of any exploit attempts!
- 2y ago
- rednafi 2y agoCool stuff. I like using Git via the CLI, but when it comes to blame, I simply use the preview UI of the VSCode GitLens extension. It takes half a second to launch it from the command palette and inspect the blame.
- davely 2y agoI have some VS Code extension (errr... not sure which) that faintly inlines the git blame result on each line of code you're working on. I find it kind of handy.
- lelandfe 2y agoGitLens https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens https://marketplace.visualstudio.com/items?itemName=eamodio.... I uninstalled it, I seem to recall it impacting the speed of VS Code a good bit.
- apple1417 2y agoThat was actually recently added as a built-in feature https://code.visualstudio.com/updates/v1_97#_git-blame-information https://code.visualstudio.com/updates/v1_97#_git-blame-infor... Like the sibling comment, I didn't want to run all of GitLens just for it, but now that it's a built-in I've also been finding it quite useful.
- fmeyer 2y agoNice one, works better than mine; I've been using a git alias for quite some time `lead = shortlog -s -n --all --no-merges`
- syhol 2y agoI've been using `git summary` from tj/git-extras for a while. It seems to do a similar job.
- einpoklum 2y agoFollowed the link, and the README said: > This requires that you have Go, Ruby, and the rake Ruby gem installed. That doesn't cut it for me. git - once built - depends on C libraries and Perl. If you want to add something onto git (that is not specifically targeting Go, or Ruby etc.) - it should not IMNSHO depend on other things. That doesn't mean you can't write your tool in some modern fashionable language, but eventually you need to bring it down to earth (or rather earth + Perl).
- eMPee584 2y agoFor anyone not aware of it: `tig` is a really cool TUI git frontend, and it has a beautiful `tig blame` sub command..
- anotherpaulg 2y agoThis is great. I do this sort of git-blame accounting to track how much code is written by AI versus humans in each release of my app. My "blame script" has been slowing down as the repo size increases. I was just about to add caching, like you have. Have you thought about adding the ability to limit the stats based on a set of file patterns? Perhaps like this, where the file follows gitignore conventions? git-who table -include-file <fname> git-who table -ignore-file <fname> I tried to quickly add this functionality but unfortunately I don't know go.
- weebst 2y agoThat's a neat idea, I can see how it'd be useful. If you have a shell that supports extended globbing, you could do something like: $ git who table */**/*.go That works for me using Bash. I believe all that's happening here is that Bash is expanding the globs and passing a long list of individual filepaths as arguments to git who. Git who then passes them to git log so that it only tallies the commits you'd get by running: $ git log */**/*.go
- anotherpaulg 2y agoYup. It’s a complex enough set of in/excludes that I think that would get unwieldy for my use case. Details here: https://github.com/Aider-AI/aider/blob/main/scripts/blame.py https://github.com/Aider-AI/aider/blob/main/scripts/blame.py Again, nice work on your tool. I’ll spend some more time trying to harness it for my need.
- weebst 2y agoThank you very much!
- JadeNB 2y ago> $ git who table */**/*.go I might have my globbing syntax wrong, but I think that `*/**/*.go` is the same as `**/*.go` unless you have `*.go` files in the working directory.
- 2y ago
- unquietwiki 2y agoThis looks like an almost pure Golang program, but still has a Ruby dependency. Is there a component/library that Ruby provides, that Go doesn't have? Or some other logic going on?
- ajanuary 2y agoI’m pretty sure it’s just using Ruby for rake, which is a task runner. So Ruby is only needed for the build process.
- m000 2y agoLovely tool. Thanks for the release! I had some fun with it before calling it a day. Here is my personal wishlist after a short test-drive. - Blame-based stats. While it is nice to see an overview of the historical contributions of Bob and Alice, this is not something that I would use on a daily basis. What would be more useful, is to present the same tables based on the blame lines of a tree-ish. This would show the de-facto "owner(s)" of modules/files, something which comes handy when asking for help with something or even assigning reviews. One could also run this iteratively over the history and get some nice timeline graph. - Support for pattern-based inclusions/exclusions. E.g. I am not interested to see stats on the json files used by tests. Or any kind of auto-generated files (e.g. django migrations). - Support for a configuration file, to store your preferred settings in your git repo. Something TOML-based perhaps. - Better packaging (nit). E.g. the linux tarball for v0.6 contains some apple-related "junk" and gnu tar complains about archive format incompatibilities.
- weebst 2y agoI'm very happy to hear you had fun with it. Thank you for the comprehensive feedback and for trying it out! Not sure what's happening with the tarball. Will take a look at that.
- ycombinatrix 2y agoThank you! My consumer-scale git blaming was leaving me with woeful feeling of inadequacy.
- eddiejaoude 2y agoI love CLI tools, plus it is open source!
- coryvirok 2y agohttps://shortlog.io/ https://shortlog.io/
- deleted 2y ago[deleted]
- dcchambers 2y agoThis is fantastic! I love it. Pretty quick too... For a rails codebase that is ~18 years old, has 1695 committers and more than 220,000 commits: time git who ... real 0m2.885s user 0m2.711s sys 0m0.767s
- kelseydh 2y agoI would love to see this get a brew release.
- camdotcom14 2y agoit's ready!
- kazinator 2y agoTiny prototype implementation: Run on log from GNU Bison. We anonymize names so that search engines don't index this comment to those names: $ ./gwho git-log-stat.txt NAME LAST-SEEN FILES LINES+ LINES- COMMITS A___ D_______ Tue Sep 20 08:19:02 2022 +0200 17083 356066 255931 4440 P___ E_____ Mon Mar 17 17:46:43 2025 -0700 4496 61898 71486 1123 J___ E_ D____ Sun Aug 21 17:35:26 2011 -0400 3922 75517 50121 612 R_____ A_____ Thu May 2 16:43:00 2002 +0000 101 7631 4522 23 J____ T____ Sun Jan 21 16:43:58 2001 +0000 200 8308 3205 60 P___ H________ Tue Feb 26 16:28:36 2013 -0800 122 5057 2864 26 A___ R_______ Wed Jan 5 15:47:25 2011 +0200 124 5297 2101 30 T________ R______ Tue Nov 13 10:38:49 2012 +0000 229 3841 2744 94 V_______ T_____ Wed Nov 11 18:55:15 2020 +0100 67 4739 1128 17 J__ M_______ Sat Jan 18 20:52:21 2020 -0800 337 1569 3894 45 J___ M_____ G_______ Mon May 12 00:58:38 2008 +0000 91 2570 2060 49 R______ M_ S_______ Mon Jan 5 00:25:39 1998 +0000 134 2978 1155 64 P____ B______ Tue Nov 11 13:37:36 2008 +0100 90 2991 786 17 A____ V___ Mon Sep 19 19:09:20 2022 +0200 130 2335 943 46 M___ A_____ Sun Jan 20 15:59:34 2002 +0000 201 1797 1291 76 D____ J__ Sun Dec 7 21:54:45 2008 -0800 57 2142 816 15 P_____ B___ Fri Oct 19 11:03:50 2001 +0000 67 1771 1182 19 E___ B____ Thu Aug 27 10:56:53 2009 -0600 61 2172 590 22 V_____ S_____ Fri Jun 29 16:23:42 2012 +0200 64 865 918 9 D____ M________ Thu Nov 10 22:34:22 1994 +0000 26 1526 84 18 D_____ H_________ Thu Jun 13 10:08:19 2013 +0200 6 1142 54 1 V______ I______ Sat Jan 23 13:25:18 2021 -0500 47 824 360 16 A_____ V___________ Thu Feb 27 09:52:03 2020 +0100 25 524 189 13 V_____ M______ C______ Fri Feb 14 18:41:55 2020 +0100 16 284 364 1 J_____ W___ Tue Feb 16 08:00:28 2021 -0600 18 306 33 3 W_______ P____ Thu Feb 21 17:08:18 2008 +0000 13 195 76 9 J___ S____ Wed Jul 26 00:30:05 2017 -0400 67 88 88 63 E___ S_ R______ Wed Feb 13 10:39:54 2019 -0500 2 133 33 1 Y_______ K_____ Mon Nov 11 08:57:15 2019 +0900 5 125 13 2 K______ K_______ Sun Jan 27 06:58:17 2019 +0100 3 85 24 1 H_ S_ T___ Fri Mar 1 06:16:54 2019 +0100 1 45 51 1 T__ L__________ Tue Mar 27 19:28:02 2012 +0000 9 81 9 5 T__ V__ H_____ Fri Jan 11 15:32:06 2002 +0000 7 55 34 3 A________ D_________ Wed May 14 18:41:48 2003 +0000 3 66 16 1 L_____ V_____ Fri Aug 9 14:24:14 2019 +0200 8 71 2 2 M______ D_ B_________ Thu Jul 30 20:53:35 2020 +0200 10 30 29 3 J______ Tue Nov 20 22:02:20 2018 +0100 3 30 28 3 J_______ N_____ Tue Dec 15 22:03:18 2009 -0600 10 42 13 3 N___ F_______ Mon Sep 6 19:51:09 1993 +0000 7 36 18 7 K__ K______ Tue Oct 13 15:39:41 2020 -0700 5 26 28 2 E_____ S________ Mon Dec 10 15:18:37 2018 +0200 10 25 25 1 M_____ R____ Wed Nov 18 09:10:01 2020 +0100 10 26 13 2 J___ B_____ Mon Oct 2 20:04:58 2000 +0000 3 26 8 1 T_____ P________ Tue May 19 22:05:22 2020 +0200 4 29 2 1 A_____ B_______ Sun Mar 6 22:19:18 2011 -0500 6 24 2 2 N___ G_____ Tue Oct 27 06:12:27 2020 +0000 4 14 11 3 B____ K_____ Sat Feb 19 19:24:07 2011 -0500 6 22 2 2 A___ S______ Wed Oct 31 14:01:31 2018 +0000 1 12 9 1 S_____ T______ Mon Nov 24 15:27:49 2008 +0100 1 11 6 1 F______ K____ Tue May 14 00:25:23 2002 +0000 3 11 6 2 A______ H______ Fri Apr 29 04:08:35 2022 -0400 2 8 6 1 B____ H_____ Sat Dec 18 18:45:46 2021 +0100 4 3 9 2 T___ C_ M_____ Tue Nov 10 07:36:11 2020 +0100 2 8 3 1 E______ S_________ Fri Nov 4 11:50:32 2022 -0700 3 5 5 1 k_____ y Mon Nov 11 23:27:37 2019 +0900 3 3 3 3 S______ L________ Sat Jul 21 17:24:23 2012 +0200 3 3 3 2 A____ D_______ Sat Feb 15 10:49:14 2020 +0100 2 2 2 2 J_____ L_ Fri Aug 24 17:35:32 2018 +0000 1 1 1 1 D_____ H______ Wed Nov 29 01:26:22 1995 +0000 1 1 1 1 A______ S_____ Sat Sep 28 00:00:34 2013 +0200 1 1 1 1 A_____ R___ Mon Jun 14 21:54:40 2021 +0000 1 1 1 1 Code: #!/usr/bin/env txr @(do (defstruct author () name e-mail last-seen (files 0) (lines+ 0) (lines- 0) (commits 0)) (defvarl ah (hash))) @(repeat) Author: @name <@addr> Date: @date @(skip) @files file@nil changed, @ins insertion@nil, @del deletion@nil @ (set name @(flow name ;; anonymize name (spl " ") (map (op map (do if (plusp @2) #\_ @1) @1 0)) (join-with " "))) @ (do (let ((a (or [ah name] (new author name name e-mail addr last-seen date)))) (inc a.commits) (inc a.files (tointz files)) (inc a.lines+ (tointz ins)) (inc a.lines- (tointz del)) (set [ah name] a))) @(end) @(do (flow (hash-values ah) (csort @1 > [callf + .lines+ .lines-]) (cons (new author name "NAME" last-seen "LAST-SEEN" files "FILES" lines+ "LINES+" lines- "LINES-" commits "COMMITS")) (each ((a @1)) (put-line `@{a.name 24} @{a.last-seen 30} @{a.files -5} \ \ @{a.lines+ -8} @{a.lines- -8} @{a.commits -7}`))))
- LarsenCC 2y agoCool stuff!
- max23_ 2y agoThis is neat. I made a similar powershell script recently but reverse search from filename to find the authors by commits.
- avalys 2y agoI look forward to the inevitable upgraded version, “git whom”.
- camdotcom14 2y agowow that is actually hilarious! XD
- physicsguy 2y agoI like this one the best: https://github.com/jayphelps/git-blame-someone-else https://github.com/jayphelps/git-blame-someone-else
- p0w3n3d 2y agoThings I'm missing in git are not how many lines or commits given developer did, which might lead in a poorly managed organisation to strangely calculated KPIs, but rather: - who deleted this line (which one?) - who is owner of this method (some guy refactored it or reformatted, but who is the REAL owner, or what was the history of this method)
- jval43 2y agoYes, exactly. That is what I use git for each and every day.
- ginko 2y ago> - who is owner of this method (some guy refactored it or reformatted, but who is the REAL owner, or what was the history of this method) It doesn't work perfectly, but with magit you can jump to the revision before the refactor/reformat, then do another blame from there. I chased a line of code through several layers of refactors that way before and while the original author was long gone it did help explain why things were initially done that way. I heavily depend on git-blame to understand code. It's one reason why I generally dislike "cleanup" changes that just change formatting/naming for the sake of it.
- seanhunter 2y agoFor a low-tech version of this, I have long had an alias (which I call "nerdwars") to "git shortlog -ns --no-merges" which just gives the number of commits by contributor from most to least. It's a good way to get a sense for who the major contributors in a project are.
- m000 2y agoNumber of commits is not a very good metric to measure contributions. It would only work when there is an agreed style of commits and everyone sticks to that. Number of blame lines per contributor would be overall much more accurate, and immune to different styles of committing (e.g. squashed mega-commits vs rebased self-contained commits).
- account42 2y agoI don't think the comparison to git blame is needed/warranted. While the 'blame' in git blame suggests the tool is about identifying authors its main purpose is to identify commits so that you can find out the context of why something was changed. Instead this tool seems to be a fancy `git shortlog -sn`.
- patrickdevivo 2y agoThis is such a cool tool. It's a better approach to solving many of the questions I built MergeStat to answer (https://github.com/mergestat/mergestat-lite https://github.com/mergestat/mergestat-lite). It's been some time, but I also wrote a `git blame ...` parser in Go: https://github.com/mergestat/gitutils/blob/main/blame/blame.go https://github.com/mergestat/gitutils/blob/main/blame/blame.... :) Amazing work and excited to dig into this more thoroughly
- dspillett 2y agoOne thing to note, maybe particularly with reference to the “who wrote vim” analysis⁰, is that depending on workflow this can attribute more to internal contributors than you might assume by the results. If a patch or pull request is sent but an internal contributor (the only internal contributor in Vim's case for a long time) reformatted it before merging then the same work is double-counted (if full history is kept) or only attributed to the reformatter (if history is squashed during/before merge). This doesn't make the result wrong, of course, the tool is doing exactly what it says on the tin. But it does mean that, without reviewing the contribution process (current and past), you might need to be less definite¹, when stating a meaning derived from the result, because how the result is interpreted might not be quite right given the input data available. ---- [0] https://sinclairtarget.com/blog/2025/03/who-will-maintain-vim-a-demo-of-git-who/ https://sinclairtarget.com/blog/2025/03/who-will-maintain-vi... in case you skipped by the link when looking at git-who's readme. [1] perhaps just by giving caveats to make sure that the reader has sufficient context regarding the limit of the process
- nbenitezl 2y agoGitlab/Github should add a feature that any submitted merge requests automatically emails the last author of the code lines being modified, to let them know about the MR and provide any feedback if needed. Or maybe someone has wrote a bot/Git hook for that?
- eddd-ddde 2y agoBasically just an OWNERS file, but asynchronously?
- fouronnes3 2y agoFor a linux user, you can already build such a system yourself quite trivially with git blame directly, piping it through grep awk and git log to email yourself that list with a cron job. (crontab -l 2>/dev/null; echo '15 22 \* \* \* /usr/bin/git blame --line-porcelain abc123.. -- /path/to/file.txt | awk "/^author-mail/ {print \$2}" | sort -u | /usr/bin/mail -s "Authors" user@example.com') | crontab -
- chrishill89 2y agoThe Git project has a script for that: https://github.com/git/git/blob/master/contrib/contacts/git-contacts https://github.com/git/git/blob/master/contrib/contacts/git-...
- _andrei_ 2y agogo install github.com/sinclairtarget/git-who@latest