Skip to content

Instantly share code, notes, and snippets.

@wagenet
Created April 21, 2011 17:46
Show Gist options
  • Save wagenet/935079 to your computer and use it in GitHub Desktop.
Save wagenet/935079 to your computer and use it in GitHub Desktop.
Git Lines of Code
#!/usr/bin/env ruby
# encoding: UTF-8
require 'optparse'
class File
def self.binary?(name)
fstat = stat(name)
return false unless fstat.file?
open(name) do |file|
blk = file.read(fstat.blksize)
return blk && (
blk.size == 0 ||
blk.count("^ -~", "^\r\n") / blk.size > 0.3 ||
blk.count("\x00") > 0
)
end
end
end
options = {}
OptionParser.new do |opts|
opts.banner = "Usage: git-rank-contributors [path] [options]"
opts.on("--file-types [TYPES]", "Comma separated list of file types") do |v|
options[:file_types] = v.split(",")
end
opts.on("--count-blank", "Count blank lines") do |v|
options[:count_blank] = v
end
opts.on("--count-comments", "Count comments") do |v|
options[:count_comments] = v
end
opts.on("--debug", "Show debugging messages") do |v|
options[:debug] = v
end
opts.on("--ignore [REGEXP]", "RegExp of files to ignore") do |v|
options[:ignore] = Regexp.new(v)
end
end.parse!
dir = ARGV[0] || "."
COMMENTS = {
'.js' => { :oneline => %r{^(//.*|.\*.*\*/)$},
:start => %r{^/\*},
:end => %r{\*/} },
'.css' => { :oneline => %r{^/\*.*\*/$},
:start => %r{^/\*},
:end => %r{\*/} }
}
cols = `tput cols`
cols = (cols =~ /^\d+$/) ? cols.to_i : 75
files = `git ls-files #{dir}`.split("\n").map(&:strip)
files.reject!{|f| File.directory?(f) } # submodules
files.reject!{|f| !options[:file_types].include?(File.extname(f)[1..-1]) } if options[:file_types]
files.reject!{|f| f =~ options[:ignore] } if options[:ignore]
files.reject!{|f| File.binary?(f) }
puts files.join("\n") if options[:debug]
contributors = {}
total_lines = 0
puts "Processing..."
files.each do |file|
file_disp = file.length < cols-3 ? file : file[0..(cols-3)]+"..."
print "#{file_disp.ljust(cols)}\r"
$stdout.flush
ext = File.extname(file)
file_lines = 0
is_comment = false
File.read(file).each do |l|
l.strip!
next if l.empty? && !options[:count_blank]
if !options[:count_comments] && regexp = COMMENTS[ext]
if is_comment
is_comment = false if l =~ regexp[:end]
next
else
next if l =~ regexp[:oneline]
if l =~ regexp[:start] && l !~ regexp[:end]
is_comment = true
next
end
end
end
file_lines += 1
end
total_lines += file_lines
commits = {}
is_comment = false
next_is_comment = false
enum = `git blame -pw #{file}`.each_line
loop do
begin
l = enum.next.strip
rescue StopIteration
break
end
if l =~ /^([a-f0-9]{40}) \d+ \d+( \d+)?$/
last_commit = $1
l = enum.next.strip
if l =~ /^author (.+)$/
commits[last_commit] = { :author => $1, :lines => 0 }
while l = enum.next
if l.strip =~ /^filename/
l = enum.next.strip
break
end
end
end
if !options[:count_comments] && regexp = COMMENTS[ext]
if is_comment
next_is_comment = false if l =~ regexp[:end]
else
if l =~ regexp[:oneline]
is_comment = true
next_is_comment = false
elsif l =~ regexp[:start] && l !~ regexp[:end]
is_comment = next_is_comment = true
end
end
end
if (options[:count_blank] || !l.empty?) && (options[:count_comments] || !is_comment)
puts l if options[:debug]
commits[last_commit][:lines] += 1
end
is_comment = next_is_comment
end
end
commits.values.each do |c|
contributors[c[:author]] ||= 0
contributors[c[:author]] += c[:lines]
end
end
puts " " * cols
# reverse sort by lines
contributors.sort{|a,b| b[1] <=> a[1] }.each do |name, lines|
puts "#{name[0..30].ljust(32)}: #{lines}"
end
puts "\n"
puts "#{"Total Lines".ljust(32)}: #{total_lines}"
puts "#{"Total Processed".ljust(32)}: #{contributors.values.inject{|sum,x| sum + x}}"
@kadishmal
Copy link

I've tried to run the above code on Mac OS X 10.8.3 on a local git repository and got the following error message:

$ ../git_lines_of_code.rb 
../git_lines_of_code.rb:9:in `stat': No such file or directory - static/js/app/permissions/PermissionsMainViewTemplate.js (Errno::ENOENT)
from ../git_lines_of_code.rb:9:in `binary?'
from ../git_lines_of_code.rb:68
from ../git_lines_of_code.rb:68:in `reject!'
from ../git_lines_of_code.rb:68

Any ideas how to fix it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment