git_statistics 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
5
+
6
+ notifications:
7
+ email: false
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem "json"
4
+ gem "trollop"
5
+
6
+ group :test do
7
+ gem "simplecov"
8
+ gem "rspec"
9
+ gem "rake"
10
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,30 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.3)
5
+ json (1.6.6)
6
+ multi_json (1.2.0)
7
+ rake (0.9.2.2)
8
+ rspec (2.9.0)
9
+ rspec-core (~> 2.9.0)
10
+ rspec-expectations (~> 2.9.0)
11
+ rspec-mocks (~> 2.9.0)
12
+ rspec-core (2.9.0)
13
+ rspec-expectations (2.9.1)
14
+ diff-lcs (~> 1.1.3)
15
+ rspec-mocks (2.9.0)
16
+ simplecov (0.6.1)
17
+ multi_json (~> 1.0)
18
+ simplecov-html (~> 0.5.3)
19
+ simplecov-html (0.5.3)
20
+ trollop (1.16.2)
21
+
22
+ PLATFORMS
23
+ ruby
24
+
25
+ DEPENDENCIES
26
+ json
27
+ rake
28
+ rspec
29
+ simplecov
30
+ trollop
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2012 Kevin Jalbert
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,34 @@
1
+ [![Build Status](https://secure.travis-ci.org/kevinjalbert/git_statistics.png?branch=master)](http://travis-ci.org/kevinjalbert/git_statistics)
2
+
3
+ # Instructions
4
+
5
+ ### Using the gem
6
+ 1. Acquire gem (`gem install git_statistics`)
7
+ 2. Run `git_statistics` in any directory that is a git repository (use -h for options)
8
+
9
+ ### Working with source
10
+ 1. Clone the repository
11
+ 2. Install dependencies (`bundle install`)
12
+ 3. Run tests `bundle exec rake`
13
+ 4. Build and install local gem `bundle exec rake install`
14
+
15
+ # Statistics
16
+
17
+ The following statistics are collected (organized by author name or author email):
18
+
19
+ * Total number of commits
20
+ * Total number of merge commits
21
+ * Total source line insertions
22
+ * Total source line deletions
23
+ * Total file creates
24
+ * Total file deletes
25
+ * Total file renames
26
+ * Total file copies
27
+
28
+ ## Contributing
29
+
30
+ 1. Fork it
31
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
32
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
33
+ 4. Push to the branch (`git push origin my-new-feature`)
34
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require 'rake/clean'
4
+ require 'rspec/core/rake_task'
5
+ require "bundler/gem_tasks"
6
+
7
+ CLOBBER.include('coverage')
8
+
9
+ desc "Run all specs"
10
+ RSpec::Core::RakeTask.new do |t|
11
+ t.pattern = "./spec/**/*spec.rb"
12
+ end
13
+
14
+ task :default => :spec
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'git_statistics'
4
+
5
+ GitStatistics::GitStatistics.new(ARGV).execute
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/git_statistics/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.homepage = 'https://github.com/kevinjalbert/git_statistics'
6
+ gem.authors = ["Kevin Jalbert"]
7
+ gem.email = ["kevin.j.jalbert@gmail.com"]
8
+ gem.name = 'git_statistics'
9
+ gem.version = GitStatistics::VERSION
10
+ gem.summary = "Gem that provides the ability to gather detailed git statistics"
11
+ gem.description = "git_statistics is a gem that provides detailed git statistics"
12
+ gem.require_paths = ["lib"]
13
+ gem.files = `git ls-files`.split($\)
14
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
15
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
+ gem.executables << 'git_statistics'
17
+ gem.required_ruby_version = '>= 1.9.1'
18
+ gem.add_dependency('json')
19
+ gem.add_dependency('trollop')
20
+ end
@@ -0,0 +1,155 @@
1
+ module GitStatistics
2
+ class Collector
3
+
4
+ attr_accessor :commits
5
+
6
+ def initialize
7
+ @commits = Commits.new
8
+ end
9
+
10
+ def collect(branch, since="")
11
+
12
+ # Collect branches to use for git log
13
+ branches = collect_branches
14
+ branches = ["", ""] if branch
15
+
16
+ pipe = open("|git --no-pager log #{branches.join(' ')} --date=iso --reverse"\
17
+ " --no-color --numstat --summary #{since}"\
18
+ " --format=\"%H,%an,%ae,%ad,%p\"")
19
+
20
+ buffer = []
21
+ pipe.each do |line|
22
+
23
+ line = line.force_encoding("ISO-8859-1").encode("UTF-8")
24
+
25
+ if line.split(',').size == 5 # Matches the number of ',' in the format
26
+ extract_buffer(buffer) if not buffer.empty?
27
+ buffer = []
28
+ end
29
+
30
+ buffer << line.strip
31
+ end
32
+
33
+ # Extract the last commit
34
+ extract_buffer(buffer) if not buffer.empty?
35
+ end
36
+
37
+ def collect_branches
38
+
39
+ pipe = open("|git --no-pager branch --no-color")
40
+
41
+ branches = []
42
+ pipe.each do |line|
43
+
44
+ # Remove the '* ' leading the current branch
45
+ line = line[1..-1] if line[0] == '*'
46
+ branches << line.strip
47
+ end
48
+
49
+ return branches
50
+ end
51
+
52
+ def extract_buffer(buffer)
53
+
54
+ commit_info = buffer[0].split(',')
55
+
56
+ commit = (@commits[ commit_info[0] ] ||= Hash.new)
57
+ commit[:author] = commit_info[1]
58
+ commit[:author_email] = commit_info[2]
59
+ commit[:time] = commit_info[3]
60
+ commit[:insertions] = 0
61
+ commit[:deletions] = 0
62
+ commit[:creates] = 0
63
+ commit[:deletes] = 0
64
+ commit[:renames] = 0
65
+ commit[:copies] = 0
66
+
67
+ if commit_info[4] == nil or commit_info[4].split(' ').size == 1
68
+ commit[:merge] = false
69
+ else
70
+ commit[:merge] = true
71
+ end
72
+
73
+ # Only extract diff details if they exist
74
+ if buffer.size > 1
75
+
76
+ buffer[2..-1].each do |line|
77
+
78
+ next if extract_changes(commit, line)
79
+ next if extract_create_delete_file(commit, line)
80
+ next if extract_rename_copy_file(commit, line)
81
+
82
+ end
83
+ end
84
+ end
85
+
86
+ def extract_changes(commit, line)
87
+ changes = line.scan( /(\d+)\s(\d+)\s(.*)/ )[0]
88
+
89
+ if changes != nil and changes.size == 3
90
+ commit[:insertions] += changes[0].to_i
91
+ commit[:deletions] += changes[1].to_i
92
+ return true
93
+ end
94
+ end
95
+
96
+ def extract_create_delete_file(commit, line)
97
+ changes = line.scan(/(create|delete) mode \d+ ([^\\\n]*)/)[0]
98
+
99
+ if changes != nil and changes.size == 2
100
+ commit[:creates] += 1 if changes[0] == "create"
101
+ commit[:deletes] += 1 if changes[0] == "delete"
102
+ return true
103
+ end
104
+ end
105
+
106
+ def extract_rename_copy_file(commit, line)
107
+ changes = line.scan(/(rename|copy)([^(]*)/)[0]
108
+
109
+ if changes != nil and changes.size == 2
110
+ commit[:renames] += 1 if changes[0] == "rename"
111
+ commit[:copies] += 1 if changes[0] == "copy"
112
+ end
113
+ return true
114
+ end
115
+
116
+ def print_summary(sort_type, email, n=0)
117
+ n = 0 if n < 0
118
+
119
+ data = @commits.author_top_n_type(email, sort_type, n)
120
+
121
+ if data == nil
122
+ puts "ERROR: Parameter for --sort is not valid"
123
+ return
124
+ end
125
+
126
+ # Find the longest name/email (used for string formatting)
127
+ total_authors = @commits.author_list.length
128
+ author_length = 17
129
+ data.each do |key,value|
130
+ author_length = key.length if key.length > author_length
131
+ end
132
+
133
+ # Print header information
134
+ if n > 0 and n < total_authors
135
+ puts "Top #{n} authors(#{total_authors}) sorted by #{sort_type.to_s}\n\n"
136
+ else
137
+ puts "All authors(#{total_authors}) sorted by #{sort_type.to_s}\n\n"
138
+ end
139
+
140
+ pattern = "%-#{author_length}s|%7s|%10s|%9s|%7s|%7s|%7s|%6s|%6s|"
141
+ puts pattern % ['Name/email', 'commits', 'insertions', 'deletions', 'creates', 'deletes', 'renames', 'copies', 'merges']
142
+ puts "-"*68 + "-"*author_length
143
+
144
+ data.each do |key,value|
145
+ puts pattern % [key, value[:commits], value[:insertions], value[:deletions],
146
+ value[:creates], value[:deletes], value[:renames], value[:copies], value[:merges]]
147
+ end
148
+
149
+ puts "-"*68 + "-"*author_length
150
+ puts pattern % ["Repository Totals", @commits.totals[:commits],
151
+ @commits.totals[:insertions], @commits.totals[:deletions], @commits.totals[:creates],
152
+ @commits.totals[:deletes], @commits.totals[:renames], @commits.totals[:copies], @commits.totals[:merges]]
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,129 @@
1
+ module GitStatistics
2
+ class Commits < Hash
3
+
4
+ attr_accessor :author_list, :data_authors, :data_authors_email, :totals
5
+
6
+ def initialize
7
+ super
8
+ end
9
+
10
+ def authors
11
+ author_list = []
12
+ self.each do |key,value|
13
+ if not author_list.include?(value[:author])
14
+ author_list << value[:author]
15
+ end
16
+ end
17
+ return author_list
18
+ end
19
+
20
+ def authors_email
21
+ author_list = []
22
+ self.each do |key,value|
23
+ if not author_list.include?(value[:author_email])
24
+ author_list << value[:author_email]
25
+ end
26
+ end
27
+ return author_list
28
+ end
29
+
30
+ def authors_statistics(email, merge)
31
+
32
+ # Identify authors and author type
33
+ if email
34
+ @author_list = authors_email
35
+ type = :author_email
36
+ else
37
+ @author_list = authors
38
+ type = :author
39
+ end
40
+
41
+ # Initialize the stats hash
42
+ stats = Hash.new
43
+ @author_list.each do |author|
44
+ stats[author] = Hash.new
45
+ stats[author][:commits] = 0
46
+ stats[author][:insertions] = 0
47
+ stats[author][:deletions] = 0
48
+ stats[author][:creates] = 0
49
+ stats[author][:deletes] = 0
50
+ stats[author][:renames] = 0
51
+ stats[author][:copies] = 0
52
+ stats[author][:merges] = 0
53
+ end
54
+
55
+ # Collect the stats for each author
56
+ self.each do |key,value|
57
+ if not merge and value[:merge]
58
+ next
59
+ else
60
+ stats[value[type]][:merges] += 1 if value[:merge]
61
+ stats[value[type]][:commits] += 1
62
+ stats[value[type]][:insertions] += value[:insertions]
63
+ stats[value[type]][:deletions] += value[:deletions]
64
+ stats[value[type]][:creates] += value[:creates]
65
+ stats[value[type]][:deletes] += value[:deletes]
66
+ stats[value[type]][:renames] += value[:renames]
67
+ stats[value[type]][:copies] += value[:copies]
68
+ end
69
+ end
70
+ return stats
71
+ end
72
+
73
+ def author_top_n_type(email, type, n=0)
74
+ n = 0 if n < 0
75
+
76
+ if email
77
+ data = @data_authors_email
78
+ else
79
+ data = @data_authors
80
+ end
81
+
82
+ return nil if data == nil || !data.first[1].has_key?(type)
83
+ return data.sorted_hash {|a,b| b[1][type.to_sym] <=> a[1][type]}.to_a[0..n-1]
84
+ end
85
+
86
+ def calculate_statistics(email, merge)
87
+
88
+ # Calculate author statistics
89
+ @data_authors_email = authors_statistics(true, merge) if email
90
+ @data_authors = authors_statistics(false, merge) if not email
91
+
92
+ # Calculate totals
93
+ @totals = Hash.new(0)
94
+ self.each do |key,value|
95
+ if not merge and value[:merge]
96
+ next
97
+ else
98
+ @totals[:merges] += 1 if value[:merge]
99
+ @totals[:commits] += 1
100
+ @totals[:insertions] += value[:insertions]
101
+ @totals[:deletions] += value[:deletions]
102
+ @totals[:creates] += value[:creates]
103
+ @totals[:deletes] += value[:deletes]
104
+ @totals[:renames] += value[:renames]
105
+ @totals[:copies] += value[:copies]
106
+ end
107
+ end
108
+ end
109
+
110
+ def load(file)
111
+ self.merge!(JSON.parse(File.read(file), :symbolize_names => true))
112
+ end
113
+
114
+ def save(file, pretty)
115
+ if pretty
116
+ File.open(file, 'w') {|file| file.write(JSON.pretty_generate(self))}
117
+ else
118
+ File.open(file, 'w') {|file| file.write(self.to_json)}
119
+ end
120
+ end
121
+ end
122
+
123
+ class Hash < Hash
124
+
125
+ def sorted_hash(&block)
126
+ self.class[sort(&block)]
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,3 @@
1
+ require 'json'
2
+ require 'trollop'
3
+ Dir.glob(File.dirname(__FILE__) + '/*.rb') {|file| require file}
@@ -0,0 +1,3 @@
1
+ module GitStatistics
2
+ VERSION = "0.1.2"
3
+ end
@@ -0,0 +1,44 @@
1
+ require 'git_statistics/initialize'
2
+
3
+ module GitStatistics
4
+ class GitStatistics
5
+ def initialize(args=nil)
6
+ @opts = Trollop::options do
7
+ opt :email, "Use author's email instead of name", :default => false
8
+ opt :merges, "Factor in merges when calculating statistics", :default => false
9
+ opt :save, "Save the commits as commits.json", :default => false
10
+ opt :pretty, "Pretty print saved commits (larger file size)", :default => false
11
+ opt :load, "Load commits.json instead of re-collecting data", :default => false
12
+ opt :update, "Update commits.json with new data (same as save and load together)", :default => false
13
+ opt :sort, "Sort authors by {commits, insertions, deletions, creates, deletes, renames, copies, merges}", :default => "commits"
14
+ opt :top, "Show the top N authors in results", :default => 0
15
+ opt :branch, "Use current branch for statistics (otherwise all branches)", :default => false
16
+ end
17
+ end
18
+
19
+ def execute
20
+ collector = Collector.new
21
+
22
+ # Collect commit data
23
+ if @opts[:load] || @opts[:update]
24
+ collector.commits.load("commits.json")
25
+ else
26
+ collector.collect(@opts[:branch])
27
+ end
28
+
29
+ # Collect incremental recent data
30
+ if @opts[:update]
31
+ collector.collect(@opts[:branch], "--since=\"`date -r commits.json \"+%F %T\"`\"")
32
+ end
33
+
34
+ # Save data
35
+ if @opts[:save] || @opts[:update]
36
+ collector.commits.save("commits.json", @opts[:pretty])
37
+ end
38
+
39
+ collector.commits.calculate_statistics(@opts[:email], @opts[:merges])
40
+
41
+ collector.print_summary(@opts[:sort].to_sym, @opts[:email], @opts[:top])
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,6 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ include GitStatistics
3
+
4
+ describe Collector do
5
+
6
+ end
@@ -0,0 +1,381 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ include GitStatistics
3
+
4
+ describe Commits do
5
+ describe "#authors" do
6
+ context "with one author" do
7
+ commits = Commits.new
8
+ commits.load(fixture("single_author.json"))
9
+ author_list = commits.authors
10
+
11
+
12
+ it { author_list.size.should be(1) }
13
+ it { author_list[0].should == "Kevin Jalbert" }
14
+ end
15
+
16
+ context "with many authors" do
17
+ commits = Commits.new
18
+ commits.load(fixture("many_authors.json"))
19
+ author_list = commits.authors
20
+
21
+ it { author_list.size.should be(3) }
22
+ it { author_list[0].should == "Bart Simpson" }
23
+ it { author_list[1].should == "Kevin Jalbert" }
24
+ it { author_list[2].should == "Maggie Simpson" }
25
+ end
26
+
27
+ context "with no authors" do
28
+ commits = Commits.new
29
+ author_list = commits.authors
30
+
31
+ it { author_list.size.should be(0) }
32
+ end
33
+ end
34
+
35
+ describe "#authors_email" do
36
+ context "with one author" do
37
+ commits = Commits.new
38
+ commits.load(fixture("single_author.json"))
39
+ author_list = commits.authors_email
40
+
41
+ it { author_list.size.should be(1) }
42
+ it { author_list[0].should == "kevin.j.jalbert@gmail.com" }
43
+ end
44
+
45
+ context "with many authors" do
46
+ commits = Commits.new
47
+ commits.load(fixture("many_authors.json"))
48
+ author_list = commits.authors_email
49
+
50
+ it { author_list.size.should be(3) }
51
+ it { author_list[0].should == "bart.simpson@gmail.com" }
52
+ it { author_list[1].should == "kevin.j.jalbert@gmail.com" }
53
+ it { author_list[2].should == "maggie.simpson@gmail.com" }
54
+ end
55
+
56
+ context "with no authors" do
57
+ commits = Commits.new
58
+ author_list = commits.authors_email
59
+
60
+ it { author_list.size.should be(0) }
61
+ end
62
+ end
63
+
64
+ describe "#authors_statistics" do
65
+ context "with email" do
66
+ context "with merge" do
67
+ commits = Commits.new
68
+ commits.load(fixture("many_authors.json"))
69
+ results = commits.authors_statistics(true, true)
70
+
71
+ it {results.size.should be(3)}
72
+ it {results["bart.simpson@gmail.com"][:commits].should be(2)}
73
+ it {results["bart.simpson@gmail.com"][:insertions].should be(13)}
74
+ it {results["bart.simpson@gmail.com"][:deletions].should be(3)}
75
+ it {results["bart.simpson@gmail.com"][:creates].should be(2)}
76
+ it {results["bart.simpson@gmail.com"][:deletes].should be(0)}
77
+ it {results["bart.simpson@gmail.com"][:renames].should be(2)}
78
+ it {results["bart.simpson@gmail.com"][:copies].should be(0)}
79
+ it {results["bart.simpson@gmail.com"][:merges].should be(1)}
80
+
81
+ it {results["kevin.j.jalbert@gmail.com"][:commits].should be(1)}
82
+ it {results["kevin.j.jalbert@gmail.com"][:insertions].should be(62)}
83
+ it {results["kevin.j.jalbert@gmail.com"][:deletions].should be(6)}
84
+ it {results["kevin.j.jalbert@gmail.com"][:creates].should be(0)}
85
+ it {results["kevin.j.jalbert@gmail.com"][:deletes].should be(1)}
86
+ it {results["kevin.j.jalbert@gmail.com"][:renames].should be(0)}
87
+ it {results["kevin.j.jalbert@gmail.com"][:copies].should be(0)}
88
+ it {results["kevin.j.jalbert@gmail.com"][:merges].should be(1)}
89
+
90
+ it {results["maggie.simpson@gmail.com"][:commits].should be(2)}
91
+ it {results["maggie.simpson@gmail.com"][:insertions].should be(211)}
92
+ it {results["maggie.simpson@gmail.com"][:deletions].should be(192)}
93
+ it {results["maggie.simpson@gmail.com"][:creates].should be(7)}
94
+ it {results["maggie.simpson@gmail.com"][:deletes].should be(2)}
95
+ it {results["maggie.simpson@gmail.com"][:renames].should be(3)}
96
+ it {results["maggie.simpson@gmail.com"][:copies].should be(1)}
97
+ it {results["maggie.simpson@gmail.com"][:merges].should be(0)}
98
+
99
+ end
100
+
101
+ context "no merge" do
102
+ commits = Commits.new
103
+ commits.load(fixture("many_authors.json"))
104
+ results = commits.authors_statistics(true, false)
105
+
106
+ it {results.size.should be(3)}
107
+ it {results["bart.simpson@gmail.com"][:commits].should be(1)}
108
+ it {results["bart.simpson@gmail.com"][:insertions].should be(3)}
109
+ it {results["bart.simpson@gmail.com"][:deletions].should be(2)}
110
+ it {results["bart.simpson@gmail.com"][:creates].should be(2)}
111
+ it {results["bart.simpson@gmail.com"][:deletes].should be(0)}
112
+ it {results["bart.simpson@gmail.com"][:renames].should be(0)}
113
+ it {results["bart.simpson@gmail.com"][:copies].should be(0)}
114
+ it {results["bart.simpson@gmail.com"][:merges].should be(0)}
115
+
116
+ it {results["kevin.j.jalbert@gmail.com"][:commits].should be(0)}
117
+ it {results["kevin.j.jalbert@gmail.com"][:insertions].should be(0)}
118
+ it {results["kevin.j.jalbert@gmail.com"][:deletions].should be(0)}
119
+ it {results["kevin.j.jalbert@gmail.com"][:creates].should be(0)}
120
+ it {results["kevin.j.jalbert@gmail.com"][:deletes].should be(0)}
121
+ it {results["kevin.j.jalbert@gmail.com"][:renames].should be(0)}
122
+ it {results["kevin.j.jalbert@gmail.com"][:copies].should be(0)}
123
+ it {results["kevin.j.jalbert@gmail.com"][:merges].should be(0)}
124
+
125
+ it {results["maggie.simpson@gmail.com"][:commits].should be(2)}
126
+ it {results["maggie.simpson@gmail.com"][:insertions].should be(211)}
127
+ it {results["maggie.simpson@gmail.com"][:deletions].should be(192)}
128
+ it {results["maggie.simpson@gmail.com"][:creates].should be(7)}
129
+ it {results["maggie.simpson@gmail.com"][:deletes].should be(2)}
130
+ it {results["maggie.simpson@gmail.com"][:renames].should be(3)}
131
+ it {results["maggie.simpson@gmail.com"][:copies].should be(1)}
132
+ it {results["maggie.simpson@gmail.com"][:merges].should be(0)}
133
+ end
134
+ end
135
+
136
+ context "no email" do
137
+ context "with merge" do
138
+ commits = Commits.new
139
+ commits.load(fixture("many_authors.json"))
140
+ results = commits.authors_statistics(false, true)
141
+
142
+ it {results.size.should be(3)}
143
+ it {results["Bart Simpson"][:commits].should be(2)}
144
+ it {results["Bart Simpson"][:insertions].should be(13)}
145
+ it {results["Bart Simpson"][:deletions].should be(3)}
146
+ it {results["Bart Simpson"][:creates].should be(2)}
147
+ it {results["Bart Simpson"][:deletes].should be(0)}
148
+ it {results["Bart Simpson"][:renames].should be(2)}
149
+ it {results["Bart Simpson"][:copies].should be(0)}
150
+ it {results["Bart Simpson"][:merges].should be(1)}
151
+
152
+ it {results["Kevin Jalbert"][:commits].should be(1)}
153
+ it {results["Kevin Jalbert"][:insertions].should be(62)}
154
+ it {results["Kevin Jalbert"][:deletions].should be(6)}
155
+ it {results["Kevin Jalbert"][:creates].should be(0)}
156
+ it {results["Kevin Jalbert"][:deletes].should be(1)}
157
+ it {results["Kevin Jalbert"][:renames].should be(0)}
158
+ it {results["Kevin Jalbert"][:copies].should be(0)}
159
+ it {results["Kevin Jalbert"][:merges].should be(1)}
160
+
161
+ it {results["Maggie Simpson"][:commits].should be(2)}
162
+ it {results["Maggie Simpson"][:insertions].should be(211)}
163
+ it {results["Maggie Simpson"][:deletions].should be(192)}
164
+ it {results["Maggie Simpson"][:creates].should be(7)}
165
+ it {results["Maggie Simpson"][:deletes].should be(2)}
166
+ it {results["Maggie Simpson"][:renames].should be(3)}
167
+ it {results["Maggie Simpson"][:copies].should be(1)}
168
+ it {results["Maggie Simpson"][:merges].should be(0)}
169
+
170
+ end
171
+
172
+ context "no merge" do
173
+ commits = Commits.new
174
+ commits.load(fixture("many_authors.json"))
175
+ results = commits.authors_statistics(false, false)
176
+
177
+ it {results.size.should be(3)}
178
+ it {results["Bart Simpson"][:commits].should be(1)}
179
+ it {results["Bart Simpson"][:insertions].should be(3)}
180
+ it {results["Bart Simpson"][:deletions].should be(2)}
181
+ it {results["Bart Simpson"][:creates].should be(2)}
182
+ it {results["Bart Simpson"][:deletes].should be(0)}
183
+ it {results["Bart Simpson"][:renames].should be(0)}
184
+ it {results["Bart Simpson"][:copies].should be(0)}
185
+ it {results["Bart Simpson"][:merges].should be(0)}
186
+
187
+ it {results["Kevin Jalbert"][:commits].should be(0)}
188
+ it {results["Kevin Jalbert"][:insertions].should be(0)}
189
+ it {results["Kevin Jalbert"][:deletions].should be(0)}
190
+ it {results["Kevin Jalbert"][:creates].should be(0)}
191
+ it {results["Kevin Jalbert"][:deletes].should be(0)}
192
+ it {results["Kevin Jalbert"][:renames].should be(0)}
193
+ it {results["Kevin Jalbert"][:copies].should be(0)}
194
+ it {results["Kevin Jalbert"][:merges].should be(0)}
195
+
196
+ it {results["Maggie Simpson"][:commits].should be(2)}
197
+ it {results["Maggie Simpson"][:insertions].should be(211)}
198
+ it {results["Maggie Simpson"][:deletions].should be(192)}
199
+ it {results["Maggie Simpson"][:creates].should be(7)}
200
+ it {results["Maggie Simpson"][:deletes].should be(2)}
201
+ it {results["Maggie Simpson"][:renames].should be(3)}
202
+ it {results["Maggie Simpson"][:copies].should be(1)}
203
+ it {results["Maggie Simpson"][:merges].should be(0)}
204
+ end
205
+ end
206
+ end
207
+
208
+ describe "#author_top_n_type" do
209
+ context "no data" do
210
+ context "with email" do
211
+ commits = Commits.new
212
+ results = commits.author_top_n_type(true, :commits)
213
+
214
+ it { results.should be(nil)}
215
+ end
216
+
217
+ context "without email" do
218
+ commits = Commits.new
219
+ results = commits.author_top_n_type(false, :commits)
220
+
221
+ it { results.should be(nil)}
222
+ end
223
+ end
224
+
225
+ context "with data" do
226
+ context "with email" do
227
+ context "n is negative" do
228
+ commits = Commits.new
229
+ commits.load(fixture("many_authors.json"))
230
+ commits.calculate_statistics(true, true)
231
+ results = commits.author_top_n_type(true, :commits, -1)
232
+
233
+ it { results.size.should be(3)}
234
+ it { results[0][0].should == "bart.simpson@gmail.com"}
235
+ it { results[1][0].should == "maggie.simpson@gmail.com"}
236
+ it { results[2][0].should == "kevin.j.jalbert@gmail.com"}
237
+
238
+ end
239
+
240
+ context "n is 0" do
241
+ commits = Commits.new
242
+ commits.load(fixture("many_authors.json"))
243
+ commits.calculate_statistics(true, true)
244
+ results = commits.author_top_n_type(true, :commits, 0)
245
+
246
+ it { results.size.should be(3)}
247
+ it { results[0][0].should == "bart.simpson@gmail.com"}
248
+ it { results[1][0].should == "maggie.simpson@gmail.com"}
249
+ it { results[2][0].should == "kevin.j.jalbert@gmail.com"}
250
+ end
251
+
252
+ context "n is less then total" do
253
+ commits = Commits.new
254
+ commits.load(fixture("many_authors.json"))
255
+ commits.calculate_statistics(true, true)
256
+ results = commits.author_top_n_type(true, :commits, 2)
257
+
258
+ it { results.size.should be(2)}
259
+ it { results[0][0].should == "bart.simpson@gmail.com"}
260
+ it { results[1][0].should == "maggie.simpson@gmail.com"}
261
+ end
262
+
263
+ context "n is greater then total" do
264
+ commits = Commits.new
265
+ commits.load(fixture("many_authors.json"))
266
+ commits.calculate_statistics(true, true)
267
+ results = commits.author_top_n_type(true, :commits, 20)
268
+
269
+ it { results.size.should be(3)}
270
+ it { results[0][0].should == "bart.simpson@gmail.com"}
271
+ it { results[1][0].should == "maggie.simpson@gmail.com"}
272
+ it { results[2][0].should == "kevin.j.jalbert@gmail.com"}
273
+ end
274
+ end
275
+
276
+ context "no email" do
277
+ context "n is negative" do
278
+ commits = Commits.new
279
+ commits.load(fixture("many_authors.json"))
280
+ commits.calculate_statistics(false, true)
281
+ results = commits.author_top_n_type(false, :commits, -1)
282
+
283
+ it { results.size.should be(3)}
284
+ it { results[0][0].should == "Bart Simpson"}
285
+ it { results[1][0].should == "Maggie Simpson"}
286
+ it { results[2][0].should == "Kevin Jalbert"}
287
+
288
+ end
289
+
290
+ context "n is 0" do
291
+ commits = Commits.new
292
+ commits.load(fixture("many_authors.json"))
293
+ commits.calculate_statistics(false, true)
294
+ results = commits.author_top_n_type(false, :commits, 0)
295
+
296
+ it { results.size.should be(3)}
297
+ it { results[0][0].should == "Bart Simpson"}
298
+ it { results[1][0].should == "Maggie Simpson"}
299
+ it { results[2][0].should == "Kevin Jalbert"}
300
+ end
301
+
302
+ context "n is less then total" do
303
+ commits = Commits.new
304
+ commits.load(fixture("many_authors.json"))
305
+ commits.calculate_statistics(false, true)
306
+ results = commits.author_top_n_type(false, :commits, 2)
307
+
308
+ it { results.size.should be(2)}
309
+ it { results[0][0].should == "Bart Simpson"}
310
+ it { results[1][0].should == "Maggie Simpson"}
311
+ end
312
+
313
+ context "n is greater then total" do
314
+ commits = Commits.new
315
+ commits.load(fixture("many_authors.json"))
316
+ commits.calculate_statistics(false, true)
317
+ results = commits.author_top_n_type(false, :commits, 20)
318
+
319
+ it { results.size.should be(3)}
320
+ it { results[0][0].should == "Bart Simpson"}
321
+ it { results[1][0].should == "Maggie Simpson"}
322
+ it { results[2][0].should == "Kevin Jalbert"}
323
+ end
324
+ end
325
+ end
326
+ end
327
+
328
+ describe "#calculate_statistics (totals)" do
329
+ context "with merge" do
330
+ commits = Commits.new
331
+ commits.load(fixture("many_authors.json"))
332
+ commits.calculate_statistics(true, true)
333
+ results = commits.totals
334
+
335
+ it {results[:commits].should be(5)}
336
+ it {results[:insertions].should be(286)}
337
+ it {results[:deletions].should be(201)}
338
+ it {results[:creates].should be(9)}
339
+ it {results[:deletes].should be(3)}
340
+ it {results[:renames].should be(5)}
341
+ it {results[:copies].should be(1)}
342
+ it {results[:merges].should be(2)}
343
+ end
344
+
345
+ context "no merge" do
346
+ commits = Commits.new
347
+ commits.load(fixture("many_authors.json"))
348
+ commits.calculate_statistics(false, false)
349
+ results = commits.totals
350
+
351
+ it {results[:commits].should be(3)}
352
+ it {results[:insertions].should be(214)}
353
+ it {results[:deletions].should be(194)}
354
+ it {results[:creates].should be(9)}
355
+ it {results[:deletes].should be(2)}
356
+ it {results[:renames].should be(3)}
357
+ it {results[:copies].should be(1)}
358
+ it {results[:merges].should be(0)}
359
+ end
360
+ end
361
+
362
+ describe "#save" do
363
+ context "with pretty" do
364
+ commits = Commits.new
365
+ commits.load(fixture("single_author.json"))
366
+ commits.save("tmp.json", true)
367
+ same = FileUtils.compare_file("tmp.json", fixture("single_author.json"))
368
+ FileUtils.remove_file("tmp.json")
369
+ it { same.should be_true }
370
+ end
371
+
372
+ context "no pretty" do
373
+ commits = Commits.new
374
+ commits.load(fixture("single_author.json"))
375
+ commits.save("tmp.json", false)
376
+ same = FileUtils.compare_file("tmp.json", fixture("single_author_unpretty.json"))
377
+ FileUtils.remove_file("tmp.json")
378
+ it { same.should be_true }
379
+ end
380
+ end
381
+ end
@@ -0,0 +1,62 @@
1
+ {
2
+ "49164f12e4cd3b253e5de31242bc716c9e7d6f95": {
3
+ "author": "Bart Simpson",
4
+ "author_email": "bart.simpson@gmail.com",
5
+ "time": "2012-04-06 11:58:16 -0400",
6
+ "insertions": 3,
7
+ "deletions": 2,
8
+ "creates": 2,
9
+ "deletes": 0,
10
+ "renames": 0,
11
+ "copies": 0,
12
+ "merge": false
13
+ },
14
+ "642276027055befb50999c7c155391402bb7c2e8": {
15
+ "author": "Kevin Jalbert",
16
+ "author_email": "kevin.j.jalbert@gmail.com",
17
+ "time": "2012-04-09 21:02:42 -0400",
18
+ "insertions": 62,
19
+ "deletions": 6,
20
+ "creates": 0,
21
+ "deletes": 1,
22
+ "renames": 0,
23
+ "copies": 0,
24
+ "merge": true
25
+ },
26
+ "754e18e83c748700d883048dbc889c7b0db7121d": {
27
+ "author": "Bart Simpson",
28
+ "author_email": "bart.simpson@gmail.com",
29
+ "time": "2012-04-09 21:15:46 -0400",
30
+ "insertions": 10,
31
+ "deletions": 1,
32
+ "creates": 0,
33
+ "deletes": 0,
34
+ "renames": 2,
35
+ "copies": 0,
36
+ "merge": true
37
+ },
38
+ "f9e638cd4bf18d4f6db27a9c085a9f558f762cc3": {
39
+ "author": "Maggie Simpson",
40
+ "author_email": "maggie.simpson@gmail.com",
41
+ "time": "2012-04-09 21:42:07 -0400",
42
+ "insertions": 8,
43
+ "deletions": 5,
44
+ "creates": 4,
45
+ "deletes": 1,
46
+ "renames": 2,
47
+ "copies": 1,
48
+ "merge": false
49
+ },
50
+ "fba10799b65b0e50c8dc355c9acae08d22b193ae": {
51
+ "author": "Maggie Simpson",
52
+ "author_email": "maggie.simpson@gmail.com",
53
+ "time": "2012-04-11 11:48:07 -0400",
54
+ "insertions": 203,
55
+ "deletions": 187,
56
+ "creates": 3,
57
+ "deletes": 1,
58
+ "renames": 1,
59
+ "copies": 0,
60
+ "merge": false
61
+ }
62
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "f9e638cd4bf18d4f6db27a9c085a9f558f762cc3": {
3
+ "author": "Kevin Jalbert",
4
+ "author_email": "kevin.j.jalbert@gmail.com",
5
+ "time": "2012-04-09 21:42:07 -0400",
6
+ "insertions": 8,
7
+ "deletions": 5,
8
+ "creates": 0,
9
+ "deletes": 0,
10
+ "renames": 0,
11
+ "copies": 0,
12
+ "merge": false
13
+ },
14
+ "fba10799b65b0e50c8dc355c9acae08d22b193ae": {
15
+ "author": "Kevin Jalbert",
16
+ "author_email": "kevin.j.jalbert@gmail.com",
17
+ "time": "2012-04-11 11:48:07 -0400",
18
+ "insertions": 203,
19
+ "deletions": 187,
20
+ "creates": 3,
21
+ "deletes": 1,
22
+ "renames": 1,
23
+ "copies": 0,
24
+ "merge": false
25
+ }
26
+ }
@@ -0,0 +1 @@
1
+ {"f9e638cd4bf18d4f6db27a9c085a9f558f762cc3":{"author":"Kevin Jalbert","author_email":"kevin.j.jalbert@gmail.com","time":"2012-04-09 21:42:07 -0400","insertions":8,"deletions":5,"creates":0,"deletes":0,"renames":0,"copies":0,"merge":false},"fba10799b65b0e50c8dc355c9acae08d22b193ae":{"author":"Kevin Jalbert","author_email":"kevin.j.jalbert@gmail.com","time":"2012-04-11 11:48:07 -0400","insertions":203,"deletions":187,"creates":3,"deletes":1,"renames":1,"copies":0,"merge":false}}
@@ -0,0 +1,11 @@
1
+ require 'simplecov'
2
+
3
+ SimpleCov.start do
4
+ add_filter "/spec/"
5
+ end
6
+
7
+ require File.dirname(__FILE__) + '/../lib/git_statistics/initialize.rb'
8
+
9
+ def fixture(file)
10
+ File.new(File.dirname(__FILE__) + '/fixtures/' + file, 'r')
11
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: git_statistics
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Kevin Jalbert
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-04-12 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: json
16
+ requirement: &14258420 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *14258420
25
+ - !ruby/object:Gem::Dependency
26
+ name: trollop
27
+ requirement: &14257940 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *14257940
36
+ description: git_statistics is a gem that provides detailed git statistics
37
+ email:
38
+ - kevin.j.jalbert@gmail.com
39
+ executables:
40
+ - git_statistics
41
+ extensions: []
42
+ extra_rdoc_files: []
43
+ files:
44
+ - .gitignore
45
+ - .travis.yml
46
+ - Gemfile
47
+ - Gemfile.lock
48
+ - LICENSE
49
+ - README.md
50
+ - Rakefile
51
+ - bin/git_statistics
52
+ - git_statistics.gemspec
53
+ - lib/git_statistics.rb
54
+ - lib/git_statistics/collector.rb
55
+ - lib/git_statistics/commits.rb
56
+ - lib/git_statistics/initialize.rb
57
+ - lib/git_statistics/version.rb
58
+ - spec/collector_spec.rb
59
+ - spec/commits_spec.rb
60
+ - spec/fixtures/many_authors.json
61
+ - spec/fixtures/single_author.json
62
+ - spec/fixtures/single_author_unpretty.json
63
+ - spec/spec_helper.rb
64
+ homepage: https://github.com/kevinjalbert/git_statistics
65
+ licenses: []
66
+ post_install_message:
67
+ rdoc_options: []
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
75
+ version: 1.9.1
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ! '>='
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ segments:
83
+ - 0
84
+ hash: -1773514383536300330
85
+ requirements: []
86
+ rubyforge_project:
87
+ rubygems_version: 1.8.15
88
+ signing_key:
89
+ specification_version: 3
90
+ summary: Gem that provides the ability to gather detailed git statistics
91
+ test_files:
92
+ - spec/collector_spec.rb
93
+ - spec/commits_spec.rb
94
+ - spec/fixtures/many_authors.json
95
+ - spec/fixtures/single_author.json
96
+ - spec/fixtures/single_author_unpretty.json
97
+ - spec/spec_helper.rb