Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/ghb/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
23 changes: 17 additions & 6 deletions lib/ghb/auto_merge_manager.rb
Original file line number Diff line number Diff line change
@@ -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 =
{
Expand All @@ -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')
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/ghb/workflow/workflow.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
36 changes: 22 additions & 14 deletions spec/ghb/auto_merge_manager_spec.rb
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
}
)
Expand All @@ -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')
)
)
Expand All @@ -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}}'))
Expand All @@ -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'))
Expand All @@ -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
Expand Down
Loading