From e44fb2cc5488dfc11a37c12910bcb0d44d028b04 Mon Sep 17 00:00:00 2001 From: Ivan Kuchin Date: Sun, 14 Jul 2024 14:51:02 +0200 Subject: [PATCH] add only_changed input to run rubocop only against changed files (#103) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add only_changed input to run rubocop only against changed files Switch to bash for process substitution and working with arrays. Ignore if there are more than 100. Protecting from possibly hitting the command line length limit, it can be much higher and can be calculated or also extracted as one more input if needed. The effect from limiting number of files processed by rubocop is also smaller. Fix ci workflow to fetch all commits for pr branch plus head commit of base branch to be able to find changed files. Co-authored-by: Oliver Günther * automatically fetch the tip of the base branch if it is not available * don't hide the name of the CI job * test only_changed --------- Co-authored-by: Oliver Günther --- .github/workflows/ci.yml | 53 ++++++++++++++++++- README.md | 8 +++ action.yml | 9 ++++ script.sh | 44 ++++++++++++++- test/only_changed/few_relevant/files/a.rb | 1 + test/only_changed/few_relevant/files/a.txt | 1 + .../few_relevant/mock_bins/rubocop | 18 +++++++ .../only_changed/nothing_relevant/files/a.txt | 1 + .../nothing_relevant/mock_bins/rubocop | 11 ++++ test/only_changed/shared_mock_bins/bundle | 9 ++++ test/only_changed/shared_mock_bins/curl | 8 +++ test/only_changed/shared_mock_bins/reviewdog | 10 ++++ .../too_many_relevant/mock_bins/rubocop | 17 ++++++ 13 files changed, 187 insertions(+), 3 deletions(-) create mode 100644 test/only_changed/few_relevant/files/a.rb create mode 100644 test/only_changed/few_relevant/files/a.txt create mode 100755 test/only_changed/few_relevant/mock_bins/rubocop create mode 100644 test/only_changed/nothing_relevant/files/a.txt create mode 100755 test/only_changed/nothing_relevant/mock_bins/rubocop create mode 100755 test/only_changed/shared_mock_bins/bundle create mode 100755 test/only_changed/shared_mock_bins/curl create mode 100755 test/only_changed/shared_mock_bins/reviewdog create mode 100755 test/only_changed/too_many_relevant/mock_bins/rubocop diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 61280dd..6a26232 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,6 @@ name: CI on: [pull_request] jobs: test-skip-install-and-use-bundler: - name: runner / rubocop runs-on: ubuntu-latest defaults: run: @@ -22,3 +21,55 @@ jobs: skip_install: 'true' use_bundler: 'true' - run: test "$(bundle exec rubocop --version)" == "1.18.1" + test-only_changed: + runs-on: ubuntu-latest + defaults: + run: + shell: bash + env: + INPUT_ONLY_CHANGED: 'true' + steps: + - uses: actions/checkout@v4 + - name: setup + run: | + git config user.email "workflow@github.com" + git config user.name "I am an automated workflow" + - name: Check when there are relevant files + run: | + git checkout ${{ github.sha }} + rm -f test/only_changed/reviewdog-was-called + + cp test/only_changed/few_relevant/files/* . + git add * + git commit -m auto + + export PATH=test/only_changed/few_relevant/mock_bins:test/only_changed/shared_mock_bins:$PATH + BASE_REF=$(git rev-parse HEAD~) HEAD_REF=$(git rev-parse HEAD) ./script.sh + + [ -f test/only_changed/reviewdog-was-called ] + - name: Check when there are no relevant files + run: | + git checkout ${{ github.sha }} + rm -f test/only_changed/reviewdog-was-called + + cp test/only_changed/nothing_relevant/files/* . + git add * + git commit -m auto + + export PATH=test/only_changed/nothing_relevant/mock_bins:test/only_changed/shared_mock_bins:$PATH + BASE_REF=$(git rev-parse HEAD~) HEAD_REF=$(git rev-parse HEAD) ./script.sh + + [ ! -f test/only_changed/reviewdog-was-called ] + - name: Check when there are too many relevant files + run: | + git checkout ${{ github.sha }} + rm -f test/only_changed/reviewdog-was-called + + touch a{00..100}.rb + git add * + git commit -m auto + + export PATH=test/only_changed/too_many_relevant/mock_bins:test/only_changed/shared_mock_bins:$PATH + BASE_REF=$(git rev-parse HEAD~) HEAD_REF=$(git rev-parse HEAD) ./script.sh + + [ -f test/only_changed/reviewdog-was-called ] diff --git a/README.md b/README.md index 551328c..7fc2719 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,14 @@ Default is `added`. Optional. Report level for reviewdog [`info`, `warning`, `error`]. It's same as `-level` flag of reviewdog. +### `only_changed` + +Optional. Run Rubocop only on changed (and added) files, for speedup [`true`, `false`]. +Default: `false`. + +Will fetch the tip of the base branch with depth 1 from remote origin if it is not available. +If you use different remote name or customize the checkout otherwise, make the tip of the base branch available before this action + ### `reporter` Optional. Reporter of reviewdog command [`github-pr-check`, `github-check`, `github-pr-review`]. diff --git a/action.yml b/action.yml index e552ebe..530a5c1 100644 --- a/action.yml +++ b/action.yml @@ -19,6 +19,12 @@ inputs: level: description: 'Report level for reviewdog [info,warning,error]' default: 'error' + only_changed: + description: | + Run Rubocop only on changed (and added) files, for speedup [`true`, `false`]. + Will fetch the tip of the base branch with depth 1 from remote origin if it is not available. + If you use different remote name or customize the checkout otherwise, make the tip of the base branch available before this action. + default: 'false' reporter: description: | Reporter of reviewdog command [github-pr-check,github-check,github-pr-review]. @@ -61,6 +67,7 @@ runs: INPUT_FILTER_MODE: ${{ inputs.filter_mode }} INPUT_GITHUB_TOKEN: ${{ inputs.github_token }} INPUT_LEVEL: ${{ inputs.level }} + INPUT_ONLY_CHANGED: ${{ inputs.only_changed }} INPUT_REPORTER: ${{ inputs.reporter }} INPUT_REVIEWDOG_FLAGS: ${{ inputs.reviewdog_flags }} INPUT_RUBOCOP_EXTENSIONS: ${{ inputs.rubocop_extensions }} @@ -70,6 +77,8 @@ runs: INPUT_TOOL_NAME: ${{ inputs.tool_name }} INPUT_USE_BUNDLER: ${{ inputs.use_bundler }} INPUT_WORKDIR: ${{ inputs.workdir }} + BASE_REF: ${{ github.event.pull_request.base.sha }} + HEAD_REF: ${{ github.sha }} branding: icon: 'check-circle' color: 'red' diff --git a/script.sh b/script.sh index 81a06bc..b34f6b6 100755 --- a/script.sh +++ b/script.sh @@ -1,4 +1,7 @@ -#!/bin/sh -e +#!/usr/bin/env bash + +set -e +set -o pipefail cd "${GITHUB_WORKSPACE}/${INPUT_WORKDIR}" || exit export REVIEWDOG_GITHUB_API_TOKEN="${INPUT_GITHUB_TOKEN}" @@ -85,9 +88,46 @@ else BUNDLE_EXEC="bundle exec " fi +if [ "${INPUT_ONLY_CHANGED}" = "true" ]; then + echo '::group:: Getting changed files list' + + # check if commit is present in repository, otherwise fetch it + if ! git cat-file -e "${BASE_REF}"; then + git fetch --depth 1 origin "${BASE_REF}" + fi + + # get intersection of changed files (excluding deleted) with target files for + # rubocop as an array + # shellcheck disable=SC2086 + readarray -t CHANGED_FILES < <( + comm -12 \ + <(git diff --diff-filter=d --name-only "${BASE_REF}..${HEAD_REF}" | sort || kill $$) \ + <(${BUNDLE_EXEC}rubocop --list-target-files | sort || kill $$) + ) + + if (( ${#CHANGED_FILES[@]} == 0 )); then + echo "No relevant files for rubocop, skipping" + exit 0 + fi + + printf '%s\n' "${CHANGED_FILES[@]}" + + if (( ${#CHANGED_FILES[@]} > 100 )); then + echo "More than 100 changed files (${#CHANGED_FILES[@]}), running rubocop on all files" + unset CHANGED_FILES + fi + + echo '::endgroup::' +fi + echo '::group:: Running rubocop with reviewdog 🐶 ...' # shellcheck disable=SC2086 -${BUNDLE_EXEC}rubocop ${INPUT_RUBOCOP_FLAGS} --require ${GITHUB_ACTION_PATH}/rdjson_formatter/rdjson_formatter.rb --format RdjsonFormatter \ +${BUNDLE_EXEC}rubocop \ + ${INPUT_RUBOCOP_FLAGS} \ + --require ${GITHUB_ACTION_PATH}/rdjson_formatter/rdjson_formatter.rb \ + --format RdjsonFormatter \ + --fail-level error \ + "${CHANGED_FILES[@]}" \ | reviewdog -f=rdjson \ -name="${INPUT_TOOL_NAME}" \ -reporter="${INPUT_REPORTER}" \ diff --git a/test/only_changed/few_relevant/files/a.rb b/test/only_changed/few_relevant/files/a.rb new file mode 100644 index 0000000..5460224 --- /dev/null +++ b/test/only_changed/few_relevant/files/a.rb @@ -0,0 +1 @@ +puts "Hello, " + "world!" diff --git a/test/only_changed/few_relevant/files/a.txt b/test/only_changed/few_relevant/files/a.txt new file mode 100644 index 0000000..5460224 --- /dev/null +++ b/test/only_changed/few_relevant/files/a.txt @@ -0,0 +1 @@ +puts "Hello, " + "world!" diff --git a/test/only_changed/few_relevant/mock_bins/rubocop b/test/only_changed/few_relevant/mock_bins/rubocop new file mode 100755 index 0000000..bcd0014 --- /dev/null +++ b/test/only_changed/few_relevant/mock_bins/rubocop @@ -0,0 +1,18 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +case ARGV +when %w[ + --list-target-files +] + puts Dir['**/*.rb'] +when %W[ + --require #{ENV['GITHUB_ACTION_PATH']}/rdjson_formatter/rdjson_formatter.rb + --format RdjsonFormatter + --fail-level error + a.rb +] + puts 'Mock message for reviewdog' +else + abort "rubocop mock called with unexpected arguments:\n#{ARGV.join("\n")}" +end diff --git a/test/only_changed/nothing_relevant/files/a.txt b/test/only_changed/nothing_relevant/files/a.txt new file mode 100644 index 0000000..5460224 --- /dev/null +++ b/test/only_changed/nothing_relevant/files/a.txt @@ -0,0 +1 @@ +puts "Hello, " + "world!" diff --git a/test/only_changed/nothing_relevant/mock_bins/rubocop b/test/only_changed/nothing_relevant/mock_bins/rubocop new file mode 100755 index 0000000..8b60d86 --- /dev/null +++ b/test/only_changed/nothing_relevant/mock_bins/rubocop @@ -0,0 +1,11 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +case ARGV +when %w[ + --list-target-files +] + puts Dir['**/*.rb'] +else + abort "rubocop mock called with unexpected arguments:\n#{ARGV.join("\n")}" +end diff --git a/test/only_changed/shared_mock_bins/bundle b/test/only_changed/shared_mock_bins/bundle new file mode 100755 index 0000000..0a28632 --- /dev/null +++ b/test/only_changed/shared_mock_bins/bundle @@ -0,0 +1,9 @@ +#!/bin/bash + +if [ "$1" = "exec" ]; then + shift + eval "$@" +else + echo "Only 'exec' command is supported" + exit 1 +fi diff --git a/test/only_changed/shared_mock_bins/curl b/test/only_changed/shared_mock_bins/curl new file mode 100755 index 0000000..3c5bc23 --- /dev/null +++ b/test/only_changed/shared_mock_bins/curl @@ -0,0 +1,8 @@ +#!/bin/bash + +arguments="$*" + +if [ "$arguments" != "-sfL https://raw.githubusercontent.com/reviewdog/reviewdog/master/install.sh" ]; then + echo "curl mock got unexpected arguments: $arguments" + exit 1 +fi diff --git a/test/only_changed/shared_mock_bins/reviewdog b/test/only_changed/shared_mock_bins/reviewdog new file mode 100755 index 0000000..5334ef7 --- /dev/null +++ b/test/only_changed/shared_mock_bins/reviewdog @@ -0,0 +1,10 @@ +#!/bin/bash + +touch test/only_changed/reviewdog-was-called + +read -r input + +if [ "$input" != "Mock message for reviewdog" ]; then + echo "reviewdog mock got unexpected input: $input" + exit 1 +fi diff --git a/test/only_changed/too_many_relevant/mock_bins/rubocop b/test/only_changed/too_many_relevant/mock_bins/rubocop new file mode 100755 index 0000000..1a2a540 --- /dev/null +++ b/test/only_changed/too_many_relevant/mock_bins/rubocop @@ -0,0 +1,17 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +case ARGV +when %w[ + --list-target-files +] + puts Dir['**/*.rb'] +when %W[ + --require #{ENV['GITHUB_ACTION_PATH']}/rdjson_formatter/rdjson_formatter.rb + --format RdjsonFormatter + --fail-level error +] + puts 'Mock message for reviewdog' +else + abort "rubocop mock called with unexpected arguments:\n#{ARGV.join("\n")}" +end