diff --git a/lib/ghb/application.rb b/lib/ghb/application.rb index 101eb89..90b2f7a 100644 --- a/lib/ghb/application.rb +++ b/lib/ghb/application.rb @@ -36,7 +36,7 @@ def initialize(argv) @exit_code = Status::SUCCESS_EXIT_CODE @dependencies_steps = [] @file_cache = {} - @auto_merge_workflow = Workflow.new('Auto-merge for code owners') + @auto_merge_workflow = Workflow.new('Auto-approve for code owners') @cron_workflow = Workflow.new('Cron Dependencies') @dockerhub_workflow = Workflow.new('Publish Docker image') @new_workflow = Workflow.new('Build') diff --git a/lib/ghb/auto_merge_manager.rb b/lib/ghb/auto_merge_manager.rb index cc22e28..cd62dcc 100644 --- a/lib/ghb/auto_merge_manager.rb +++ b/lib/ghb/auto_merge_manager.rb @@ -1,14 +1,21 @@ # frozen_string_literal: true +require 'fileutils' + module GHB - # Manages auto-merge workflow for code owners. + # Manages the auto-approve workflow for code owners. The workflow only + # approves code-owner PRs; it never merges, so it is named "Auto-approve". class AutoMergeManager + OLD_WORKFLOW_FILE = '.github/workflows/auto-merge.yml' + WORKFLOW_FILE = '.github/workflows/auto-approve.yml' + private_constant :OLD_WORKFLOW_FILE, :WORKFLOW_FILE + def initialize(auto_merge_workflow:) @auto_merge_workflow = auto_merge_workflow end def save - puts(' Adding auto-merge workflow...') + puts(' Adding auto-approve workflow...') @auto_merge_workflow.on = { @@ -28,12 +35,12 @@ def save # Cancel superseded runs on rapid pushes to the same PR. @auto_merge_workflow.concurrency = { - group: 'auto-merge-${{github.event.pull_request.number}}', + group: 'auto-approve-${{github.event.pull_request.number}}', 'cancel-in-progress': true } - @auto_merge_workflow.do_job(:enable_automerge) do - do_name('Enable Auto-merge') + @auto_merge_workflow.do_job(:auto_approve) do + do_name('Auto-approve') # Skip drafts, and never run the privileged pull_request_target token # against code checked out from a fork. do_if('github.event.pull_request.draft == false && github.event.pull_request.head.repo.full_name == github.repository') @@ -117,8 +124,12 @@ def save end end + # Remove the legacy auto-merge.yml so regenerated repos do not end up + # running both the old and the renamed workflow. + FileUtils.rm_f(OLD_WORKFLOW_FILE) + @auto_merge_workflow.write( - '.github/workflows/auto-merge.yml', + WORKFLOW_FILE, header: "# AUTO-GENERATED by github-build (auto_merge_manager.rb). Do not edit directly; changes will be overwritten.\n" ) end diff --git a/lib/ghb/workflow/workflow.rb b/lib/ghb/workflow/workflow.rb index 6d69dd7..373ffea 100644 --- a/lib/ghb/workflow/workflow.rb +++ b/lib/ghb/workflow/workflow.rb @@ -121,7 +121,7 @@ def write(file, header: '') content = header + data.to_yaml({ line_width: -1 }) # Convert secrets.GITHUB_TOKEN to secrets.GH_PAT for higher rate limits - content.gsub!('${{secrets.GITHUB_TOKEN}}', '${{secrets.GH_PAT}}') unless file.include?('auto-merge') + content.gsub!('${{secrets.GITHUB_TOKEN}}', '${{secrets.GH_PAT}}') unless file.match?(/auto-(merge|approve)/) File.write(file, content) end diff --git a/spec/ghb/auto_merge_manager_spec.rb b/spec/ghb/auto_merge_manager_spec.rb index 7797a82..ed95cd6 100644 --- a/spec/ghb/auto_merge_manager_spec.rb +++ b/spec/ghb/auto_merge_manager_spec.rb @@ -1,20 +1,28 @@ # frozen_string_literal: true RSpec.describe(GHB::AutoMergeManager) do - let(:auto_merge_workflow) { GHB::Workflow.new('Auto-merge for code owners') } + let(:auto_merge_workflow) { GHB::Workflow.new('Auto-approve for code owners') } before do allow($stdout).to(receive(:puts)) allow(FileUtils).to(receive(:mkdir_p)) + allow(FileUtils).to(receive(:rm_f)) allow(File).to(receive(:write)) end describe '#save' do - it 'generates the auto-merge workflow file' do + it 'generates the auto-approve workflow file' do manager = described_class.new(auto_merge_workflow: auto_merge_workflow) manager.save - expect(File).to(have_received(:write).with('.github/workflows/auto-merge.yml', anything)) + expect(File).to(have_received(:write).with('.github/workflows/auto-approve.yml', anything)) + end + + it 'removes the legacy auto-merge workflow file' do + manager = described_class.new(auto_merge_workflow: auto_merge_workflow) + manager.save + + expect(FileUtils).to(have_received(:rm_f).with('.github/workflows/auto-merge.yml')) end it 'sets the correct trigger' do # rubocop:disable RSpec/ExampleLength @@ -48,19 +56,19 @@ ) end - it 'creates the enable_automerge job' do # rubocop:disable RSpec/MultipleExpectations + it 'creates the auto_approve job' do # rubocop:disable RSpec/MultipleExpectations manager = described_class.new(auto_merge_workflow: auto_merge_workflow) manager.save - expect(auto_merge_workflow.jobs).to(have_key(:enable_automerge)) - expect(auto_merge_workflow.jobs[:enable_automerge].name).to(eq('Enable Auto-merge')) + expect(auto_merge_workflow.jobs).to(have_key(:auto_approve)) + expect(auto_merge_workflow.jobs[:auto_approve].name).to(eq('Auto-approve')) end it 'skips drafts and blocks fork pull requests' do manager = described_class.new(auto_merge_workflow: auto_merge_workflow) manager.save - expect(auto_merge_workflow.jobs[:enable_automerge].if).to( + expect(auto_merge_workflow.jobs[:auto_approve].if).to( eq('github.event.pull_request.draft == false && github.event.pull_request.head.repo.full_name == github.repository') ) end @@ -72,7 +80,7 @@ expect(auto_merge_workflow.concurrency).to( eq( { - group: 'auto-merge-${{github.event.pull_request.number}}', + group: 'auto-approve-${{github.event.pull_request.number}}', 'cancel-in-progress': true } ) @@ -85,7 +93,7 @@ expect(File).to( have_received(:write).with( - '.github/workflows/auto-merge.yml', + '.github/workflows/auto-approve.yml', a_string_starting_with('# AUTO-GENERATED by github-build') ) ) @@ -95,7 +103,7 @@ manager = described_class.new(auto_merge_workflow: auto_merge_workflow) manager.save - checkout_step = auto_merge_workflow.jobs[:enable_automerge].steps.find { |s| s.name == 'Checkout' } + checkout_step = auto_merge_workflow.jobs[:auto_approve].steps.find { |s| s.name == 'Checkout' } expect(checkout_step).not_to(be_nil) expect(checkout_step.uses).to(eq('actions/checkout@v6')) expect(checkout_step.with[:ref]).to(eq('${{github.event.pull_request.base.sha}}')) @@ -105,7 +113,7 @@ manager = described_class.new(auto_merge_workflow: auto_merge_workflow) manager.save - check_step = auto_merge_workflow.jobs[:enable_automerge].steps.find { |s| s.name == 'Check if PR author is a code owner' } + check_step = auto_merge_workflow.jobs[:auto_approve].steps.find { |s| s.name == 'Check if PR author is a code owner' } expect(check_step).not_to(be_nil) expect(check_step.id).to(eq('check')) expect(check_step.run).to(include('CODEOWNERS')) @@ -117,18 +125,18 @@ manager = described_class.new(auto_merge_workflow: auto_merge_workflow) manager.save - approve_step = auto_merge_workflow.jobs[:enable_automerge].steps.find { |s| s.name == 'Approve PR' } + approve_step = auto_merge_workflow.jobs[:auto_approve].steps.find { |s| s.name == 'Approve PR' } expect(approve_step).not_to(be_nil) expect(approve_step.if).to(eq("steps.check.outputs.is_owner == 'true'")) expect(approve_step.run).to(eq('gh pr review --approve "$PR"')) expect(approve_step.env[:GH_TOKEN]).to(eq('${{secrets.GH_BOT_PAT}}')) end - it 'does not include an enable auto-merge step' do + it 'does not include a merge step' do manager = described_class.new(auto_merge_workflow: auto_merge_workflow) manager.save - merge_step = auto_merge_workflow.jobs[:enable_automerge].steps.find { |s| s.name == 'Enable auto-merge' } + merge_step = auto_merge_workflow.jobs[:auto_approve].steps.find { |s| s.name&.match?(/merge/i) } expect(merge_step).to(be_nil) end end