#!/bin/bash # # project_stats # # stats on project/release from git database # ### ### settings ### set -e # exit on any uncaught error set +o histexpand # don't expand history expressions shopt -s nocasematch # case-insensitive regular expressions ### ### configurable global variables ### # these paths relative to project root declare -a cask_paths=(Casks) declare -a code_paths=(bin developer lib test brew-cask.rb Rakefile Gemfile Gemfile.lock .travis.yml .gitignore) declare -a doc_paths=(doc LICENSE "*.md") end_object="HEAD" ### ### global variables ### cask_authors='' ### ### functions ### warn () { local message="$@" if ! [[ $message =~ "\n"$ ]]; then message="${message}\n" fi printf "$message" 1>&2 } die () { warn "$@" exit 1 } cd_to_project_root () { local script_dir="$(/usr/bin/dirname "$0")" cd "$script_dir" local git_root="$(git rev-parse --show-toplevel)" if [[ -z "$git_root" ]]; then die "ERROR: Could not find git project root" fi cd "$git_root" } warn_if_off_branch () { local wanted_branch='master' if [[ -n "$1" ]]; then wanted_branch="$1" fi local current_branch="$(git rev-parse --abbrev-ref HEAD)" if ! [[ "$current_branch" = "$wanted_branch" ]]; then warn "\nWARNING: you are running from branch '$current_branch', not '$wanted_branch'\n\n" fi } verify_git_object () { local object="$1" if ! git rev-parse --verify "$object" -- >/dev/null 2>&1; then die "\nERROR: No such commit object: '$object'\n\n" fi } print_contributor_stats () { local start_object="$1" local initial_commit="$2" printf "====================\n" printf "Contributors\n" printf "====================\n" local git_log_cmd="git log --no-merges --format='%ae' ${start_object}..${end_object}" printf "Unique contributors" if ! [[ "$start_object" = "$initial_commit" ]]; then printf " since ${start_object#v}" fi printf "\n" cask_authors="$($git_log_cmd -- "${cask_paths[@]}" | /usr/bin/sort | /usr/bin/uniq | /usr/bin/wc -l)" printf " Casks\t$cask_authors\n" printf " code\t" $git_log_cmd -- "${code_paths[@]}" | /usr/bin/sort | /usr/bin/uniq | /usr/bin/wc -l printf " docs\t" $git_log_cmd -- "${doc_paths[@]}" | /usr/bin/sort | /usr/bin/uniq | /usr/bin/wc -l printf " any\t" $git_log_cmd -- . | /usr/bin/sort | /usr/bin/uniq | /usr/bin/wc -l if ! [[ "$start_object" = "$initial_commit" ]]; then local alltime_contribs="$(git log --no-merges --format='%ae' ${initial_commit}..${end_object} -- . | /usr/bin/sort | /usr/bin/uniq | /usr/bin/wc -l)" local prior_contribs="$(git log --no-merges --format='%ae' ${initial_commit}..${start_object} -- . | /usr/bin/sort | /usr/bin/uniq | /usr/bin/wc -l)" # arithmetic removes whitespace ((alltime_contribs += 0)) ((new_contribs = alltime_contribs - prior_contribs)) printf "\nAll-time contributors\t$alltime_contribs\n" printf "New contributors since ${start_object#v}\t$new_contribs\n" fi printf "\n" } print_commit_stats () { local start_object="$1" local initial_commit="$2" printf "====================\n" printf "Commits\n" printf "====================\n" local git_log_cmd="git log --no-merges --format='%ae' ${start_object}..${end_object}" printf "Commit count" if ! [[ "$start_object" = "$initial_commit" ]]; then printf " since ${start_object#v}" fi printf "\n" printf " Casks\t" $git_log_cmd -- "${cask_paths[@]}" | /usr/bin/wc -l printf " code\t" $git_log_cmd -- "${code_paths[@]}" | /usr/bin/wc -l printf " docs\t" $git_log_cmd -- "${doc_paths[@]}" | /usr/bin/wc -l printf " any\t" $git_log_cmd -- . | /usr/bin/wc -l if ! [[ "$start_object" = "$initial_commit" ]]; then printf "\nAll-time commits\t" git log --no-merges --format='%ae' "${initial_commit}".."${end_object}" -- . | /usr/bin/wc -l fi printf "\n" } print_doc_stats () { local start_object="$1" local initial_commit="$2" printf "====================\n" printf "Docs\n" printf "====================\n" local git_log_cmd="git log --no-merges --format='%ae' ${start_object}..${end_object}" printf "Doc contributors" if ! [[ "$start_object" = "$initial_commit" ]]; then printf " since ${start_object#v}" fi printf "\n " $git_log_cmd -- "${doc_paths[@]}" | /usr/bin/sort | /usr/bin/uniq | \ /usr/bin/egrep -v $'^\'(paul\\.t\\.hinze@gmail\\.com|fanquake@users\\.noreply\\.github\\.com|fanquake@gmail\\.com|kevin@suttle\\.io|leoj3n@gmail\\.com|nano@fdp\\.io|nanoid\\.xd@gmail\\.com|me@passcod\\.name|walker@pobox\\.com|info@vitorgalvao\\.com)\'$' | \ /usr/bin/xargs | /usr/bin/perl -pe 's{ }{, }g' # ' printf "\n" } print_cask_stats () { local start_object="$1" local initial_commit="$2" printf "====================\n" printf "Casks\n" printf "====================\n" if ! [[ "$start_object" = "$initial_commit" ]]; then local new_casks="$(git diff --name-status "$start_object" "$end_object" -- "${cask_paths[@]}" | /usr/bin/grep '^A.*\.rb' | cut -f2 | /usr/bin/sort | /usr/bin/uniq | /usr/bin/wc -l)" local deleted_casks="$(git diff --name-status "$start_object" "$end_object" -- "${cask_paths[@]}" | /usr/bin/grep '^D.*\.rb' | cut -f2 | /usr/bin/sort | /usr/bin/uniq | /usr/bin/wc -l)" local updated_casks="$(git diff --name-status "$start_object" "$end_object" -- "${cask_paths[@]}" | /usr/bin/grep '^M.*\.rb' | cut -f2 | /usr/bin/sort | /usr/bin/uniq | /usr/bin/wc -l)" # arithmetic removes whitespace ((cask_authors += 0)) ((deleted_casks += 0)) ((new_casks -= deleted_casks)) ((updated_casks += 0)) printf "$new_casks Casks added ($updated_casks updated) by $cask_authors contributors since ${start_object#v}\n" fi printf "Total current Casks in HEAD\t" /usr/bin/find "${cask_paths[@]}" -name '*.rb' | /usr/bin/wc -l printf "\n" } ### ### main ### _project_stats () { local arg_object="$1" cd_to_project_root warn_if_off_branch 'master' local initial_commit="$(git log --pretty=format:%H -- | /usr/bin/tail -1)" verify_git_object "$initial_commit" local start_object="$initial_commit" if [[ "$arg_object" = 'release' ]]; then start_object="$(./developer/bin/get_release_tag)" elif [[ -n "$arg_object" ]]; then start_object="$arg_object" fi verify_git_object "$start_object" print_contributor_stats "$start_object" "$initial_commit" print_commit_stats "$start_object" "$initial_commit" print_doc_stats "$start_object" "$initial_commit" print_cask_stats "$start_object" "$initial_commit" } # process args if [[ $1 =~ ^-+h(elp)?$ ]]; then printf "project_stats [ ] With optional single argument, (eg a tag or commit-hash) show statistics since that commit object. Use the special argument 'release' to calculate since the most recent tag (usually the same as the last release). Without argument, show statistics since first commit. " exit fi # dispatch main _project_stats "${@}" #