commit-comment-tools 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.
- 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
|