danger-packwerk 0.14.1 → 0.14.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/danger-packwerk/danger_package_todo_yml_changes.rb +21 -118
- data/lib/danger-packwerk/danger_packwerk.rb +12 -7
- data/lib/danger-packwerk/private/git.rb +65 -0
- data/lib/danger-packwerk/private/todo_yml_changes.rb +135 -0
- data/lib/danger-packwerk/private.rb +1 -0
- data/lib/danger-packwerk/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 11de71d95ae9ff78daff3f87835d184e206d6088ecece8608e03d7e42eaa0424
|
4
|
+
data.tar.gz: 9386a9c42dbc1ef0fface734b0c6e1b9aa031629c46d3961364a37b1afda1b36
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0114bf7aa642b184bd8ba990764eff04925bafd7defc7c3c77af8b4edcef5c576c8412471d5ab31e3150df4298582774b5f1943ad3106c04b3b0fa02c619fae6
|
7
|
+
data.tar.gz: 05ec9637958c0f80a2f2ec0cd3d25578081fc015d98b9f1a7a5b296ef3a8c8e2304319ed6af8c3b4ceffb5a4e5e5f421596f92593ed1002690f60cbb465bd5f3
|
@@ -29,22 +29,25 @@ module DangerPackwerk
|
|
29
29
|
offenses_formatter: T.nilable(Update::OffensesFormatter),
|
30
30
|
before_comment: BeforeComment,
|
31
31
|
max_comments: Integer,
|
32
|
-
violation_types: T::Array[String]
|
32
|
+
violation_types: T::Array[String],
|
33
|
+
root_path: T.nilable(String)
|
33
34
|
).void
|
34
35
|
end
|
35
36
|
def check(
|
36
37
|
offenses_formatter: nil,
|
37
38
|
before_comment: DEFAULT_BEFORE_COMMENT,
|
38
39
|
max_comments: DEFAULT_MAX_COMMENTS,
|
39
|
-
violation_types: DEFAULT_VIOLATION_TYPES
|
40
|
+
violation_types: DEFAULT_VIOLATION_TYPES,
|
41
|
+
root_path: nil
|
40
42
|
)
|
41
43
|
offenses_formatter ||= Update::DefaultFormatter.new
|
42
44
|
repo_link = github.pr_json[:base][:repo][:html_url]
|
43
45
|
org_name = github.pr_json[:base][:repo][:owner][:login]
|
44
46
|
|
45
|
-
|
47
|
+
git_filesystem = Private::GitFilesystem.new(git: git, root: root_path || '')
|
48
|
+
changed_package_todo_ymls = (git_filesystem.modified_files + git_filesystem.added_files + git_filesystem.deleted_files).grep(PACKAGE_TODO_PATTERN)
|
46
49
|
|
47
|
-
violation_diff = get_violation_diff(violation_types)
|
50
|
+
violation_diff = get_violation_diff(violation_types, root_path: root_path)
|
48
51
|
|
49
52
|
before_comment.call(
|
50
53
|
violation_diff,
|
@@ -61,130 +64,30 @@ module DangerPackwerk
|
|
61
64
|
markdown(
|
62
65
|
offenses_formatter.format_offenses(violations, repo_link, org_name),
|
63
66
|
line: location.line_number,
|
64
|
-
file: location.file
|
67
|
+
file: git_filesystem.convert_to_filesystem(location.file)
|
65
68
|
)
|
66
69
|
|
67
70
|
current_comment_count += 1
|
68
71
|
end
|
69
72
|
end
|
70
73
|
|
71
|
-
sig
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
added_violations += BasicReferenceOffense.from(added_package_todo_yml_file)
|
80
|
-
end
|
81
|
-
|
82
|
-
git.deleted_files.grep(PACKAGE_TODO_PATTERN).each do |deleted_package_todo_yml_file|
|
83
|
-
# Since the file is deleted, we know on the HEAD commit there are no violations related to this pack,
|
84
|
-
# and that all violations from this file are deleted
|
85
|
-
deleted_violations = get_violations_before_patch_for(deleted_package_todo_yml_file)
|
86
|
-
removed_violations += deleted_violations
|
87
|
-
end
|
88
|
-
|
89
|
-
# The format for git.renamed_files is a T::Array[{after: "some/path/new", before: "some/path/old"}]
|
90
|
-
renamed_files_before = git.renamed_files.map { |before_after_file| before_after_file[:before] }
|
91
|
-
renamed_files_after = git.renamed_files.map { |before_after_file| before_after_file[:after] }
|
92
|
-
|
93
|
-
git.modified_files.grep(PACKAGE_TODO_PATTERN).each do |modified_package_todo_yml_file|
|
94
|
-
# We skip over modified files if one of the modified files is a renamed `package_todo.yml` file.
|
95
|
-
# This allows us to rename packs while ignoring "new violations" in those renamed packs.
|
96
|
-
next if renamed_files_before.include?(modified_package_todo_yml_file)
|
97
|
-
|
98
|
-
head_commit_violations = BasicReferenceOffense.from(modified_package_todo_yml_file)
|
99
|
-
base_commit_violations = get_violations_before_patch_for(modified_package_todo_yml_file)
|
100
|
-
added_violations += head_commit_violations - base_commit_violations
|
101
|
-
removed_violations += base_commit_violations - head_commit_violations
|
102
|
-
end
|
103
|
-
|
104
|
-
#
|
105
|
-
# This implementation creates some false negatives:
|
106
|
-
# That is – it doesn't capture some cases:
|
107
|
-
# 1) A file has been renamed without renaming a constant.
|
108
|
-
# That can happen if we change only the autoloaded portion of a filename.
|
109
|
-
# For example: `packs/foo/app/services/my_class.rb` (defines: `MyClass`)
|
110
|
-
# is changed to `packs/foo/app/public/my_class.rb` (still defines: `MyClass`)
|
111
|
-
#
|
112
|
-
# This implementation also doesn't cover these false positives:
|
113
|
-
# That is – it leaves a comment when it should not.
|
114
|
-
# 1) A CONSTANT within a class or module has been renamed.
|
115
|
-
# e.g. `class MyClass; MY_CONSTANT = 1; end` becomes `class MyClass; RENAMED_CONSTANT = 1; end`
|
116
|
-
# We would not detect based on file renames that `MY_CONSTANT` has been renamed.
|
117
|
-
#
|
118
|
-
renamed_constants = []
|
119
|
-
|
120
|
-
added_violations.each do |violation|
|
121
|
-
filepath_that_defines_this_constant = Private.constant_resolver.resolve(violation.class_name)&.location
|
122
|
-
renamed_constants << violation.class_name if renamed_files_after.include?(filepath_that_defines_this_constant)
|
123
|
-
end
|
124
|
-
|
125
|
-
relevant_added_violations = added_violations.reject do |violation|
|
126
|
-
renamed_files_after.include?(violation.file) ||
|
127
|
-
renamed_constants.include?(violation.class_name) ||
|
128
|
-
!violation_types.include?(violation.type)
|
129
|
-
end
|
74
|
+
sig do
|
75
|
+
params(
|
76
|
+
violation_types: T::Array[String],
|
77
|
+
root_path: T.nilable(String)
|
78
|
+
).returns(ViolationDiff)
|
79
|
+
end
|
80
|
+
def get_violation_diff(violation_types, root_path: nil)
|
81
|
+
git_filesystem = Private::GitFilesystem.new(git: git, root: root_path || '')
|
130
82
|
|
131
|
-
|
132
|
-
violation_types
|
133
|
-
|
83
|
+
added_violations, removed_violations = Private::TodoYmlChanges.get_reference_offenses(
|
84
|
+
violation_types, git_filesystem
|
85
|
+
)
|
134
86
|
|
135
87
|
ViolationDiff.new(
|
136
|
-
added_violations:
|
137
|
-
removed_violations:
|
88
|
+
added_violations: added_violations,
|
89
|
+
removed_violations: removed_violations
|
138
90
|
)
|
139
91
|
end
|
140
|
-
|
141
|
-
private
|
142
|
-
|
143
|
-
sig { params(package_todo_yml_file: String).returns(T::Array[BasicReferenceOffense]) }
|
144
|
-
def get_violations_before_patch_for(package_todo_yml_file)
|
145
|
-
# The strategy to get the violations before this PR is to reverse the patch on each `package_todo.yml`.
|
146
|
-
# A previous strategy attempted to use `git merge-base --fork-point`, but there are many situations where it returns
|
147
|
-
# empty values. That strategy is fickle because it depends on the state of the `reflog` within the CI suite, which appears
|
148
|
-
# to not be reliable to depend on.
|
149
|
-
#
|
150
|
-
# Instead, just inverting the patch should hopefully provide a more reliable way to figure out what was the state of the file before
|
151
|
-
# the PR without needing to use git commands that interpret the branch history based on local git history.
|
152
|
-
#
|
153
|
-
# We apply the patch to the original file so that we can seamlessly reverse the patch applied to that file (since patches are coupled to
|
154
|
-
# the files they modify). After parsing the violations from that `package_todo.yml` file with the patch reversed,
|
155
|
-
# we use a temporary copy of the original file to rewrite to it with the original contents.
|
156
|
-
# Note that practically speaking, we don't need to rewrite the original contents (since we already fetched the
|
157
|
-
# original contents above and the CI file system should be ephemeral). However, we do this anyways in case we later change these
|
158
|
-
# assumptions, or another client's environment is different and expects these files not to be mutated.
|
159
|
-
|
160
|
-
# Keep track of the original file contents. If the original file has been deleted, then we delete the file after inverting the patch at the end, rather than rewriting it.
|
161
|
-
package_todo_yml_file_copy = (File.read(package_todo_yml_file) if File.exist?(package_todo_yml_file))
|
162
|
-
|
163
|
-
Tempfile.create do |patch_file|
|
164
|
-
# Normally we'd use `git.diff_for_file(package_todo_yml_file).patch` here, but there is a bug where it does not work for deleted files yet.
|
165
|
-
# I have a fix for that here: https://github.com/danger/danger/pull/1357
|
166
|
-
# Until that lands, I'm just using the underlying implementation of that method to get the diff for a file.
|
167
|
-
# Note that I might want to use a safe escape operator, `&.patch` and return gracefully if the patch cannot be found.
|
168
|
-
# However I'd be interested in why that ever happens, so for now going to proceed as is.
|
169
|
-
# (Note that better yet we'd have observability into these so I can just log under those circumstances rather than surfacing an error to the user,
|
170
|
-
# but we don't have that quite yet.)
|
171
|
-
patch_for_file = git.diff[package_todo_yml_file].patch
|
172
|
-
# This appears to be a known issue that patches require new lines at the end. It seems like this is an issue with Danger that
|
173
|
-
# it gives us a patch without a newline.
|
174
|
-
# https://stackoverflow.com/questions/18142870/git-error-fatal-corrupt-patch-at-line-36
|
175
|
-
patch_file << "#{patch_for_file}\n"
|
176
|
-
patch_file.rewind
|
177
|
-
# https://git-scm.com/docs/git-apply
|
178
|
-
_stdout, _stderr, _status = Open3.capture3("git apply --reverse #{patch_file.path}")
|
179
|
-
# https://www.rubyguides.com/2019/05/ruby-tempfile/
|
180
|
-
BasicReferenceOffense.from(package_todo_yml_file)
|
181
|
-
end
|
182
|
-
ensure
|
183
|
-
if package_todo_yml_file_copy
|
184
|
-
File.write(package_todo_yml_file, package_todo_yml_file_copy)
|
185
|
-
else
|
186
|
-
File.delete(package_todo_yml_file)
|
187
|
-
end
|
188
|
-
end
|
189
92
|
end
|
190
93
|
end
|
@@ -6,6 +6,7 @@ require 'packwerk'
|
|
6
6
|
require 'parse_packwerk'
|
7
7
|
require 'sorbet-runtime'
|
8
8
|
require 'danger-packwerk/packwerk_wrapper'
|
9
|
+
require 'danger-packwerk/private/git'
|
9
10
|
|
10
11
|
module DangerPackwerk
|
11
12
|
# Note that Danger names the plugin (i.e. anything that inherits from `Danger::Plugin`) by taking the name of the class and gsubbing out "Danger"
|
@@ -43,7 +44,8 @@ module DangerPackwerk
|
|
43
44
|
failure_message: String,
|
44
45
|
on_failure: OnFailure,
|
45
46
|
violation_types: T::Array[String],
|
46
|
-
grouping_strategy: CommentGroupingStrategy
|
47
|
+
grouping_strategy: CommentGroupingStrategy,
|
48
|
+
root_path: T.nilable(String)
|
47
49
|
).void
|
48
50
|
end
|
49
51
|
def check(
|
@@ -53,7 +55,8 @@ module DangerPackwerk
|
|
53
55
|
failure_message: DEFAULT_FAILURE_MESSAGE,
|
54
56
|
on_failure: DEFAULT_ON_FAILURE,
|
55
57
|
violation_types: DEFAULT_VIOLATION_TYPES,
|
56
|
-
grouping_strategy: CommentGroupingStrategy::PerConstantPerLocation
|
58
|
+
grouping_strategy: CommentGroupingStrategy::PerConstantPerLocation,
|
59
|
+
root_path: nil
|
57
60
|
)
|
58
61
|
offenses_formatter ||= Check::DefaultFormatter.new
|
59
62
|
repo_link = github.pr_json[:base][:repo][:html_url]
|
@@ -67,9 +70,12 @@ module DangerPackwerk
|
|
67
70
|
# trigger the warning message, which is good, since we only want to trigger on new code.
|
68
71
|
github.dismiss_out_of_range_messages
|
69
72
|
|
73
|
+
git_filesystem = Private::GitFilesystem.new(git: git, root: root_path || '')
|
74
|
+
|
70
75
|
# https://github.com/danger/danger/blob/eca19719d3e585fe1cc46bc5377f9aa955ebf609/lib/danger/danger_core/plugins/dangerfile_git_plugin.rb#L80
|
71
|
-
renamed_files_after =
|
72
|
-
|
76
|
+
renamed_files_after = git_filesystem.renamed_files.map { |f| f[:after] }
|
77
|
+
|
78
|
+
targeted_files = (git_filesystem.modified_files + git_filesystem.added_files + renamed_files_after).select do |f|
|
73
79
|
path = Pathname.new(f)
|
74
80
|
|
75
81
|
# We probably want to check the `include` key of `packwerk.yml`. By default, this value is "**/*.{rb,rake,erb}",
|
@@ -92,7 +98,7 @@ module DangerPackwerk
|
|
92
98
|
|
93
99
|
packwerk_reference_offenses = PackwerkWrapper.get_offenses_for_files(targeted_files.to_a).compact
|
94
100
|
|
95
|
-
renamed_files =
|
101
|
+
renamed_files = git_filesystem.renamed_files.map { |before_after_file| before_after_file[:after] }
|
96
102
|
|
97
103
|
packwerk_reference_offenses_to_care_about = packwerk_reference_offenses.reject do |packwerk_reference_offense|
|
98
104
|
constant_name = packwerk_reference_offense.reference.constant.name
|
@@ -131,8 +137,7 @@ module DangerPackwerk
|
|
131
137
|
referencing_file = reference_offense.reference.relative_path
|
132
138
|
|
133
139
|
message = offenses_formatter.format_offenses(unique_packwerk_reference_offenses, repo_link, org_name)
|
134
|
-
|
135
|
-
markdown(message, file: referencing_file, line: line_number)
|
140
|
+
markdown(message, file: git_filesystem.convert_to_filesystem(referencing_file), line: line_number)
|
136
141
|
end
|
137
142
|
|
138
143
|
if current_comment_count > 0
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
require 'code_ownership'
|
4
|
+
require 'packs'
|
5
|
+
|
6
|
+
# In order to support running danger-packwerk from a non-root filepath, we need
|
7
|
+
# to wrap some git functions in filesystem wrappers: packwerk runs relative to
|
8
|
+
# the rails app root, whereas git returns paths on the actual filesystem.
|
9
|
+
module DangerPackwerk
|
10
|
+
module Private
|
11
|
+
class GitFilesystem < T::Struct
|
12
|
+
extend T::Sig
|
13
|
+
|
14
|
+
const :git, Danger::DangerfileGitPlugin
|
15
|
+
const :root, String
|
16
|
+
|
17
|
+
sig { returns(T::Array[{ after: String, before: String }]) }
|
18
|
+
def renamed_files
|
19
|
+
@git.renamed_files.map do |f|
|
20
|
+
{
|
21
|
+
after: convert_file_from_filesystem(f[:after]),
|
22
|
+
before: convert_file_from_filesystem(f[:before])
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
sig { returns(T::Array[String]) }
|
28
|
+
def modified_files
|
29
|
+
convert_from_filesystem(@git.modified_files.to_a)
|
30
|
+
end
|
31
|
+
|
32
|
+
sig { returns(T::Array[String]) }
|
33
|
+
def deleted_files
|
34
|
+
convert_from_filesystem(@git.deleted_files.to_a)
|
35
|
+
end
|
36
|
+
|
37
|
+
sig { returns(T::Array[String]) }
|
38
|
+
def added_files
|
39
|
+
convert_from_filesystem(@git.added_files.to_a)
|
40
|
+
end
|
41
|
+
|
42
|
+
sig { params(filename_on_disk: String).returns(::Git::Diff::DiffFile) }
|
43
|
+
def diff(filename_on_disk)
|
44
|
+
@git.diff[filename_on_disk]
|
45
|
+
end
|
46
|
+
|
47
|
+
sig { params(path: String).returns(String) }
|
48
|
+
def convert_to_filesystem(path)
|
49
|
+
Pathname(@root).join(path).to_s
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
sig { params(files: T::Array[String]).returns(T::Array[String]) }
|
55
|
+
def convert_from_filesystem(files)
|
56
|
+
files.map { |f| convert_file_from_filesystem(f) }
|
57
|
+
end
|
58
|
+
|
59
|
+
sig { params(file: String).returns(String) }
|
60
|
+
def convert_file_from_filesystem(file)
|
61
|
+
Pathname(file).relative_path_from(@root).to_s
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module DangerPackwerk
|
4
|
+
module Private
|
5
|
+
class TodoYmlChanges
|
6
|
+
extend T::Sig
|
7
|
+
extend T::Helpers
|
8
|
+
|
9
|
+
sig do
|
10
|
+
params(
|
11
|
+
violation_types: T::Array[String],
|
12
|
+
git_filesystem: GitFilesystem
|
13
|
+
).returns([T::Array[BasicReferenceOffense], T::Array[BasicReferenceOffense]])
|
14
|
+
end
|
15
|
+
def self.get_reference_offenses(violation_types, git_filesystem)
|
16
|
+
added_violations = T.let([], T::Array[BasicReferenceOffense])
|
17
|
+
removed_violations = T.let([], T::Array[BasicReferenceOffense])
|
18
|
+
|
19
|
+
git_filesystem.added_files.grep(PACKAGE_TODO_PATTERN).each do |added_package_todo_yml_file|
|
20
|
+
# Since the file is added, we know on the base commit there are no violations related to this pack,
|
21
|
+
# and that all violations from this file are new
|
22
|
+
added_violations += BasicReferenceOffense.from(added_package_todo_yml_file)
|
23
|
+
end
|
24
|
+
|
25
|
+
git_filesystem.deleted_files.grep(PACKAGE_TODO_PATTERN).each do |deleted_package_todo_yml_file|
|
26
|
+
# Since the file is deleted, we know on the HEAD commit there are no violations related to this pack,
|
27
|
+
# and that all violations from this file are deleted
|
28
|
+
deleted_violations = get_violations_before_patch_for(git_filesystem, deleted_package_todo_yml_file)
|
29
|
+
removed_violations += deleted_violations
|
30
|
+
end
|
31
|
+
|
32
|
+
# The format for git.renamed_files is a T::Array[{after: "some/path/new", before: "some/path/old"}]
|
33
|
+
renamed_files_before = git_filesystem.renamed_files.map { |before_after_file| before_after_file[:before] }
|
34
|
+
renamed_files_after = git_filesystem.renamed_files.map { |before_after_file| before_after_file[:after] }
|
35
|
+
|
36
|
+
git_filesystem.modified_files.grep(PACKAGE_TODO_PATTERN).each do |modified_package_todo_yml_file|
|
37
|
+
# We skip over modified files if one of the modified files is a renamed `package_todo.yml` file.
|
38
|
+
# This allows us to rename packs while ignoring "new violations" in those renamed packs.
|
39
|
+
next if renamed_files_before.include?(modified_package_todo_yml_file)
|
40
|
+
|
41
|
+
head_commit_violations = BasicReferenceOffense.from(modified_package_todo_yml_file)
|
42
|
+
base_commit_violations = get_violations_before_patch_for(git_filesystem, modified_package_todo_yml_file)
|
43
|
+
added_violations += head_commit_violations - base_commit_violations
|
44
|
+
removed_violations += base_commit_violations - head_commit_violations
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# This implementation creates some false negatives:
|
49
|
+
# That is – it doesn't capture some cases:
|
50
|
+
# 1) A file has been renamed without renaming a constant.
|
51
|
+
# That can happen if we change only the autoloaded portion of a filename.
|
52
|
+
# For example: `packs/foo/app/services/my_class.rb` (defines: `MyClass`)
|
53
|
+
# is changed to `packs/foo/app/public/my_class.rb` (still defines: `MyClass`)
|
54
|
+
#
|
55
|
+
# This implementation also doesn't cover these false positives:
|
56
|
+
# That is – it leaves a comment when it should not.
|
57
|
+
# 1) A CONSTANT within a class or module has been renamed.
|
58
|
+
# e.g. `class MyClass; MY_CONSTANT = 1; end` becomes `class MyClass; RENAMED_CONSTANT = 1; end`
|
59
|
+
# We would not detect based on file renames that `MY_CONSTANT` has been renamed.
|
60
|
+
#
|
61
|
+
renamed_constants = []
|
62
|
+
|
63
|
+
added_violations.each do |violation|
|
64
|
+
filepath_that_defines_this_constant = Private.constant_resolver.resolve(violation.class_name)&.location
|
65
|
+
renamed_constants << violation.class_name if renamed_files_after.include?(filepath_that_defines_this_constant)
|
66
|
+
end
|
67
|
+
|
68
|
+
relevant_added_violations = added_violations.reject do |violation|
|
69
|
+
renamed_files_after.include?(violation.file) ||
|
70
|
+
renamed_constants.include?(violation.class_name) ||
|
71
|
+
!violation_types.include?(violation.type)
|
72
|
+
end
|
73
|
+
|
74
|
+
relevant_removed_violations = removed_violations.select do |violation|
|
75
|
+
violation_types.include?(violation.type)
|
76
|
+
end
|
77
|
+
|
78
|
+
[relevant_added_violations, relevant_removed_violations]
|
79
|
+
end
|
80
|
+
|
81
|
+
sig do
|
82
|
+
params(
|
83
|
+
git_filesystem: GitFilesystem,
|
84
|
+
package_todo_yml_file: String
|
85
|
+
).returns(T::Array[BasicReferenceOffense])
|
86
|
+
end
|
87
|
+
def self.get_violations_before_patch_for(git_filesystem, package_todo_yml_file)
|
88
|
+
# The strategy to get the violations before this PR is to reverse the patch on each `package_todo.yml`.
|
89
|
+
# A previous strategy attempted to use `git merge-base --fork-point`, but there are many situations where it returns
|
90
|
+
# empty values. That strategy is fickle because it depends on the state of the `reflog` within the CI suite, which appears
|
91
|
+
# to not be reliable to depend on.
|
92
|
+
#
|
93
|
+
# Instead, just inverting the patch should hopefully provide a more reliable way to figure out what was the state of the file before
|
94
|
+
# the PR without needing to use git commands that interpret the branch history based on local git history.
|
95
|
+
#
|
96
|
+
# We apply the patch to the original file so that we can seamlessly reverse the patch applied to that file (since patches are coupled to
|
97
|
+
# the files they modify). After parsing the violations from that `package_todo.yml` file with the patch reversed,
|
98
|
+
# we use a temporary copy of the original file to rewrite to it with the original contents.
|
99
|
+
# Note that practically speaking, we don't need to rewrite the original contents (since we already fetched the
|
100
|
+
# original contents above and the CI file system should be ephemeral). However, we do this anyways in case we later change these
|
101
|
+
# assumptions, or another client's environment is different and expects these files not to be mutated.
|
102
|
+
|
103
|
+
# Keep track of the original file contents. If the original file has been deleted, then we delete the file after inverting the patch at the end, rather than rewriting it.
|
104
|
+
package_todo_yml_file_copy = (File.read(package_todo_yml_file) if File.exist?(package_todo_yml_file))
|
105
|
+
|
106
|
+
Tempfile.create do |patch_file|
|
107
|
+
# Normally we'd use `git.diff_for_file(package_todo_yml_file).patch` here, but there is a bug where it does not work for deleted files yet.
|
108
|
+
# I have a fix for that here: https://github.com/danger/danger/pull/1357
|
109
|
+
# Until that lands, I'm just using the underlying implementation of that method to get the diff for a file.
|
110
|
+
# Note that I might want to use a safe escape operator, `&.patch` and return gracefully if the patch cannot be found.
|
111
|
+
# However I'd be interested in why that ever happens, so for now going to proceed as is.
|
112
|
+
# (Note that better yet we'd have observability into these so I can just log under those circumstances rather than surfacing an error to the user,
|
113
|
+
# but we don't have that quite yet.)
|
114
|
+
package_todo_filesystem_path = git_filesystem.convert_to_filesystem(package_todo_yml_file)
|
115
|
+
patch_for_file = git_filesystem.diff(package_todo_filesystem_path).patch
|
116
|
+
# This appears to be a known issue that patches require new lines at the end. It seems like this is an issue with Danger that
|
117
|
+
# it gives us a patch without a newline.
|
118
|
+
# https://stackoverflow.com/questions/18142870/git-error-fatal-corrupt-patch-at-line-36
|
119
|
+
patch_file << "#{patch_for_file}\n"
|
120
|
+
patch_file.rewind
|
121
|
+
# https://git-scm.com/docs/git-apply
|
122
|
+
_stdout, _stderr, _status = Open3.capture3("git apply --reverse #{patch_file.path}")
|
123
|
+
# https://www.rubyguides.com/2019/05/ruby-tempfile/
|
124
|
+
BasicReferenceOffense.from(package_todo_yml_file)
|
125
|
+
end
|
126
|
+
ensure
|
127
|
+
if package_todo_yml_file_copy
|
128
|
+
File.write(package_todo_yml_file, package_todo_yml_file_copy)
|
129
|
+
else
|
130
|
+
File.delete(package_todo_yml_file)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: danger-packwerk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.14.
|
4
|
+
version: 0.14.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gusto Engineers
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-09-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: code_ownership
|
@@ -208,7 +208,9 @@ files:
|
|
208
208
|
- lib/danger-packwerk/danger_packwerk.rb
|
209
209
|
- lib/danger-packwerk/packwerk_wrapper.rb
|
210
210
|
- lib/danger-packwerk/private.rb
|
211
|
+
- lib/danger-packwerk/private/git.rb
|
211
212
|
- lib/danger-packwerk/private/ownership_information.rb
|
213
|
+
- lib/danger-packwerk/private/todo_yml_changes.rb
|
212
214
|
- lib/danger-packwerk/update/default_formatter.rb
|
213
215
|
- lib/danger-packwerk/update/offenses_formatter.rb
|
214
216
|
- lib/danger-packwerk/version.rb
|