commit-comment-tools 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +8 -0
- data/Gemfile +20 -0
- data/README.md +26 -0
- data/Rakefile +46 -0
- data/bin/git-stats +71 -0
- data/bin/report-analyzer +73 -0
- data/commit-comment-tools.gemspec +67 -0
- data/doc/text/gpl-3.0.txt +674 -0
- data/doc/text/news.md +5 -0
- data/doc/text/scenario.txt +168 -0
- data/lib/commit-comment-tools/entry.rb +45 -0
- data/lib/commit-comment-tools/generator.rb +4 -0
- data/lib/commit-comment-tools/generator/csv.rb +63 -0
- data/lib/commit-comment-tools/generator/graph.rb +25 -0
- data/lib/commit-comment-tools/report-normalizer.rb +58 -0
- data/lib/commit-comment-tools/report-parser.rb +98 -0
- data/lib/commit-comment-tools/repository-loader.rb +131 -0
- data/lib/commit-comment-tools/repository-stats.rb +113 -0
- data/lib/commit-comment-tools/version.rb +20 -0
- data/test/run-test.rb +32 -0
- data/test/test-entry.rb +78 -0
- data/test/test-report-normalizer.rb +87 -0
- data/test/test-report-parser.rb +111 -0
- metadata +230 -0
@@ -0,0 +1,131 @@
|
|
1
|
+
require "grit"
|
2
|
+
require "active_record"
|
3
|
+
|
4
|
+
Grit::Git.git_timeout = 30 # timeout in secs
|
5
|
+
Grit::Git.git_max_size = 2 * 1024 * 1024 # size in bytes (2MB)
|
6
|
+
|
7
|
+
module Grit
|
8
|
+
class Commit
|
9
|
+
def diff_bytesize
|
10
|
+
@diff_bytesize ||= diffs.inject(0) do |memo, _diff|
|
11
|
+
memo + _diff.diff.bytesize
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def diff_lines_count
|
16
|
+
@diff_lines_count ||= diffs.inject(0) do |memo, _diff|
|
17
|
+
memo + _diff.diff.lines.to_a.size
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module CommitCommentTools
|
24
|
+
class Commit < ActiveRecord::Base
|
25
|
+
end
|
26
|
+
|
27
|
+
class RepositoryLoader
|
28
|
+
def initialize(repository_path, base_branch_name, branch_name="")
|
29
|
+
create_table
|
30
|
+
@repository_path = repository_path
|
31
|
+
@repository = Grit::Repo.new(repository_path)
|
32
|
+
@repository_name = File.basename(repository_path)
|
33
|
+
@base_branch_name = base_branch_name
|
34
|
+
@target_branches = @repository.remotes.select do |branch|
|
35
|
+
branch_name === branch.name
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def load_commits
|
40
|
+
puts @repository_name
|
41
|
+
load_base_branch_commits
|
42
|
+
@target_branches.each do |branch|
|
43
|
+
load_branch_commits(branch.name)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def load_base_branch_commits
|
50
|
+
n_commits = 0
|
51
|
+
n_records = 0
|
52
|
+
skip = Commit.where(repository_name: @repository_name,
|
53
|
+
branch_name: @base_branch_name).count
|
54
|
+
@repository.commits(@base_branch_name, nil, skip).each do |commit|
|
55
|
+
n_commits += 1
|
56
|
+
next if Commit.where(repository_name: @repository_name,
|
57
|
+
branch_name: @base_branch_name,
|
58
|
+
commit_hash: commit.id).exists?
|
59
|
+
begin
|
60
|
+
create_commit(@base_branch_name, commit)
|
61
|
+
n_records += 1
|
62
|
+
rescue Grit::Git::GitTimeout => ex
|
63
|
+
$stderr.puts "#{ex.message}:#{@repository_name}:#{@base_branch_name}:#{commit.id}:#{commit.committed_date}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
puts "#{@base_branch_name}:#{n_records}/#{n_commits}, skip=#{skip}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def load_branch_commits(branch_name)
|
70
|
+
n_commits = 0
|
71
|
+
n_records = 0
|
72
|
+
base_commit_id = base_commit(branch_name)
|
73
|
+
@repository.commits_between(base_commit_id, branch_name).each do |commit|
|
74
|
+
n_commits += 1
|
75
|
+
next if Commit.where(repository_name: @repository_name,
|
76
|
+
branch_name: branch_name,
|
77
|
+
commit_hash: commit.id).exists?
|
78
|
+
begin
|
79
|
+
create_commit(branch_name, commit)
|
80
|
+
n_records += 1
|
81
|
+
rescue Grit::Git::GitTimeout => ex
|
82
|
+
$stderr.puts "#{ex.message}:#{commit.id}:#{commit.committed_date}"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
puts "#{branch_name}:#{base_commit_id}:#{n_records}/#{n_commits}"
|
86
|
+
end
|
87
|
+
|
88
|
+
def create_commit(branch_name, commit)
|
89
|
+
Commit.create(repository_name: @repository_name,
|
90
|
+
branch_name: branch_name,
|
91
|
+
commit_hash: commit.id,
|
92
|
+
commit_message: commit.message,
|
93
|
+
committer_name: commit.committer.name,
|
94
|
+
committer_email: commit.committer.email,
|
95
|
+
committed_date: commit.committed_date,
|
96
|
+
diff_lines_count: commit.diff_lines_count,
|
97
|
+
diff_bytesize: commit.diff_bytesize)
|
98
|
+
end
|
99
|
+
|
100
|
+
def base_commit(branch_name)
|
101
|
+
log = ""
|
102
|
+
Dir.chdir(@repository_path) do
|
103
|
+
log = `git show-branch --sha1-name #{@base_branch_name} #{branch_name} | tail -1`
|
104
|
+
end
|
105
|
+
log[/\[(.+?)\]/, 1]
|
106
|
+
end
|
107
|
+
|
108
|
+
def create_table
|
109
|
+
return if Commit.table_exists?
|
110
|
+
ActiveRecord::Schema.define(:version => 1) do
|
111
|
+
create_table "commits", :force => false do |t|
|
112
|
+
t.string "repository_name"
|
113
|
+
t.string "branch_name"
|
114
|
+
t.string "commit_hash"
|
115
|
+
t.text "commit_message"
|
116
|
+
t.string "committer_name"
|
117
|
+
t.string "committer_email"
|
118
|
+
t.datetime "committed_date"
|
119
|
+
t.integer "diff_lines_count"
|
120
|
+
t.integer "diff_bytesize"
|
121
|
+
end
|
122
|
+
|
123
|
+
add_index("commits",
|
124
|
+
["repository_name", "branch_name", "commit_hash"],
|
125
|
+
:name => "index_commits_on_commit_hash",
|
126
|
+
:unique => true)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require "grit"
|
2
|
+
require "pp"
|
3
|
+
|
4
|
+
# TODO move this monkey patch
|
5
|
+
module Grit
|
6
|
+
class Commit
|
7
|
+
def diff_bytesize
|
8
|
+
@diff_bytesize ||= diffs.inject(0) do |memo, _diff|
|
9
|
+
memo + _diff.diff.bytesize
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def diff_lines_count
|
14
|
+
@diff_lines_count ||= diffs.inject(0) do |memo, _diff|
|
15
|
+
memo + _diff.diff.lines.to_a.size
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def autor_name
|
20
|
+
author.name
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module CommitCommentTools
|
26
|
+
class RepositoryStats
|
27
|
+
|
28
|
+
class CommitGroup
|
29
|
+
attr_reader :branch_name, :key, :commits
|
30
|
+
|
31
|
+
def initialize(branch_name, key, commits)
|
32
|
+
@branch_name = branch_name
|
33
|
+
@key = key
|
34
|
+
@commits = commits
|
35
|
+
end
|
36
|
+
|
37
|
+
def size
|
38
|
+
@commits.size
|
39
|
+
end
|
40
|
+
|
41
|
+
def diff_lines_count
|
42
|
+
@diff_lines_count ||= commits.inject(0) do |memo, commit|
|
43
|
+
memo + commit.diff_lines_count
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def diff_bytesize
|
48
|
+
@diff_bytesize ||= commits.inject(0) do |memo, commit|
|
49
|
+
memo + commit.diff_bytesize
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize(repository_path, branch_name, resolution=:day)
|
55
|
+
@repository = Grit::Repo.new(repository_path)
|
56
|
+
@target_branches = @repository.remotes.select do |branch|
|
57
|
+
branch_name === branch.name
|
58
|
+
end
|
59
|
+
@resolution = resolution
|
60
|
+
end
|
61
|
+
|
62
|
+
def stats
|
63
|
+
# TODO format data
|
64
|
+
case @resolution
|
65
|
+
when :day
|
66
|
+
commit_groups = commit_groups_by_date
|
67
|
+
when :week
|
68
|
+
commit_groups = commit_groups_by_week
|
69
|
+
when :month
|
70
|
+
commit_groups = commit_groups_by_month
|
71
|
+
end
|
72
|
+
# TODO make simple
|
73
|
+
commit_groups.group_by do |commit_group|
|
74
|
+
commit_group.branch_name
|
75
|
+
end.each do |branch_name, commit_groups|
|
76
|
+
puts branch_name
|
77
|
+
commit_groups.sort_by do |group|
|
78
|
+
group.key
|
79
|
+
end.each do |group|
|
80
|
+
puts "#{group.key},#{group.size},#{group.diff_lines_count},#{group.diff_bytesize}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def commit_groups_by_date
|
86
|
+
create_commit_groups("%Y-%m-%d")
|
87
|
+
end
|
88
|
+
|
89
|
+
def commit_groups_by_week
|
90
|
+
create_commit_groups("%Y-%Uw")
|
91
|
+
end
|
92
|
+
|
93
|
+
def commit_groups_by_month
|
94
|
+
create_commit_groups("%Y-%m")
|
95
|
+
end
|
96
|
+
|
97
|
+
def create_commit_groups(key_format="%Y-%m-%d")
|
98
|
+
@target_branches.map do |branch|
|
99
|
+
create_commit_group(branch.name, key_format)
|
100
|
+
end.flatten
|
101
|
+
end
|
102
|
+
|
103
|
+
def create_commit_group(branch_name, key_format="%Y-%m-%d")
|
104
|
+
# TODO Use Grit::Commit.find_all
|
105
|
+
groups = @repository.commits(branch_name, nil).group_by do |commit|
|
106
|
+
commit.date.strftime(key_format)
|
107
|
+
end
|
108
|
+
groups.map do |key, commits|
|
109
|
+
CommitGroup.new(branch_name, key, commits)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright (C) 2013 Haruka Yoshihara <yoshihara@clear-code.com>
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
8
|
+
# (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
|
18
|
+
module CommitCommentTools
|
19
|
+
VERSION = "0.0.1"
|
20
|
+
end
|
data/test/run-test.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
#
|
4
|
+
# Copyright (C) 2013 Haruka Yoshihara <yoshihara@clear-code.com>
|
5
|
+
#
|
6
|
+
# This program is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
$VERBOSE = true
|
20
|
+
|
21
|
+
base_dir = File.expand_path(File.join(File.dirname(__FILE__), ".."))
|
22
|
+
lib_dir = File.join(base_dir, "lib")
|
23
|
+
test_dir = File.join(base_dir, "test")
|
24
|
+
|
25
|
+
$LOAD_PATH.unshift(lib_dir)
|
26
|
+
|
27
|
+
require "test-unit"
|
28
|
+
require "test/unit/notify"
|
29
|
+
|
30
|
+
Thread.abort_on_exception = true
|
31
|
+
|
32
|
+
exit Test::Unit::AutoRunner.run(true, test_dir)
|
data/test/test-entry.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright (C) 2013 Haruka Yoshihara <yoshihara@clear-code.com>
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
8
|
+
# (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
|
18
|
+
require "commit-comment-tools/entry"
|
19
|
+
|
20
|
+
class EntryTest < Test::Unit::TestCase
|
21
|
+
def test_name
|
22
|
+
expected_name = "yamada"
|
23
|
+
entry = generate_entry(:name => expected_name)
|
24
|
+
assert_equal(expected_name, entry.name)
|
25
|
+
end
|
26
|
+
|
27
|
+
data(:with_zero_padding => "2013-02-05",
|
28
|
+
:without_zero_padding => "2013-2-5")
|
29
|
+
def test_date(date)
|
30
|
+
entry = generate_entry(:date => date)
|
31
|
+
expected_date = Date.parse("2013-02-05").strftime("%Y-%m-%d")
|
32
|
+
assert_equal(expected_date, entry.date)
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_read_ratio
|
36
|
+
entry = generate_entry(:read_ratio => "10")
|
37
|
+
assert_equal(10, entry.read_ratio)
|
38
|
+
end
|
39
|
+
|
40
|
+
data(:one_line => "typoのコミットが多かった。",
|
41
|
+
:continues_line => "typoのコミットが多かった。\ngit-rebaseって何?",
|
42
|
+
:nonexistent => "")
|
43
|
+
def test_comment(comment)
|
44
|
+
entry = generate_entry(:comment => comment)
|
45
|
+
assert_equal(comment, entry.comment)
|
46
|
+
end
|
47
|
+
|
48
|
+
class InvalidEntryTest < self
|
49
|
+
data(:no_separate => "20130205",
|
50
|
+
:slash_separate => "2013/02/05",
|
51
|
+
:nonexistent => "")
|
52
|
+
def test_date(invalid_date)
|
53
|
+
assert_raise(CommitCommentTools::Entry::InvalidEntryError) do
|
54
|
+
generate_entry(:date => invalid_date)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
data(:decimal_number => "10.5",
|
59
|
+
:no_number => "NoNumber",
|
60
|
+
:nonexistent => "")
|
61
|
+
def test_read_ratio(invalid_read_ratio)
|
62
|
+
assert_raise(CommitCommentTools::Entry::InvalidEntryError) do
|
63
|
+
generate_entry(:read_ratio => invalid_read_ratio)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
def generate_entry(options={})
|
70
|
+
name = options[:name] || "yamada"
|
71
|
+
date = options[:date] || "2013-02-20"
|
72
|
+
read_ratio = options[:read_ratio] || "10"
|
73
|
+
comment = options[:comment] || "あんまり読めなかった。"
|
74
|
+
|
75
|
+
entry_chunk = "#{date}:#{read_ratio}%:#{comment}"
|
76
|
+
CommitCommentTools::Entry.new(name, entry_chunk)
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright (C) 2013 Haruka Yoshihara <yoshihara@clear-code.com>
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
8
|
+
# (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
|
18
|
+
require "commit-comment-tools/report-normalizer"
|
19
|
+
|
20
|
+
class TestReportNormalizer < Test::Unit::TestCase
|
21
|
+
def setup
|
22
|
+
@normalizer = CommitCommentTools::ReportNormlizer.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_normalize
|
26
|
+
daily_report = {
|
27
|
+
"2013-1-30" => {
|
28
|
+
:read_ratio => "40",
|
29
|
+
:comment => "特になし"
|
30
|
+
},
|
31
|
+
"2013-2-1" => {
|
32
|
+
:read_ratio => "80",
|
33
|
+
:comment => "typoが多かった"
|
34
|
+
}
|
35
|
+
}
|
36
|
+
person_report = {"yamada" => daily_report}
|
37
|
+
actual_report = @normalizer.normalize(person_report)
|
38
|
+
assert_normalize_report(actual_report)
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_normalize_daily_report_with_zero_padding
|
42
|
+
daily_report = {
|
43
|
+
"2013-1-30" => {
|
44
|
+
:read_ratio => "40",
|
45
|
+
:comment => "特になし"
|
46
|
+
},
|
47
|
+
"2013-2-1" => {
|
48
|
+
:read_ratio => "80",
|
49
|
+
:comment => "typoが多かった"
|
50
|
+
}
|
51
|
+
}
|
52
|
+
actual_report = @normalizer.normalize_daily_report("yamada", daily_report)
|
53
|
+
assert_normalize_report(actual_report)
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_normalize_daily_report_without_zero_padding
|
57
|
+
daily_report = {
|
58
|
+
"2013-1-30" => {
|
59
|
+
:read_ratio => "40",
|
60
|
+
:comment => "特になし"
|
61
|
+
},
|
62
|
+
"2013-2-1" => {
|
63
|
+
:read_ratio => "80",
|
64
|
+
:comment => "typoが多かった"
|
65
|
+
}
|
66
|
+
}
|
67
|
+
actual_report = @normalizer.normalize_daily_report("yamada", daily_report)
|
68
|
+
assert_normalize_report(actual_report)
|
69
|
+
end
|
70
|
+
|
71
|
+
def assert_normalize_report(actual_report)
|
72
|
+
expected_report = {
|
73
|
+
"yamada" =>
|
74
|
+
{"2013-01-30" =>
|
75
|
+
{
|
76
|
+
:read_ratio => 40,
|
77
|
+
:comment => "特になし"
|
78
|
+
},
|
79
|
+
"2013-02-01" =>
|
80
|
+
{:read_ratio => 80,
|
81
|
+
:comment => "typoが多かった"
|
82
|
+
}
|
83
|
+
}
|
84
|
+
}
|
85
|
+
assert_equal(expected_report, actual_report)
|
86
|
+
end
|
87
|
+
end
|