commit-comment-tools 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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)
@@ -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