debtective 0.2.3.4 → 0.2.3.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/debtective +2 -22
- data/lib/debtective/command.rb +32 -0
- data/lib/debtective/comments/build_comment.rb +49 -0
- data/lib/debtective/comments/command.rb +61 -0
- data/lib/debtective/comments/comment/base.rb +104 -0
- data/lib/debtective/comments/comment/fixme.rb +20 -0
- data/lib/debtective/comments/comment/magic.rb +27 -0
- data/lib/debtective/comments/comment/note.rb +12 -0
- data/lib/debtective/comments/comment/offense.rb +12 -0
- data/lib/debtective/comments/comment/shebang.rb +27 -0
- data/lib/debtective/comments/comment/todo.rb +20 -0
- data/lib/debtective/comments/comment/yard.rb +28 -0
- data/lib/debtective/comments/export.rb +132 -0
- data/lib/debtective/comments/find_commit.rb +66 -0
- data/lib/debtective/comments/find_end_of_statement.rb +61 -0
- data/lib/debtective/comments/print.rb +83 -0
- data/lib/debtective/stderr_helper.rb +1 -0
- data/lib/debtective/version.rb +1 -1
- data/lib/debtective.rb +1 -15
- metadata +31 -13
- data/lib/debtective/configuration.rb +0 -8
- data/lib/debtective/export.rb +0 -64
- data/lib/debtective/find_commit.rb +0 -67
- data/lib/debtective/find_end_of_statement.rb +0 -72
- data/lib/debtective/offenses/export.rb +0 -58
- data/lib/debtective/offenses/offense.rb +0 -57
- data/lib/debtective/print.rb +0 -76
- data/lib/debtective/railtie.rb +0 -10
- data/lib/debtective/todos/build.rb +0 -68
- data/lib/debtective/todos/export.rb +0 -88
- data/lib/debtective/todos/todo.rb +0 -68
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0ca6f9979cf0b568bf7abd46ec7bca857f7fd2e62a8d15e85cd80920450395a7
|
4
|
+
data.tar.gz: c7df027f440b683be96278a5c57ae8b1829ac261b861a11b68d78b2c502be2cf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b02f93cac47d43efbd15521a6be821d9de597b8d56d14980a42456211b57f9c6c06161d4aae27f66b2bcc47bee034112dd721e232f87bb698eacef504ce1ea0e
|
7
|
+
data.tar.gz: f7e4f1cd2af557f31e4e36ec89adb77af8eecffe6dfdfeb3ea6df5ff3d40c9d0aca0a66a7b9b6fe786b6e0883f0d8f81a01c4b5b0301904ae1f0f8bab9a3b54e
|
data/bin/debtective
CHANGED
@@ -1,26 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require "debtective"
|
4
|
+
require "debtective/command"
|
5
5
|
|
6
|
-
|
7
|
-
if ARGV.include?("--me")
|
8
|
-
`git config user.name`.strip
|
9
|
-
elsif ARGV.include?("--user")
|
10
|
-
ARGV[ARGV.index("--user") + 1]
|
11
|
-
end
|
12
|
-
|
13
|
-
quiet = ARGV.include?("--quiet")
|
14
|
-
|
15
|
-
case ARGV[0]
|
16
|
-
when "--todos"
|
17
|
-
require "debtective/todos/export"
|
18
|
-
Debtective::Todos::Export.new(user_name: user_name, quiet: quiet).call
|
19
|
-
when "--offenses"
|
20
|
-
require "debtective/offenses/export"
|
21
|
-
Debtective::Offenses::Export.new(user_name: user_name, quiet: quiet).call
|
22
|
-
when "--gems"
|
23
|
-
puts "Upcoming feature"
|
24
|
-
else
|
25
|
-
puts "Please pass one of this options: [--todos, --offenses, --gems]"
|
26
|
-
end
|
6
|
+
Debtective::Command.new(ARGV).call
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "debtective"
|
4
|
+
require_relative "comments/command"
|
5
|
+
|
6
|
+
module Debtective
|
7
|
+
# Handle commands from CLI
|
8
|
+
class Command
|
9
|
+
# @param args [Array<String>] ARGVs from command line (order matters)
|
10
|
+
def initialize(args)
|
11
|
+
@args = args
|
12
|
+
end
|
13
|
+
|
14
|
+
# Forward to the proper command
|
15
|
+
def call
|
16
|
+
case @args.first&.delete("--")
|
17
|
+
when "comments"
|
18
|
+
Debtective::Comments::Command.new(@args, quiet: quiet?).call
|
19
|
+
when "gems"
|
20
|
+
puts "Upcoming feature!"
|
21
|
+
else
|
22
|
+
puts "Please pass one of this options: [--comments, --gems]"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def quiet?
|
29
|
+
@args.include?("--quiet")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "comment/fixme"
|
4
|
+
require_relative "comment/magic"
|
5
|
+
require_relative "comment/note"
|
6
|
+
require_relative "comment/offense"
|
7
|
+
require_relative "comment/shebang"
|
8
|
+
require_relative "comment/todo"
|
9
|
+
require_relative "comment/yard"
|
10
|
+
|
11
|
+
module Debtective
|
12
|
+
module Comments
|
13
|
+
# Build proper comment type given the code line
|
14
|
+
class BuildComment
|
15
|
+
# Order matters, for example the Comment::Note regex matches
|
16
|
+
# (almost) all previous regexes so it needs to be tested last
|
17
|
+
TYPES = {
|
18
|
+
/#\sTODO:/ => Comment::Todo,
|
19
|
+
/#\sFIXME:/ => Comment::Fixme,
|
20
|
+
/\s# rubocop:disable (.*)/ => Comment::Offense,
|
21
|
+
/#\s@/ => Comment::Yard,
|
22
|
+
/^# (frozen_string_literal|coding|encoding|warn_indent|sharable_constant_value):/ => Comment::Magic,
|
23
|
+
/^#!/ => Comment::Shebang,
|
24
|
+
/(^|\s)#\s/ => Comment::Note
|
25
|
+
}.freeze
|
26
|
+
|
27
|
+
# @param line [String] code line
|
28
|
+
# @param pathname [Pathname] file path
|
29
|
+
# @param index [Integer] position of the line in the file
|
30
|
+
def initialize(line:, pathname:, index:)
|
31
|
+
@line = line
|
32
|
+
@pathname = pathname
|
33
|
+
@index = index
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [Debtective::Comments::Base]
|
37
|
+
def call
|
38
|
+
klass&.new(pathname: @pathname, index: @index)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def klass
|
44
|
+
TYPES.each { |regex, klass| return klass if @line.match?(regex) }
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "export"
|
4
|
+
|
5
|
+
module Debtective
|
6
|
+
module Comments
|
7
|
+
# Handle comments command and given ARGVs
|
8
|
+
class Command
|
9
|
+
TYPE_OPTIONS = %w[fixme magic note offense shebang todo yard].freeze
|
10
|
+
|
11
|
+
# @param args [Array<String>] ARGVs from command line (order matters)
|
12
|
+
# @param quiet [Boolean]
|
13
|
+
def initialize(args, quiet: false)
|
14
|
+
@args = args
|
15
|
+
@quiet = quiet
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [Debtective::Comments::Export]
|
19
|
+
def call
|
20
|
+
Export.new(
|
21
|
+
user_name: user_name,
|
22
|
+
quiet: @quiet,
|
23
|
+
included_types: included_types,
|
24
|
+
excluded_types: excluded_types,
|
25
|
+
included_paths: paths("include"),
|
26
|
+
excluded_paths: paths("exclude")
|
27
|
+
).call
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def user_name
|
33
|
+
if @args.include?("--me")
|
34
|
+
`git config user.name`.strip
|
35
|
+
elsif @args.include?("--user")
|
36
|
+
@args[@args.index("--user") + 1]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def included_types
|
41
|
+
TYPE_OPTIONS.select { @args.include?("--#{_1}") }
|
42
|
+
end
|
43
|
+
|
44
|
+
def excluded_types
|
45
|
+
TYPE_OPTIONS.select { @args.include?("--no-#{_1}") }
|
46
|
+
end
|
47
|
+
|
48
|
+
def paths(rule)
|
49
|
+
return [] unless @args.include?("--#{rule}")
|
50
|
+
|
51
|
+
paths = []
|
52
|
+
@args[(@args.index("--#{rule}") + 1)..].each do |arg|
|
53
|
+
return paths if arg.match?(/^--.*/)
|
54
|
+
|
55
|
+
paths << arg
|
56
|
+
end
|
57
|
+
paths
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../find_commit"
|
4
|
+
|
5
|
+
module Debtective
|
6
|
+
module Comments
|
7
|
+
module Comment
|
8
|
+
# Hold comment information
|
9
|
+
class Base
|
10
|
+
# Does not match YARD or shebang comments
|
11
|
+
REGULAR_COMMENT = /^\s*#\s(?!@)/
|
12
|
+
|
13
|
+
attr_accessor :pathname
|
14
|
+
|
15
|
+
# @param pathname [Pathname] path of the file
|
16
|
+
# @param index [Integer] position of the comment in the file
|
17
|
+
def initialize(pathname:, index:)
|
18
|
+
@pathname = pathname
|
19
|
+
@index = index
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [String]
|
23
|
+
def type
|
24
|
+
self.class.name.split("::").last.downcase
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [Integer]
|
28
|
+
def comment_start
|
29
|
+
@index
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [Integer]
|
33
|
+
def comment_end
|
34
|
+
@comment_end ||= last_following_comment_index || comment_start
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [Integer]
|
38
|
+
def statement_start
|
39
|
+
@statement_start ||= inline? ? @index : comment_end.next
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [Integer]
|
43
|
+
def statement_end
|
44
|
+
statement_start
|
45
|
+
end
|
46
|
+
|
47
|
+
# Location in the codebase (for clickable link)
|
48
|
+
# @return [String]
|
49
|
+
def location
|
50
|
+
"#{@pathname.to_s.gsub(%r{^./}, "")}:#{comment_start + 1}"
|
51
|
+
end
|
52
|
+
|
53
|
+
# Return commit that introduced the todo
|
54
|
+
# @return [Debtective::Comments::FindCommit::Commit]
|
55
|
+
def commit
|
56
|
+
@commit ||= FindCommit.new(pathname: @pathname, line: lines[@index]).call
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [Integer]
|
60
|
+
def days
|
61
|
+
return if commit.time.nil?
|
62
|
+
|
63
|
+
((Time.now - commit.time) / (24 * 60 * 60)).round
|
64
|
+
end
|
65
|
+
|
66
|
+
# @return [Hash]
|
67
|
+
def to_h
|
68
|
+
{
|
69
|
+
pathname: @pathname,
|
70
|
+
location: location,
|
71
|
+
type: type,
|
72
|
+
comment_boundaries: [comment_start, comment_end],
|
73
|
+
statement_boundaries: [statement_start, statement_end],
|
74
|
+
commit: commit.sha,
|
75
|
+
author: commit.author.to_h,
|
76
|
+
time: commit.time
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def lines
|
83
|
+
@lines ||= @pathname.readlines
|
84
|
+
end
|
85
|
+
|
86
|
+
def inline?
|
87
|
+
@inline ||= !lines[@index].match?(/^\s*#/)
|
88
|
+
end
|
89
|
+
|
90
|
+
def last_following_comment_index
|
91
|
+
if inline?
|
92
|
+
@index
|
93
|
+
else
|
94
|
+
lines.index.with_index do |line, i|
|
95
|
+
i > @index &&
|
96
|
+
!line.strip.empty? &&
|
97
|
+
!line.match?(REGULAR_COMMENT)
|
98
|
+
end&.-(1)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
require_relative "../find_end_of_statement"
|
5
|
+
|
6
|
+
module Debtective
|
7
|
+
module Comments
|
8
|
+
module Comment
|
9
|
+
# Hold FIXME comment information
|
10
|
+
class Fixme < Base
|
11
|
+
# (see Base#statement_end)
|
12
|
+
def statement_end
|
13
|
+
return super if inline?
|
14
|
+
|
15
|
+
FindEndOfStatement.new(lines: lines, first_line_index: statement_start).call
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
module Debtective
|
6
|
+
module Comments
|
7
|
+
module Comment
|
8
|
+
# Hold magic comment information
|
9
|
+
class Magic < Base
|
10
|
+
# (see Base#comment_end)
|
11
|
+
def comment_end
|
12
|
+
@index
|
13
|
+
end
|
14
|
+
|
15
|
+
# (see Base#statement_start)
|
16
|
+
def statement_start
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
# (see Base#statement_end)
|
21
|
+
def statement_end
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
module Debtective
|
6
|
+
module Comments
|
7
|
+
module Comment
|
8
|
+
# Hold shebang comment (#!) information
|
9
|
+
class Shebang < Base
|
10
|
+
# (see Base#comment_end)
|
11
|
+
def comment_end
|
12
|
+
@index
|
13
|
+
end
|
14
|
+
|
15
|
+
# (see Base#statement_start)
|
16
|
+
def statement_start
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
# (see Base#statement_end)
|
21
|
+
def statement_end
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
require_relative "../find_end_of_statement"
|
5
|
+
|
6
|
+
module Debtective
|
7
|
+
module Comments
|
8
|
+
module Comment
|
9
|
+
# Hold TODO comment information
|
10
|
+
class Todo < Base
|
11
|
+
# (see Base#statement_end)
|
12
|
+
def statement_end
|
13
|
+
return super if inline?
|
14
|
+
|
15
|
+
FindEndOfStatement.new(lines: lines, first_line_index: statement_start).call
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
require_relative "../find_end_of_statement"
|
5
|
+
|
6
|
+
module Debtective
|
7
|
+
module Comments
|
8
|
+
module Comment
|
9
|
+
# Hold YARD comment information
|
10
|
+
class Yard < Base
|
11
|
+
# (see Base#comment_end)
|
12
|
+
def comment_end
|
13
|
+
@index
|
14
|
+
end
|
15
|
+
|
16
|
+
# (see Base#statement_start)
|
17
|
+
def statement_start
|
18
|
+
last_following_comment_index.next
|
19
|
+
end
|
20
|
+
|
21
|
+
# (see Base#statement_end)
|
22
|
+
def statement_end
|
23
|
+
FindEndOfStatement.new(lines: lines, first_line_index: statement_start).call
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
require "json"
|
5
|
+
require "parser/current"
|
6
|
+
require_relative "../stderr_helper"
|
7
|
+
require_relative "build_comment"
|
8
|
+
require_relative "print"
|
9
|
+
|
10
|
+
module Debtective
|
11
|
+
module Comments
|
12
|
+
# Export comments in a JSON file and to stdout
|
13
|
+
class Export
|
14
|
+
include StderrHelper
|
15
|
+
|
16
|
+
OPTIONS = {
|
17
|
+
user_name: nil,
|
18
|
+
quiet: false,
|
19
|
+
included_types: [],
|
20
|
+
excluded_types: [],
|
21
|
+
included_paths: [],
|
22
|
+
excluded_paths: []
|
23
|
+
}.freeze
|
24
|
+
|
25
|
+
FILE_PATH = "comments.json"
|
26
|
+
|
27
|
+
# @param user_name [String] git user email to filter
|
28
|
+
# @param quiet [boolean]
|
29
|
+
# @param included_types [Array<String>] types of comment to export
|
30
|
+
# @param excluded_types [Array<String>] types of comment to skip
|
31
|
+
# @param included_paths [Array<String>] paths to explore
|
32
|
+
# @param excluded_paths [Array<String>] paths to skip
|
33
|
+
def initialize(**options)
|
34
|
+
OPTIONS.each do |key, default|
|
35
|
+
instance_variable_set(:"@#{key}", options[key] || default)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [void]
|
40
|
+
def call
|
41
|
+
# suppress_stderr prevents stderr outputs from compilations
|
42
|
+
suppress_stderr do
|
43
|
+
@comments = @quiet ? find_comments : log_table
|
44
|
+
end
|
45
|
+
filter_comments!
|
46
|
+
log_counts unless @quiet
|
47
|
+
write_json_file
|
48
|
+
puts(FILE_PATH) unless @quiet
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def find_comments
|
54
|
+
pathnames.flat_map do |pathname|
|
55
|
+
last_comment_found = nil
|
56
|
+
pathname.readlines.filter_map.with_index do |line, index|
|
57
|
+
next if last_comment_found && index < last_comment_found.comment_end.next
|
58
|
+
next unless comment?(line)
|
59
|
+
|
60
|
+
comment = BuildComment.new(line: line, pathname: pathname, index: index).call
|
61
|
+
next if comment.nil?
|
62
|
+
|
63
|
+
last_comment_found = comment
|
64
|
+
export_comment(comment)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def comment?(line)
|
70
|
+
return true if line =~ /^\s*#(\s|!)/
|
71
|
+
return false unless line =~ /\s(#\s.*)/
|
72
|
+
|
73
|
+
# Ensure it is really a comment to avoid something like
|
74
|
+
# puts("hello # world")
|
75
|
+
# to be considered as an inline comment
|
76
|
+
!Parser::CurrentRuby.parse(line).to_s.include?(Regexp.last_match[1])
|
77
|
+
rescue Parser::SyntaxError
|
78
|
+
false
|
79
|
+
end
|
80
|
+
|
81
|
+
# Export the comment if asked so by the user
|
82
|
+
# @return [Debtective::Comments::Comment::Base, nil]
|
83
|
+
# @note This method is watched by Print class to log in stdout
|
84
|
+
def export_comment(comment)
|
85
|
+
if @included_types.include?(comment.type) ||
|
86
|
+
(@included_types.empty? && !@excluded_types.include?(comment.type))
|
87
|
+
|
88
|
+
comment
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def log_table
|
93
|
+
Print.new(user_name: @user_name).call { find_comments }
|
94
|
+
end
|
95
|
+
|
96
|
+
# List of paths to search in
|
97
|
+
def pathnames
|
98
|
+
Dir["./**/*"]
|
99
|
+
.map { Pathname(_1) }
|
100
|
+
.select { _1.file? && _1.extname == ".rb" && explore?(_1) }
|
101
|
+
end
|
102
|
+
|
103
|
+
def explore?(path)
|
104
|
+
return false if @excluded_paths.any? { path.to_s.gsub(%r{^./}, "").match?(/^#{_1}/) }
|
105
|
+
return true if @included_paths.empty?
|
106
|
+
|
107
|
+
@included_paths.any? { path.to_s.gsub(%r{^./}, "").match?(/^#{_1}/) }
|
108
|
+
end
|
109
|
+
|
110
|
+
# Select comments committed by the user
|
111
|
+
def filter_comments!
|
112
|
+
return if @user_name.nil?
|
113
|
+
|
114
|
+
@comments.select! { _1.commit.author.name == @user_name }
|
115
|
+
end
|
116
|
+
|
117
|
+
def log_counts
|
118
|
+
puts "total: #{@comments.count}"
|
119
|
+
end
|
120
|
+
|
121
|
+
def write_json_file
|
122
|
+
File.open(self.class::FILE_PATH, "w") do |file|
|
123
|
+
file.puts(
|
124
|
+
JSON.pretty_generate(
|
125
|
+
@comments.map(&:to_h)
|
126
|
+
)
|
127
|
+
)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "git"
|
4
|
+
require "open3"
|
5
|
+
|
6
|
+
module Debtective
|
7
|
+
module Comments
|
8
|
+
# Find the commit that introduced the given line of code
|
9
|
+
class FindCommit
|
10
|
+
Author = Struct.new(:email, :name)
|
11
|
+
Commit = Struct.new(:sha, :author, :time)
|
12
|
+
|
13
|
+
# @param pathname [Pathname] file path
|
14
|
+
# @param line [String] line of code
|
15
|
+
def initialize(pathname:, line:)
|
16
|
+
@pathname = pathname
|
17
|
+
@line = line
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Debtective::Comments::FindCommit::Commit]
|
21
|
+
def call
|
22
|
+
Commit.new(sha, author, time)
|
23
|
+
rescue Git::GitExecuteError
|
24
|
+
author = Author.new(nil, nil)
|
25
|
+
Commit.new(nil, author, nil)
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [Debtective::Comments::FindCommit::Author]
|
29
|
+
def author
|
30
|
+
Author.new(commit.author.email, commit.author.name)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [Time]
|
34
|
+
def time
|
35
|
+
commit.date
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [String]
|
39
|
+
def sha
|
40
|
+
@sha ||=
|
41
|
+
begin
|
42
|
+
cmd = "git log -S \"#{safe_code}\" #{@pathname}"
|
43
|
+
stdout, _stderr, _status = ::Open3.capture3(cmd)
|
44
|
+
stdout[/commit (\w{40})\n/, 1]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
# @return [Git::Base]
|
51
|
+
def git
|
52
|
+
Git.open(".")
|
53
|
+
end
|
54
|
+
|
55
|
+
# @return [Git::Object::Commit]
|
56
|
+
def commit
|
57
|
+
git.gcommit(sha)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Characters " and ` can break the git command
|
61
|
+
def safe_code
|
62
|
+
@line.gsub(/"/, "\\\"").gsub("`", "\\\\`")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|