method_log 0.0.5 → 0.0.6

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: 41e9aab264b13fe45d2a9647bf235cea14ea5378
4
- data.tar.gz: 15fd978c654b949dc848f1c91057d6a104f008b8
3
+ metadata.gz: 8e04ff3b961b6a3955ede7783834922849c7e959
4
+ data.tar.gz: 36bd0e165bbba5bb13cdabef4110709d80e42921
5
5
  SHA512:
6
- metadata.gz: 6d077e6eb40d283f0f490e0420a2650f29a6eb8382e38ab36c6ab8551c6fe88fccc6f087d6eed70851ee4749f88e5c6fdb1889c98380076a02ba568daf0d05f8
7
- data.tar.gz: 7dd0f4d2191b7e83b9d9b483f1452c0cde6af6f0cd5ee8e30e0395d2a88fa623a1c25c48f819a2077031657fff3bdd21bb7825983e82b4fa9343d69f13c1c83f
6
+ metadata.gz: 54e9280555d0f24e5fc215351c81a92889b502604e314caad19f6f6a72e891e10dc029e716fbc2100e76737f7f2998540eb95dbcc99d12ec88874e7e80588c87
7
+ data.tar.gz: ebba91b228114666d7b69ab73eb7a4e089b03eb60b4b00503da92a6c4313fce0e7f101ad14ee4d6cbf94d9152c0407c101cd3abfdcf25101a0f9cc7cfd0ee46f
@@ -2,4 +2,5 @@ language: ruby
2
2
  rvm:
3
3
  - 2.1.0
4
4
  - 2.0.0
5
+ - 1.9.3
5
6
 
data/README.md CHANGED
@@ -4,11 +4,12 @@ Trace the history of an individual method in a git repository.
4
4
 
5
5
  This is a work-in-progress and nowhere near production-ready.
6
6
 
7
- ### Dependencies
7
+ ### Requirements
8
8
 
9
- * Ruby >= v2.0.0 (just because I'm using named parameters)
9
+ * Ruby >= v1.9.3 (due to requirements of the `rugged` gem)
10
10
  * The [rugged](https://github.com/libgit2/rugged) Ruby gem (listed as dependency in gemspec)
11
11
  * The [libgit2](https://github.com/libgit2/libgit2) C library (included as part of rugged gem)
12
+ * The [parser](https://github.com/whitequark/parser) Ruby gem (listed as dependency in gemspec)
12
13
 
13
14
  ### Install
14
15
 
@@ -20,15 +21,12 @@ This is a work-in-progress and nowhere near production-ready.
20
21
 
21
22
  ### Todo
22
23
 
23
- * Support earlier versions of Ruby (it ought to be possible to support down to v1.9.3 fairly easily)
24
- * Investigate whether parser gem can only parse source code written using the current Ruby version
25
- * It would be nice to have a better error message if parsing fails
26
24
  * Support for Rspec tests
27
25
  * Default to looking for commits in current git branch
26
+ * Check what happens with merge commits
28
27
  * Maybe add as new git command or extension to existing command e.g. `git log`
29
28
  * Optimise search for method definitions:
30
29
  * First look in file where method was last defined
31
- * By default stop when method disappears from history
32
30
  * Find "similar" method implementations e.g. by comparing ASTs of implementations
33
31
 
34
32
  ### Credits
@@ -1,19 +1,35 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'bundler/setup'
4
- require 'method_log'
5
- require 'method_log/repository'
6
- require 'method_log/api'
7
4
  require 'trollop'
8
5
 
9
6
  options = Trollop::options do
10
7
  opt :patch, 'Generate patch.', short: 'p'
8
+ opt :ruby_version, 'Parser Ruby version (18, 19, 20, 21)', default: 'current'
11
9
  opt :max_count, 'Limit the number of commits to output.', type: :integer, short: 'n'
10
+ opt :stop_at_latest_introduction_of_method, 'Stop at lastest introduction of method.', default: true
11
+ end
12
+
13
+ case ruby_version = options[:ruby_version]
14
+ when 'current'
15
+ require 'parser/current'
16
+ when '18', '19', '20', '21'
17
+ require 'parser/ruby18'
18
+ require 'parser/ruby19'
19
+ require 'parser/ruby20'
20
+ require 'parser/ruby21'
21
+ Parser::CurrentRuby = Parser.const_get("Ruby#{ruby_version}")
22
+ else
23
+ raise "Ruby version not supported: #{ruby_version}"
12
24
  end
13
25
 
14
- repository = MethodLog::Repository.new(path: Dir.pwd)
15
- api = MethodLog::API.new(repository: repository)
16
- api.diffs(ARGV[0], max_count: options[:max_count]).each do |method_commit, method_diff|
26
+ require 'method_log'
27
+ require 'method_log/repository'
28
+ require 'method_log/api'
29
+
30
+ repository = MethodLog::Repository.new(Dir.pwd)
31
+ api = MethodLog::API.new(repository)
32
+ api.diffs(ARGV[0], options).each do |method_commit, method_diff|
17
33
  puts "commit #{method_commit.sha}"
18
34
  puts "Author: #{method_commit.author[:name]} <#{method_commit.author[:email]}>"
19
35
  puts "Date: #{method_commit.author[:time].strftime('%a %b %-e %T %Y %z')}"
@@ -4,31 +4,32 @@ require 'method_log/method_diff'
4
4
 
5
5
  module MethodLog
6
6
  class API
7
- def initialize(repository: nil)
7
+ def initialize(repository)
8
8
  @repository = repository
9
9
  end
10
10
 
11
- def history(method_identifier, max_count: nil)
12
- method_name = method_identifier.split(Regexp.union('#', '.')).last
11
+ def history(method_identifier, options = {})
13
12
  Enumerator.new do |yielder|
14
- last_source_file = nil
15
- @repository.commits(max_count: max_count).each do |commit|
16
- next if last_source_file && commit.contains?(last_source_file)
17
- source_files = commit.source_files.select { |sf| sf.source[Regexp.new(method_name)] }
18
- method_definition = nil
19
- source_files.map { |sf| MethodFinder.new(source_file: sf) }.each do |method_finder|
20
- break if method_definition = method_finder.find(method_identifier)
13
+ last_method_commit = nil
14
+ @repository.commits(options).each do |commit|
15
+ last_source_file = last_method_commit && last_method_commit.source_file
16
+ if last_source_file && commit.contains?(last_source_file)
17
+ last_method_commit.update(commit)
18
+ else
19
+ method_definition = commit.find(method_identifier)
20
+ yielder << last_method_commit if last_method_commit
21
+ last_method_commit = MethodCommit.new(commit, method_definition)
22
+ yielder << last_method_commit
23
+ break if options[:stop_at_latest_introduction_of_method] && method_definition.nil?
21
24
  end
22
- last_source_file = method_definition && method_definition.source_file
23
- yielder << MethodCommit.new(commit: commit, method_definition: method_definition)
24
25
  end
25
26
  end
26
27
  end
27
28
 
28
- def diffs(method_identifier, max_count: nil)
29
+ def diffs(method_identifier, options = {})
29
30
  Enumerator.new do |yielder|
30
- history(method_identifier, max_count: max_count).each_cons(2) do |(commit, parent)|
31
- diff = MethodDiff.new(first_commit: parent, second_commit: commit)
31
+ history(method_identifier, options).each_cons(2) do |(commit, parent)|
32
+ diff = MethodDiff.new(parent, commit)
32
33
  unless diff.empty?
33
34
  yielder << [commit, diff]
34
35
  end
@@ -6,7 +6,7 @@ module MethodLog
6
6
  class Commit
7
7
  attr_reader :sha
8
8
 
9
- def initialize(repository: nil, sha: nil)
9
+ def initialize(sha, repository = nil)
10
10
  @repository = repository
11
11
  @sha = sha
12
12
  @index = Rugged::Index.new
@@ -17,7 +17,8 @@ module MethodLog
17
17
  @index.add(path: source_file.path, oid: oid, mode: 0100644)
18
18
  end
19
19
 
20
- def apply(user: { email: 'test@example.com', name: 'test', time: Time.now }, message: 'commit-message')
20
+ def apply(options = {})
21
+ user, message = options[:user], options[:message]
21
22
  tree = @index.write_tree(@repository)
22
23
  parents = @repository.empty? ? [] : [@repository.head.target].compact
23
24
  @sha = Rugged::Commit.create(@repository, tree: tree, parents: parents, update_ref: 'HEAD', author: user, committer: user, message: message)
@@ -38,6 +39,17 @@ module MethodLog
38
39
  source_files_by_path[source_file.path] == source_file
39
40
  end
40
41
 
42
+ def find(method_identifier)
43
+ method_definition = nil
44
+ method_name = method_identifier.split(Regexp.union('#', '.')).last
45
+ source_files.each do |source_file|
46
+ next unless source_file.source[Regexp.new(method_name)]
47
+ method_finder = MethodFinder.new(source_file)
48
+ break if method_definition = method_finder.find(method_identifier)
49
+ end
50
+ method_definition
51
+ end
52
+
41
53
  def author
42
54
  commit.author
43
55
  end
@@ -1,10 +1,16 @@
1
1
  module MethodLog
2
2
  class MethodCommit
3
- def initialize(commit: nil, method_definition: nil)
3
+ attr_reader :method_definition
4
+
5
+ def initialize(commit, method_definition)
4
6
  @commit = commit
5
7
  @method_definition = method_definition
6
8
  end
7
9
 
10
+ def update(commit)
11
+ @commit = commit
12
+ end
13
+
8
14
  def ==(other)
9
15
  (commit == other.commit) && (method_definition == other.method_definition)
10
16
  end
@@ -29,9 +35,12 @@ module MethodLog
29
35
  method_definition && method_definition.source + $/
30
36
  end
31
37
 
38
+ def source_file
39
+ method_definition && method_definition.source_file
40
+ end
41
+
32
42
  protected
33
43
 
34
44
  attr_reader :commit
35
- attr_reader :method_definition
36
45
  end
37
46
  end
@@ -2,7 +2,7 @@ module MethodLog
2
2
  class MethodDefinition
3
3
  attr_reader :source_file
4
4
 
5
- def initialize(source_file: nil, lines: nil)
5
+ def initialize(source_file, lines)
6
6
  @source_file = source_file
7
7
  @lines = lines
8
8
  end
@@ -2,7 +2,7 @@ require 'diffy'
2
2
 
3
3
  module MethodLog
4
4
  class MethodDiff
5
- def initialize(first_commit: nil, second_commit: nil)
5
+ def initialize(first_commit, second_commit)
6
6
  @first_commit, @second_commit = first_commit, second_commit
7
7
  end
8
8
 
@@ -1,11 +1,13 @@
1
- require 'parser/current'
1
+ unless defined?(Parser::CurrentRuby)
2
+ require 'parser/current'
3
+ end
2
4
 
3
5
  require 'method_log/method_definition'
4
6
  require 'method_log/scope'
5
7
 
6
8
  module MethodLog
7
9
  class MethodFinder < Parser::AST::Processor
8
- def initialize(source_file: nil)
10
+ def initialize(source_file)
9
11
  @source_file = source_file
10
12
  @scope = Scope::Root.new
11
13
  @methods = {}
@@ -76,7 +78,7 @@ module MethodLog
76
78
  end
77
79
 
78
80
  def record_method_definition(scope, name, node)
79
- definition = MethodDefinition.new(source_file: @source_file, lines: lines_for(node))
81
+ definition = MethodDefinition.new(@source_file, lines_for(node))
80
82
  identifier = scope.method_identifier(name)
81
83
  @methods[identifier] = definition
82
84
  end
@@ -4,26 +4,32 @@ require 'method_log/commit'
4
4
 
5
5
  module MethodLog
6
6
  class Repository
7
- def initialize(path: nil)
7
+ def initialize(path)
8
8
  @repository = Rugged::Repository.new(path)
9
9
  @commits = []
10
10
  end
11
11
 
12
- def build_commit(sha: nil)
13
- Commit.new(repository: @repository, sha: sha)
12
+ def build_commit(sha = nil)
13
+ Commit.new(sha, @repository)
14
14
  end
15
15
 
16
- def add(commit)
17
- commit.apply
18
- @commits << commit
16
+ def commit(*source_files)
17
+ options = source_files.pop if source_files.last.is_a?(Hash)
18
+ options ||= {}
19
+ options = { user: { email: 'test@example.com', name: 'test', time: Time.now }, message: 'commit-message' }.merge(options)
20
+ build_commit.tap do |commit|
21
+ source_files.each { |sf| commit.add(sf) }
22
+ commit.apply(options)
23
+ @commits << commit
24
+ end
19
25
  end
20
26
 
21
- def commits(max_count: nil)
27
+ def commits(options = {})
22
28
  Enumerator.new do |yielder|
23
29
  if @repository.ref('refs/heads/master')
24
30
  @repository.walk(@repository.last_commit).with_index do |commit, index|
25
- break if max_count && index >= max_count - 1
26
- yielder << build_commit(sha: commit.oid)
31
+ break if options[:max_count] && index >= options[:max_count] - 1
32
+ yielder << build_commit(commit.oid)
27
33
  end
28
34
  end
29
35
  end
@@ -2,7 +2,7 @@ module MethodLog
2
2
  class Scope
3
3
  class Null < Scope
4
4
  def initialize
5
- super(name: 'null')
5
+ super('null')
6
6
  end
7
7
 
8
8
  def lookup(name)
@@ -12,7 +12,7 @@ module MethodLog
12
12
 
13
13
  class Root < Scope
14
14
  def initialize
15
- super(name: 'root', parent: Scope::Null.new)
15
+ super('root', parent = Scope::Null.new)
16
16
  end
17
17
 
18
18
  def names
@@ -26,7 +26,7 @@ module MethodLog
26
26
 
27
27
  attr_reader :parent
28
28
 
29
- def initialize(name: nil, parent: nil, singleton: false)
29
+ def initialize(name, parent = nil, singleton = false)
30
30
  @name = name
31
31
  @parent = parent
32
32
  @singleton = singleton
@@ -46,7 +46,7 @@ module MethodLog
46
46
  end
47
47
 
48
48
  def define(name)
49
- @modules[name] = Scope.new(name: name, parent: self)
49
+ @modules[name] = Scope.new(name, parent = self)
50
50
  end
51
51
 
52
52
  def lookup(name)
@@ -54,7 +54,7 @@ module MethodLog
54
54
  end
55
55
 
56
56
  def singleton
57
- Scope.new(name: @name, parent: self, singleton: true)
57
+ Scope.new(@name, parent = self, singleton = true)
58
58
  end
59
59
 
60
60
  def method_identifier(name)
@@ -3,11 +3,11 @@ module MethodLog
3
3
  attr_reader :path
4
4
  attr_reader :sha
5
5
 
6
- def initialize(path: nil, source: nil, repository: nil, sha: nil)
7
- @path = path
8
- @source = source
9
- @repository = repository
10
- @sha = sha
6
+ def initialize(options = {})
7
+ @path = options[:path]
8
+ @source = options[:source]
9
+ @repository = options[:repository]
10
+ @sha = options[:sha]
11
11
  end
12
12
 
13
13
  def source
@@ -1,3 +1,3 @@
1
1
  module MethodLog
2
- VERSION = '0.0.5'
2
+ VERSION = '0.0.6'
3
3
  end
@@ -18,6 +18,8 @@ Gem::Specification.new do |s|
18
18
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
19
  s.require_paths = ['lib']
20
20
 
21
+ s.required_ruby_version = '>= 1.9.3'
22
+
21
23
  s.add_dependency 'rugged'
22
24
  s.add_dependency 'parser'
23
25
  s.add_dependency 'diffy'
@@ -7,99 +7,87 @@ require 'method_log/commit'
7
7
  require 'method_log/method_definition'
8
8
  require 'method_log/method_commit'
9
9
 
10
- describe MethodLog::API do
11
- let(:repository_path) { File.expand_path('../repository.git', __FILE__) }
12
-
13
- before do
14
- FileUtils.mkdir_p(repository_path)
15
- Rugged::Repository.init_at(repository_path, :bare)
16
- end
17
-
18
- after do
19
- FileUtils.rm_rf(repository_path)
20
- end
21
-
22
- it 'finds class instance method in repository with two commits with single source file' do
23
- foo_1 = MethodLog::SourceFile.new(path: 'foo.rb', source: %{
24
- class Foo
25
- def bar
26
- # implementation 1
27
- end
28
- end
29
- }.strip)
30
-
31
- foo_2 = MethodLog::SourceFile.new(path: 'foo.rb', source: %{
32
- # move method definition down one line
33
- class Foo
34
- def bar
35
- # implementation 2
36
- end
37
- end
38
- }.strip)
39
-
40
- repository = MethodLog::Repository.new(path: repository_path)
41
-
42
- commit_1 = repository.build_commit
43
- commit_1.add(foo_1)
44
- repository.add(commit_1)
45
-
46
- commit_2 = repository.build_commit
47
- commit_2.add(foo_2)
48
- repository.add(commit_2)
49
-
50
- repository = MethodLog::Repository.new(path: repository_path)
51
- api = MethodLog::API.new(repository: repository)
52
- method_commits = api.history('Foo#bar').to_a
53
-
54
- method_definition_1 = MethodLog::MethodDefinition.new(source_file: foo_1, lines: 1..3)
55
- method_definition_2 = MethodLog::MethodDefinition.new(source_file: foo_2, lines: 2..4)
56
-
57
- method_commit_1 = MethodLog::MethodCommit.new(commit: commit_1, method_definition: method_definition_1)
58
- method_commit_2 = MethodLog::MethodCommit.new(commit: commit_2, method_definition: method_definition_2)
59
-
60
- expect(method_commits).to eq([method_commit_2, method_commit_1])
61
-
62
- method_commit, method_diff = api.diffs('Foo#bar').first
63
- expect(method_diff.to_s.strip).to eq(%{
64
- def bar
65
- - # implementation 1
66
- + # implementation 2
67
- end
68
- }.strip)
69
- end
70
-
71
- it 'finds method which is defined in one commit, then removed in the next commit, and defined again in the next commit' do
72
- foo_with_bar = MethodLog::SourceFile.new(path: 'foo.rb', source: %{
73
- class Foo
74
- def bar; end
75
- end
76
- }.strip)
77
-
78
- foo_without_bar = MethodLog::SourceFile.new(path: 'foo.rb', source: %{
79
- class Foo
80
- end
81
- }.strip)
82
-
83
- repository = MethodLog::Repository.new(path: repository_path)
84
-
85
- commit_1 = repository.build_commit
86
- commit_1.add(foo_with_bar)
87
- repository.add(commit_1)
88
-
89
- commit_2 = repository.build_commit
90
- commit_2.add(foo_without_bar)
91
- repository.add(commit_2)
92
-
93
- commit_3 = repository.build_commit
94
- commit_3.add(foo_with_bar)
95
- repository.add(commit_3)
96
-
97
- repository = MethodLog::Repository.new(path: repository_path)
98
- api = MethodLog::API.new(repository: repository)
99
- diffs = api.diffs('Foo#bar').map(&:last).map(&:to_s)
100
- expect(diffs).to eq([
101
- "+ def bar; end\n",
102
- "- def bar; end\n"
103
- ])
10
+ module MethodLog
11
+ describe API do
12
+ let(:repository_path) { File.expand_path('../repository.git', __FILE__) }
13
+
14
+ before do
15
+ FileUtils.mkdir_p(repository_path)
16
+ Rugged::Repository.init_at(repository_path, :bare)
17
+ end
18
+
19
+ after do
20
+ FileUtils.rm_rf(repository_path)
21
+ end
22
+
23
+ it 'finds class instance method in repository with two commits with single source file' do
24
+ repository = Repository.new(repository_path)
25
+ commit_1 = repository.commit(source(path: 'foo.rb', source: %{
26
+ class Foo
27
+ def bar
28
+ # implementation 1
29
+ end
30
+ end
31
+ }))
32
+ commit_2 = repository.commit(source(path: 'foo.rb', source: %{
33
+ # move method definition down one line
34
+ class Foo
35
+ def bar
36
+ # implementation 2
37
+ end
38
+ end
39
+ }))
40
+
41
+ method_commits, method_diffs = commits_and_diffs_for('Foo#bar')
42
+
43
+ expect(method_commits.first.sha).to eq(commit_2.sha)
44
+ expect(method_diffs.first.to_s.chomp).to eq(unindent(%{
45
+ def bar
46
+ - # implementation 1
47
+ + # implementation 2
48
+ end
49
+ }))
50
+ end
51
+
52
+ it 'finds method which is defined, then removed, and then defined again' do
53
+ repository = Repository.new(repository_path)
54
+ commit_1 = repository.commit(source(path: 'foo.rb', source: %{
55
+ class Foo
56
+ def bar; end
57
+ end
58
+ }))
59
+ commit_2 = repository.commit(source(path: 'foo.rb', source: %{
60
+ class Foo
61
+ end
62
+ }))
63
+ commit_3 = repository.commit(source(path: 'foo.rb', source: %{
64
+ class Foo
65
+ def bar; end
66
+ end
67
+ }))
68
+ commit_4 = repository.commit(source(path: 'foo.rb', source: %{
69
+ class Foo
70
+ def bar; end
71
+ end
72
+ }))
73
+
74
+ method_commits, method_diffs = commits_and_diffs_for('Foo#bar')
75
+
76
+ expect(method_commits.map(&:sha)).to eq([commit_3.sha, commit_2.sha])
77
+ expect(method_diffs.map(&:to_s)).to eq([
78
+ "+ def bar; end\n",
79
+ "- def bar; end\n"
80
+ ])
81
+ end
82
+
83
+ private
84
+
85
+ def commits_and_diffs_for(method_identifier)
86
+ api = API.new(Repository.new(repository_path))
87
+ commits_and_diffs = api.diffs(method_identifier)
88
+ method_commits = commits_and_diffs.map(&:first)
89
+ method_diffs = commits_and_diffs.map(&:last)
90
+ [method_commits, method_diffs]
91
+ end
104
92
  end
105
- end
93
+ end