40

I want to see the number of removed/added line, grouped by author for a given branch in Git history. There is git shortlog -s which shows me the number of commits per author. Is there anything similar to get an overall diffstat?

knittl
  • 246,190
  • 53
  • 318
  • 364
  • 1
    Would http://stackoverflow.com/questions/1265040/how-to-count-total-lines-changed-by-a-specific-author-in-a-git-repository help? As in `git shortlog abranch --numbered --summary` – VonC May 07 '10 at 08:42
  • 2
    @VonC, i `git shortlog --numbered --summary` is the same as `git shortlog -s -n`, it will only show the number of commits, not the changed lines – knittl May 07 '10 at 08:53
  • I use 'git shortlog -sn' instead of 'git shortlog -s' --- it allows me to sort authors by counts amount – Eugene Kaurov Sep 12 '18 at 10:38

6 Answers6

56

It's an old post but if someone is still looking for it:

install git extras

brew install git-extras

then

git summary --line

https://github.com/tj/git-extras

Sebastien Horin
  • 10,803
  • 4
  • 52
  • 54
  • 7
    `apt-get install git-extras` for Linux users – alex Jul 26 '16 at 09:51
  • 2
    `fatal: unrecognized argument: --line` I think they've removed the option in the newest release – Maghoumi Jul 29 '16 at 21:07
  • 1
    @M2X, it looks like that `git line-summary` works, though it is said in the docs, that its deprecated in favor of `--line` https://github.com/tj/git-extras/blob/master/Commands.md#git-line-summary – dav Aug 05 '16 at 17:15
  • I liked the output of this tool. Nice one. – janeshs Dec 08 '16 at 08:43
  • @alex you for people whose distribution uses apt to manage packets... :) – Bacon Mar 05 '17 at 15:10
  • Is there a way to make the line version of the command show only changes from a certain commit onward? The help shows it allows only without `--line`. – Sakari Cajanus Jun 17 '22 at 08:29
41

one line code(support time range selection):

git log --since=4.weeks --numstat --pretty="%ae %H" | sed 's/@.*//g' | awk '{ if (NF == 1){ name = $1}; if(NF == 3) {plus[name] += $1; minus[name] += $2}} END { for (name in plus) {print name": +"plus[name]" -"minus[name]}}' | sort -k2 -gr

explain:

git log --since=4.weeks --numstat --pretty="%ae %H" \
    | sed 's/@.*//g'  \
    | awk '{ if (NF == 1){ name = $1}; if(NF == 3) {plus[name] += $1; minus[name] += $2}} END { for (name in plus) {print name": +"plus[name]" -"minus[name]}}' \
    | sort -k2 -gr

# query log by time range
# get author email prefix
# count plus / minus lines
# sort result

output:

user-a: +5455 -3471
user-b: +5118 -1934

Update: maybe somebody will like my little script: https://github.com/alswl/.oOo./blob/master/local/bin/git-code-numbers-by-authors

alswl
  • 1,265
  • 11
  • 11
19

Since the SO question "How to count total lines changed by a specific author in a Git repository?" is not completely satisfactory, commandlinefu has alternatives (albeit not per branch):

git ls-files | while read i; do git blame $i | sed -e 's/^[^(]*(//' -e 's/^\([^[:digit:]]*\)[[:space:]]\+[[:digit:]].*/\1/'; done | sort | uniq -ic | sort -nr

It includes binary files, which is not good, so you could (to remove really random binary files):

git ls-files | grep -v "\.\(pdf\|psd\|tif\)$"

(Note: as commented by trcarden, a -x or --exclude option wouldn't work.
From git ls-files man page, git ls-files -x "*pdf" ... would only excluded untracked content, if --others or --ignored were added to the git ls-files command.)

Or:

git ls-files "*.py" "*.html" "*.css" 

to only include specific file types.


Still, a "git log"-based solution should be better, like:

git log --numstat --pretty="%H" --author="Your Name" commit1..commit2 | awk 'NF==3 {plus+=$1; minus+=$2} END {printf("+%d, -%d\n", plus, minus)}'

but again, this is for one path (here 2 commits), not for all branches per branches.

Community
  • 1
  • 1
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • 1
    git log is the only thing that doesn't barf for me, nice suggestion! – jjxtra Aug 21 '12 at 22:42
  • You actually can't ignore binary files via the method specified. the -x command on ls-files is only available for "untracked files" Common error. – trcarden Feb 12 '14 at 09:30
  • @trcarden Very good point. I have edited the answer and proposed an alternative way of excluding binaries. – VonC Feb 12 '14 at 09:51
3

This script here will do it. Put it into authorship.sh, chmod +x it, and you're all set.

#!/bin/sh
declare -A map
while read line; do
    if grep "^[a-zA-Z]" <<< "$line" > /dev/null; then
        current="$line"
        if [ -z "${map[$current]}" ]; then 
            map[$current]=0
        fi
    elif grep "^[0-9]" <<<"$line" >/dev/null; then
        for i in $(cut -f 1,2 <<< "$line"); do
            map[$current]=$((map[$current] + $i))
        done
    fi
done <<< "$(git log --numstat --pretty="%aN")"

for i in "${!map[@]}"; do
    echo -e "$i:${map[$i]}"
done | sort -nr -t ":" -k 2 | column -t -s ":"
  • 3
    Getting this on both Mac OS X 10.6.8 and Debian Linux 5.0.8: `/Users/slippyd/Desktop/git-authorship: line 3: declare: -A: invalid option declare: usage: declare [-afFirtx] [-p] [name[=value] ...]` – Slipp D. Thompson Jan 27 '12 at 17:10
  • getting same error as slipp on a mac, i changed the -A to -a (as specified in the error message), but the script fails anyway, apparently it does't handle spaces in names (like the space between the first and last name), still looking of a solution that works, you might think that this is something many people need, to get promotion :), apparently not. well, i will just say 90% boss ! – Pizzaiola Gorgonzola Sep 28 '13 at 00:03
  • Use `#!/bin/bash` instead of `#!/bin/sh` if it complains about that declare error. – Donatas Olsevičius Oct 29 '13 at 08:32
  • @DonatasOlsevičius `bash` still doesnt help on mac, and I am getting same problem as Pizzaiola, I think, space related errors – Karthik T Oct 30 '13 at 01:09
  • I have no idea what's the problem with mac. Maybe there's no bash and it uses a different shell instead? – Donatas Olsevičius Oct 31 '13 at 06:47
  • This script requires bash 4.0, which I don't think mac has. It should really run under /bin/bash, because on debian /bin/sh is linked to a lightweight posix-compliant shell without bash-specific extensions. – Score_Under Aug 27 '14 at 12:37
2

On my repos I've gotten a lot of trash output from the one-liners floating around, so here is a Python script to do it right:

import subprocess
import collections
import sys


def get_lines_from_call(command):
    return subprocess.check_output(command).splitlines()

def get_files(paths=()):
    command = ['git', 'ls-files']
    command.extend(paths)
    return get_lines_from_call(command)

def get_blame(path):
    return get_lines_from_call(['git', 'blame', path])


def extract_name(line):
    """
    Extract the author from a line of a standard git blame
    """
    return line.split('(', 1)[1].split(')', 1)[0].rsplit(None, 4)[0]


def get_file_authors(path):
    return [extract_name(line) for line in get_blame(path)]


def blame_stats(paths=()):
    counter = collections.Counter()
    for filename in get_files(paths):
        counter.update(get_file_authors(filename))
    return counter


def main():
    counter = blame_stats(sys.argv[1:])
    max_width = len(str(counter.most_common(1)[0][1]))
    for name, count in reversed(counter.most_common()):
        print('%s %s' % (str(count).rjust(max_width), name))

if __name__ == '__main__':
    main()

Note that the arguments to the script will be passed to git ls-files, so if you only want to show Python files: blame_stats.py '**/*.py'

If you only want to show files in one subdirectory:blame_stats.py some_dir

And so on.

pydsigner
  • 2,779
  • 1
  • 20
  • 33
0

From How to count total lines changed by a specific author in a Git repository?

The output of the following command should be reasonably easy to send to script to add up the totals:

git log --author="<authorname>" --oneline --shortstat

This gives stats for all commits on the current HEAD. If you want to add up stats in other branches you will have to supply them as arguments to git log.

Community
  • 1
  • 1
Nicolas
  • 359
  • 1
  • 4
  • 13