├── .gitignore ├── LICENSE-MIT ├── Makefile ├── README.md ├── bin └── git-cloc.sh ├── install.sh ├── man ├── git-cloc-man.md └── git-cloc.1 └── uninstall.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # cortex 2 | .cortex/build/ 3 | .cortex/latest-pack 4 | 5 | # PHP files 6 | index.php 7 | 8 | # Numerous always-ignore extensions 9 | *.bak 10 | *.patch 11 | *.diff 12 | *.err 13 | *.orig # temp file for git conflict merging 14 | *.log 15 | *.rej 16 | *.swo 17 | *.swp 18 | *.zip 19 | *.vi 20 | *~ 21 | *.sass-cache 22 | *.tmp.html 23 | 24 | # OS or Editor folders 25 | .DS_Store 26 | ._* 27 | .cache 28 | .project 29 | .settings 30 | .tmproj 31 | *.esproj 32 | *.sublime-project 33 | *.sublime-workspace 34 | nbproject 35 | thumbs.db 36 | 37 | # Folders to ignore 38 | .hg 39 | .svn 40 | .CVS 41 | .idea 42 | node_modules 43 | build/ 44 | combo/ 45 | reference/ 46 | jscoverage_lib/ 47 | temp/ 48 | *-notrack/ 49 | 50 | # Filter old Folders 51 | unit-test/ 52 | old/ 53 | *-old/ 54 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Kael Zhang , contributors 2 | http://kael.me/ 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PREFIX ?= /usr/local 2 | MANPREFIX ?= "$(PREFIX)/share/man/man1" 3 | 4 | install: 5 | @mkdir -p $(DESTDIR)$(MANPREFIX) 6 | @mkdir -p $(DESTDIR)$(PREFIX)/bin 7 | 8 | @echo "... installing bins to $(DESTDIR)$(PREFIX)/bin" 9 | cp -f bin/git-cloc.sh $(DESTDIR)$(PREFIX)/bin/git-cloc 10 | @chmod 755 $(DESTDIR)$(PREFIX)/bin/git-cloc 11 | 12 | @echo "... installing man pages to $(DESTDIR)$(MANPREFIX)" 13 | cp -f man/git-cloc.1 $(DESTDIR)$(MANPREFIX) 14 | 15 | 16 | uninstall: 17 | @echo "... uninstalling bins" 18 | rm -f $(DESTDIR)$(PREFIX)/bin/git-cloc 19 | 20 | @echo "... uninstalling man pages" 21 | rm -f $(DESTDIR)$(MANPREFIX)/git-cloc.1 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | _______ ___ _______ _______ ___ _______ _______ 2 | | || | | | | || | | || | 3 | | ___|| | |_ _| | || | | _ || | 4 | | | __ | | | | | || | | | | || | 5 | | || || | | | | _|| |___ | |_| || _| 6 | | |_| || | | | | |_ | || || |_ 7 | |_______||___| |___| |_______||_______||_______||_______| 8 | 9 | 10 | Git-cloc 11 | ==== 12 | Count lines of code for GIT 13 | 14 | 15 | Examples 16 | ==== 17 | 18 | Count lines within a specified period inside a git repo 19 | 20 | git cloc --after 2013-02-01 --before 2013-03-01 21 | 22 | Search all git repos within the current directory 23 | 24 | git cloc -r 25 | 26 | Show git-cloc manual for more details 27 | 28 | git cloc --help 29 | 30 | # or 31 | git help cloc 32 | 33 | Install 34 | ==== 35 | 36 | # maybe you need a super user permission to do this 37 | make install 38 | 39 | Uninstall 40 | ==== 41 | make uninstall 42 | -------------------------------------------------------------------------------- /bin/git-cloc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # flags 4 | debug= 5 | 6 | # tools 7 | debug(){ 8 | if [ -n "$debug" ]; then 9 | echo "$@" >&2 10 | fi 11 | } 12 | 13 | #overload arguments 14 | after= 15 | before= 16 | author= 17 | # branch=master 18 | recursive= 19 | recurse_depth=10 20 | no_summary= 21 | output= 22 | month= 23 | cwd="$PWD" 24 | 25 | Y=`date +%Y` 26 | M=`date +%m` 27 | D=`date +%d` 28 | h=`date +%H` 29 | m=`date +%M` 30 | s=`date +%S` 31 | TIME="$Y-$M-$D $h.$m.$s" 32 | 33 | cloc_counter=0 34 | print_result=() 35 | print_result[$cloc_counter]="Repos Changed-Files Insertions Deletions\n" 36 | 37 | add_print(){ 38 | (( cloc_counter += 1 )) 39 | print_result[$cloc_counter]="$1\n" 40 | } 41 | 42 | 43 | while [[ $# -gt 0 ]]; do 44 | opt="$1" 45 | shift 46 | case "$opt" in 47 | --no-summary) 48 | no_summary=1; debug "no summary: $no_summary" 49 | ;; 50 | 51 | --since|--after) 52 | after="$1"; debug "after: $after" 53 | shift 54 | ;; 55 | 56 | --until|--before) 57 | before="$1"; debug "before: $before" 58 | shift 59 | ;; 60 | 61 | # TODO 62 | -b|--branch) 63 | branch="$1"; debug "branch: $branch" 64 | shift 65 | ;; 66 | 67 | -r|--recursive) 68 | recursive=1; debug "recursive: on" 69 | # no shift 70 | ;; 71 | 72 | --month) 73 | month="$1"; debug "month: $month" 74 | shift 75 | ;; 76 | 77 | --recurse-depth) 78 | recurse_depth="$1"; debug "recurse depth: $recurse_depth" 79 | shift 80 | ;; 81 | 82 | -o|--output) 83 | output="$1"; debug "output file: $output" 84 | shift 85 | ;; 86 | 87 | -c|--cwd) 88 | cwd="$1"; debug "cwd: $cwd" 89 | shift 90 | ;; 91 | 92 | --) 93 | break 94 | ;; 95 | 96 | *) 97 | echo "Unexpected option: $opt" 98 | exit 1 99 | ;; 100 | esac 101 | done 102 | 103 | # generate git log query 104 | log_query="git log" 105 | 106 | if [[ -n "$month" ]]; then 107 | single_year=`echo $month | cut -d '-' -f1` 108 | single_month=`echo $month | cut -d '-' -f2` 109 | after="$month-1" 110 | before="$single_year-`expr $single_month + 1`-1" 111 | fi 112 | 113 | if [[ -n "$author" ]]; then 114 | log_query+=" --author $author" 115 | fi 116 | 117 | if [[ -n "$after" ]]; then 118 | log_query+=" --after $after" 119 | fi 120 | 121 | if [[ -n "$before" ]]; then 122 | log_query+=" --before $before" 123 | fi 124 | 125 | if [[ -n "$branch" ]]; then 126 | : # log_query=`echo "$log_query --branches $branch"` 127 | fi 128 | 129 | debug "git log query: $log_query" 130 | 131 | 132 | # directory walker 133 | # @private 134 | # @param {string} $1 directory 135 | # @param {int} $2 depth 136 | git_repos(){ 137 | # debug "seaching git repos in: $1" 138 | 139 | local current_depth="$2" 140 | local sub_depth= 141 | 142 | # or `expr` will throw a syntax error 143 | if [[ ! -n "$current_depth" ]]; then 144 | current_depth=0 145 | fi 146 | 147 | for file in $1/* 148 | do 149 | if [[ -d "$file" ]]; then 150 | if [[ -d "$file/.git" ]]; then 151 | # debug "git repo found: $file" 152 | cloc $file 153 | else 154 | if [[ "$current_depth" -gt "$recurse_depth" ]]; then 155 | continue 156 | fi 157 | 158 | sub_depth=`expr $current_depth + 1` 159 | git_repos $file $sub_depth 160 | fi 161 | fi 162 | done 163 | } 164 | 165 | 166 | # main function 167 | cloc(){ 168 | cd $1 169 | 170 | local last_commit=$($log_query --pretty=format:'%H' -1) 171 | 172 | # use `echo` to convert the stdout into a single line 173 | # cut the first part 174 | local first_commit=`echo $($log_query --pretty=format:'%H' --reverse) | cut -d ' ' -f1` 175 | local diff_result= 176 | local repo=`basename $1` 177 | local print_line="$repo" 178 | 179 | local info= 180 | local info_len= 181 | 182 | local slice= 183 | local slice_i= 184 | local slice_i_plus_one= 185 | 186 | # debug "first commit: $first_commit" 187 | # debug "last commit: $last_commit" 188 | 189 | # test if `first_commit` is already the earlist commit 190 | # direct both stdout and stderr to NULL 191 | if git diff "$first_commit^1" "$first_commit" --shortstat &> /dev/null; then 192 | first_commit="$first_commit^1" 193 | fi 194 | 195 | if [[ -n "$last_commit" && -n "$first_commit" ]]; then 196 | diff_result=`git diff "$first_commit" "$last_commit" --shortstat` 197 | 198 | if [[ -n "$diff_result" ]]; then 199 | 200 | info=( $diff_result ) 201 | info_len=${#info[@]} 202 | 203 | slice_i=0 204 | while [[ $slice_i -lt $info_len ]]; do 205 | 206 | slice=${info[$slice_i]} 207 | slice_i_plus_one=`expr $slice_i + 1` 208 | (( slice_i += 1 )) 209 | 210 | if [[ $slice_i_plus_one -ge $info_len ]]; then 211 | continue 212 | fi 213 | 214 | case ${info[$slice_i_plus_one]} in 215 | 216 | # file or files 217 | file* ) 218 | print_line="$print_line $slice"; debug "files add $slice" 219 | (( slice_i += 1 )) 220 | ;; 221 | 222 | # insertions 223 | insertion* ) 224 | print_line="$print_line $slice"; debug "insertions add $slice" 225 | (( slice_i += 1 )) 226 | ;; 227 | 228 | # deletions 229 | deletion* ) 230 | print_line="$print_line $slice"; debug "deletions add $slice" 231 | (( slice_i += 1 )) 232 | ;; 233 | 234 | * ) 235 | ;; 236 | esac 237 | done # end while slice_i 238 | fi 239 | 240 | else 241 | print_line="$print_line 0 0 0" 242 | fi 243 | 244 | add_print "$print_line" 245 | } 246 | 247 | 248 | # print result 249 | summary(){ 250 | echo -e ${print_result[@]} | awk '{printf "%-30s %-15s %-12s %-11s\n",$1,$2,$3,$4}' 251 | 252 | if [[ -n $output ]]; then 253 | echo -e ${print_result[@]} | awk '{printf "%-30s %-15s %-12s %-11s\n",$1,$2,$3,$4}' > "$output" 254 | fi 255 | 256 | if [[ ! -n $no_summary ]]; then 257 | # local repos=${#print_result[@]} 258 | local repos=`echo -e ${print_result[@]} | awk 'NR!=1{a+=1;} END {print a}'` 259 | local changed_files=`echo -e ${print_result[@]} | awk 'NR!=1{a+=$2;} END {print a}'` 260 | local insertions=`echo -e ${print_result[@]} | awk 'NR!=1{a+=$3;} END {print a}'` 261 | local deletions=`echo -e ${print_result[@]} | awk 'NR!=1{a+=$4;} END {print a}'` 262 | 263 | echo "Summary: ----------------------------" 264 | echo "Repos : $repos" 265 | echo "Changed files: $changed_files" 266 | echo "Insertions : $insertions" 267 | echo "Deletions : $deletions" 268 | fi 269 | } 270 | 271 | 272 | if [[ -n "$recursive" ]]; then 273 | git_repos $cwd 274 | else 275 | if [[ -d "$cwd/.git" ]]; then 276 | cloc $cwd 277 | else 278 | # TODO: 279 | # support sub directories of a git repo 280 | # (or any of the parent directories) 281 | echo "fatal: Not a git repository: .git" 282 | echo "Use '-r' option, if you wanna recursively search all git repos" 283 | exit 1 284 | fi 285 | fi 286 | 287 | summary 288 | 289 | exit 0 290 | 291 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | PREFIX="/usr/local" 2 | MANPREFIX="$PREFIX/share/man/man1" 3 | 4 | mkdir -p $MANPREFIX 5 | mkdir -p $PREFIX/bin 6 | 7 | echo "... installing bins to $PREFIX/bin" 8 | cp -f bin/git-cloc.sh $DESTDIR$PREFIX/bin 9 | chmod 755 $PREFIX/bin/git-cloc 10 | 11 | echo "... installing man pages to $MANPREFIX" 12 | cp -f man/git-cloc.1 $MANPREFIX -------------------------------------------------------------------------------- /man/git-cloc-man.md: -------------------------------------------------------------------------------- 1 | git-cloc(1) 2 | =========== 3 | 4 | NAME 5 | ---- 6 | 7 | git-cloc - Count lines for code for git 8 | 9 | SYNOPSIS 10 | -------- 11 | 12 | git-cloc [] 13 | 14 | DESCRIPTION 15 | ----------- 16 | 17 | Git-cloc helps to count the file changes, insertions and deletions of a specified git repo. 18 | 19 | You could also use it to traverse all git repos inside the current or an assigned directory and generate a summary. 20 | 21 | OPTIONS 22 | ------- 23 | --since , --after 24 | The beginning date, "2012-01-01" 25 | 26 | --until , --before 27 | The end date, "2012-02-01" 28 | 29 | -c , --cwd 30 | Specify the current working directory 31 | 32 | -r, --recursive 33 | Use this option, git-cloc will walk through the current working directory to find all git repos and analysis them 34 | 35 | --recurse-depth 36 | The maximun depth git-cloc will traverse a directory into. Default to 10 37 | 38 | --month 39 | The month which will limit the counting, "2013-03" 40 | 41 | -o, --output 42 | The file path where standard output will redirect and write into 43 | -------------------------------------------------------------------------------- /man/git-cloc.1: -------------------------------------------------------------------------------- 1 | .TH "GIT\-CLOC\-MAN" "" "April 2013" "" "" 2 | . 3 | .SH "NAME" 4 | git\-cloc \- Count lines for code for git 5 | . 6 | .SH "SYNOPSIS" 7 | git\-cloc [\fIoptions\fR] 8 | . 9 | .SH "DESCRIPTION" 10 | Git\-cloc helps to count the file changes, insertions and deletions of a specified git repo\. 11 | . 12 | .P 13 | You could also use it to traverse all git repos inside the current or an assigned directory and generate a summary\. 14 | . 15 | .SH "OPTIONS" 16 | \-\-since \fIdate\fR, \-\-after \fIdate\fR 17 | . 18 | .IP "" 4 19 | . 20 | .nf 21 | 22 | The beginning date, "2012\-01\-01" 23 | . 24 | .fi 25 | . 26 | .IP "" 0 27 | . 28 | .P 29 | \-\-until \fIdate\fR, \-\-before \fIdate\fR 30 | . 31 | .IP "" 4 32 | . 33 | .nf 34 | 35 | The end date, "2012\-02\-01" 36 | . 37 | .fi 38 | . 39 | .IP "" 0 40 | . 41 | .P 42 | \-c \fIpath\fR, \-\-cwd \fIpath\fR 43 | . 44 | .IP "" 4 45 | . 46 | .nf 47 | 48 | Specify the current working directory 49 | . 50 | .fi 51 | . 52 | .IP "" 0 53 | . 54 | .P 55 | \-r, \-\-recursive 56 | . 57 | .IP "" 4 58 | . 59 | .nf 60 | 61 | Use this option, git\-cloc will walk through the current working directory to find all git repos and analysis them 62 | . 63 | .fi 64 | . 65 | .IP "" 0 66 | . 67 | .P 68 | \-\-recurse\-depth 69 | . 70 | .IP "" 4 71 | . 72 | .nf 73 | 74 | The maximun depth git\-cloc will traverse a directory into\. Default to 10 75 | . 76 | .fi 77 | . 78 | .IP "" 0 79 | . 80 | .P 81 | \-\-month \fIY\-M\fR 82 | . 83 | .IP "" 4 84 | . 85 | .nf 86 | 87 | The month which will limit the counting, "2013\-03" 88 | . 89 | .fi 90 | . 91 | .IP "" 0 92 | . 93 | .P 94 | \-o, \-\-output 95 | . 96 | .IP "" 4 97 | . 98 | .nf 99 | 100 | The file path where standard output will redirect and write into 101 | . 102 | .fi 103 | . 104 | .IP "" 0 105 | 106 | -------------------------------------------------------------------------------- /uninstall.sh: -------------------------------------------------------------------------------- 1 | PREFIX="/usr/local" 2 | MANPREFIX="$PREFIX/share/man/man1" 3 | 4 | mkdir -p $MANPREFIX 5 | mkdir -p $PREFIX/bin 6 | 7 | echo "... uninstalling bins" 8 | rm -f $PREFIX/bin/git-cloc 9 | 10 | echo "... uninstalling man pages" 11 | rm -f $MANPREFIX/git-cloc.1 --------------------------------------------------------------------------------