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