debtective 0.2.3.4 → 0.2.3.7
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/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
|