git_team_stats 0.0.1
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.
- 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: []
|