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