Class: Fastlane::Actions::IsCheckRequiredAction
- Inherits:
-
Action
- Object
- Action
- Fastlane::Actions::IsCheckRequiredAction
- Defined in:
- lib/fastlane/plugin/stream_actions/actions/is_check_required.rb
Documentation collapse
Class Method Summary collapse
-
.changed_file_paths(params) ⇒ Object
For pull_request: use full PR for
opened(etc.); forsynchronizepass github_event_before/after (e.g. github.event.before/after) to scope to this push. -
.commit_files(repo, sha) ⇒ Object
Files changed by a single commit (relative to its first parent).
- .compare_push_files(repo, before, after) ⇒ Object
- .gh_path_lines(output) ⇒ Object
-
.latest_conclusions(output) ⇒ Object
Reduces "name\tconclusion\tcompleted_at" lines to the latest conclusion per check name, so re-runs of the same check supersede earlier attempts.
-
.pr_commit_shas(pr_num) ⇒ Object
PR commits, newest first (gh returns them oldest first).
-
.required_checks_passed?(repo, sha, required_checks) ⇒ Boolean
True only if every required check has its latest run concluded as 'success' on the commit.
-
.required_due_to_history(params, required_checks) ⇒ Object
Walks the PR commits from newest to oldest.
- .run(params) ⇒ Object
- .touches_sources?(files, sources) ⇒ Boolean
Class Method Details
.available_options ⇒ Object
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
# File 'lib/fastlane/plugin/stream_actions/actions/is_check_required.rb', line 161 def self. [ FastlaneCore::ConfigItem.new( key: :sources, description: 'Array of paths to scan', is_string: false, verify_block: proc do |array| UI.user_error!("Sources have to be specified") unless array.kind_of?(Array) && array.size.positive? end ), FastlaneCore::ConfigItem.new( env_name: 'GITHUB_PR_NUM', key: :github_pr_num, description: 'GitHub PR number', optional: true ), FastlaneCore::ConfigItem.new( key: :required_checks, description: 'Names of GitHub check runs that must have concluded as success on the last commit ' \ 'that changed :sources for the check to be skipped when the current push does not ' \ 'touch :sources. When empty, the check is skipped as soon as the current push does ' \ 'not touch :sources', is_string: false, optional: true, default_value: [] ), FastlaneCore::ConfigItem.new( key: :force_check, description: 'GitHub PR number', optional: true, is_string: false ), FastlaneCore::ConfigItem.new( env_name: 'GITHUB_EVENT_ACTION', key: :github_event_action, description: 'pull_request action: e.g. opened, synchronize. When synchronize and before/after ' \ 'are set, only files in that push are considered', optional: true ), FastlaneCore::ConfigItem.new( env_name: 'GITHUB_EVENT_BEFORE', key: :github_event_before, description: 'github.event.before (head ref before the push) for pull_request', optional: true ), FastlaneCore::ConfigItem.new( env_name: 'GITHUB_EVENT_AFTER', key: :github_event_after, description: 'github.event.after (head ref after the push) for pull_request', optional: true ), FastlaneCore::ConfigItem.new( env_name: 'GITHUB_REPOSITORY', key: :github_repository, description: 'owner/repo; required for push-scoped file list (defaults to GITHUB_REPOSITORY in CI)', optional: true ) ] end |
.changed_file_paths(params) ⇒ Object
For pull_request: use full PR for opened (etc.); for synchronize pass
github_event_before/after (e.g. github.event.before/after) to scope to this push.
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/fastlane/plugin/stream_actions/actions/is_check_required.rb', line 119 def self.changed_file_paths(params) action = params[:github_event_action].to_s before = params[:github_event_before].to_s.strip after = params[:github_event_after].to_s.strip repo = (params[:github_repository] || ENV['GITHUB_REPOSITORY']).to_s if action == 'synchronize' && !before.empty? && !after.empty? && !repo.empty? if before.match?(/\A0+\z/) || !before.match?(/\A[0-9a-f]{7,40}\z/i) || !after.match?(/\A[0-9a-f]{7,40}\z/i) UI.important("Invalid before/after for compare; falling back to full PR file list") else out = self.compare_push_files(repo, before, after) return out unless out.nil? UI.important("Could not list push diff (e.g. fork/cross-repo); falling back to full PR file list") end end self.gh_path_lines(Actions.sh("gh pr view #{params[:github_pr_num]} --json files -q '.files[].path'")) end |
.commit_files(repo, sha) ⇒ Object
Files changed by a single commit (relative to its first parent).
80 81 82 83 84 |
# File 'lib/fastlane/plugin/stream_actions/actions/is_check_required.rb', line 80 def self.commit_files(repo, sha) self.gh_path_lines(Actions.sh("gh api repos/#{repo}/commits/#{sha} --paginate -q '.files[].filename'")) rescue StandardError [] end |
.compare_push_files(repo, before, after) ⇒ Object
139 140 141 142 143 144 145 146 147 |
# File 'lib/fastlane/plugin/stream_actions/actions/is_check_required.rb', line 139 def self.compare_push_files(repo, before, after) self.gh_path_lines(Actions.sh( "gh api \"repos/#{repo}/compare/#{before}...#{after}\" " \ "-H \"Accept: application/vnd.github.v3+json\" " \ "-q '.files[].filename'" )) rescue StandardError nil end |
.description ⇒ Object
157 158 159 |
# File 'lib/fastlane/plugin/stream_actions/actions/is_check_required.rb', line 157 def self.description 'Analyzes the impact of changes on PR' end |
.gh_path_lines(output) ⇒ Object
149 150 151 |
# File 'lib/fastlane/plugin/stream_actions/actions/is_check_required.rb', line 149 def self.gh_path_lines(output) output.to_s.split("\n", -1).map(&:strip).reject(&:empty?) end |
.is_supported?(platform) ⇒ Boolean
221 222 223 |
# File 'lib/fastlane/plugin/stream_actions/actions/is_check_required.rb', line 221 def self.is_supported?(platform) true end |
.latest_conclusions(output) ⇒ Object
Reduces "name\tconclusion\tcompleted_at" lines to the latest conclusion per check name, so re-runs of the same check supersede earlier attempts.
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/fastlane/plugin/stream_actions/actions/is_check_required.rb', line 101 def self.latest_conclusions(output) latest = {} seen_at = {} output.to_s.split("\n", -1).each do |line| name, conclusion, completed_at = line.split("\t", -1) next if name.nil? || name.strip.empty? completed_at = completed_at.to_s next if seen_at.key?(name) && completed_at < seen_at[name] seen_at[name] = completed_at latest[name] = conclusion.to_s end latest end |
.pr_commit_shas(pr_num) ⇒ Object
PR commits, newest first (gh returns them oldest first).
73 74 75 76 77 |
# File 'lib/fastlane/plugin/stream_actions/actions/is_check_required.rb', line 73 def self.pr_commit_shas(pr_num) self.gh_path_lines(Actions.sh("gh pr view #{pr_num} --json commits -q '.commits[].oid'")).reverse rescue StandardError [] end |
.required_checks_passed?(repo, sha, required_checks) ⇒ Boolean
True only if every required check has its latest run concluded as 'success' on the commit.
87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/fastlane/plugin/stream_actions/actions/is_check_required.rb', line 87 def self.required_checks_passed?(repo, sha, required_checks) out = Actions.sh( "gh api \"repos/#{repo}/commits/#{sha}/check-runs?per_page=100\" --paginate " \ "-H \"Accept: application/vnd.github.v3+json\" " \ "-q '.check_runs[] | \"\\(.name)\\t\\(.conclusion)\\t\\(.completed_at)\"'" ) latest = self.latest_conclusions(out) required_checks.all? { |name| latest[name] == 'success' } rescue StandardError false end |
.required_due_to_history(params, required_checks) ⇒ Object
Walks the PR commits from newest to oldest. Every commit newer than the last :sources change has the current :sources tree, so a passing run on any of them means :sources were verified. The walk stops at the commit that changed :sources, since older commits have different sources.
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/fastlane/plugin/stream_actions/actions/is_check_required.rb', line 42 def self.required_due_to_history(params, required_checks) repo = (params[:github_repository] || ENV['GITHUB_REPOSITORY']).to_s if repo.empty? UI.important("No repository provided; cannot verify previous runs, running check") return true end shas = self.pr_commit_shas(params[:github_pr_num]) if shas.empty? UI.important("Could not list PR commits; running check") return true end shas.each do |sha| short = sha[0, 7] if self.required_checks_passed?(repo, sha, required_checks) UI.("Commit #{short} passed required checks for the current sources; safe to skip") return false end next unless self.touches_sources?(self.commit_files(repo, sha), params[:sources]) UI.important("Last sources commit #{short} was not verified by a passing run; running check") return true end UI.("No commit in this PR changed sources; nothing to test") false end |
.run(params) ⇒ Object
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/fastlane/plugin/stream_actions/actions/is_check_required.rb', line 4 def self.run(params) return true if params[:force_check] || params[:github_pr_num].nil? || params[:github_pr_num].strip.empty? UI.("Checking if check is required for PR ##{params[:github_pr_num]}") changed_files = self.changed_file_paths(params) too_many_files = changed_files.size > 99 # TODO: https://github.com/cli/cli/issues/5368 if too_many_files UI.important("Check is required because there were too many files changed.") return true end if self.touches_sources?(changed_files, params[:sources]) UI.important("Check is required: true") return true end required_checks = params[:required_checks].to_a if required_checks.empty? UI.important("Check is required: false") return false end # The current push does not touch :sources. It is only safe to skip if :sources were already # verified, i.e. all required checks passed on some commit that has the current :sources tree. is_check_required = self.required_due_to_history(params, required_checks) UI.important("Check is required: #{is_check_required}") is_check_required end |
.touches_sources?(files, sources) ⇒ Boolean
35 36 37 |
# File 'lib/fastlane/plugin/stream_actions/actions/is_check_required.rb', line 35 def self.touches_sources?(files, sources) files.any? { |path| sources.any? { |required| path.start_with?(required) } } end |