git_evolution 0.0.1 → 0.1.2

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