git_team_stats 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.git_team_stats.json.example +28 -0
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +206 -0
- data/Rakefile +8 -0
- data/bin/git_team_stats +207 -0
- data/git_team_stats.gemspec +31 -0
- data/lib/git_team_stats.rb +138 -0
- data/lib/git_team_stats/git_parse.rb +197 -0
- data/lib/git_team_stats/version.rb +3 -0
- metadata +156 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5157e6d24bbac821867bd8c7dd0384a3202169db
|
4
|
+
data.tar.gz: 136c1b15bcf2263f886c67685dd5b95cc032dd53
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fdda33147352b1d66709a865b84e03428bb6a2068486cce3104d2fa00412f9b829d75d1eb7410a75200d470a4874cc694af657578f0987c9d5a00060df6d0f7b
|
7
|
+
data.tar.gz: 8d2d3ac426e201a568a222e7e923c637736d63e3839401b841bad95f6be34008031e83e849dcfc8b6ff2d4f6a6a9f7446a27a9c5579da58ea07c492bfd3fdbb1
|
@@ -0,0 +1,28 @@
|
|
1
|
+
{
|
2
|
+
"users" : [
|
3
|
+
{
|
4
|
+
"name" : "you",
|
5
|
+
"aliases" : ["Your Alias #1", "yourotheralias"],
|
6
|
+
"default" : true
|
7
|
+
},
|
8
|
+
{
|
9
|
+
"name" : "collaborator",
|
10
|
+
"aliases" : ["Their alias"]
|
11
|
+
}
|
12
|
+
],
|
13
|
+
"projects" : [
|
14
|
+
{
|
15
|
+
"name" : "project 1",
|
16
|
+
"repos" : ["~/src/proj1", "~/src/dependencies/proj1-dep"],
|
17
|
+
"ignored_directories" : ["Some/directory/you/forgot/to/gitignore/"],
|
18
|
+
"default" : true,
|
19
|
+
"start_date" : 1369253382,
|
20
|
+
"end_date" : 1369356790
|
21
|
+
},
|
22
|
+
{
|
23
|
+
"name" : "Other project",
|
24
|
+
"repos" : ["~/src/OtherProject"],
|
25
|
+
"ignored_directories" : []
|
26
|
+
}
|
27
|
+
]
|
28
|
+
}
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Simon Holroyd
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,206 @@
|
|
1
|
+
# GitTeamStats
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'git_team_stats'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install git_team_stats
|
18
|
+
|
19
|
+
|
20
|
+
## Configure
|
21
|
+
|
22
|
+
Configuration uses ```.gitstatistics.json``` file. To start, add your own ```.gitstatistics.json``` based off of ```.gitstatistics.json.example``` and edit according to your needs.
|
23
|
+
|
24
|
+
```json
|
25
|
+
{
|
26
|
+
"users" : [
|
27
|
+
{
|
28
|
+
"name" : "you",
|
29
|
+
"aliases" : ["Your Alias #1", "yourotheralias"],
|
30
|
+
"default" : true
|
31
|
+
},
|
32
|
+
{
|
33
|
+
"name" : "collaborator",
|
34
|
+
"aliases" : ["Their alias"]
|
35
|
+
}
|
36
|
+
],
|
37
|
+
"projects" : [
|
38
|
+
{
|
39
|
+
"name" : "project 1",
|
40
|
+
"repos" : ["~/src/proj1", "~/src/dependencies/proj1-dep"],
|
41
|
+
"ignored_directories" : ["Some/directory/you/forgot/to/gitignore/"],
|
42
|
+
"default" : true,
|
43
|
+
"start_date" : 1369253382,
|
44
|
+
"end_date" : 1369356790
|
45
|
+
},
|
46
|
+
{
|
47
|
+
"name" : "Other project",
|
48
|
+
"repos" : ["~/src/OtherProject"],
|
49
|
+
"ignored_directories" : []
|
50
|
+
}
|
51
|
+
]
|
52
|
+
}
|
53
|
+
```
|
54
|
+
|
55
|
+
### Defaults
|
56
|
+
|
57
|
+
If ```projects``` is left empty, the program will default to running on the current working directory
|
58
|
+
|
59
|
+
## Usage
|
60
|
+
|
61
|
+
Basic usage is as follows
|
62
|
+
|
63
|
+
```
|
64
|
+
$ git_team_stats [global options] command [command options] [arguments...]
|
65
|
+
```
|
66
|
+
|
67
|
+
### Options
|
68
|
+
|
69
|
+
**cache_path**: ```--cache_path=path```
|
70
|
+
- The path to your cache files (default: ./tmp)
|
71
|
+
|
72
|
+
**config_path**: ```--config_path=path```
|
73
|
+
- The path to your config file (default: ./.git_team_stats.json)
|
74
|
+
|
75
|
+
**project**: ```--project=name```
|
76
|
+
- The project name to run on (default: none). Make sure you have projects set in your config file.
|
77
|
+
|
78
|
+
**help**: ```--help```
|
79
|
+
- Show this message
|
80
|
+
|
81
|
+
**ignore_cache**: ```--ignore_cache```
|
82
|
+
- A switch to turn off loading from cache
|
83
|
+
|
84
|
+
**no_cache**: ```--no_cache```
|
85
|
+
- A switch to turn off writing to cache
|
86
|
+
|
87
|
+
### Commands
|
88
|
+
|
89
|
+
##### Count Commits
|
90
|
+
|
91
|
+
```
|
92
|
+
$ git_team_stats count_commits
|
93
|
+
```
|
94
|
+
|
95
|
+
Returns the total number of commits across all your repos in the given project
|
96
|
+
|
97
|
+
|
98
|
+
##### Inspect Commits
|
99
|
+
|
100
|
+
```
|
101
|
+
$ git_team_stats inspect_commits
|
102
|
+
```
|
103
|
+
|
104
|
+
Runs the full inspection on your project repos
|
105
|
+
|
106
|
+
|
107
|
+
##### Team Cumulative Commits
|
108
|
+
|
109
|
+
```
|
110
|
+
$ git_team_stats team_cumulative_stats
|
111
|
+
```
|
112
|
+
|
113
|
+
Gather cumulative statistics on the team. Example output:
|
114
|
+
|
115
|
+
```
|
116
|
+
~~~ Cumulative Team Statistics ~~~~~~~~
|
117
|
+
commits : 1641
|
118
|
+
lines : 73630
|
119
|
+
edits : 196752
|
120
|
+
languages:
|
121
|
+
Objective-C
|
122
|
+
edits : 127530
|
123
|
+
lines : 47940
|
124
|
+
Ruby
|
125
|
+
edits : 2810
|
126
|
+
lines : 2118
|
127
|
+
Markdown
|
128
|
+
edits : 3592
|
129
|
+
lines : 112
|
130
|
+
Shell
|
131
|
+
edits : 427
|
132
|
+
lines : 381
|
133
|
+
YAML
|
134
|
+
edits : 203
|
135
|
+
lines : 165
|
136
|
+
PHP
|
137
|
+
edits : 16206
|
138
|
+
lines : 10630
|
139
|
+
SCSS
|
140
|
+
edits : 4746
|
141
|
+
lines : 3164
|
142
|
+
JavaScript
|
143
|
+
edits : 12926
|
144
|
+
lines : 11672
|
145
|
+
JSON
|
146
|
+
edits : 7221
|
147
|
+
lines : 7183
|
148
|
+
```
|
149
|
+
|
150
|
+
|
151
|
+
##### User Cumulative Commits
|
152
|
+
|
153
|
+
```
|
154
|
+
$ git_team_stats user_cumulative_stats [--user]
|
155
|
+
```
|
156
|
+
|
157
|
+
Gather cumulative statistics on a team member. Note you should either have a default user in your config file or pass a user name in the CLI. Example output:
|
158
|
+
|
159
|
+
```
|
160
|
+
~~~ Cumulative User Statistics (Simon Holroyd) ~~~~~~~~
|
161
|
+
commits : 839
|
162
|
+
lines : 82525
|
163
|
+
edits : 110461
|
164
|
+
languages:
|
165
|
+
Objective-C
|
166
|
+
edits : 97762
|
167
|
+
lines : 75868
|
168
|
+
Markdown
|
169
|
+
edits : 1720
|
170
|
+
lines : 1696
|
171
|
+
PHP
|
172
|
+
edits : 1636
|
173
|
+
lines : 660
|
174
|
+
SCSS
|
175
|
+
edits : 1719
|
176
|
+
lines : 977
|
177
|
+
JavaScript
|
178
|
+
edits : 173
|
179
|
+
lines : 41
|
180
|
+
```
|
181
|
+
|
182
|
+
|
183
|
+
##### Team Most Productive Hour
|
184
|
+
|
185
|
+
Coming soon
|
186
|
+
|
187
|
+
##### User Most Productive Hour
|
188
|
+
|
189
|
+
Coming soon
|
190
|
+
|
191
|
+
##### Codebase over time
|
192
|
+
|
193
|
+
Coming soon
|
194
|
+
|
195
|
+
##### User contribution over time
|
196
|
+
|
197
|
+
Coming soon
|
198
|
+
|
199
|
+
|
200
|
+
## Contributing
|
201
|
+
|
202
|
+
1. Fork it
|
203
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
204
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
205
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
206
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/bin/git_team_stats
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'progress_bar'
|
5
|
+
require 'language_sniffer'
|
6
|
+
require 'gli'
|
7
|
+
require 'git_team_stats'
|
8
|
+
|
9
|
+
include GLI::App
|
10
|
+
|
11
|
+
program_desc 'Analyze commits and committers in mulitple git repos and output project-level statistics'
|
12
|
+
|
13
|
+
desc "The path to your config file"
|
14
|
+
flag [:config_path], :default_value => File.join(Dir.pwd,'.git_team_stats.json')
|
15
|
+
desc "The path to your cache files"
|
16
|
+
flag [:cache_path], :default_value => File.join(Dir.pwd,'tmp')
|
17
|
+
desc "The time period to group values by"
|
18
|
+
flag [:time_period], :default_value => :week
|
19
|
+
desc "The project name to run on"
|
20
|
+
flag [:project], :default_value => :none
|
21
|
+
desc "A switch to turn off loading from cache"
|
22
|
+
switch [:ignore_cache]
|
23
|
+
desc "A switch to turn off writing to cache"
|
24
|
+
switch [:no_cache]
|
25
|
+
|
26
|
+
pre do |global_options,command,options,args|
|
27
|
+
$dir = Dir.pwd
|
28
|
+
|
29
|
+
$users = []
|
30
|
+
$projects = []
|
31
|
+
$project = nil
|
32
|
+
|
33
|
+
if File.exists? global_options[:config_path]
|
34
|
+
config = JSON.parse(File.read(global_options[:config_path]), {:symbolize_names => true})
|
35
|
+
|
36
|
+
$users = (config[:users].empty?) ? [] : config[:users]
|
37
|
+
$projects = (config[:projects].empty?) ? [] : config[:projects]
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
if $projects.empty?
|
43
|
+
$projects.push({
|
44
|
+
:name => "current",
|
45
|
+
:repos => [Dir.pwd],
|
46
|
+
:ignored_directories => [],
|
47
|
+
:default => true
|
48
|
+
})
|
49
|
+
end
|
50
|
+
|
51
|
+
true
|
52
|
+
end
|
53
|
+
|
54
|
+
def require_user(options)
|
55
|
+
|
56
|
+
if options[:user] == :none
|
57
|
+
if $users.select{ :default == true }.empty?
|
58
|
+
help_now!('user is required, either as a command line arg or a default in .git_team_stats.json')
|
59
|
+
else
|
60
|
+
$user = $users.select{ :default == true }[0]
|
61
|
+
end
|
62
|
+
else
|
63
|
+
$user = {
|
64
|
+
:name => options[:user].to_s,
|
65
|
+
:aliases => [options[:user].to_s]
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
def require_project(global_options)
|
72
|
+
if global_options[:project] == :none
|
73
|
+
if !$projects.select{ |proj| proj[:default] == true }.empty?
|
74
|
+
$project = $projects.select{ |proj| proj[:default] == true }[0]
|
75
|
+
end
|
76
|
+
else
|
77
|
+
if !$projects.select{ |proj| proj[:name] == global_options[:project] }.empty?
|
78
|
+
$project = $projects.select{ |proj| proj[:name] == global_options[:project] }[0]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
if ($project == nil)
|
82
|
+
puts $projects
|
83
|
+
help_now!('project is required, either as a command line arg or a default in .git_team_stats.json')
|
84
|
+
end
|
85
|
+
|
86
|
+
GitTeamStats.start($project)
|
87
|
+
|
88
|
+
load_cache(global_options)
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
def load_cache(global_options)
|
93
|
+
if (global_options[:ignore_cache])
|
94
|
+
return
|
95
|
+
end
|
96
|
+
|
97
|
+
cache_file_path = File.join(global_options[:cache_path], GitTeamStats.get_cache_file_name() + '.json')
|
98
|
+
|
99
|
+
if (File.exist?(cache_file_path))
|
100
|
+
output = JSON.parse(File.open(cache_file_path, 'rb').read, {:symbolize_names => true})
|
101
|
+
puts "Loading data from cache".color(:yellow)
|
102
|
+
GitTeamStats.load_from_cache(output)
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
desc "Count the total commits in the project"
|
109
|
+
command :count_commits do |c|
|
110
|
+
c.action do |global_options,options,args|
|
111
|
+
require_project(global_options)
|
112
|
+
|
113
|
+
GitTeamStats.inspect_commits()
|
114
|
+
puts "Total commits : " + GitTeamStats.count_commits().to_s
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
desc "Run the standard project inspection"
|
119
|
+
command :inspect_commits do |c|
|
120
|
+
c.action do |global_options,options,args|
|
121
|
+
require_project(global_options)
|
122
|
+
|
123
|
+
puts GitTeamStats.inspect_commits()
|
124
|
+
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
desc "Gather cumulative statistics on the team"
|
129
|
+
command :team_cumulative_stats do |c|
|
130
|
+
c.action do |global_options,options,args|
|
131
|
+
require_project(global_options)
|
132
|
+
|
133
|
+
stats = GitTeamStats.team_cumulative_stats()
|
134
|
+
puts "~~~ Cumulative Team Statistics ~~~~~~~~".color(:green)
|
135
|
+
stats.each{ |key, value|
|
136
|
+
if (key == :file_types)
|
137
|
+
puts "languages: "
|
138
|
+
value.each{ |language, data|
|
139
|
+
puts " " + language.to_s
|
140
|
+
puts " edits : " + data[:edits].to_s
|
141
|
+
puts " lines : " + data[:lines].to_s
|
142
|
+
}
|
143
|
+
else
|
144
|
+
puts key.to_s + " : " + value.to_s
|
145
|
+
end
|
146
|
+
}
|
147
|
+
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
desc "Gather cumulative statistics on a specific user"
|
152
|
+
command :user_cumulative_stats do |c|
|
153
|
+
c.flag [:user], :default_value => :none
|
154
|
+
c.action do |global_options,options,args|
|
155
|
+
require_project(global_options)
|
156
|
+
require_user(options)
|
157
|
+
|
158
|
+
stats = GitTeamStats.user_cumulative_stats($user)
|
159
|
+
headline = "~~~ Cumulative User Statistics (%s) ~~~~~~~~" % $user[:name].to_s
|
160
|
+
puts headline.color(:green)
|
161
|
+
stats.each{ |key, value|
|
162
|
+
if (key == :file_types)
|
163
|
+
puts "languages: "
|
164
|
+
value.each{ |language, data|
|
165
|
+
puts " " + language.to_s
|
166
|
+
puts " edits : " + data[:edits].to_s
|
167
|
+
puts " lines : " + data[:lines].to_s
|
168
|
+
}
|
169
|
+
else
|
170
|
+
puts key.to_s + " : " + value.to_s
|
171
|
+
end
|
172
|
+
}
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
|
177
|
+
post do |global_options,command,options,args|
|
178
|
+
if (global_options[:no_cache])
|
179
|
+
return
|
180
|
+
end
|
181
|
+
|
182
|
+
if (Dir.exist?(global_options[:cache_path]))
|
183
|
+
output = GitTeamStats.get_cache_output()
|
184
|
+
cache_file_path = File.join(global_options[:cache_path], GitTeamStats.get_cache_file_name() + '.json')
|
185
|
+
|
186
|
+
if (File.exist?(cache_file_path))
|
187
|
+
f = File.open(cache_file_path, "w+")
|
188
|
+
else
|
189
|
+
f = File.new(cache_file_path, "w+")
|
190
|
+
end
|
191
|
+
f.write(output.to_json)
|
192
|
+
f.close()
|
193
|
+
|
194
|
+
|
195
|
+
else
|
196
|
+
puts "Could not cache results, could not write to cache directory".color(:yellow)
|
197
|
+
end
|
198
|
+
|
199
|
+
true
|
200
|
+
end
|
201
|
+
|
202
|
+
on_error do |exception|
|
203
|
+
puts exception
|
204
|
+
end
|
205
|
+
|
206
|
+
|
207
|
+
exit run(ARGV)
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'git_team_stats/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "git_team_stats"
|
8
|
+
spec.version = GitTeamStats::VERSION
|
9
|
+
spec.authors = ["Simon Holroyd"]
|
10
|
+
spec.email = ["sholroyd@gmail.com"]
|
11
|
+
spec.description = %q{git_team_stats is a gem that compiles contribution statistics for projects that span mulitple repos}
|
12
|
+
spec.summary = %q{git_team_stats is a gem that compiles contribution statistics for projects that span mulitple repos}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
|
24
|
+
spec.add_dependency 'json'
|
25
|
+
spec.add_dependency 'progress_bar'
|
26
|
+
spec.add_dependency 'rainbow'
|
27
|
+
spec.add_dependency 'language_sniffer'
|
28
|
+
spec.add_dependency 'gli'
|
29
|
+
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require "git_team_stats/version"
|
2
|
+
require "git_team_stats/git_parse"
|
3
|
+
|
4
|
+
module GitTeamStats
|
5
|
+
|
6
|
+
@project = nil
|
7
|
+
|
8
|
+
@inspected = false
|
9
|
+
|
10
|
+
def self.start(project)
|
11
|
+
@project = project
|
12
|
+
|
13
|
+
@commits_in_detail = []
|
14
|
+
|
15
|
+
@project[:parsers] = []
|
16
|
+
|
17
|
+
@project[:repos].each do |repo|
|
18
|
+
@project[:parsers].push(
|
19
|
+
GitParse.new(repo)
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.count_commits
|
26
|
+
count = 0
|
27
|
+
|
28
|
+
if !@inspected
|
29
|
+
self.inspect_commits()
|
30
|
+
end
|
31
|
+
|
32
|
+
return @commits_in_detail.length
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.collect_commits
|
36
|
+
unless @inspected
|
37
|
+
|
38
|
+
@project[:parsers].each do |parser|
|
39
|
+
parser.get_commits()
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.inspect_commits
|
45
|
+
unless @inspected
|
46
|
+
|
47
|
+
self.collect_commits()
|
48
|
+
|
49
|
+
@project[:parsers].each do |parser|
|
50
|
+
@commits_in_detail += parser.get_commit_details()
|
51
|
+
end
|
52
|
+
|
53
|
+
@inspected = true
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.team_cumulative_stats
|
58
|
+
self.inspect_commits()
|
59
|
+
|
60
|
+
team_cumulative = {
|
61
|
+
:commits => 0,
|
62
|
+
:lines => 0,
|
63
|
+
:edits => 0,
|
64
|
+
:file_types => {}
|
65
|
+
}
|
66
|
+
@commits_in_detail.each do |commit|
|
67
|
+
team_cumulative[:commits] += 1
|
68
|
+
team_cumulative[:lines] += commit[:lines]
|
69
|
+
team_cumulative[:edits] += commit[:edits]
|
70
|
+
|
71
|
+
commit[:file_types].each do |language, details|
|
72
|
+
if team_cumulative[:file_types].key? language
|
73
|
+
team_cumulative[:file_types][language][:lines] += details[:lines]
|
74
|
+
team_cumulative[:file_types][language][:edits] += details[:edits]
|
75
|
+
else
|
76
|
+
team_cumulative[:file_types][language] = {
|
77
|
+
:lines => details[:lines],
|
78
|
+
:edits => details[:edits]
|
79
|
+
}
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
return team_cumulative
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.user_cumulative_stats(user)
|
88
|
+
self.inspect_commits()
|
89
|
+
|
90
|
+
user_cumulative = {
|
91
|
+
:commits => 0,
|
92
|
+
:lines => 0,
|
93
|
+
:edits => 0,
|
94
|
+
:file_types => {}
|
95
|
+
}
|
96
|
+
@commits_in_detail.each do |commit|
|
97
|
+
if user[:aliases].any?{ |name| name == commit[:committer] }
|
98
|
+
user_cumulative[:commits] += 1
|
99
|
+
user_cumulative[:lines] += commit[:lines]
|
100
|
+
user_cumulative[:edits] += commit[:edits]
|
101
|
+
|
102
|
+
commit[:file_types].each do |language, details|
|
103
|
+
if user_cumulative[:file_types].key? language
|
104
|
+
user_cumulative[:file_types][language][:lines] += details[:lines]
|
105
|
+
user_cumulative[:file_types][language][:edits] += details[:edits]
|
106
|
+
else
|
107
|
+
user_cumulative[:file_types][language] = {
|
108
|
+
:lines => details[:lines],
|
109
|
+
:edits => details[:edits]
|
110
|
+
}
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
return user_cumulative
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.get_cache_output
|
120
|
+
return @commits_in_detail
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.load_from_cache(data)
|
124
|
+
@commits_in_detail = data
|
125
|
+
@inspected = true
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.get_cache_file_name
|
129
|
+
hash_str = ""
|
130
|
+
@project[:parsers].each{ |parser|
|
131
|
+
hash_str += parser.get_head_short_hash().to_s + "-"
|
132
|
+
}
|
133
|
+
|
134
|
+
return $project[:name].to_s + "-" + hash_str + @project[:start_date].to_s + '-' + @project[:end_date].to_s
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
require 'progress_bar'
|
2
|
+
require 'rainbow'
|
3
|
+
require 'language_sniffer'
|
4
|
+
|
5
|
+
class GitParse
|
6
|
+
|
7
|
+
|
8
|
+
def initialize(directory)
|
9
|
+
@commits = []
|
10
|
+
|
11
|
+
@ignored_directories = []
|
12
|
+
@start_date = nil
|
13
|
+
@end_date = nil
|
14
|
+
|
15
|
+
@directory = directory
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def execute(command, directory = ".")
|
20
|
+
directory = `cd #{directory}; #{command}`
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def get_commits
|
25
|
+
|
26
|
+
hash = ""
|
27
|
+
|
28
|
+
puts "finding commits in #{@directory}".color(:yellow)
|
29
|
+
rev_list = get_rev_list()
|
30
|
+
bar = ProgressBar.new(rev_list.lines.length, :bar, :percentage, :eta);
|
31
|
+
rev_list.lines.each do |line|
|
32
|
+
bar.increment!
|
33
|
+
if ( line =~ /^commit ([\w]+)/)
|
34
|
+
hash = $1
|
35
|
+
next
|
36
|
+
else hash != ""
|
37
|
+
if ( line =~ /^([\d]+)\s(.+)\s<(.+)>$/ )
|
38
|
+
timestamp = $1.to_i
|
39
|
+
committer = $2
|
40
|
+
email = $3
|
41
|
+
end
|
42
|
+
|
43
|
+
@commits.push({
|
44
|
+
:hash => hash,
|
45
|
+
:committer => committer,
|
46
|
+
:path => @directory,
|
47
|
+
:timestamp => timestamp,
|
48
|
+
})
|
49
|
+
|
50
|
+
hash = ""
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
return @commits
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
|
59
|
+
def count_commits
|
60
|
+
if @commits.empty?
|
61
|
+
get_commits()
|
62
|
+
end
|
63
|
+
|
64
|
+
return @commits.count
|
65
|
+
end
|
66
|
+
|
67
|
+
def get_authors
|
68
|
+
users = []
|
69
|
+
get_authors_from_shortlog().lines.each do |line|
|
70
|
+
if !users.any? {|user| user[:name] = line }
|
71
|
+
users.push({
|
72
|
+
:name => line.strip,
|
73
|
+
:aliases => [line.strip]
|
74
|
+
})
|
75
|
+
end
|
76
|
+
end
|
77
|
+
return users
|
78
|
+
end
|
79
|
+
|
80
|
+
def get_authors_from_shortlog
|
81
|
+
return execute("git shortlog -s | cut -c8-", @directory)
|
82
|
+
end
|
83
|
+
|
84
|
+
def get_head_short_hash
|
85
|
+
return execute("git rev-parse --short HEAD", @directory).strip
|
86
|
+
end
|
87
|
+
|
88
|
+
def get_rev_list
|
89
|
+
date_str = ""
|
90
|
+
if @start_date != nil
|
91
|
+
date_str += "--max-age=%s " % [@start_date.to_i.to_s]
|
92
|
+
end
|
93
|
+
if @end_date != nil
|
94
|
+
date_str += "--min-age=%s " % [@end_date.to_i.to_s]
|
95
|
+
end
|
96
|
+
return execute("git rev-list --reverse --pretty=format:\"%%at %%aN <%%aE>\" %s HEAD" % date_str, @directory)
|
97
|
+
end
|
98
|
+
|
99
|
+
def get_diff_tree(commit_hash)
|
100
|
+
return execute("git diff-tree %s --numstat" % commit_hash, @directory)
|
101
|
+
end
|
102
|
+
|
103
|
+
def get_file(commit_hash, file_name)
|
104
|
+
return execute("git show %s:%s 2>&1" % [commit_hash, file_name], @directory)
|
105
|
+
end
|
106
|
+
|
107
|
+
def parse_diff_tree(commit_hash)
|
108
|
+
|
109
|
+
total_lines = 0
|
110
|
+
total_edits = 0
|
111
|
+
file_types = {}
|
112
|
+
|
113
|
+
diff_tree = get_diff_tree(commit_hash)
|
114
|
+
|
115
|
+
diff_tree.lines.each do |line|
|
116
|
+
|
117
|
+
if ( line =~ /^([\d]+)\s([\d]+)(.*[^\w](\w+))$/)
|
118
|
+
|
119
|
+
insertions = $1.to_i
|
120
|
+
deletions = $2.to_i
|
121
|
+
|
122
|
+
lines = insertions - deletions
|
123
|
+
edits = insertions + deletions
|
124
|
+
|
125
|
+
file_name = $3.strip!
|
126
|
+
|
127
|
+
extension = $4
|
128
|
+
|
129
|
+
if (@ignored_directories.any?{ |obj| (file_name.index(obj) == 0) })
|
130
|
+
next
|
131
|
+
end
|
132
|
+
|
133
|
+
language = get_language_for_file(file_name, commit_hash)
|
134
|
+
|
135
|
+
if language == nil
|
136
|
+
next
|
137
|
+
end
|
138
|
+
|
139
|
+
if (file_types.key? language.name.to_sym)
|
140
|
+
file_types[language.name.to_sym][:lines] += lines
|
141
|
+
file_types[language.name.to_sym][:edits] += edits
|
142
|
+
else
|
143
|
+
file_types[language.name.to_sym] = {
|
144
|
+
:lines => lines,
|
145
|
+
:edits => edits
|
146
|
+
}
|
147
|
+
end
|
148
|
+
|
149
|
+
total_lines += lines
|
150
|
+
total_edits += edits
|
151
|
+
|
152
|
+
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
return {
|
157
|
+
:lines => total_lines,
|
158
|
+
:edits => total_edits,
|
159
|
+
:file_types => file_types
|
160
|
+
}
|
161
|
+
|
162
|
+
end
|
163
|
+
|
164
|
+
def get_language_for_file(file_name, commit_hash)
|
165
|
+
escaped_file_name = file_name.gsub(/([\[\(\)\]\{\}\*\?\\])/, '\\\\\1')
|
166
|
+
|
167
|
+
full_file_path = File.join(Dir.home, @directory.gsub(/^~/, ""), file_name)
|
168
|
+
|
169
|
+
if File.file? full_file_path
|
170
|
+
language = LanguageSniffer.detect(full_file_path).language;
|
171
|
+
else
|
172
|
+
language = LanguageSniffer.detect(full_file_path, :content => get_file(commit_hash, escaped_file_name), :path => full_file_path).language;
|
173
|
+
end
|
174
|
+
|
175
|
+
return language
|
176
|
+
end
|
177
|
+
|
178
|
+
def get_commit_details
|
179
|
+
|
180
|
+
puts "analyzing commits in #{@directory}".color(:yellow)
|
181
|
+
|
182
|
+
bar = ProgressBar.new(@commits.length, :bar, :percentage, :eta);
|
183
|
+
|
184
|
+
@commits.each { |commit|
|
185
|
+
|
186
|
+
bar.increment!
|
187
|
+
|
188
|
+
diff_tree = parse_diff_tree(commit[:hash])
|
189
|
+
|
190
|
+
commit.merge!(diff_tree)
|
191
|
+
}
|
192
|
+
|
193
|
+
return @commits
|
194
|
+
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|
metadata
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: git_team_stats
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Simon Holroyd
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-06-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: json
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: progress_bar
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rainbow
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: language_sniffer
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: gli
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
description: git_team_stats is a gem that compiles contribution statistics for projects
|
112
|
+
that span mulitple repos
|
113
|
+
email:
|
114
|
+
- sholroyd@gmail.com
|
115
|
+
executables:
|
116
|
+
- git_team_stats
|
117
|
+
extensions: []
|
118
|
+
extra_rdoc_files: []
|
119
|
+
files:
|
120
|
+
- .git_team_stats.json.example
|
121
|
+
- .gitignore
|
122
|
+
- Gemfile
|
123
|
+
- LICENSE.txt
|
124
|
+
- README.md
|
125
|
+
- Rakefile
|
126
|
+
- bin/git_team_stats
|
127
|
+
- git_team_stats.gemspec
|
128
|
+
- lib/git_team_stats.rb
|
129
|
+
- lib/git_team_stats/git_parse.rb
|
130
|
+
- lib/git_team_stats/version.rb
|
131
|
+
homepage: ''
|
132
|
+
licenses:
|
133
|
+
- MIT
|
134
|
+
metadata: {}
|
135
|
+
post_install_message:
|
136
|
+
rdoc_options: []
|
137
|
+
require_paths:
|
138
|
+
- lib
|
139
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
140
|
+
requirements:
|
141
|
+
- - '>='
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '0'
|
144
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
145
|
+
requirements:
|
146
|
+
- - '>='
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: '0'
|
149
|
+
requirements: []
|
150
|
+
rubyforge_project:
|
151
|
+
rubygems_version: 2.0.0
|
152
|
+
signing_key:
|
153
|
+
specification_version: 4
|
154
|
+
summary: git_team_stats is a gem that compiles contribution statistics for projects
|
155
|
+
that span mulitple repos
|
156
|
+
test_files: []
|