Skip to content
Latchkey

Automerge For Humans workflow (asyncapi/spec)

The Automerge For Humans workflow from asyncapi/spec, explained and optimized by Latchkey.

A

CI health: A - excellent

Point runs-on at Latchkey and get job timeouts, self-healing for flaky steps, and up to 58% lower cost, applied automatically.

Grade your own workflow free or run it on Latchkey →
Source: asyncapi/spec.github/workflows/automerge-for-humans-merging.ymlLicense Apache-2.0View source

What it does

This is the Automerge For Humans workflow from the asyncapi/spec repository, a real project running GitHub Actions. It is shown here with attribution under its Apache-2.0 license.

Below, Latchkey shows a faster, safer version produced by its optimization engine.

The workflow

workflow (.yml)
# This workflow is centrally managed in https://github.com/asyncapi/.github/
# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo

# Purpose of this workflow is to allow people to merge PR without a need of maintainer doing it. If all checks are in place (including maintainers approval) - JUST MERGE IT!
name: Automerge For Humans

on:
  pull_request_target:
    types:
      - labeled
      - unlabeled
      - synchronize
      - opened
      - edited
      - ready_for_review
      - reopened
      - unlocked                              # zizmor: ignore[dangerous-triggers] needed if we want author to be our bot

permissions: {}

jobs:
  automerge-for-humans:
    name: Automerge PRs labeled with ready-to-merge
    permissions:
      contents: read # required for PR commit metadata reads
      pull-requests: read # required to read pull request details in github-script steps
    # it runs only if PR actor is not a bot, at least not a bot that we know
    if: |
      github.event.pull_request.draft == false && 
      !contains(fromJSON('["asyncapi-bot","dependabot[bot]","dependabot-preview[bot]"]'), github.event.pull_request.user.login)
    runs-on: ubuntu-latest
    steps:
      - name: Get PR authors
        id: authors
        uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
        with:
          script: |
            // Get paginated list of all commits in the PR
            try {
              const commitOpts = github.rest.pulls.listCommits.endpoint.merge({
                owner: context.repo.owner,
                repo: context.repo.repo,
                pull_number: context.issue.number
              });

              const commits = await github.paginate(commitOpts);

              if (commits.length === 0) {
                core.setFailed('No commits found in the PR');
                return '';
              }

              // Get unique authors from the commits list
              const authors = commits.reduce((acc, commit) => {
                const username = commit.author?.login || commit.commit.author?.name;
                if (username && !acc[username]) {
                  acc[username] = {
                    name: commit.commit.author?.name,
                    email: commit.commit.author?.email,
                  }
                }

                return acc;
              }, {});

              return authors;
            } catch (error) {
              core.setFailed(error.message);
              return [];
            }

      - name: Create commit message
        id: create-commit-message
        uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
        env:
          AUTHORS_JSON: ${{ steps.authors.outputs.result }}
        with:
          script: |
            const authors = JSON.parse(process.env.AUTHORS_JSON);

            if (Object.keys(authors).length === 0) {
              core.setFailed('No authors found in the PR');
              return '';
            }

            // Create a string of the form "Co-authored-by: Name <email>"
            // ref: https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/creating-a-commit-with-multiple-authors
            const coAuthors = Object.values(authors).map(author => {
              return `Co-authored-by: ${author.name} <${author.email}>`;
            }).join('\n');

            core.debug(coAuthors);;

            return coAuthors;

      - name: Automerge PR
        uses: pascalgn/automerge-action@22948e0bc22f0aa673800da838595a3e7347e584 #v0.15.6 https://github.com/pascalgn/automerge-action/releases/tag/v0.15.6
        env:
          GITHUB_TOKEN: "${{ secrets.GH_TOKEN }}"
          MERGE_LABELS: "!do-not-merge,ready-to-merge"
          MERGE_METHOD: "squash"
          # Using the output of the previous step (`Co-authored-by: ...` lines) as commit description.
          # Important to keep 2 empty lines as https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/creating-a-commit-with-multiple-authors#creating-co-authored-commits-on-the-command-line mentions
          MERGE_COMMIT_MESSAGE: "{pullRequest.title} (#{pullRequest.number})\n\n\n${{ fromJSON(steps.create-commit-message.outputs.result) }}"
          MERGE_RETRIES: "20"
          MERGE_RETRY_SLEEP: "30000"

The same workflow, on Latchkey

Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.

# This workflow is centrally managed in https://github.com/asyncapi/.github/
# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo
 
# Purpose of this workflow is to allow people to merge PR without a need of maintainer doing it. If all checks are in place (including maintainers approval) - JUST MERGE IT!
name: Automerge For Humans
 
on:
  pull_request_target:
    types:
      - labeled
      - unlabeled
      - synchronize
      - opened
      - edited
      - ready_for_review
      - reopened
      - unlocked                              # zizmor: ignore[dangerous-triggers] needed if we want author to be our bot
 
permissions: {}
 
jobs:
  automerge-for-humans:
    timeout-minutes: 30
    name: Automerge PRs labeled with ready-to-merge
    permissions:
      contents: read # required for PR commit metadata reads
      pull-requests: read # required to read pull request details in github-script steps
    # it runs only if PR actor is not a bot, at least not a bot that we know
    if: |
      github.event.pull_request.draft == false && 
      !contains(fromJSON('["asyncapi-bot","dependabot[bot]","dependabot-preview[bot]"]'), github.event.pull_request.user.login)
    runs-on: latchkey-small
    steps:
      - name: Get PR authors
        id: authors
        uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
        with:
          script: |
            // Get paginated list of all commits in the PR
            try {
              const commitOpts = github.rest.pulls.listCommits.endpoint.merge({
                owner: context.repo.owner,
                repo: context.repo.repo,
                pull_number: context.issue.number
              });
 
              const commits = await github.paginate(commitOpts);
 
              if (commits.length === 0) {
                core.setFailed('No commits found in the PR');
                return '';
              }
 
              // Get unique authors from the commits list
              const authors = commits.reduce((acc, commit) => {
                const username = commit.author?.login || commit.commit.author?.name;
                if (username && !acc[username]) {
                  acc[username] = {
                    name: commit.commit.author?.name,
                    email: commit.commit.author?.email,
                  }
                }
 
                return acc;
              }, {});
 
              return authors;
            } catch (error) {
              core.setFailed(error.message);
              return [];
            }
 
      - name: Create commit message
        id: create-commit-message
        uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
        env:
          AUTHORS_JSON: ${{ steps.authors.outputs.result }}
        with:
          script: |
            const authors = JSON.parse(process.env.AUTHORS_JSON);
 
            if (Object.keys(authors).length === 0) {
              core.setFailed('No authors found in the PR');
              return '';
            }
 
            // Create a string of the form "Co-authored-by: Name <email>"
            // ref: https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/creating-a-commit-with-multiple-authors
            const coAuthors = Object.values(authors).map(author => {
              return `Co-authored-by: ${author.name} <${author.email}>`;
            }).join('\n');
 
            core.debug(coAuthors);;
 
            return coAuthors;
 
      - name: Automerge PR
        uses: pascalgn/automerge-action@22948e0bc22f0aa673800da838595a3e7347e584 #v0.15.6 https://github.com/pascalgn/automerge-action/releases/tag/v0.15.6
        env:
          GITHUB_TOKEN: "${{ secrets.GH_TOKEN }}"
          MERGE_LABELS: "!do-not-merge,ready-to-merge"
          MERGE_METHOD: "squash"
          # Using the output of the previous step (`Co-authored-by: ...` lines) as commit description.
          # Important to keep 2 empty lines as https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/creating-a-commit-with-multiple-authors#creating-co-authored-commits-on-the-command-line mentions
          MERGE_COMMIT_MESSAGE: "{pullRequest.title} (#{pullRequest.number})\n\n\n${{ fromJSON(steps.create-commit-message.outputs.result) }}"
          MERGE_RETRIES: "20"
          MERGE_RETRY_SLEEP: "30000"
 

What changed

This workflow runs 1 job per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.

Actions used in this workflow