meta_commit 0.1.0.alpha → 0.2.0.pre.alpha
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 +3 -0
- data/CONTRIBUTING.md +61 -0
- data/README.md +36 -5
- data/Rakefile +7 -1
- data/config/default.yml +11 -0
- data/exe/meta_commit +5 -1
- data/lib/meta_commit/changelog/adapters/changelog.rb +94 -0
- data/lib/meta_commit/changelog/commands/commit_diff_examiner.rb +50 -0
- data/lib/meta_commit/changelog/formatters/keep_a_changelog_ver_report_builder.rb +123 -0
- data/lib/meta_commit/cli.rb +71 -17
- data/lib/meta_commit/configuration.rb +45 -0
- data/lib/meta_commit/configuration_store.rb +27 -0
- data/lib/meta_commit/container.rb +80 -0
- data/lib/meta_commit/errors.rb +12 -0
- data/lib/meta_commit/factories/contextual_ast_node_factory.rb +61 -0
- data/lib/meta_commit/factories/diff_factory.rb +37 -0
- data/lib/meta_commit/factories/parser_factory.rb +30 -0
- data/lib/meta_commit/git/repo.rb +124 -2
- data/lib/meta_commit/index/adapters/git_notes.rb +34 -0
- data/lib/meta_commit/index/commands/diff_examiner.rb +58 -0
- data/lib/meta_commit/message/commands/diff_index_examiner.rb +48 -0
- data/lib/meta_commit/message/formatters/commit_message_builder.rb +16 -0
- data/lib/meta_commit/models/changes/commit.rb +24 -0
- data/lib/meta_commit/models/changes/repository.rb +12 -0
- data/lib/meta_commit/models/contextual_ast_node.rb +9 -0
- data/lib/meta_commit/models/diffs/diff.rb +31 -3
- data/lib/meta_commit/services/change_saver.rb +3 -26
- data/lib/meta_commit/services/parse.rb +21 -0
- data/lib/meta_commit/version.rb +1 -1
- data/lib/meta_commit.rb +34 -27
- data/meta_commit.gemspec +5 -0
- metadata +90 -23
- data/lib/meta_commit/adapters/dump.rb +0 -31
- data/lib/meta_commit/adapters/git_notes.rb +0 -28
- data/lib/meta_commit/models/ast_path.rb +0 -111
- data/lib/meta_commit/models/changes/file.rb +0 -27
- data/lib/meta_commit/models/diffs/addition.rb +0 -34
- data/lib/meta_commit/models/diffs/changes_in_method.rb +0 -14
- data/lib/meta_commit/models/diffs/class_creation.rb +0 -20
- data/lib/meta_commit/models/diffs/class_deletion.rb +0 -16
- data/lib/meta_commit/models/diffs/class_rename.rb +0 -11
- data/lib/meta_commit/models/diffs/deletion.rb +0 -37
- data/lib/meta_commit/models/diffs/method_creation.rb +0 -22
- data/lib/meta_commit/models/diffs/method_deletion.rb +0 -22
- data/lib/meta_commit/models/diffs/module_creation.rb +0 -14
- data/lib/meta_commit/models/diffs/module_deletion.rb +0 -14
- data/lib/meta_commit/models/diffs/module_rename.rb +0 -11
- data/lib/meta_commit/models/diffs/replacement.rb +0 -19
- data/lib/meta_commit/models/factories/ast_path_factory.rb +0 -40
- data/lib/meta_commit/models/factories/diff_factory.rb +0 -39
- data/lib/meta_commit/services/commit_message_builder.rb +0 -23
- data/lib/meta_commit/services/diff_examiner.rb +0 -121
- data/lib/meta_commit/services/diff_index_examiner.rb +0 -112
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'dry/container'
|
2
|
+
module MetaCommit
|
3
|
+
class Container
|
4
|
+
include Dry::Container::Mixin
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
super
|
8
|
+
register :parser_classes, []
|
9
|
+
register :diff_classes, []
|
10
|
+
end
|
11
|
+
|
12
|
+
# @param [MetaCommit::ConfigStore] config_store
|
13
|
+
# @return [MetaCommit::Container]
|
14
|
+
def boot(config_store)
|
15
|
+
load_packages(config_store.get(:extensions))
|
16
|
+
|
17
|
+
register :diff_factory, MetaCommit::Factories::DiffFactory.new(self[:diff_classes])
|
18
|
+
register :parser_factory, MetaCommit::Factories::ParserFactory.new(self[:parser_classes])
|
19
|
+
register :parse_command, MetaCommit::Services::Parse.new(self[:parser_factory])
|
20
|
+
register :contextual_ast_node_factory, MetaCommit::Factories::ContextualAstNodeFactory.new
|
21
|
+
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param [Array<String>] extensions_to_load
|
26
|
+
def load_packages(extensions_to_load)
|
27
|
+
# @TODO refactor so it search in GEM_HOME dir (?)
|
28
|
+
required_extensions(extensions_to_load).each do |gem|
|
29
|
+
gem.require_paths.each do |path|
|
30
|
+
gem_source_path = File.join(gem.full_gem_path, path)
|
31
|
+
$LOAD_PATH.unshift gem_source_path
|
32
|
+
require gem.name
|
33
|
+
end
|
34
|
+
|
35
|
+
extension = extension_class(camelize(without_extension_prefix(gem.name))).new
|
36
|
+
|
37
|
+
self[:parser_classes].concat(extension.parsers)
|
38
|
+
self[:diff_classes].concat(extension.diffs)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
protected :load_packages
|
43
|
+
|
44
|
+
# @param [Array<String>] extensions_to_load
|
45
|
+
# @return [Array<String>]
|
46
|
+
def required_extensions(extensions_to_load)
|
47
|
+
extensions = Gem::Specification.find_all.select do |gem|
|
48
|
+
gem.name =~ /^meta_commit_/ && gem.name != 'meta_commit_contracts' && extensions_to_load.include?(without_extension_prefix gem.name)
|
49
|
+
end
|
50
|
+
extensions.uniq {|gem| gem.name}
|
51
|
+
end
|
52
|
+
|
53
|
+
protected :required_extensions
|
54
|
+
|
55
|
+
# @param [String] extension name
|
56
|
+
# @return [Class] extension class
|
57
|
+
def extension_class(extension)
|
58
|
+
"MetaCommit::Extension::#{extension}::Locator".split('::').inject(Object) {|o, c| o.const_get(c)}
|
59
|
+
end
|
60
|
+
|
61
|
+
protected :extension_class
|
62
|
+
|
63
|
+
# @param [String] extension_name
|
64
|
+
# @return [String]
|
65
|
+
def camelize(extension_name)
|
66
|
+
return extension_name.slice(0, 1).capitalize + extension_name.slice(1..-1) unless extension_name.include?('_') # capitalize only first letter if name is not in snake case
|
67
|
+
extension_name.split('_').collect(&:capitalize).join
|
68
|
+
end
|
69
|
+
|
70
|
+
protected :camelize
|
71
|
+
|
72
|
+
# @param [String] extension_name
|
73
|
+
# @return [String]
|
74
|
+
def without_extension_prefix(extension_name)
|
75
|
+
extension_name.sub('meta_commit_', '')
|
76
|
+
end
|
77
|
+
|
78
|
+
protected :without_extension_prefix
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module MetaCommit::Errors
|
2
|
+
class MissingRepoError < StandardError
|
3
|
+
def initialize(msg='Passed directory is not a git repo')
|
4
|
+
super(msg)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
class MissingConfigError < StandardError
|
8
|
+
def initialize(msg='Passed config file does not exist')
|
9
|
+
super(msg)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module MetaCommit::Factories
|
2
|
+
# Factory that builds ContextualAstNode from ast nodes
|
3
|
+
class ContextualAstNodeFactory
|
4
|
+
|
5
|
+
# @param [MetaCommit::Contracts::Ast] source_ast
|
6
|
+
# @param [Integer] line_number
|
7
|
+
# @return [MetaCommit::Models::ContextualAstNode]
|
8
|
+
def create_ast_path(source_ast, line_number)
|
9
|
+
visited_nodes = []
|
10
|
+
ast_path = MetaCommit::Models::ContextualAstNode.new
|
11
|
+
ast_path.parser_class = source_ast.parser_class
|
12
|
+
ast_path.target_node = collect_path_to_ast_at_line(source_ast, line_number, visited_nodes)
|
13
|
+
ast_path.context_nodes = visited_nodes
|
14
|
+
ast_path
|
15
|
+
end
|
16
|
+
|
17
|
+
# @param [MetaCommit::Contracts::Ast] ast
|
18
|
+
# @param [Integer] lineno
|
19
|
+
# @param [Array<MetaCommit::Contracts::Ast>] accumulator
|
20
|
+
# @return [MetaCommit::Contracts::Ast]
|
21
|
+
def collect_path_to_ast_at_line(ast, lineno, accumulator)
|
22
|
+
return nil if ast.nil? or not covers_line(ast, lineno)
|
23
|
+
closest_ast = ast
|
24
|
+
accumulator.push(closest_ast)
|
25
|
+
ast.children.each do |child|
|
26
|
+
found_ast = collect_path_to_ast_at_line(child, lineno, accumulator)
|
27
|
+
closest_ast = found_ast unless found_ast.nil?
|
28
|
+
end
|
29
|
+
closest_ast
|
30
|
+
end
|
31
|
+
|
32
|
+
protected :collect_path_to_ast_at_line
|
33
|
+
|
34
|
+
|
35
|
+
# @param [MetaCommit::Contracts::Ast] ast
|
36
|
+
# @param [Integer] lineno
|
37
|
+
# @return [MetaCommit::Contracts::Ast]
|
38
|
+
def get_ast_at_line(ast, lineno)
|
39
|
+
return nil unless covers_line(ast, lineno)
|
40
|
+
closest_ast = ast
|
41
|
+
ast.children.each do |child|
|
42
|
+
found_ast = get_ast_at_line(child, lineno)
|
43
|
+
closest_ast = found_ast unless found_ast.nil?
|
44
|
+
end
|
45
|
+
closest_ast
|
46
|
+
end
|
47
|
+
|
48
|
+
protected :get_ast_at_line
|
49
|
+
|
50
|
+
|
51
|
+
# @param [MetaCommit::Contracts::Ast] ast
|
52
|
+
# @param [Integer] line
|
53
|
+
# @return [Boolean]
|
54
|
+
def covers_line(ast, line)
|
55
|
+
return false if ((ast.first_line.nil?) && (ast.last_line.nil?))
|
56
|
+
((ast.first_line <= line) && (ast.last_line >= line))
|
57
|
+
end
|
58
|
+
|
59
|
+
protected :covers_line
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module MetaCommit::Factories
|
2
|
+
# Diff factory
|
3
|
+
# @attr [Array<Class>] available_diff_classes diff classes that factory can build
|
4
|
+
class DiffFactory
|
5
|
+
attr_accessor :available_diff_classes
|
6
|
+
|
7
|
+
def initialize(diff_classes)
|
8
|
+
@available_diff_classes = diff_classes
|
9
|
+
end
|
10
|
+
|
11
|
+
# Factory method
|
12
|
+
# @param [Symbol] type
|
13
|
+
# @param [Hash] options
|
14
|
+
# @return [Diff, nil] created diff or nil if matched diff not found
|
15
|
+
def create_diff_of_type(type, options)
|
16
|
+
@available_diff_classes.each do |diff_class|
|
17
|
+
diff = diff_class.new
|
18
|
+
if diff.supports_parser?(options[:old_ast_path].parser_class) &&
|
19
|
+
diff.supports_parser?(options[:new_ast_path].parser_class) &&
|
20
|
+
diff.supports_change(type, options[:old_file_path], options[:new_file_path], options[:old_ast_path], options[:new_ast_path])
|
21
|
+
line = options[:line]
|
22
|
+
diff.diff_type = line.line_origin
|
23
|
+
diff.commit_old = options[:commit_id_old]
|
24
|
+
diff.commit_new = options[:commit_id_new]
|
25
|
+
diff.old_file = options[:old_file_path]
|
26
|
+
diff.new_file = options[:new_file_path]
|
27
|
+
diff.old_lineno = line.old_lineno
|
28
|
+
diff.new_lineno = line.new_lineno
|
29
|
+
diff.old_ast_path = options[:old_ast_path]
|
30
|
+
diff.new_ast_path = options[:new_ast_path]
|
31
|
+
return diff
|
32
|
+
end
|
33
|
+
end
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module MetaCommit::Factories
|
2
|
+
# Parser factory
|
3
|
+
# @attr [Array<Class>] available_parser_classes parser classes that factory can build
|
4
|
+
class ParserFactory
|
5
|
+
attr_accessor :available_parser_classes
|
6
|
+
|
7
|
+
# @param [Array<Class>] parser_classes
|
8
|
+
def initialize(parser_classes)
|
9
|
+
@available_parser_classes = parser_classes
|
10
|
+
end
|
11
|
+
|
12
|
+
# @param [String] filename
|
13
|
+
# @param [String] content
|
14
|
+
# @return [MetaCommit::Contracts::Parser, Nil]
|
15
|
+
def create_parser_for(filename, content)
|
16
|
+
@available_parser_classes.each do |parser|
|
17
|
+
return parser.new if parser_supports(parser, filename, content)
|
18
|
+
end
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
# @param [Class] parser
|
23
|
+
# @param [String] filename
|
24
|
+
# @param [String] content
|
25
|
+
# @return [Boolean]
|
26
|
+
def parser_supports(parser, filename, content)
|
27
|
+
(parser.supported_file_extensions.any? {|ext| filename.end_with?(ext)}) && (parser.supports_syntax?(content))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/meta_commit/git/repo.rb
CHANGED
@@ -1,13 +1,26 @@
|
|
1
1
|
require 'rugged'
|
2
2
|
|
3
3
|
module MetaCommit::Git
|
4
|
+
# Rugged::Repository wrapper
|
5
|
+
# @attr [Rugged::Repository] repo
|
4
6
|
class Repo
|
7
|
+
DIFF_OPTIONS = {:context_lines => 0, :ignore_whitespace => true}
|
8
|
+
FILE_NOT_EXISTS_OID = '0000000000000000000000000000000000000000'
|
9
|
+
|
5
10
|
attr_accessor :repo
|
6
11
|
|
12
|
+
# @param [String] repo_path
|
7
13
|
def initialize(repo_path)
|
8
|
-
|
14
|
+
begin
|
15
|
+
@repo = Rugged::Repository.new(repo_path)
|
16
|
+
rescue Rugged::OSError
|
17
|
+
raise MetaCommit::Errors::MissingRepoError
|
18
|
+
rescue Rugged::RepositoryError
|
19
|
+
raise MetaCommit::Errors::MissingRepoError
|
20
|
+
end
|
9
21
|
end
|
10
22
|
|
23
|
+
# @return [Rugged::Walker] commit iterator
|
11
24
|
def walker
|
12
25
|
walker = Rugged::Walker.new(@repo)
|
13
26
|
walker.sorting(Rugged::SORT_REVERSE)
|
@@ -15,6 +28,8 @@ module MetaCommit::Git
|
|
15
28
|
walker
|
16
29
|
end
|
17
30
|
|
31
|
+
# Starts commit walker and yields following commits for easier iteration
|
32
|
+
# @yield [previous_commit, current_commit]
|
18
33
|
def walk_by_commits
|
19
34
|
walker = Rugged::Walker.new(@repo)
|
20
35
|
walker.sorting(Rugged::SORT_REVERSE)
|
@@ -29,22 +44,64 @@ module MetaCommit::Git
|
|
29
44
|
end
|
30
45
|
end
|
31
46
|
|
47
|
+
# Proxy to Rugged::Repository#diff
|
48
|
+
# @param [Object] left
|
49
|
+
# @param [Object] right
|
50
|
+
# @param [Hash] options
|
51
|
+
# @return [Rugged::Diff::Delta]
|
32
52
|
def diff(left, right, options)
|
33
53
|
@repo.diff(left, right, options)
|
34
54
|
end
|
35
55
|
|
56
|
+
# Iterates over optimized lines in diff
|
57
|
+
# @param [Object] left
|
58
|
+
# @param [Object] right
|
59
|
+
# @yield [old_file_path, new_file_path, patch, line]
|
60
|
+
# @return [Object]
|
61
|
+
def diff_with_optimized_lines(left, right)
|
62
|
+
diff = @repo.diff(left, right, DIFF_OPTIONS)
|
63
|
+
diff.deltas.zip(diff.patches).each do |delta, patch|
|
64
|
+
lines = organize_lines(delta, patch)
|
65
|
+
lines.each do |line|
|
66
|
+
yield(delta.old_file[:path], delta.new_file[:path], patch, line)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Iterates over optimized lines in index diff
|
72
|
+
# @yield [old_file_path, new_file_path, patch, line]
|
73
|
+
# @return [Object]
|
74
|
+
def index_diff_with_optimized_lines
|
75
|
+
diff = index_diff(DIFF_OPTIONS)
|
76
|
+
diff.deltas.zip(diff.patches).each do |delta, patch|
|
77
|
+
lines = organize_lines(delta, patch)
|
78
|
+
lines.each do |line|
|
79
|
+
yield(delta.old_file[:path], delta.new_file[:path], patch, line)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Proxy to Rugged::Index#diff
|
85
|
+
# @param [Hash] options
|
86
|
+
# @return [Rugged::Diff::Delta]
|
36
87
|
def index_diff(options)
|
37
88
|
@repo.index.diff(@repo.head.target.tree, options)
|
38
89
|
end
|
39
90
|
|
91
|
+
# @param [String] revision commit hash
|
92
|
+
# @param [String] path
|
93
|
+
# @param [String] default value to be returned if error appears
|
40
94
|
def get_blob_at(revision, path, default = nil)
|
41
95
|
blob = @repo.blob_at(revision, path)
|
42
96
|
return blob.content unless blob.nil?
|
43
97
|
default
|
44
98
|
end
|
45
99
|
|
100
|
+
# @param [String] rel_repo_file_path file path relative to repository root
|
101
|
+
# @param [String] default value to be returned if error appears
|
102
|
+
# @return [String] file content or default value
|
46
103
|
def get_content_of(rel_repo_file_path, default = nil)
|
47
|
-
absolute_file_path =
|
104
|
+
absolute_file_path = dir + rel_repo_file_path
|
48
105
|
begin
|
49
106
|
content = open(absolute_file_path).read
|
50
107
|
rescue Errno::ENOENT
|
@@ -53,8 +110,73 @@ module MetaCommit::Git
|
|
53
110
|
content
|
54
111
|
end
|
55
112
|
|
113
|
+
# @return [String] path to .git folder of repository
|
56
114
|
def path
|
57
115
|
@repo.path
|
58
116
|
end
|
117
|
+
|
118
|
+
# @return [String] directory of repository
|
119
|
+
def dir
|
120
|
+
@repo.path.reverse.sub('/.git'.reverse, '').reverse
|
121
|
+
end
|
122
|
+
|
123
|
+
# @param [String] search_tag
|
124
|
+
# @return [Rugged::Commit, nil]
|
125
|
+
def commit_of_tag(search_tag)
|
126
|
+
@repo.tags.each do |tag|
|
127
|
+
return tag.target if tag.name == search_tag
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# @return [String] last commit oid
|
132
|
+
def last_commit_oid
|
133
|
+
@repo.last_commit.oid
|
134
|
+
end
|
135
|
+
|
136
|
+
# @param [Object] delta
|
137
|
+
# @param [Object] patch
|
138
|
+
# @return [Array<MetaCommit::Models::Line>]
|
139
|
+
def organize_lines(delta, patch)
|
140
|
+
lines_to_walk = []
|
141
|
+
# if whole file was changed examine one time only
|
142
|
+
whole_file_changed = (delta.old_file[:oid] == FILE_NOT_EXISTS_OID) || (delta.new_file[:oid] == FILE_NOT_EXISTS_OID)
|
143
|
+
skip_walking = false
|
144
|
+
skip_next_line = false
|
145
|
+
patch.hunks.each_with_index do |hunk|
|
146
|
+
break if skip_walking
|
147
|
+
hunk.lines.each_with_index do |line, line_index|
|
148
|
+
break if skip_walking
|
149
|
+
|
150
|
+
if skip_next_line
|
151
|
+
skip_next_line = false
|
152
|
+
next
|
153
|
+
end
|
154
|
+
|
155
|
+
next_line = hunk.lines[line_index + 1]
|
156
|
+
is_replace_change = (line.deletion?) && (!next_line.nil? && next_line.addition?) && (line.old_lineno && next_line.new_lineno)
|
157
|
+
|
158
|
+
line_to_walk = MetaCommit::Models::Line.new
|
159
|
+
if is_replace_change
|
160
|
+
line_to_walk.line_origin=:replace
|
161
|
+
line_to_walk.old_lineno=line.old_lineno
|
162
|
+
line_to_walk.new_lineno=next_line.new_lineno
|
163
|
+
else
|
164
|
+
line_to_walk.line_origin=line.line_origin
|
165
|
+
line_to_walk.old_lineno=line.old_lineno
|
166
|
+
line_to_walk.new_lineno=line.new_lineno
|
167
|
+
end
|
168
|
+
if is_replace_change
|
169
|
+
skip_next_line = true
|
170
|
+
end
|
171
|
+
lines_to_walk.push(line_to_walk)
|
172
|
+
|
173
|
+
skip_walking = true if whole_file_changed
|
174
|
+
end
|
175
|
+
end
|
176
|
+
lines_to_walk
|
177
|
+
end
|
178
|
+
|
179
|
+
private :organize_lines
|
180
|
+
|
59
181
|
end
|
60
182
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module MetaCommit::Index
|
2
|
+
module Adapters
|
3
|
+
# Adapter class to write repository changes to git notes
|
4
|
+
# @attr [String] git_folder_path
|
5
|
+
class GitNotes
|
6
|
+
attr_accessor :git_folder_path
|
7
|
+
|
8
|
+
# @param [String] git_folder_path
|
9
|
+
def initialize(git_folder_path)
|
10
|
+
@git_folder_path = git_folder_path
|
11
|
+
end
|
12
|
+
|
13
|
+
# @param [MetaCommit::Git::Repo] repo
|
14
|
+
# @param [MetaCommit::Models::Changes::Repository] repository_changes
|
15
|
+
def write_repository_change_chunk(repo, repository_changes)
|
16
|
+
repository_changes.each do |commit_changes|
|
17
|
+
diffs=[]
|
18
|
+
commit_changes.file_changes.uniq.each do |change|
|
19
|
+
diffs.push(" - #{change.string_representation}")
|
20
|
+
end
|
21
|
+
write_to_notes(commit_changes.new_commit_id, diffs.join("\n"))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param [String] commit_id
|
26
|
+
# @param [String] message
|
27
|
+
def write_to_notes(commit_id, message)
|
28
|
+
system("git --git-dir '#{@git_folder_path}' notes add -f -m '#{message}' #{commit_id}")
|
29
|
+
end
|
30
|
+
|
31
|
+
protected :write_to_notes
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module MetaCommit::Index
|
2
|
+
module Commands
|
3
|
+
class DiffExaminer
|
4
|
+
# @param [MetaCommit::Services::Parse] parse_command
|
5
|
+
# @param [MetaCommit::Factories::ContextualAstNodeFactory] ast_path_factory
|
6
|
+
# @param [MetaCommit::Factories::DiffFactory] diff_factory
|
7
|
+
def initialize(parse_command, ast_path_factory, diff_factory)
|
8
|
+
@parse_command = parse_command
|
9
|
+
@ast_path_factory = ast_path_factory
|
10
|
+
@diff_factory = diff_factory
|
11
|
+
end
|
12
|
+
|
13
|
+
# Creates diff objects with meta information of changes in repository commits
|
14
|
+
# @param [MetaCommit::Git::Repo] repo
|
15
|
+
def meta(repo)
|
16
|
+
repo_changes = MetaCommit::Models::Changes::Repository.new(repo.path)
|
17
|
+
repo.walk_by_commits do |left_commit, right_commit|
|
18
|
+
commit_changes = MetaCommit::Models::Changes::Commit.new(left_commit.oid, right_commit.oid)
|
19
|
+
diffs = examine_diff(repo, left_commit, right_commit)
|
20
|
+
commit_changes.push_changes(diffs)
|
21
|
+
repo_changes.push(commit_changes)
|
22
|
+
end
|
23
|
+
repo_changes
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [Array<MetaCommit::Models::Diffs::Diff>]
|
27
|
+
def examine_diff(repo, left_commit, right_commit)
|
28
|
+
diffs = []
|
29
|
+
repo.diff_with_optimized_lines(left_commit, right_commit) do |old_file_path, new_file_path, patch, line|
|
30
|
+
old_file_content = repo.get_blob_at(left_commit.oid, old_file_path, '')
|
31
|
+
new_file_content = repo.get_blob_at(right_commit.oid, new_file_path, '')
|
32
|
+
|
33
|
+
old_file_ast = @parse_command.execute(old_file_path, old_file_content)
|
34
|
+
next if old_file_ast.nil?
|
35
|
+
new_file_ast = @parse_command.execute(new_file_path, new_file_content)
|
36
|
+
next if new_file_ast.nil?
|
37
|
+
|
38
|
+
old_ast_path = @ast_path_factory.create_ast_path(old_file_ast, line.old_lineno)
|
39
|
+
new_ast_path = @ast_path_factory.create_ast_path(new_file_ast, line.new_lineno)
|
40
|
+
|
41
|
+
created_diff = @diff_factory.create_diff_of_type(line.line_origin, {
|
42
|
+
:line => line,
|
43
|
+
:commit_id_old => left_commit.oid,
|
44
|
+
:commit_id_new => right_commit.oid,
|
45
|
+
:old_ast_path => old_ast_path,
|
46
|
+
:new_ast_path => new_ast_path,
|
47
|
+
:old_file_path => old_file_path,
|
48
|
+
:new_file_path => new_file_path,
|
49
|
+
})
|
50
|
+
diffs.push(created_diff) unless created_diff.nil?
|
51
|
+
end
|
52
|
+
diffs
|
53
|
+
end
|
54
|
+
|
55
|
+
private :examine_diff
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module MetaCommit::Message
|
2
|
+
module Commands
|
3
|
+
class DiffIndexExaminer
|
4
|
+
# @param [MetaCommit::Services::Parse] parse_command
|
5
|
+
# @param [MetaCommit::Factories::ContextualAstNodeFactory] ast_path_factory
|
6
|
+
# @param [MetaCommit::Factories::DiffFactory] diff_factory
|
7
|
+
def initialize(parse_command, ast_path_factory, diff_factory)
|
8
|
+
@parse_command = parse_command
|
9
|
+
@ast_path_factory = ast_path_factory
|
10
|
+
@diff_factory = diff_factory
|
11
|
+
end
|
12
|
+
|
13
|
+
# Creates diff objects with meta information of changes in index staged files
|
14
|
+
# @param [MetaCommit::Git::Repo] repo
|
15
|
+
# @return [Array<MetaCommit::Models::Diffs::Diff>]
|
16
|
+
def index_meta(repo)
|
17
|
+
diffs = MetaCommit::Models::Changes::Commit.new(repo.last_commit_oid, 'staged')
|
18
|
+
repo.index_diff_with_optimized_lines do |old_file_path, new_file_path, patch, line|
|
19
|
+
commit_id_old = repo.last_commit_oid
|
20
|
+
commit_id_new = 'staged'
|
21
|
+
|
22
|
+
old_file_content = repo.get_blob_at(commit_id_old, old_file_path, '')
|
23
|
+
new_file_content = repo.get_content_of(new_file_path, '')
|
24
|
+
|
25
|
+
old_file_ast = @parse_command.execute(old_file_path, old_file_content)
|
26
|
+
next if old_file_ast.nil?
|
27
|
+
new_file_ast = @parse_command.execute(new_file_path, new_file_content)
|
28
|
+
next if new_file_ast.nil?
|
29
|
+
|
30
|
+
old_ast_path = @ast_path_factory.create_ast_path(old_file_ast, line.old_lineno)
|
31
|
+
new_ast_path = @ast_path_factory.create_ast_path(new_file_ast, line.new_lineno)
|
32
|
+
|
33
|
+
created_diff = @diff_factory.create_diff_of_type(line.line_origin, {
|
34
|
+
:line => line,
|
35
|
+
:commit_id_old => commit_id_old,
|
36
|
+
:commit_id_new => commit_id_new,
|
37
|
+
:old_ast_path => old_ast_path,
|
38
|
+
:new_ast_path => new_ast_path,
|
39
|
+
:old_file_path => old_file_path,
|
40
|
+
:new_file_path => new_file_path,
|
41
|
+
})
|
42
|
+
diffs.push(created_diff) unless created_diff.nil?
|
43
|
+
end
|
44
|
+
diffs
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module MetaCommit::Message
|
2
|
+
module Formatters
|
3
|
+
# Class builds message to commit changes
|
4
|
+
class CommitMessageBuilder
|
5
|
+
# @param [Array<MetaCommit::Models::Diffs::Diff>] diffs
|
6
|
+
# @return [String]
|
7
|
+
def build(diffs)
|
8
|
+
result = ''
|
9
|
+
diffs.each do |diff|
|
10
|
+
result << "- #{diff.string_representation} \n"
|
11
|
+
end
|
12
|
+
result
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -1,17 +1,30 @@
|
|
1
1
|
module MetaCommit::Models::Changes
|
2
|
+
# Collection of file changes
|
3
|
+
# @attr [Symbol] old_commit_id
|
4
|
+
# @attr [Symbol] new_commit_id
|
5
|
+
# @attr [Array<MetaCommit::Contracts::Diff>] file_changes
|
2
6
|
class Commit
|
3
7
|
attr_accessor :old_commit_id, :new_commit_id, :file_changes
|
4
8
|
|
9
|
+
# @param [Symbol] old_commit_id
|
10
|
+
# @param [Symbol] new_commit_id
|
5
11
|
def initialize(old_commit_id, new_commit_id)
|
6
12
|
@old_commit_id = old_commit_id
|
7
13
|
@new_commit_id = new_commit_id
|
8
14
|
@file_changes = []
|
9
15
|
end
|
10
16
|
|
17
|
+
# @param [MetaCommit::Contracts::Diff] file_change
|
11
18
|
def push(file_change)
|
12
19
|
@file_changes.push(file_change)
|
13
20
|
end
|
14
21
|
|
22
|
+
# @param [Array<MetaCommit::Contracts::Diff>] file_changes
|
23
|
+
def push_changes(file_changes)
|
24
|
+
@file_changes+=file_changes
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [String]
|
15
28
|
def commit_id
|
16
29
|
if @old_commit_id == @new_commit_id
|
17
30
|
@new_commit_id
|
@@ -20,8 +33,19 @@ module MetaCommit::Models::Changes
|
|
20
33
|
end
|
21
34
|
end
|
22
35
|
|
36
|
+
# @yield file changes
|
23
37
|
def each(&block)
|
24
38
|
@file_changes.each(&block)
|
25
39
|
end
|
40
|
+
|
41
|
+
# @return [Boolean]
|
42
|
+
def empty?
|
43
|
+
@file_changes.empty?
|
44
|
+
end
|
45
|
+
|
46
|
+
# @return [Boolean]
|
47
|
+
def include?(change)
|
48
|
+
@file_changes.include?(change)
|
49
|
+
end
|
26
50
|
end
|
27
51
|
end
|
@@ -1,18 +1,30 @@
|
|
1
1
|
module MetaCommit::Models::Changes
|
2
|
+
# Collection of commit changes
|
3
|
+
# @attr [String] repo_id
|
4
|
+
# @attr [Array<MetaCommit::Models::Commit>] commit_changes
|
2
5
|
class Repository
|
3
6
|
attr_accessor :repo_id, :commit_changes
|
4
7
|
|
8
|
+
# @param [String] repo_id
|
5
9
|
def initialize(repo_id)
|
6
10
|
@repo_id = repo_id
|
7
11
|
@commit_changes = []
|
8
12
|
end
|
9
13
|
|
14
|
+
|
15
|
+
# @param [MetaCommit::Models::Commit] commit_change
|
10
16
|
def push(commit_change)
|
11
17
|
@commit_changes.push(commit_change)
|
12
18
|
end
|
13
19
|
|
20
|
+
# @yield commit changes
|
14
21
|
def each(&block)
|
15
22
|
@commit_changes.each(&block)
|
16
23
|
end
|
24
|
+
|
25
|
+
# @return [Boolean]
|
26
|
+
def empty?
|
27
|
+
@commit_changes.empty?
|
28
|
+
end
|
17
29
|
end
|
18
30
|
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module MetaCommit::Models
|
2
|
+
# Stores specific node from ast and all nodes bypassed on the way to target node
|
3
|
+
# @attr [MetaCommit::Contracts::Ast] target_node Target node from AST
|
4
|
+
# @attr [Array<MetaCommit::Contracts::Ast>] context_nodes Nodes bypassed on the way to target node
|
5
|
+
# @attr [Class] parser_class
|
6
|
+
class ContextualAstNode
|
7
|
+
attr_accessor :target_node, :context_nodes, :parser_class
|
8
|
+
end
|
9
|
+
end
|