git_evolution 0.0.1 → 0.1.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 60bfaea797ed777510a8c4f99684fda966a1cc1b
4
- data.tar.gz: 795f12647c399d9c5f50a5a9bedff8863a5e0099
3
+ metadata.gz: bf16cc71418f589e13752aa32e110fe41dda318a
4
+ data.tar.gz: f527d0c64625e859bbda0759eaec9453d4651809
5
5
  SHA512:
6
- metadata.gz: a6a9523c1ccbb8897b73ac661bc911b2431321d6bab13ac2ba882e832b5561b37d99a57ef315e30c6ceac6ada3442af1b87883b9a86fb4555ff083bff2c8d3e6
7
- data.tar.gz: 29469fc6c07388b645d431bd0479f88b6704fbf85ce02cb02444e7f170499e6b1afa7b62b8d6682a04b9721a69817ae7cb96e8a36e331355ee83fde6a9ab0696
6
+ metadata.gz: 75f452c8f3cd00f2e99ed0eb7afedb2391205e693a310ef4e4d6cd0cd7aa4d099c32ed4b414aef33271881dca134677a17616174170d8dd9832b4526af4c36c3
7
+ data.tar.gz: cdfdee0e7dc34c330da4b40f0ba190911dc87291d9186ed288e2660bf0acf15253db26033a45394a89768743de100f7b564e33be2c7f186d46c03e8d59b0a0a9
data/Gemfile CHANGED
@@ -2,3 +2,14 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in git_evolution.gemspec
4
4
  gemspec
5
+
6
+ group :test do
7
+ gem 'simplecov'
8
+ gem 'coveralls', require: false
9
+ gem 'rspec'
10
+ end
11
+
12
+ group :development do
13
+ gem 'pry-byebug'
14
+ gem 'rubocop'
15
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,92 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ git_evolution (0.1.2)
5
+ bundler (~> 1.0)
6
+ chronic (~> 0.10.0)
7
+ rake (~> 10.0)
8
+ rugged (~> 0.21.0)
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ ast (2.0.0)
14
+ astrolabe (1.3.0)
15
+ parser (>= 2.2.0.pre.3, < 3.0)
16
+ byebug (3.5.1)
17
+ columnize (~> 0.8)
18
+ debugger-linecache (~> 1.2)
19
+ slop (~> 3.6)
20
+ chronic (0.10.2)
21
+ coderay (1.1.0)
22
+ columnize (0.9.0)
23
+ coveralls (0.7.10)
24
+ multi_json (~> 1.10)
25
+ rest-client (>= 1.6.8, < 2)
26
+ simplecov (~> 0.9.1)
27
+ term-ansicolor (~> 1.3)
28
+ thor (~> 0.19.1)
29
+ debugger-linecache (1.2.0)
30
+ diff-lcs (1.2.5)
31
+ docile (1.1.5)
32
+ method_source (0.8.2)
33
+ mime-types (2.4.3)
34
+ multi_json (1.10.1)
35
+ netrc (0.10.2)
36
+ parser (2.2.0.3)
37
+ ast (>= 1.1, < 3.0)
38
+ powerpack (0.1.0)
39
+ pry (0.10.1)
40
+ coderay (~> 1.1.0)
41
+ method_source (~> 0.8.1)
42
+ slop (~> 3.4)
43
+ pry-byebug (3.0.1)
44
+ byebug (~> 3.4)
45
+ pry (~> 0.10)
46
+ rainbow (2.0.0)
47
+ rake (10.4.2)
48
+ rest-client (1.7.3)
49
+ mime-types (>= 1.16, < 3.0)
50
+ netrc (~> 0.7)
51
+ rspec (3.2.0)
52
+ rspec-core (~> 3.2.0)
53
+ rspec-expectations (~> 3.2.0)
54
+ rspec-mocks (~> 3.2.0)
55
+ rspec-core (3.2.0)
56
+ rspec-support (~> 3.2.0)
57
+ rspec-expectations (3.2.0)
58
+ diff-lcs (>= 1.2.0, < 2.0)
59
+ rspec-support (~> 3.2.0)
60
+ rspec-mocks (3.2.0)
61
+ diff-lcs (>= 1.2.0, < 2.0)
62
+ rspec-support (~> 3.2.0)
63
+ rspec-support (3.2.1)
64
+ rubocop (0.29.1)
65
+ astrolabe (~> 1.3)
66
+ parser (>= 2.2.0.1, < 3.0)
67
+ powerpack (~> 0.1)
68
+ rainbow (>= 1.99.1, < 3.0)
69
+ ruby-progressbar (~> 1.4)
70
+ ruby-progressbar (1.7.1)
71
+ rugged (0.21.4)
72
+ simplecov (0.9.2)
73
+ docile (~> 1.1.0)
74
+ multi_json (~> 1.0)
75
+ simplecov-html (~> 0.9.0)
76
+ simplecov-html (0.9.0)
77
+ slop (3.6.0)
78
+ term-ansicolor (1.3.0)
79
+ tins (~> 1.0)
80
+ thor (0.19.1)
81
+ tins (1.3.4)
82
+
83
+ PLATFORMS
84
+ ruby
85
+
86
+ DEPENDENCIES
87
+ coveralls
88
+ git_evolution!
89
+ pry-byebug
90
+ rspec
91
+ rubocop
92
+ simplecov
data/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # GitEvolution
2
2
 
3
+ [![Build Status](https://travis-ci.org/kevinjalbert/git_evolution.svg)](https://travis-ci.org/kevinjalbert/git_evolution)
4
+ [![Coverage Status](https://coveralls.io/repos/kevinjalbert/git_evolution/badge.svg)](https://coveralls.io/r/kevinjalbert/git_evolution)
5
+ [![Code Climate](https://codeclimate.com/github/kevinjalbert/git_evolution/badges/gpa.svg)](https://codeclimate.com/github/kevinjalbert/git_evolution)
6
+
3
7
  TODO: Write a gem description
4
8
 
5
9
  ## Installation
data/Rakefile CHANGED
@@ -1,2 +1,5 @@
1
- require "bundler/gem_tasks"
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'rake/clean'
2
4
 
5
+ CLOBBER.include('coverage')
data/bin/git-evolution CHANGED
@@ -2,4 +2,4 @@
2
2
 
3
3
  require 'git_evolution'
4
4
 
5
- GitEvolution.new.run(ARGV)
5
+ GitEvolution.run(ARGV)
data/bin/git_evolution CHANGED
@@ -2,4 +2,4 @@
2
2
 
3
3
  require 'git_evolution'
4
4
 
5
- GitEvolution.new.run(ARGV)
5
+ GitEvolution.run(ARGV)
@@ -13,11 +13,15 @@ Gem::Specification.new do |spec|
13
13
  spec.homepage = ''
14
14
  spec.license = 'MIT'
15
15
 
16
- spec.files = `git ls-files -z`.split("\x0")
17
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
16
+ spec.files = Dir['**/*']
17
+ spec.test_files = Dir['{test,spec,features}/**/*']
18
+ spec.executables = Dir['bin/*'].map { |f| File.basename(f) }
19
19
  spec.require_paths = ['lib']
20
20
 
21
- spec.add_development_dependency 'bundler', '~> 1.7'
22
- spec.add_development_dependency 'rake', '~> 10.0'
21
+ spec.add_dependency 'bundler', '~> 1.0'
22
+ spec.add_dependency 'rake', '~> 10.0'
23
+ spec.add_dependency 'rugged', '~> 0.21.0'
24
+ spec.add_dependency 'chronic', '~> 0.10.0'
25
+
26
+ spec.required_ruby_version = '~> 2.0'
23
27
  end
data/lib/git_evolution.rb CHANGED
@@ -1,43 +1,15 @@
1
- class GitEvolution
2
- def run(args)
3
- start_line = args[0]
4
- end_line = args[1]
5
- file = args[2]
1
+ require_relative './git_evolution/initialize.rb'
6
2
 
7
- results = `git --no-pager log -L#{start_line},#{end_line}:#{file} --follow #{file}`
3
+ module GitEvolution
4
+ def self.run(args)
5
+ options = OptionHandler.parse_options(args)
8
6
 
9
- commit_shas = results.scan(/^commit ([0-9a-f]{40})/)
10
- commit_shas = commit_shas.flatten
7
+ repo = Repository.new(options.file)
8
+ commits = repo.line_commits(options.start_line, options.end_line, options.file, options.since)
11
9
 
12
- commit_info = {}
13
- commit_shas.each do |sha|
14
- commit = `git --no-pager show -s --format=%an%n%n%at%n%n%s%n%n%b #{sha}`
15
- commit_data = commit.split("\n\n")
16
- commit_info[sha] = {
17
- author: commit_data[0],
18
- date: commit_data[1],
19
- title: commit_data[2],
20
- body: commit_data[3..-1].join
21
- }
22
- end
23
-
24
- ownership = Hash.new(0)
25
-
26
- puts 'Commits:'
27
- commit_info.each do |sha, data|
28
- puts "#{data[:author]} (#{Time.at(data[:date].to_i)}) - #{sha}"
29
- puts "#{data[:title]}"
30
- puts
31
-
32
- ownership[data[:author]] = ownership[data[:author]] + 1
33
- end
34
-
35
- puts '-' * 80
36
-
37
- puts
38
- puts 'Ownership:'
39
- ownership.each do |author, count|
40
- puts "#{author} - #{count}/#{commit_info.size} (#{(count.to_f / commit_info.size * 100).round(2)}%)"
41
- end
10
+ ReportPresenter.new(commits).print
11
+ rescue StandardError => e
12
+ puts "[#{e.class}] #{e.message}"
13
+ puts e.backtrace
42
14
  end
43
15
  end
@@ -0,0 +1,42 @@
1
+ module GitEvolution
2
+ class Commit
3
+ attr_reader :raw_commit, :sha, :author, :date, :subject, :body, :additions, :deletions
4
+
5
+ def initialize(raw_commit)
6
+ @raw_commit = raw_commit
7
+
8
+ parse_meta_data!
9
+ parse_body_data!
10
+ parse_diff_data!
11
+ end
12
+
13
+ def parse_meta_data!
14
+ @sha = raw_commit.scan(/^commit\s+(.*?)$/).flatten.first.strip
15
+ @author = raw_commit.scan(/^Author:\s+(.*?)$/).flatten.first.strip
16
+ @date= raw_commit.scan(/^Date:\s+(.*?)$/).flatten.first.strip
17
+ end
18
+
19
+ def parse_body_data!
20
+ raw_body_lines = (raw_commit + "\n\u0000").scan(/^Date:.*?$(.*?)^[diff|\u0000]/m).flatten.first.strip.split("\n")
21
+ @subject = raw_body_lines.first.strip
22
+
23
+ if raw_body_lines.size > 1
24
+ @body = raw_body_lines[1..-1].map { |line| line.gsub(/^\s+/, '') }.join("\n")
25
+ @body.sub!(/\n+/, '') if @body.start_with?("\n")
26
+ end
27
+ end
28
+
29
+ def parse_diff_data!
30
+ raw_diff_lines = raw_commit.scan(/^@@.*?$(.*)?/m).flatten.first
31
+
32
+ if raw_diff_lines
33
+ raw_diff_lines = raw_diff_lines.strip.split("\n")
34
+ @additions = raw_diff_lines.count { |line| line.start_with?('+') }
35
+ @deletions = raw_diff_lines.count { |line| line.start_with?('-') }
36
+ else
37
+ @additions = 0
38
+ @deletions = 0
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,6 @@
1
+ require 'rugged'
2
+ require 'chronic'
3
+ require 'ostruct'
4
+ require 'optparse'
5
+
6
+ Dir.glob(File.dirname(__FILE__) + '/**/*.rb') { |file| require file }
@@ -0,0 +1,62 @@
1
+ module GitEvolution
2
+ module OptionHandler
3
+ def self.parse_options(args)
4
+ options = OpenStruct.new(
5
+ range: nil,
6
+ since: nil,
7
+ start_line: nil,
8
+ end_line: nil,
9
+ )
10
+
11
+ OptionParser.new do |opts|
12
+ opts.banner = 'Usage: git_evolution [options] <file>'
13
+ opts.version = VERSION
14
+ opts.on '-r', '--range N:N', String, 'The specified range of lines to consider within the file (optional)' do |value|
15
+ options.range = value
16
+ end
17
+ opts.on '-s', '--since STRING', String, 'Consider the commits which are more recent than the specified time (optional)' do |value|
18
+ options.since = value
19
+ end
20
+ end.parse!
21
+
22
+ options[:file] = File.expand_path(args[0])
23
+ options[:start_line], options[:end_line] = parse_range(options[:range])
24
+
25
+ validate_options!(options)
26
+
27
+ options
28
+ end
29
+
30
+ def self.parse_range(range)
31
+ return if range.nil?
32
+
33
+ regex_matches = range.match(/^(\d+):(\d+)/)
34
+ raise 'The --range option was not in the valid format (N:N)' if regex_matches.nil?
35
+
36
+ start_line = regex_matches[1].to_i
37
+ end_line = regex_matches[2].to_i
38
+
39
+ return start_line, end_line
40
+ end
41
+
42
+ def self.validate_options!(options)
43
+ if options.file.nil?
44
+ raise 'Missing required file argument'
45
+ elsif !File.exist?(options.file)
46
+ raise "File #{options.file} does not exist"
47
+ end
48
+
49
+ if !options.range.nil?
50
+ raise 'Start line cannot be greater than the end line' if options.start_line > options.end_line
51
+
52
+ file_length = File.new(options.file).readlines.size
53
+ raise "End line cannot be larger than the length of the file (#{file_length})" if options.end_line > file_length
54
+ end
55
+
56
+ if !options.since.nil?
57
+ options.since = Chronic.parse(options.since)
58
+ raise 'The since time could not be properly parsed' if options.since.nil?
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,64 @@
1
+ module GitEvolution
2
+ class ReportPresenter
3
+ def initialize(commits)
4
+ @commits = commits
5
+ @ownership = { commits: Hash.new(0), changes: Hash.new(0) }
6
+
7
+ calculate_ownership!
8
+ end
9
+
10
+ def print
11
+ print_commits
12
+ puts
13
+ puts '-' * 80
14
+ puts
15
+ print_commit_ownership
16
+ puts
17
+ print_changes_ownership
18
+ puts
19
+ end
20
+
21
+ def print_commits
22
+ puts 'Commits:'
23
+ @commits.each do |commit|
24
+ puts "#{commit.author} (#{commit.date}) - #{commit.sha}"
25
+ puts "#{commit.subject}"
26
+ puts
27
+ end
28
+ end
29
+
30
+ def print_commit_ownership
31
+ puts 'Ownership (Commits):'
32
+ @ownership[:commits].each do |author, count|
33
+ puts "#{author} - #{count}/#{@commits.size} (#{(count.to_f / @commits.size * 100).round(2)}%)"
34
+ end
35
+ end
36
+
37
+ def print_changes_ownership
38
+ puts 'Ownership (Changes):'
39
+
40
+ total_additions = @commits.inject(0) { |sum, commit| sum + commit.additions }
41
+ total_deletions = @commits.inject(0) { |sum, commit| sum + commit.deletions }
42
+ total_changes = total_additions + total_deletions
43
+
44
+ @ownership[:changes].each do |author, count|
45
+ puts "#{author} - #{count}/#{total_changes} (#{(count.to_f / total_changes * 100).round(2)}%)"
46
+ end
47
+ end
48
+
49
+ def calculate_ownership!
50
+ @commits.each do |commit|
51
+ @ownership[:commits][commit.author] = @ownership[:commits][commit.author] + 1
52
+ @ownership[:changes][commit.author] = @ownership[:changes][commit.author] + commit.additions + commit.deletions
53
+ end
54
+
55
+ sort_ownership!
56
+ end
57
+
58
+ def sort_ownership!
59
+ @ownership.keys.each do |keys|
60
+ @ownership[keys] = @ownership[keys].sort { |a,b| b[1] <=> a[1] }.to_h
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,27 @@
1
+ module GitEvolution
2
+ class Repository
3
+ def initialize(directory_name)
4
+ @git_repo = Rugged::Repository.discover(File.expand_path(directory_name))
5
+ end
6
+
7
+ def dir
8
+ @git_repo.workdir
9
+ end
10
+
11
+ def line_commits(start_line, end_line, file, since = nil)
12
+ raw_results = raw_line_history(start_line, end_line, file, since)
13
+ raw_results.split("\u0000").map { |raw_commit| Commit.new(raw_commit) }
14
+ end
15
+
16
+ def raw_line_history(start_line, end_line, file, since = nil)
17
+ since_option = "--since '#{since}'"
18
+
19
+ Dir.chdir(dir) do
20
+ return `git --no-pager log -p -z\
21
+ #{since_option if since}\
22
+ #{"-L#{start_line},#{end_line}:#{file}" if start_line && end_line}\
23
+ --follow #{file}`
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,3 +1,3 @@
1
- class GitEvolution
2
- VERSION = '0.0.1'
1
+ module GitEvolution
2
+ VERSION = '0.1.2'
3
3
  end
Binary file
Binary file
@@ -0,0 +1,18 @@
1
+ commit 01da64f8b1021a1007fc3ee9d0acbe87c02217e7
2
+ Author: Kevin Jalbert <kevin.j.jalbert@gmail.com>
3
+ Date: Mon Jun 8 07:31:34 2015 -0400
4
+
5
+ Add ability to acquire the ordered commits for a line range
6
+
7
+ Add spec to test #line_commits. Slight refactoring to make use of
8
+ #line_commits.
9
+
10
+ diff --git a/lib/git_evolution/repository.rb b/lib/git_evolution/repository.rb
11
+ --- a/lib/git_evolution/repository.rb
12
+ +++ b/lib/git_evolution/repository.rb
13
+ @@ -24,0 +25,5 @@
14
+ + def line_history(start_line, end_line, file)
15
+ + Dir.chdir(dir) do
16
+ + return `git --no-pager log -L#{start_line},#{end_line}:#{file} --follow #{file}`
17
+ + end
18
+ + end
@@ -0,0 +1,19 @@
1
+ commit 326f5329333e65aebb6ce7f8566d88a58964022a
2
+ Author: Kevin Jalbert <kevin.j.jalbert@gmail.com>
3
+ Date: Mon Jun 8 07:47:13 2015 -0400
4
+
5
+ Add ability to specify the '--since' option for line_{history|commits}
6
+
7
+ diff --git a/lib/git_evolution/repository.rb b/lib/git_evolution/repository.rb
8
+ --- a/lib/git_evolution/repository.rb
9
+ +++ b/lib/git_evolution/repository.rb
10
+ @@ -25,5 +25,7 @@
11
+ - def line_history(start_line, end_line, file)
12
+ + def line_history(start_line, end_line, file, since = nil)
13
+ + since_option = "--since '#{since}'"
14
+ +
15
+ Dir.chdir(dir) do
16
+ - return `git --no-pager log -L#{start_line},#{end_line}:#{file} --follow #{file}`
17
+ + return `git --no-pager log #{since_option if since} -L#{start_line},#{end_line}:#{file} --follow #{file}`
18
+ end
19
+ end
@@ -0,0 +1,8 @@
1
+ commit 01da64f8b1021a1007fc3ee9d0acbe87c02217e7
2
+ Author: Kevin Jalbert <kevin.j.jalbert@gmail.com>
3
+ Date: Mon Jun 8 07:31:34 2015 -0400
4
+
5
+ Add ability to acquire the ordered commits for a line range
6
+
7
+ Add spec to test #line_commits. Slight refactoring to make use of
8
+ #line_commits.
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe GitEvolution::Commit do
4
+ describe '.new' do
5
+ let(:raw_commit) { fixture('raw_commit.txt') }
6
+
7
+ subject { described_class.new(raw_commit) }
8
+
9
+ it 'valid commit parsing' do
10
+ expect(subject.sha).to eq('01da64f8b1021a1007fc3ee9d0acbe87c02217e7')
11
+ expect(subject.author).to eq('Kevin Jalbert <kevin.j.jalbert@gmail.com>')
12
+ expect(subject.date).to eq('Mon Jun 8 07:31:34 2015 -0400')
13
+ expect(subject.subject).to eq("Add ability to acquire the ordered commits for a line range")
14
+ expect(subject.body).to eq("Add spec to test #line_commits. Slight refactoring to make use of\n#line_commits.")
15
+ expect(subject.additions).to eq(5)
16
+ expect(subject.deletions).to eq(0)
17
+ end
18
+
19
+ context 'with no body' do
20
+ let(:raw_commit) { fixture('raw_commit_with_no_body.txt') }
21
+
22
+ it 'valid commit parsing' do
23
+ expect(subject.sha).to eq('326f5329333e65aebb6ce7f8566d88a58964022a')
24
+ expect(subject.author).to eq('Kevin Jalbert <kevin.j.jalbert@gmail.com>')
25
+ expect(subject.date).to eq('Mon Jun 8 07:47:13 2015 -0400')
26
+ expect(subject.subject).to eq("Add ability to specify the '--since' option for line_{history|commits}")
27
+ expect(subject.body).to eq(nil)
28
+ expect(subject.additions).to eq(4)
29
+ expect(subject.deletions).to eq(2)
30
+ end
31
+ end
32
+
33
+ context 'with no diff (i.e., merge commit)' do
34
+ let(:raw_commit) { fixture('raw_commit_with_no_diff.txt') }
35
+
36
+ it 'valid commit parsing' do
37
+ expect(subject.sha).to eq('01da64f8b1021a1007fc3ee9d0acbe87c02217e7')
38
+ expect(subject.author).to eq('Kevin Jalbert <kevin.j.jalbert@gmail.com>')
39
+ expect(subject.date).to eq('Mon Jun 8 07:31:34 2015 -0400')
40
+ expect(subject.subject).to eq("Add ability to acquire the ordered commits for a line range")
41
+ expect(subject.body).to eq("Add spec to test #line_commits. Slight refactoring to make use of\n#line_commits.")
42
+ expect(subject.additions).to eq(0)
43
+ expect(subject.deletions).to eq(0)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,135 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe GitEvolution::OptionHandler do
4
+ describe '.parse_options' do
5
+ subject { described_class.parse_options([file]) }
6
+
7
+ let!(:tmp_dir) { Dir.mktmpdir }
8
+ let(:file) { tmp_dir + '/file.txt' }
9
+ let(:start_line) { 1 }
10
+ let(:end_line) { 10 }
11
+
12
+ before do
13
+ IO.write(file, (1..20).map { ".\n" }.join)
14
+ allow(described_class).to receive(:parse_range) { [start_line, end_line] }
15
+ end
16
+
17
+ after { FileUtils.rm_r(tmp_dir) }
18
+
19
+ it 'parses options correctly' do
20
+ expect(subject.start_line).to eq(start_line)
21
+ expect(subject.end_line).to eq(end_line)
22
+ expect(subject.file).to eq(file)
23
+ end
24
+ end
25
+
26
+ describe '.parse_range' do
27
+ context 'valid range' do
28
+ let(:range) { '10:20' }
29
+ let(:expected_start_line) { 10 }
30
+ let(:expected_end_line) { 20 }
31
+
32
+ it 'detects range' do
33
+ start_line, end_line = subject.parse_range(range)
34
+ expect(start_line).to eq(expected_start_line)
35
+ expect(end_line).to eq(expected_end_line)
36
+ end
37
+ end
38
+
39
+ context 'invalid range' do
40
+ let(:range) { '10.20' }
41
+
42
+ it 'raises exception' do
43
+ expect { described_class.parse_range(range) }.to raise_error
44
+ end
45
+ end
46
+
47
+ context 'nil range' do
48
+ let(:range) { nil }
49
+
50
+ it 'returns nil' do
51
+ expect(described_class.parse_range(range)).to be_nil
52
+ end
53
+ end
54
+ end
55
+
56
+ describe '.validate_options!' do
57
+ let(:options) do
58
+ OpenStruct.new(
59
+ file: file,
60
+ range: range,
61
+ since: since,
62
+ start_line: start_line,
63
+ end_line: end_line
64
+ )
65
+ end
66
+
67
+ let!(:tmp_dir) { Dir.mktmpdir }
68
+ let(:file) { tmp_dir + '/file.txt' }
69
+ let(:range) { '1:2' }
70
+ let(:since) { '1 day ago' }
71
+ let(:start_line) { 1 }
72
+ let(:end_line) { 20 }
73
+
74
+ before { IO.write(file, (1..20).map { ".\n" }.join) }
75
+
76
+ after { FileUtils.rm_r(tmp_dir) }
77
+
78
+ it 'valid options' do
79
+ expect { described_class.validate_options!(options) }.to_not raise_error
80
+ end
81
+
82
+ context 'start_line is larger than end_line' do
83
+ let(:start_line) { 10 }
84
+ let(:end_line) { 1 }
85
+
86
+ it 'invalid options' do
87
+ expect { described_class.validate_options!(options) }.to raise_error
88
+ end
89
+ end
90
+
91
+ context 'file does not exist' do
92
+ let(:file) { tmp_dir + '/not_here.txt' }
93
+
94
+ before { FileUtils.rm(file) }
95
+
96
+ it 'invalid options' do
97
+ expect { described_class.validate_options!(options) }.to raise_error
98
+ end
99
+
100
+ context 'missing file argument' do
101
+ it 'invalid options' do
102
+ options.file = nil
103
+ expect { described_class.validate_options!(options) }.to raise_error
104
+ end
105
+ end
106
+ end
107
+
108
+ context 'end_line is larger than file length' do
109
+ let(:start_line) { 10 }
110
+ let(:end_line) { 40 }
111
+
112
+ it 'invalid options' do
113
+ expect { described_class.validate_options!(options) }.to raise_error
114
+ end
115
+ end
116
+
117
+ context 'since option' do
118
+ context 'since is not parsable' do
119
+ let(:since) { 'csdcsdc' }
120
+
121
+ it 'invalid options' do
122
+ expect { described_class.validate_options!(options) }.to raise_error
123
+ end
124
+ end
125
+
126
+ context 'since is parsable' do
127
+ let(:since) { '1 days ago' }
128
+
129
+ it 'valid options' do
130
+ expect { described_class.validate_options!(options) }.to_not raise_error
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe GitEvolution::Repository do
4
+ describe '.new' do
5
+ context 'valid repository directory' do
6
+ before(:each) { create_repository }
7
+ after(:each) { delete_repository }
8
+
9
+ it 'detects repository' do
10
+ repo = described_class.new(repository_dir)
11
+ expect(repo.dir).to eq(repository_dir)
12
+ end
13
+ end
14
+
15
+ context 'invalid repository directory' do
16
+ let!(:tmp_dir) { Dir.mktmpdir }
17
+ after { FileUtils.rm_r(tmp_dir) }
18
+
19
+ it 'detects no repository' do
20
+ expect { described_class.new(tmp_dir) }.to raise_error(Rugged::RepositoryError)
21
+ end
22
+ end
23
+ end
24
+
25
+ describe '#line_commits' do
26
+ context 'valid repository directory' do
27
+ before(:each) do
28
+ create_repository
29
+
30
+ add_to_index('README.md', "This is a Reedme\n\TODO stuff")
31
+ create_commit('John Smith', 'john@smith.com', Chronic.parse('1 day ago'), commit1_subject)
32
+
33
+ add_to_index('README.md', "This is a Readme\n\TODO stuff")
34
+ create_commit('John', 'john@smith.com', Chronic.parse('now'), commit2_subject, 'Fix typo: Reedme -> Readme')
35
+ end
36
+ after(:each) { delete_repository }
37
+
38
+ let(:commit1_subject) { 'Initial Commit' }
39
+ let(:commit2_subject) { 'Fix typo in README' }
40
+
41
+ it 'returns commits for the specifed line range' do
42
+ repo = described_class.new(repository_dir)
43
+ line_commits = repo.line_commits(1, 2, 'README.md')
44
+
45
+ expect(line_commits.size).to eq(2)
46
+ expect(line_commits.first).to be_a(GitEvolution::Commit)
47
+ expect(line_commits.first.subject).to eq(commit2_subject)
48
+ expect(line_commits.last.subject).to eq(commit1_subject)
49
+ end
50
+ end
51
+ end
52
+
53
+ describe '#raw_line_history' do
54
+ context 'valid repository directory' do
55
+ before(:each) do
56
+ create_repository
57
+
58
+ add_to_index('README.md', "This is a Reedme\n\TODO stuff")
59
+ create_commit('John Smith', 'john@smith.com', Chronic.parse('1 day ago'), commit1_subject)
60
+
61
+ add_to_index('README.md', "This is a Readme\n\TODO stuff")
62
+ create_commit('John', 'john@smith.com', Chronic.parse('now'), commit2_subject, 'Fix typo: Reedme -> Readme')
63
+ end
64
+ after(:each) { delete_repository }
65
+
66
+ let(:commit1_subject) { 'Initial Commit' }
67
+ let(:commit2_subject) { 'Fix typo in README' }
68
+
69
+ it 'returns raw commit log containing commits' do
70
+ repo = described_class.new(repository_dir)
71
+ raw_line_history = repo.raw_line_history(1, 2, 'README.md')
72
+
73
+ expect(raw_line_history).to include(commit1_subject)
74
+ expect(raw_line_history).to include(commit2_subject)
75
+ end
76
+
77
+ context 'with since option' do
78
+ it 'returns only commit within last 6 hours' do
79
+ repo = described_class.new(repository_dir)
80
+ raw_line_history = repo.raw_line_history(1, 2, 'README.md', '6 hours ago')
81
+
82
+ expect(raw_line_history).to_not include(commit1_subject)
83
+ expect(raw_line_history).to include(commit2_subject)
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,16 @@
1
+ $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
2
+
3
+ Bundler.require(:test)
4
+ Bundler.require(:development)
5
+
6
+ Dir.glob(Dir.pwd + '/spec/support/**/*.rb') { |file| require file }
7
+
8
+ require 'git_evolution/initialize'
9
+
10
+ def fixture(file_name)
11
+ File.read([Dir.pwd, 'spec', 'fixtures', file_name].join('/'))
12
+ end
13
+
14
+ RSpec.configure do |c|
15
+ c.include RepositoryHelper
16
+ end
@@ -0,0 +1,16 @@
1
+ begin
2
+ if ENV['CI']
3
+ require 'coveralls'
4
+ Coveralls.wear!
5
+ elsif ENV['COVERAGE']
6
+ require 'simplecov'
7
+ end
8
+
9
+ if ENV['CI'] || ENV['COVERAGE']
10
+ SimpleCov.start do
11
+ add_filter '/spec/'
12
+ end
13
+ end
14
+ rescue LoadError => e
15
+ warn(e)
16
+ end
@@ -0,0 +1,39 @@
1
+ module RepositoryHelper
2
+ module_function
3
+
4
+ def create_repository
5
+ @tmp_git_dir = Dir.mktmpdir
6
+ @repo = Rugged::Repository.init_at(@tmp_git_dir)
7
+ end
8
+
9
+ def delete_repository
10
+ FileUtils.rm_r(@tmp_git_dir)
11
+ end
12
+
13
+ def repository_dir
14
+ File.realpath(@tmp_git_dir) + '/'
15
+ end
16
+
17
+ def add_to_index(file_name, blob_content)
18
+ object_id = @repo.write(blob_content, :blob)
19
+ @repo.index.add(path: file_name, oid: object_id, mode: 0100644)
20
+ end
21
+
22
+ def create_commit(author_name, author_email, time, subject, body = nil)
23
+ author = { email: author_email, name: author_name, time: time }
24
+
25
+ tree = @repo.index.write_tree(@repo)
26
+
27
+ commit = Rugged::Commit.create(@repo,
28
+ author: author,
29
+ message: "#{subject}\n\n#{body}".strip,
30
+ committer: author,
31
+ parents: @repo.empty? ? [] : [@repo.head.target].compact,
32
+ tree: tree,
33
+ update_ref: 'HEAD')
34
+
35
+ @repo.checkout('master', strategy: [:force])
36
+
37
+ commit
38
+ end
39
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: git_evolution
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Jalbert
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-20 00:00:00.000000000 Z
11
+ date: 2015-07-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.7'
20
- type: :development
19
+ version: '1.0'
20
+ type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.7'
26
+ version: '1.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -31,13 +31,41 @@ dependencies:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
33
  version: '10.0'
34
- type: :development
34
+ type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rugged
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.21.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.21.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: chronic
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.10.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.10.0
41
69
  description: Gem that provides the ability to determine the evolution of code within
42
70
  a git repository
43
71
  email:
@@ -48,9 +76,8 @@ executables:
48
76
  extensions: []
49
77
  extra_rdoc_files: []
50
78
  files:
51
- - ".gitignore"
52
- - ".ruby-version"
53
79
  - Gemfile
80
+ - Gemfile.lock
54
81
  - LICENSE
55
82
  - README.md
56
83
  - Rakefile
@@ -58,7 +85,23 @@ files:
58
85
  - bin/git_evolution
59
86
  - git_evolution.gemspec
60
87
  - lib/git_evolution.rb
88
+ - lib/git_evolution/commit.rb
89
+ - lib/git_evolution/initialize.rb
90
+ - lib/git_evolution/option_handler.rb
91
+ - lib/git_evolution/report_presenter.rb
92
+ - lib/git_evolution/repository.rb
61
93
  - lib/git_evolution/version.rb
94
+ - pkg/git_evolution-0.1.1.gem
95
+ - pkg/git_evolution-0.1.2.gem
96
+ - spec/fixtures/raw_commit.txt
97
+ - spec/fixtures/raw_commit_with_no_body.txt
98
+ - spec/fixtures/raw_commit_with_no_diff.txt
99
+ - spec/git_evolution/commit_spec.rb
100
+ - spec/git_evolution/option_handler_spec.rb
101
+ - spec/git_evolution/repository_spec.rb
102
+ - spec/spec_helper.rb
103
+ - spec/support/coverage.rb
104
+ - spec/support/repository_helper.rb
62
105
  homepage: ''
63
106
  licenses:
64
107
  - MIT
@@ -69,9 +112,9 @@ require_paths:
69
112
  - lib
70
113
  required_ruby_version: !ruby/object:Gem::Requirement
71
114
  requirements:
72
- - - ">="
115
+ - - "~>"
73
116
  - !ruby/object:Gem::Version
74
- version: '0'
117
+ version: '2.0'
75
118
  required_rubygems_version: !ruby/object:Gem::Requirement
76
119
  requirements:
77
120
  - - ">="
@@ -79,9 +122,18 @@ required_rubygems_version: !ruby/object:Gem::Requirement
79
122
  version: '0'
80
123
  requirements: []
81
124
  rubyforge_project:
82
- rubygems_version: 2.4.1
125
+ rubygems_version: 2.4.6
83
126
  signing_key:
84
127
  specification_version: 4
85
128
  summary: Gem that provides the ability to determine the evolution of code within a
86
129
  git repository
87
- test_files: []
130
+ test_files:
131
+ - spec/fixtures/raw_commit.txt
132
+ - spec/fixtures/raw_commit_with_no_body.txt
133
+ - spec/fixtures/raw_commit_with_no_diff.txt
134
+ - spec/git_evolution/commit_spec.rb
135
+ - spec/git_evolution/option_handler_spec.rb
136
+ - spec/git_evolution/repository_spec.rb
137
+ - spec/spec_helper.rb
138
+ - spec/support/coverage.rb
139
+ - spec/support/repository_helper.rb
data/.gitignore DELETED
@@ -1,34 +0,0 @@
1
- *.gem
2
- *.rbc
3
- /.config
4
- /coverage/
5
- /InstalledFiles
6
- /pkg/
7
- /spec/reports/
8
- /test/tmp/
9
- /test/version_tmp/
10
- /tmp/
11
-
12
- ## Specific to RubyMotion:
13
- .dat*
14
- .repl_history
15
- build/
16
-
17
- ## Documentation cache and generated files:
18
- /.yardoc/
19
- /_yardoc/
20
- /doc/
21
- /rdoc/
22
-
23
- ## Environment normalisation:
24
- /.bundle/
25
- /lib/bundler/man/
26
-
27
- # for a library or gem, you might want to ignore these files since the code is
28
- # intended to run in multiple environments; otherwise, check them in:
29
- # Gemfile.lock
30
- # .ruby-version
31
- # .ruby-gemset
32
-
33
- # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
34
- .rvmrc
data/.ruby-version DELETED
@@ -1 +0,0 @@
1
- 2.2.0