danger-packwerk 0.14.1 → 0.14.2
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/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
|