renamed hash to sha && refactor

This commit is contained in:
Tomasz Gieniusz 2012-10-20 22:38:11 +02:00
parent 6d45e3fd57
commit 35d03d9392
17 changed files with 116 additions and 81 deletions

View file

@ -5,14 +5,14 @@ module GitStats
class Blob class Blob
include HashInitializable include HashInitializable
attr_reader :repo, :hash, :filename attr_reader :repo, :sha, :filename
def lines_count def lines_count
@lines_count ||= binary? ? 0 : repo.run("git cat-file blob #{self.hash} | wc -l").to_i @lines_count ||= binary? ? 0 : repo.run("git cat-file blob #{self.sha} | wc -l").to_i
end end
def content def content
@content ||= repo.run("git cat-file blob #{self.hash}") @content ||= repo.run("git cat-file blob #{self.sha}")
end end
def extension def extension
@ -20,15 +20,15 @@ module GitStats
end end
def binary? def binary?
repo.run("git cat-file blob #{self.hash} | grep -m 1 '^'") =~ /Binary file/ repo.run("git cat-file blob #{self.sha} | grep -m 1 '^'") =~ /Binary file/
end end
def to_s def to_s
"#{self.class} #@hash #@filename" "#{self.class} #@sha #@filename"
end end
def ==(other) def ==(other)
[self.repo, self.hash, self.filename] == [other.repo, other.hash, other.filename] [self.repo, self.sha, self.filename] == [other.repo, other.sha, other.filename]
end end
end end

View file

@ -15,15 +15,15 @@ module GitStats
def parse_ls_tree(result, params) def parse_ls_tree(result, params)
result.lines.map do |line| result.lines.map do |line|
mode, type, hash, filename = line.scan(/(.*) (.*) (.*)\t(.*)/).first.map(&:strip) mode, type, sha, filename = line.scan(/(.*) (.*) (.*)\t(.*)/).first.map(&:strip)
{mode: mode, type: type, hash: hash, filename: filename} {mode: mode, type: type, sha: sha, filename: filename}
end end
end end
def parse_rev_list(result, params) def parse_rev_list(result, params)
result.lines.map do |line| result.lines.map do |line|
hash, stamp, date, author_email = line.split('|').map(&:strip) sha, stamp, date, author_email = line.split('|').map(&:strip)
{hash: hash, stamp: stamp, date: date, author_email: author_email} {sha: sha, stamp: stamp, date: date, author_email: author_email}
end end
end end

View file

@ -5,14 +5,22 @@ module GitStats
class Commit class Commit
include HashInitializable include HashInitializable
attr_reader :repo, :hash, :stamp, :date, :author attr_reader :repo, :sha, :stamp, :date, :author
def files def files
@files ||= repo.run_and_parse("git ls-tree -r #{self.hash}").map do |file| @files ||= repo.run_and_parse("git ls-tree -r #{self.sha}").map do |file|
Blob.new(repo: repo, filename: file[:filename], hash: file[:hash]) Blob.new(repo: repo, filename: file[:filename], sha: file[:sha])
end.extend(ByFieldFinder) end.extend(ByFieldFinder)
end end
def binary_files
@binary_files ||= files.select { |f| f.binary? }
end
def text_files
@text_files ||= files - binary_files
end
def files_by_extension def files_by_extension
@files_by_extension ||= files.inject({}) { |acc, f| acc[f.extension] ||= []; acc[f.extension] << f; acc } @files_by_extension ||= files.inject({}) { |acc, f| acc[f.extension] ||= []; acc[f.extension] << f; acc }
end end
@ -28,19 +36,11 @@ module GitStats
end end
def files_count def files_count
@files_count ||= repo.run("git ls-tree -r --name-only #{self.hash} | wc -l").to_i @files_count ||= repo.run("git ls-tree -r --name-only #{self.sha} | wc -l").to_i
end
def binary_files_count
files.find_all { |f| f.binary? }.size
end
def text_files_count
files_count - binary_files_count
end end
def lines_count def lines_count
@lines_count ||= repo.run("git diff --shortstat `git hash-object -t tree /dev/null` #{self.hash}").lines.map do |line| @lines_count ||= repo.run("git diff --shortstat `git hash-object -t tree /dev/null` #{self.sha}").lines.map do |line|
line[/(\d+) insertions?/, 1].to_i line[/(\d+) insertions?/, 1].to_i
end.sum end.sum
end end
@ -50,12 +50,12 @@ module GitStats
end end
def to_s def to_s
"#{self.class} #@hash" "#{self.class} #@sha"
end end
def ==(other) def ==(other)
[self.repo, self.hash, self.stamp, self.date, self.author] == [self.repo, self.sha, self.stamp, self.date, self.author] ==
[other.repo, other.hash, other.stamp, other.date, other.author] [other.repo, other.sha, other.stamp, other.date, other.author]
end end
end end
end end

View file

@ -8,7 +8,7 @@ module GitStats
attr_reader :path attr_reader :path
delegate :files, :files_by_extension, :files_by_extension_count, :lines_by_extension, delegate :files, :files_by_extension, :files_by_extension_count, :lines_by_extension,
:files_count, :binary_files_count, :text_files_count, :lines_count, to: :last_commit :files_count, :binary_files, :text_files, :lines_count, to: :last_commit
def initialize(params) def initialize(params)
super(params) super(params)
@ -25,7 +25,7 @@ module GitStats
@commits ||= run_and_parse("git rev-list --pretty=format:'%h|%at|%ai|%aE' #{commit_range} | grep -v commit").map do |commit_line| @commits ||= run_and_parse("git rev-list --pretty=format:'%h|%at|%ai|%aE' #{commit_range} | grep -v commit").map do |commit_line|
Commit.new( Commit.new(
repo: self, repo: self,
hash: commit_line[:hash], sha: commit_line[:sha],
stamp: commit_line[:stamp], stamp: commit_line[:stamp],
date: DateTime.parse(commit_line[:date]), date: DateTime.parse(commit_line[:date]),
author: authors.by_email(commit_line[:author_email]) author: authors.by_email(commit_line[:author_email])
@ -74,11 +74,11 @@ module GitStats
end end
def commit_range def commit_range
@first_commit_hash ? "#@first_commit_hash..#{last_commit_hash}" : last_commit_hash @first_commit_sha ? "#@first_commit_sha..#{last_commit_sha}" : last_commit_sha
end end
def last_commit_hash def last_commit_sha
@last_commit_hash ||= 'HEAD' @last_commit_sha ||= 'HEAD'
end end
def short_stats def short_stats

View file

@ -14,7 +14,7 @@ module GitStats
private private
def calculate_stat def calculate_stat
stat_line = commit.repo.run("git show --shortstat --oneline #{commit.hash}").lines.to_a[1] stat_line = commit.repo.run("git show --shortstat --oneline #{commit.sha}").lines.to_a[1]
if stat_line.blank? if stat_line.blank?
@files_changed = @insertions = @deletions = 0 @files_changed = @insertions = @deletions = 0
else else

View file

@ -15,7 +15,7 @@ FactoryGirl.define do
end end
factory :commit, class: GitStats::GitData::Commit do factory :commit, class: GitStats::GitData::Commit do
sequence(:hash) { |i| i } sequence(:sha) { |i| i }
sequence(:stamp) { |i| i } sequence(:stamp) { |i| i }
sequence(:date) { |i| Date.new(i) } sequence(:date) { |i| Date.new(i) }
association :repo, strategy: :build association :repo, strategy: :build

View file

@ -4,8 +4,8 @@ describe GitStats::GitData::Author do
let(:repo) { build(:repo) } let(:repo) { build(:repo) }
let(:author) { build(:author, repo: repo) } let(:author) { build(:author, repo: repo) }
let(:other_author) { build(:author, repo: repo) } let(:other_author) { build(:author, repo: repo) }
let(:my_commits) { 10.times.map { |i| double("my_commit #{i}", :author => author) } } let(:my_commits) { 10.times.map { |i| double("my_commit #{i}", author: author, short_stat: double("my_short_stat #{i}", insertions: 5, deletions: 10)) } }
let(:other_commits) { 10.times.map { |i| double("other_commit #{i}", :author => other_author) } } let(:other_commits) { 10.times.map { |i| double("other_commit #{i}", author: other_author) } }
before { repo.stub(:commits => my_commits + other_commits) } before { repo.stub(:commits => my_commits + other_commits) }
@ -13,5 +13,12 @@ describe GitStats::GitData::Author do
author.commits.should == my_commits author.commits.should == my_commits
end end
it 'should count lines added from short stat' do
author.lines_added.should == 50
end
it 'should count lines deleted from short stat' do
author.lines_deleted.should == 100
end
end end

View file

@ -2,8 +2,8 @@ require 'spec_helper'
describe GitStats::GitData::Blob do describe GitStats::GitData::Blob do
let(:repo) { double } let(:repo) { double }
let(:png_blob) { GitStats::GitData::Blob.new(filename: 'abc.png', hash: 'hash_png', repo: repo) } let(:png_blob) { GitStats::GitData::Blob.new(filename: 'abc.png', sha: 'hash_png', repo: repo) }
let(:txt_blob) { GitStats::GitData::Blob.new(filename: 'abc.txt', hash: 'hash_txt', repo: repo) } let(:txt_blob) { GitStats::GitData::Blob.new(filename: 'abc.txt', sha: 'hash_txt', repo: repo) }
it 'should return 0 as lines count when files is binary' do it 'should return 0 as lines count when files is binary' do
png_blob.should_receive(:binary?).and_return true png_blob.should_receive(:binary?).and_return true

View file

@ -9,22 +9,22 @@ describe GitStats::GitData::Repo do
end end
it 'should return last_commit if it was given' do it 'should return last_commit if it was given' do
repo = build(:repo, last_commit_hash: 'abc') repo = build(:repo, last_commit_sha: 'abc')
repo.commit_range.should == 'abc' repo.commit_range.should == 'abc'
end end
it 'should return range from first_commit to HEAD if first_commit was given' do it 'should return range from first_commit to HEAD if first_commit was given' do
repo = build(:repo, first_commit_hash: 'abc') repo = build(:repo, first_commit_sha: 'abc')
repo.commit_range.should == 'abc..HEAD' repo.commit_range.should == 'abc..HEAD'
end end
it 'should return range from first to last commit if both were given' do it 'should return range from first to last commit if both were given' do
repo = build(:repo, first_commit_hash: 'abc', last_commit_hash: 'def') repo = build(:repo, first_commit_sha: 'abc', last_commit_sha: 'def')
repo.commit_range.should == 'abc..def' repo.commit_range.should == 'abc..def'
end end
context 'git commands using range' do context 'git commands using range' do
let(:repo) { build(:repo, first_commit_hash: 'abc', last_commit_hash: 'def') } let(:repo) { build(:repo, first_commit_sha: 'abc', last_commit_sha: 'def') }
it 'should affect authors command' do it 'should affect authors command' do
repo.should_receive(:run).with('git shortlog -se abc..def').and_return("") repo.should_receive(:run).with('git shortlog -se abc..def').and_return("")

View file

@ -1,7 +1,7 @@
require 'spec_helper' require 'spec_helper'
describe GitStats::GitData::Commit do describe GitStats::GitData::Commit do
let(:commit) { build(:commit, hash: 'abc') } let(:commit) { build(:commit, sha: 'abc') }
describe 'git output parsing' do describe 'git output parsing' do
context 'parsing git ls-tree output' do context 'parsing git ls-tree output' do
@ -14,26 +14,26 @@ describe GitStats::GitData::Commit do
it 'should be parsed to files' do it 'should be parsed to files' do
commit.files.should == [ commit.files.should == [
GitStats::GitData::Blob.new(repo: commit.repo, hash: "5ade7ad51a75ee7db4eb06cecd3918d38134087d", filename: "lib/git_stats/git_data/commit.rb"), GitStats::GitData::Blob.new(repo: commit.repo, sha: "5ade7ad51a75ee7db4eb06cecd3918d38134087d", filename: "lib/git_stats/git_data/commit.rb"),
GitStats::GitData::Blob.new(repo: commit.repo, hash: "db01e94677a8f72289848e507a52a43de2ea109a", filename: "lib/git_stats/git_data/repo.rb"), GitStats::GitData::Blob.new(repo: commit.repo, sha: "db01e94677a8f72289848e507a52a43de2ea109a", filename: "lib/git_stats/git_data/repo.rb"),
GitStats::GitData::Blob.new(repo: commit.repo, hash: "1463eacb3ac9f95f21f360f1eb935a84a9ee0895", filename: "templates/activity.haml"), GitStats::GitData::Blob.new(repo: commit.repo, sha: "1463eacb3ac9f95f21f360f1eb935a84a9ee0895", filename: "templates/activity.haml"),
GitStats::GitData::Blob.new(repo: commit.repo, hash: "31d8b960a67f195bdedaaf9e7aa70b2389f3f1a8", filename: "templates/assets/bootstrap/css/bootstrap.min.css"), GitStats::GitData::Blob.new(repo: commit.repo, sha: "31d8b960a67f195bdedaaf9e7aa70b2389f3f1a8", filename: "templates/assets/bootstrap/css/bootstrap.min.css"),
] ]
end end
it 'should group files by extension' do it 'should group files by extension' do
commit.files_by_extension.should == {'.rb' => [ commit.files_by_extension.should == {'.rb' => [
GitStats::GitData::Blob.new(repo: commit.repo, hash: "5ade7ad51a75ee7db4eb06cecd3918d38134087d", filename: "lib/git_stats/git_data/commit.rb"), GitStats::GitData::Blob.new(repo: commit.repo, sha: "5ade7ad51a75ee7db4eb06cecd3918d38134087d", filename: "lib/git_stats/git_data/commit.rb"),
GitStats::GitData::Blob.new(repo: commit.repo, hash: "db01e94677a8f72289848e507a52a43de2ea109a", filename: "lib/git_stats/git_data/repo.rb") GitStats::GitData::Blob.new(repo: commit.repo, sha: "db01e94677a8f72289848e507a52a43de2ea109a", filename: "lib/git_stats/git_data/repo.rb")
], '.haml' => [ ], '.haml' => [
GitStats::GitData::Blob.new(repo: commit.repo, hash: "1463eacb3ac9f95f21f360f1eb935a84a9ee0895", filename: "templates/activity.haml") GitStats::GitData::Blob.new(repo: commit.repo, sha: "1463eacb3ac9f95f21f360f1eb935a84a9ee0895", filename: "templates/activity.haml")
], '.css' => [ ], '.css' => [
GitStats::GitData::Blob.new(repo: commit.repo, hash: "31d8b960a67f195bdedaaf9e7aa70b2389f3f1a8", filename: "templates/assets/bootstrap/css/bootstrap.min.css") GitStats::GitData::Blob.new(repo: commit.repo, sha: "31d8b960a67f195bdedaaf9e7aa70b2389f3f1a8", filename: "templates/assets/bootstrap/css/bootstrap.min.css")
] ]
} }
end end
it 'should count lines by extension' do it 'should count lines by extension excluding empty or binary files' do
GitStats::GitData::Blob.should_receive(:new).and_return( GitStats::GitData::Blob.should_receive(:new).and_return(
double(lines_count: 40, extension: '.rb'), double(lines_count: 40, extension: '.rb'),
double(lines_count: 60, extension: '.rb'), double(lines_count: 60, extension: '.rb'),

View file

@ -26,13 +26,13 @@ ce34874|1347482927|2012-09-12 22:48:47 +0200|joe.doe@gmail.com
repo.commits.should == [ repo.commits.should == [
GitStats::GitData::Commit.new( GitStats::GitData::Commit.new(
repo: repo, hash: "5eab339", stamp: "1345835073", date: DateTime.parse("2012-08-24 21:04:33 +0200"), repo: repo, sha: "5eab339", stamp: "1345835073", date: DateTime.parse("2012-08-24 21:04:33 +0200"),
author: repo.authors.by_email("john.doe@gmail.com")), author: repo.authors.by_email("john.doe@gmail.com")),
GitStats::GitData::Commit.new( GitStats::GitData::Commit.new(
repo: repo, hash: "ce34874", stamp: "1347482927", date: DateTime.parse("2012-09-12 22:48:47 +0200"), repo: repo, sha: "ce34874", stamp: "1347482927", date: DateTime.parse("2012-09-12 22:48:47 +0200"),
author: repo.authors.by_email("joe.doe@gmail.com")), author: repo.authors.by_email("joe.doe@gmail.com")),
GitStats::GitData::Commit.new( GitStats::GitData::Commit.new(
repo: repo, hash: "e4412c3", stamp: "1348603824", date: DateTime.parse("2012-09-25 22:10:24 +0200"), repo: repo, sha: "e4412c3", stamp: "1348603824", date: DateTime.parse("2012-09-25 22:10:24 +0200"),
author: repo.authors.by_email("john.doe@gmail.com")) author: repo.authors.by_email("john.doe@gmail.com"))
] ]
end end

View file

@ -1,7 +1,7 @@
require 'spec_helper' require 'spec_helper'
describe GitStats::GitData::ShortStat do describe GitStats::GitData::ShortStat do
let(:commit) { build(:commit, hash: 'abc') } let(:commit) { build(:commit, sha: 'abc') }
describe 'git output parsing' do describe 'git output parsing' do
context 'parsing git show output' do context 'parsing git show output' do

View file

@ -7,8 +7,8 @@ describe GitStats::GitData::Activity do
let(:jd) { repo.authors.by_email('john.doe@gmail.com') } let(:jd) { repo.authors.by_email('john.doe@gmail.com') }
it 'should filter commits to author' do it 'should filter commits to author' do
tg.commits.map(&:hash).should =~ %w(b3b4f81 d60b5ec ab47ef8 2c11f5e c87ecf9 b621a5d 4e7d0e9 872955c) tg.commits.map(&:sha).should =~ %w(b3b4f81 d60b5ec ab47ef8 2c11f5e c87ecf9 b621a5d 4e7d0e9 872955c)
jd.commits.map(&:hash).should =~ %w(fd66657 81e8bef) jd.commits.map(&:sha).should =~ %w(fd66657 81e8bef)
end end
context 'activity' do context 'activity' do

View file

@ -8,7 +8,7 @@ describe GitStats::GitData::Repo do
end end
it 'should retrieve correct file content for old file' do it 'should retrieve correct file content for old file' do
repo.commits.by_hash('c87ecf9').files.by_filename('test.txt').content.should == "bb repo.commits.by_sha('c87ecf9').files.by_filename('test.txt').content.should == "bb

View file

@ -1,7 +1,7 @@
require 'spec_helper' require 'spec_helper'
describe GitStats::GitData::Repo do describe GitStats::GitData::Repo do
let(:repo) { build(:test_repo, last_commit_hash: '872955c') } let(:repo) { build(:test_repo, last_commit_sha: '872955c') }
it 'should gather all authors' do it 'should gather all authors' do
repo.authors.should =~ [ repo.authors.should =~ [
@ -15,7 +15,7 @@ describe GitStats::GitData::Repo do
end end
it 'should gather all commits sorted by date' do it 'should gather all commits sorted by date' do
repo.commits.map(&:hash).should =~ %w(b3b4f81 d60b5ec ab47ef8 2c11f5e c87ecf9 b621a5d fd66657 81e8bef 4e7d0e9 872955c) repo.commits.map(&:sha).should =~ %w(b3b4f81 d60b5ec ab47ef8 2c11f5e c87ecf9 b621a5d fd66657 81e8bef 4e7d0e9 872955c)
end end
it 'should return project name from dir' do it 'should return project name from dir' do

View file

@ -1,16 +1,27 @@
require 'spec_helper' require 'spec_helper'
shared_examples_for "column_chart" do
it 'should be a column chart' do
chart.should be_a GitStats::StatsView::Charts::Chart
chart.options[:chart][:type].should == "column"
end
end
shared_examples_for "datetime_chart" do
it 'should be a datetime chart' do
chart.should be_a GitStats::StatsView::Charts::Chart
chart.options[:xAxis][:type].should == "datetime"
end
end
describe GitStats::StatsView::Charts::RepoCharts do describe GitStats::StatsView::Charts::RepoCharts do
let(:charts) { GitStats::StatsView::Charts::All.new(repo) } let(:charts) { GitStats::StatsView::Charts::All.new(repo) }
context 'files_by_extension chart' do context 'files_by_extension chart' do
let(:repo) { double(files_by_extension_count: {'.rb' => 5, '.txt' => 3}) } let(:repo) { double(files_by_extension_count: {'.rb' => 5, '.txt' => 3}) }
let(:chart) { charts.repo_charts.files_by_extension } let(:chart) { charts.files_by_extension }
it 'should be a column chart' do it_behaves_like "column_chart"
chart.should be_a GitStats::StatsView::Charts::Chart
chart.options[:chart][:type].should == "column"
end
it 'should have 1 data series with files_by_extension_count' do it 'should have 1 data series with files_by_extension_count' do
chart.should have(1).data chart.should have(1).data
@ -21,12 +32,9 @@ describe GitStats::StatsView::Charts::RepoCharts do
context 'lines_by_extension chart' do context 'lines_by_extension chart' do
let(:repo) { double(lines_by_extension: {'.rb' => 50, '.txt' => 30}) } let(:repo) { double(lines_by_extension: {'.rb' => 50, '.txt' => 30}) }
let(:chart) { charts.repo_charts.lines_by_extension } let(:chart) { charts.lines_by_extension }
it 'should be a column chart' do it_behaves_like "column_chart"
chart.should be_a GitStats::StatsView::Charts::Chart
chart.options[:chart][:type].should == "column"
end
it 'should have 1 data series with lines_by_extension' do it 'should have 1 data series with lines_by_extension' do
chart.should have(1).data chart.should have(1).data
@ -37,12 +45,9 @@ describe GitStats::StatsView::Charts::RepoCharts do
context 'files_by_date chart' do context 'files_by_date chart' do
let(:repo) { double(commits: [double(date: 5)], files_count_each_day: [10, 15, 12, 20]) } let(:repo) { double(commits: [double(date: 5)], files_count_each_day: [10, 15, 12, 20]) }
let(:chart) { charts.repo_charts.files_by_date } let(:chart) { charts.files_by_date }
it 'should be a datetime chart' do it_behaves_like "datetime_chart"
chart.should be_a GitStats::StatsView::Charts::Chart
chart.options[:chart][:type].should == "datetime"
end
it 'should have 1 data series with files_by_date' do it 'should have 1 data series with files_by_date' do
chart.should have(1).data chart.should have(1).data
@ -53,12 +58,9 @@ describe GitStats::StatsView::Charts::RepoCharts do
context 'lines_by_date chart' do context 'lines_by_date chart' do
let(:repo) { double(commits: [double(date: 6)], lines_count_each_day: [100, 150, 120, 200]) } let(:repo) { double(commits: [double(date: 6)], lines_count_each_day: [100, 150, 120, 200]) }
let(:chart) { charts.repo_charts.lines_by_date } let(:chart) { charts.lines_by_date }
it 'should be a datetime chart' do it_behaves_like "datetime_chart"
chart.should be_a GitStats::StatsView::Charts::Chart
chart.options[:chart][:type].should == "datetime"
end
it 'should have 1 data series with lines_by_date' do it 'should have 1 data series with lines_by_date' do
chart.should have(1).data chart.should have(1).data
@ -66,4 +68,30 @@ describe GitStats::StatsView::Charts::RepoCharts do
chart.data[0][:pointStart].should == 6000 chart.data[0][:pointStart].should == 6000
end end
end end
context 'lines_added_by_author chart' do
let(:repo) { double(commits: [double(date: 6)], lines_added_by_author: {double(email: 'author1') => 50, double(email: 'author2') => 30}) }
let(:chart) { charts.lines_added_by_author }
it_behaves_like "column_chart"
it 'should have 1 data series with lines_added_by_author' do
chart.should have(1).data
chart.options[:xAxis][:categories].should == %w(author1 author2)
chart.data[0][:data].should == [50, 30]
end
end
context 'lines_deleted_by_author chart' do
let(:repo) { double(commits: [double(date: 6)], lines_deleted_by_author: {double(email: 'author1') => 30, double(email: 'author2') => 50}) }
let(:chart) { charts.lines_deleted_by_author }
it_behaves_like "column_chart"
it 'should have 1 data series with lines_deleted_by_author' do
chart.should have(1).data
chart.options[:xAxis][:categories].should == %w(author1 author2)
chart.data[0][:data].should == [30, 50]
end
end
end end

View file

@ -16,7 +16,7 @@
%td= repo.commits_period.map {|d| d.to_formatted_s(:long)}.join(" .. ") %td= repo.commits_period.map {|d| d.to_formatted_s(:long)}.join(" .. ")
%tr %tr
%td= :total_files.t %td= :total_files.t
%td= "#{repo.files_count} (binary: #{repo.binary_files_count}, text: #{repo.text_files_count})" %td= "#{repo.files_count} (binary: #{repo.binary_files.size}, text: #{repo.text_files.size})"
%tr %tr
%td= :total_lines.t %td= :total_lines.t
%td= "#{repo.lines_count} lines (#{repo.short_stats.map(&:insertions).sum} insertions, #{repo.short_stats.map(&:deletions).sum} deletions)" %td= "#{repo.lines_count} lines (#{repo.short_stats.map(&:insertions).sum} insertions, #{repo.short_stats.map(&:deletions).sum} deletions)"