method_log 0.0.5 → 0.0.6

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: 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