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 +4 -4
- data/.travis.yml +1 -0
- data/README.md +4 -6
- data/bin/method_log +22 -6
- data/lib/method_log/api.rb +16 -15
- data/lib/method_log/commit.rb +14 -2
- data/lib/method_log/method_commit.rb +11 -2
- data/lib/method_log/method_definition.rb +1 -1
- data/lib/method_log/method_diff.rb +1 -1
- data/lib/method_log/method_finder.rb +5 -3
- data/lib/method_log/repository.rb +15 -9
- data/lib/method_log/scope.rb +5 -5
- data/lib/method_log/source_file.rb +5 -5
- data/lib/method_log/version.rb +1 -1
- data/method_log.gemspec +2 -0
- data/spec/api_spec.rb +83 -95
- data/spec/commit_spec.rb +71 -73
- data/spec/method_commit_spec.rb +27 -23
- data/spec/method_definition_spec.rb +28 -24
- data/spec/method_diff_spec.rb +13 -9
- data/spec/method_finder_spec.rb +215 -219
- data/spec/scope_spec.rb +91 -89
- data/spec/source_file_spec.rb +41 -39
- data/spec/spec_helper.rb +25 -0
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8e04ff3b961b6a3955ede7783834922849c7e959
|
4
|
+
data.tar.gz: 36bd0e165bbba5bb13cdabef4110709d80e42921
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 54e9280555d0f24e5fc215351c81a92889b502604e314caad19f6f6a72e891e10dc029e716fbc2100e76737f7f2998540eb95dbcc99d12ec88874e7e80588c87
|
7
|
+
data.tar.gz: ebba91b228114666d7b69ab73eb7a4e089b03eb60b4b00503da92a6c4313fce0e7f101ad14ee4d6cbf94d9152c0407c101cd3abfdcf25101a0f9cc7cfd0ee46f
|
data/.travis.yml
CHANGED
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
|
-
###
|
7
|
+
### Requirements
|
8
8
|
|
9
|
-
* Ruby >=
|
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
|
data/bin/method_log
CHANGED
@@ -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
|
-
|
15
|
-
|
16
|
-
api
|
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')}"
|
data/lib/method_log/api.rb
CHANGED
@@ -4,31 +4,32 @@ require 'method_log/method_diff'
|
|
4
4
|
|
5
5
|
module MethodLog
|
6
6
|
class API
|
7
|
-
def initialize(repository
|
7
|
+
def initialize(repository)
|
8
8
|
@repository = repository
|
9
9
|
end
|
10
10
|
|
11
|
-
def history(method_identifier,
|
12
|
-
method_name = method_identifier.split(Regexp.union('#', '.')).last
|
11
|
+
def history(method_identifier, options = {})
|
13
12
|
Enumerator.new do |yielder|
|
14
|
-
|
15
|
-
@repository.commits(
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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,
|
29
|
+
def diffs(method_identifier, options = {})
|
29
30
|
Enumerator.new do |yielder|
|
30
|
-
history(method_identifier,
|
31
|
-
diff = MethodDiff.new(
|
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
|
data/lib/method_log/commit.rb
CHANGED
@@ -6,7 +6,7 @@ module MethodLog
|
|
6
6
|
class Commit
|
7
7
|
attr_reader :sha
|
8
8
|
|
9
|
-
def initialize(repository
|
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(
|
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
|
-
|
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
|
@@ -1,11 +1,13 @@
|
|
1
|
-
|
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
|
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(
|
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
|
7
|
+
def initialize(path)
|
8
8
|
@repository = Rugged::Repository.new(path)
|
9
9
|
@commits = []
|
10
10
|
end
|
11
11
|
|
12
|
-
def build_commit(sha
|
13
|
-
Commit.new(
|
12
|
+
def build_commit(sha = nil)
|
13
|
+
Commit.new(sha, @repository)
|
14
14
|
end
|
15
15
|
|
16
|
-
def
|
17
|
-
|
18
|
-
|
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(
|
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(
|
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
|
data/lib/method_log/scope.rb
CHANGED
@@ -2,7 +2,7 @@ module MethodLog
|
|
2
2
|
class Scope
|
3
3
|
class Null < Scope
|
4
4
|
def initialize
|
5
|
-
super(
|
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(
|
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
|
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
|
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(
|
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(
|
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
|
data/lib/method_log/version.rb
CHANGED
data/method_log.gemspec
CHANGED
@@ -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'
|
data/spec/api_spec.rb
CHANGED
@@ -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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
end
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
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
|