Skip to content

Commit f8b05f4

Browse files
committed
Merge pull request #61 from kevinjalbert/move-to-rugged
Move from Grit to Rugged for git processing
2 parents ed24dae + eb92d38 commit f8b05f4

23 files changed

+240
-423
lines changed

.ruby-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.9.3-p392
1+
2.0.0

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
language: ruby
22

33
rvm:
4-
- 1.9.2
54
- 1.9.3
5+
- 2.0.0
66

77
bundler_args: --without darwin debug
88

Rakefile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ end
1616

1717
desc "Run git_statistics on current/specified directory (for debugging)"
1818
task :run, :dir do |t, args|
19-
puts args[:dir]
2019
Bundler.require(:debug)
2120
require 'git_statistics'
2221
GitStatistics::CLI.new(args[:dir]).execute

git_statistics.gemspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ Gem::Specification.new do |gem|
1414
gem.files = Dir['lib/**/*']
1515
gem.test_files = Dir['spec/**/*_spec.rb']
1616
gem.executables = %w[ git_statistics git-statistics ]
17-
gem.required_ruby_version = '>= 1.9.1'
17+
gem.required_ruby_version = '>= 1.9.3'
1818

1919
gem.add_dependency('json')
20-
gem.add_dependency('grit')
20+
gem.add_dependency('rugged')
2121
gem.add_dependency('language_sniffer')
2222

2323
gem.add_development_dependency "rspec", "~> 2.12.0"

lib/git_statistics.rb

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ module GitStatistics
44
class CLI
55
attr_reader :repository, :options
66

7+
DEFAULT_BRANCH = "master"
8+
79
def initialize(dir)
8-
@repository = dir.nil? ? Repo.new(Dir.pwd) : Repo.new(dir)
10+
repository_location = dir.nil? ? Rugged::Repository.discover(Dir.pwd) : Rugged::Repository.discover(dir)
11+
@repository = Rugged::Repository.new(repository_location)
912
@collected = false
1013
@collector = nil
1114
@options = OpenStruct.new(
@@ -15,7 +18,7 @@ def initialize(dir)
1518
update: false,
1619
sort: "commits",
1720
top: 0,
18-
branch: false,
21+
branch: DEFAULT_BRANCH,
1922
verbose: false,
2023
debug: false,
2124
limit: 100
@@ -35,14 +38,13 @@ def collect_and_only_update
3538
if options.update
3639
# Ensure commit directory is present
3740
@collector = Collector.new(repository, options.limit, false, options.pretty)
38-
commits_directory = repository.working_dir + ".git_statistics"
41+
commits_directory = repository.workdir + ".git_statistics/"
3942
FileUtils.mkdir_p(commits_directory)
4043
file_count = Utilities.number_of_matching_files(commits_directory, /\d+\.json/) - 1
4144

4245
if file_count >= 0
43-
time_since = Utilities.get_modified_time(commits_directory + "#{file_count}.json")
44-
# Only use --since if there is data present
45-
@collector.collect(options.branch, {:since => time_since})
46+
time_since = Utilities.get_modified_time(commits_directory + "#{file_count}.json").to_s
47+
@collector.collect({:branch => options.branch, :time_since => time_since})
4648
@collected = true
4749
end
4850
end
@@ -59,7 +61,7 @@ def output_results
5961

6062
def fresh_collect!
6163
@collector = Collector.new(repository, options.limit, true, options.pretty)
62-
@collector.collect(options.branch)
64+
@collector.collect({:branch => options.branch})
6365
end
6466

6567
def parse_options
@@ -83,8 +85,8 @@ def parse_options
8385
opt.on "-t", "--top N", Float,"Show the top N authors in results" do |value|
8486
options.top = value
8587
end
86-
opt.on "-b", "--branch", "Use current branch for statistics (otherwise all branches)" do
87-
options.branch = true
88+
opt.on "-b", "--branch BRANCH", "Use the specified branch for statistics (otherwise the master branch is used)" do |branch|
89+
options.branch = branch
8890
end
8991
opt.on "-v", "--verbose", "Verbose output (shows INFO level log statements)" do
9092
options.verbose = true

lib/git_statistics/blob_finder.rb

Lines changed: 0 additions & 54 deletions
This file was deleted.

lib/git_statistics/collector.rb

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,52 +4,67 @@ class Collector
44
attr_accessor :repo, :commits_path, :commits
55

66
def initialize(repo, limit, fresh, pretty)
7-
Grit::Git.git_timeout = 0
8-
Grit::Git.git_max_size = 0
9-
107
@repo = repo
11-
@commits_path = repo.working_dir + ".git_statistics"
8+
@commits_path = repo.workdir + ".git_statistics"
129
@commits = Commits.new(@commits_path, fresh, limit, pretty)
1310
end
1411

15-
def collect(branch, options = {})
16-
# Collect branches to use for git log
17-
branches = branch ? [] : @repo.branches.compact.map(&:name)
18-
Grit::Commit.find_all(@repo, nil, options).each do |commit|
19-
extract_commit(commit)
20-
@commits.flush_commits
12+
def collect(options = {})
13+
branch = options[:branch] ? options[:branch] : CLI::DEFAULT_BRANCH
14+
branch_head = Rugged::Branch.lookup(repo, branch).tip
15+
16+
walker = Rugged::Walker.new(repo)
17+
walker.push(branch_head)
18+
19+
walker.each_with_index do |commit, count|
20+
if valid_commit?(commit, options)
21+
extract_commit(commit, count + 1)
22+
@commits.flush_commits
23+
end
2124
end
25+
2226
@commits.flush_commits(true)
2327
end
2428

29+
def valid_commit?(commit, options)
30+
if !options[:time_since].nil?
31+
return false unless commit.author[:time] > DateTime.parse(options[:time_since].to_s).to_time
32+
end
33+
34+
if !options[:time_until].nil?
35+
return false unless commit.author[:time] < DateTime.parse(options[:time_until].to_s).to_time
36+
end
37+
38+
return true
39+
end
40+
2541
def acquire_commit_meta(commit_summary)
2642
# Initialize commit data
27-
data = (@commits[commit_summary.sha] ||= Hash.new(0))
43+
data = (@commits[commit_summary.oid] ||= Hash.new(0))
2844

29-
data[:author] = commit_summary.author.name
30-
data[:author_email] = commit_summary.author.email
31-
data[:time] = commit_summary.authored_date.to_s
45+
data[:author] = commit_summary.author[:name]
46+
data[:author_email] = commit_summary.author[:email]
47+
data[:time] = commit_summary.author[:time].to_s
3248
data[:merge] = commit_summary.merge?
3349
data[:additions] = commit_summary.additions
3450
data[:deletions] = commit_summary.deletions
3551
data[:net] = commit_summary.net
36-
data[:new_files] = commit_summary.new_files
37-
data[:removed_files] = commit_summary.removed_files
38-
data[:files] = commit_summary.files
52+
data[:added_files] = commit_summary.added_files
53+
data[:deleted_files] = commit_summary.deleted_files
54+
data[:modified_files] = commit_summary.modified_files
55+
data[:files] = commit_summary.file_stats.map{ |file| file.to_json }
3956

4057
return data
4158
end
4259

43-
def extract_commit(commit)
44-
unless commit.nil?
45-
commit_summary = CommitSummary.new(commit)
46-
Log.info "Extracting #{commit_summary.sha}"
60+
def extract_commit(commit, count)
61+
Log.info "Extracting(#{count}) #{commit.oid}"
62+
commit_summary = CommitSummary.new(@repo, commit)
4763

48-
# Acquire meta information about commit
49-
commit_data = acquire_commit_meta(commit_summary)
64+
# Acquire meta information about commit
65+
commit_data = acquire_commit_meta(commit_summary)
5066

51-
return commit_data
52-
end
67+
return commit_data
5368
end
5469

5570
end

lib/git_statistics/commit_summary.rb

Lines changed: 19 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
module GitStatistics
22
class CommitSummary < SimpleDelegator
3-
def initialize(commit)
3+
def initialize(repo, commit)
44
super(commit)
5+
@repo = repo
6+
@diff = diff(commit.parents.first)
7+
@patches = @diff.patches
58
end
69

710
# A Git commit is a merge if it has more than one parent
@@ -10,13 +13,18 @@ def merge?
1013
end
1114

1215
# How many files were removed in this commit
13-
def removed_files
14-
cached_show.select { |diff| diff.deleted_file == true }.count
16+
def deleted_files
17+
file_stats.select { |file| file.status == :deleted }.count
1518
end
1619

1720
# How many files were added in this commit
18-
def new_files
19-
cached_show.select { |diff| diff.new_file == true }.count
21+
def added_files
22+
file_stats.select { |file| file.status == :added }.count
23+
end
24+
25+
# How many files were modified (not added/deleted) in this commit
26+
def modified_files
27+
file_stats.select { |file| file.status == :modified }.count
2028
end
2129

2230
# How many total additions in this commit?
@@ -35,18 +43,18 @@ def net
3543
end
3644

3745
def file_stats
38-
@cached_file_stats ||= diffstats.map { |diff| DiffSummary.new(diff, current_tree) }
46+
@cached_file_stats ||= diffstats.map { |diff| DiffSummary.new(@repo, diff) }
3947
end
4048

41-
LanguageSummary = Struct.new(:name, :additions, :deletions, :net)
49+
LanguageSummary = Struct.new(:name, :additions, :deletions, :net, :added_files, :deleted_files, :modified_files)
4250

4351
# Array of LanguageSummary objects (one for each language) for simple calculations
4452
def languages
4553
grouped_language_files.collect do |language, stats|
4654
additions = summarize(stats, :additions)
4755
deletions = summarize(stats, :deletions)
4856
net = summarize(stats, :net)
49-
LanguageSummary.new(language, additions, deletions, net)
57+
LanguageSummary.new(language, additions, deletions, net, added_files, deleted_files, modified_files)
5058
end
5159
end
5260

@@ -55,72 +63,13 @@ def grouped_language_files
5563
file_stats.group_by(&:language)
5664
end
5765

58-
FileSummary = Struct.new(:name, :language, :additions, :deletions, :net, :filestatus)
59-
60-
# Array of FileSummary objects (one for each file) for simple calculations
61-
def files
62-
file_stats.collect{ |stats| determine_file_summary(stats) }
63-
end
64-
65-
def cached_show
66-
@cached_commit_show ||= show
67-
end
68-
6966
# Files touched in this commit
70-
def file_names
71-
diffstats.map(&:filename)
72-
end
73-
74-
# Fetch the current Grit::Repo tree from this commit
75-
def current_tree
76-
@current_tree ||= repo.tree(sha)
67+
def filenames
68+
file_stats.map(&:filename)
7769
end
7870

7971
private
8072

81-
def determine_file_summary(stats)
82-
filestatus = :modified
83-
language = stats.language
84-
85-
# Determine if this file could be a new or deleted file
86-
if (stats.additions > 0 && stats.deletions == 0) || (stats.additions == 0 && stats.deletions > 0)
87-
# Extract file status from commit's diff object
88-
cached_show.each do |diff|
89-
if stats.filename == diff.b_path
90-
filestatus = :create if diff.new_file
91-
filestatus = :delete if diff.deleted_file
92-
break
93-
end
94-
end
95-
end
96-
97-
# Determine language of blob
98-
if stats.tree?
99-
# Trees have no language (the tree's blobs are still processed via the remainder diffstats)
100-
language = "Unknown"
101-
elsif stats.submodule?
102-
language = "Submodule"
103-
elsif stats.blob.nil?
104-
# If blob is nil (i.e., deleted file) grab the previous version of this blob using the parents of the current commit
105-
blob = BlobFinder.get_blob(self.parents.first, stats.filename)
106-
blob = BlobFinder.get_blob(self.parents.last, stats.filename) if blob.nil?
107-
108-
# Determine language of newly found blob
109-
if blob.kind_of? Grit::Tree
110-
language = "Unknown"
111-
elsif blob.kind_of? Grit::Submodule
112-
language = "Submodule"
113-
elsif blob.nil? || blob.language.nil?
114-
language = "Unknown"
115-
else
116-
language = blob.language.to_s
117-
end
118-
end
119-
120-
# TODO Converts file summary into hash to keep json compatibility (for now)
121-
Hash[FileSummary.new(stats.filename, language, stats.additions, stats.deletions, stats.net, filestatus).each_pair.to_a]
122-
end
123-
12473
def summarize(stats, what)
12574
stats.map(&what).inject(0, :+)
12675
end
@@ -130,19 +79,7 @@ def commit_summary(what)
13079
end
13180

13281
def diffstats
133-
if merge?
134-
merge_diffstats
135-
else
136-
stats.to_diffstat
137-
end
138-
end
139-
140-
# Hackery coming...
141-
DIFFSTAT_REGEX = /([-|\d]+)\s+([-|\d]+)\s+(.+)/i
142-
def merge_diffstats
143-
native_diff = repo.git.native(:diff, {numstat: true}, parents.join("..."))
144-
per_file_info = native_diff.scan(DIFFSTAT_REGEX)
145-
per_file_info.map { |add, del, file| Grit::DiffStat.new(file, add.to_i, del.to_i) }
82+
@patches
14683
end
14784

14885
end

0 commit comments

Comments
 (0)