Skip to content
Latchkey

Publish Release workflow (jorisroovers/gitlint)

The Publish Release workflow from jorisroovers/gitlint, explained and optimized by Latchkey.

C

CI health: C - fair

Run this on Latchkey for self-healing, caching, and up to 58% lower cost.

Grade your own workflow free or run it on Latchkey →
Source: jorisroovers/gitlint.github/workflows/publish-release.ymlLicense MITView source

What it does

This is the Publish Release workflow from the jorisroovers/gitlint repository, a real project running GitHub Actions. It is shown here with attribution under its MIT license.

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

The workflow

workflow (.yml)
name: Publish Release
run-name: "Publish Release (environment=${{ inputs.environment }}, repo_release_ref=${{ inputs.repo_release_ref }})"

on:
  # Trigger release workflow from other workflows (e.g. release dev build as part of CI)
  workflow_call:
    inputs:
      environment:
        description: "Target environment"
        type: string
        required: true
      repo_release_ref:
        description: "Gitlint git reference to publish release for"
        type: string
        default: "main"
      docker_image_tag:
        description: "Docker image tag"
        required: true
        type: string
        default: "latest_dev"
    outputs:
      gitlint_version:
        description: "Gitlint version that was published"
        value: ${{ jobs.publish.outputs.gitlint_version }}
  
  # Manually trigger a release
  workflow_dispatch:
    inputs:
      environment:
        description: "Target environment"
        type: environment
        required: true
      repo_release_ref:
        description: "Gitlint git reference to publish release for"
        type: string
        default: "main"
      docker_image_tag:
        description: "Docker image tag"
        required: true
        type: choice
        options:
          - "latest_dev"
          - "latest"
          - "Use $gitlint_version"
        default: "latest_dev"

jobs:
  publish:
    timeout-minutes: 15
    runs-on: "ubuntu-latest"
    environment: ${{ inputs.environment }}
    permissions:
      # Required for trusted publishing to PyPI
      id-token: write
    outputs:
      gitlint_version: ${{ steps.set_version.outputs.gitlint_version }}
    steps:
      - name: Setup python
        uses: actions/setup-python@v4.7.0
        with:
          python-version: "3.11"

      - name: Install pypa/build
        run: python -m pip install build==0.10.0

      - name: Install Hatch
        run: python -m pip install hatch==1.7.0

      - uses: actions/checkout@v3.3.0
        with:
          ref: ${{ inputs.repo_release_ref }}
          fetch-depth: 0 # checkout all history, needed for hatch versioning

      # Run hatch version once to avoid additional output ("Setting up build environment for missing dependencies")
      # during the next step
      - name: Hatch version
        run: hatch version

      # Hatch versioning is based on git (using hatch-vcs). If there is no explicit tag for the commit we're trying to
      # publish, hatch versioning strings will have this format: 0.19.0.dev52+g9f7dc7d
      # With the string after '+' being the 'g<short-sha>' of the commit.
      #
      # However, PyPI doesn't allow '+' in version numbers (no PEP440 local versions allowed on PyPI).
      # To work around this, we override the version string by setting the SETUPTOOLS_SCM_PRETEND_VERSION env var
      # to the version string without the '+' and everything after it.
      # We do this by setting the `gitlint_version` step output here and re-using it later to
      # set SETUPTOOLS_SCM_PRETEND_VERSION.
      #
      # We only actually publish such releases on the main branch to guarantee the dev numbering scheme remains
      # unique.
      # Note that when a tag *is* present (i.e. v0.19.0), hatch versioning will return the tag name (i.e. 0.19.0)
      # and this step has no effect, ie. SETUPTOOLS_SCM_PRETEND_VERSION will be the same as `hatch version`.
      - name: Set SETUPTOOLS_SCM_PRETEND_VERSION
        id: set_version
        run: |
          echo "gitlint_version=$(hatch version | cut -d+ -f1)" >> $GITHUB_OUTPUT

      - name: Build (gitlint-core)
        run: python -m build
        working-directory: ./gitlint-core
        env: 
          SETUPTOOLS_SCM_PRETEND_VERSION: ${{ steps.set_version.outputs.gitlint_version }}

      - name: Build (gitlint)
        run: python -m build
        env: 
          SETUPTOOLS_SCM_PRETEND_VERSION: ${{ steps.set_version.outputs.gitlint_version }}

      - name: Publish gitlint-core πŸπŸ“¦ to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          packages-dir: gitlint-core/dist/
          repository-url: ${{ vars.PYPI_TARGET }} # PYPI_TARGET is defined in the GHA environment

      - name: Publish gitlint πŸπŸ“¦ to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          repository-url: ${{ vars.PYPI_TARGET }} # PYPI_TARGET is defined in the GHA environment

  # Wait for gitlint package to be available in PyPI for installation
  wait-for-package:
    environment: ${{ inputs.environment }}
    needs:
      - publish
    runs-on: "ubuntu-latest"
    steps:
      - name: Install gitlint
        uses: nick-fields/retry@v2.8.3
        with:
          timeout_minutes: 1
          max_attempts: 10
          # We need to add the --extra-index-url to the pip install command to deal with PYPI_TARGET=https://test.pypi.org/legacy,
          # because gitlint's dependencies are not available on Test PyPI
          command: |
            python -m pip install --no-cache-dir -i ${{ vars.PYPI_TARGET }} --extra-index-url https://pypi.org/simple gitlint==${{ needs.publish.outputs.gitlint_version }}

      - name: gitlint --version
        run: |
          gitlint --version
          [ "$(gitlint --version)" == "gitlint, version ${{ needs.publish.outputs.gitlint_version }}" ]

      # Unfortunately, it's not because the newly published package installation worked once that replication
      # has finished amongst all PyPI servers (subsequent installations might still fail). We sleep for 10 min here
      # to increase the odds that replication has finished.
      - name: Sleep
        run: sleep 600

  test-release:
    needs:
      - publish
      - wait-for-package
    uses: ./.github/workflows/test-release.yml
    with:
      gitlint_version: ${{ needs.publish.outputs.gitlint_version }}
      environment: ${{ inputs.environment }}
      repo_test_ref: ${{ inputs.repo_release_ref }}

  publish-docker:
    needs:
      - publish
      - test-release
    uses: ./.github/workflows/publish-docker.yml
    secrets: inherit # pass all secrets (required to access secrets in a called workflow)
    with:
      gitlint_version: ${{ needs.publish.outputs.gitlint_version }}
      docker_image_tag: ${{ inputs.docker_image_tag }}
      push_to_dockerhub: true

The same workflow, on Latchkey

Estimated ~20% faster on cache hits, plus fewer wasted runs and a safer supply chain. Added and changed lines are highlighted.

name: Publish Release
run-name: "Publish Release (environment=${{ inputs.environment }}, repo_release_ref=${{ inputs.repo_release_ref }})"
 
on:
  # Trigger release workflow from other workflows (e.g. release dev build as part of CI)
  workflow_call:
    inputs:
      environment:
        description: "Target environment"
        type: string
        required: true
      repo_release_ref:
        description: "Gitlint git reference to publish release for"
        type: string
        default: "main"
      docker_image_tag:
        description: "Docker image tag"
        required: true
        type: string
        default: "latest_dev"
    outputs:
      gitlint_version:
        description: "Gitlint version that was published"
        value: ${{ jobs.publish.outputs.gitlint_version }}
  
  # Manually trigger a release
  workflow_dispatch:
    inputs:
      environment:
        description: "Target environment"
        type: environment
        required: true
      repo_release_ref:
        description: "Gitlint git reference to publish release for"
        type: string
        default: "main"
      docker_image_tag:
        description: "Docker image tag"
        required: true
        type: choice
        options:
          - "latest_dev"
          - "latest"
          - "Use $gitlint_version"
        default: "latest_dev"
 
jobs:
  publish:
    timeout-minutes: 15
    runs-on: "ubuntu-latest"
    environment: ${{ inputs.environment }}
    permissions:
      # Required for trusted publishing to PyPI
      id-token: write
    outputs:
      gitlint_version: ${{ steps.set_version.outputs.gitlint_version }}
    steps:
      - name: Setup python
        uses: actions/setup-python@v4.7.0
        with:
          cache: 'pip'
          python-version: "3.11"
 
      - name: Install pypa/build
        run: python -m pip install build==0.10.0
 
      - name: Install Hatch
        run: python -m pip install hatch==1.7.0
 
      - uses: actions/checkout@v3.3.0
        with:
          ref: ${{ inputs.repo_release_ref }}
          fetch-depth: 0 # checkout all history, needed for hatch versioning
 
      # Run hatch version once to avoid additional output ("Setting up build environment for missing dependencies")
      # during the next step
      - name: Hatch version
        run: hatch version
 
      # Hatch versioning is based on git (using hatch-vcs). If there is no explicit tag for the commit we're trying to
      # publish, hatch versioning strings will have this format: 0.19.0.dev52+g9f7dc7d
      # With the string after '+' being the 'g<short-sha>' of the commit.
      #
      # However, PyPI doesn't allow '+' in version numbers (no PEP440 local versions allowed on PyPI).
      # To work around this, we override the version string by setting the SETUPTOOLS_SCM_PRETEND_VERSION env var
      # to the version string without the '+' and everything after it.
      # We do this by setting the `gitlint_version` step output here and re-using it later to
      # set SETUPTOOLS_SCM_PRETEND_VERSION.
      #
      # We only actually publish such releases on the main branch to guarantee the dev numbering scheme remains
      # unique.
      # Note that when a tag *is* present (i.e. v0.19.0), hatch versioning will return the tag name (i.e. 0.19.0)
      # and this step has no effect, ie. SETUPTOOLS_SCM_PRETEND_VERSION will be the same as `hatch version`.
      - name: Set SETUPTOOLS_SCM_PRETEND_VERSION
        id: set_version
        run: |
          echo "gitlint_version=$(hatch version | cut -d+ -f1)" >> $GITHUB_OUTPUT
 
      - name: Build (gitlint-core)
        run: python -m build
        working-directory: ./gitlint-core
        env: 
          SETUPTOOLS_SCM_PRETEND_VERSION: ${{ steps.set_version.outputs.gitlint_version }}
 
      - name: Build (gitlint)
        run: python -m build
        env: 
          SETUPTOOLS_SCM_PRETEND_VERSION: ${{ steps.set_version.outputs.gitlint_version }}
 
      - name: Publish gitlint-core πŸπŸ“¦ to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          packages-dir: gitlint-core/dist/
          repository-url: ${{ vars.PYPI_TARGET }} # PYPI_TARGET is defined in the GHA environment
 
      - name: Publish gitlint πŸπŸ“¦ to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          repository-url: ${{ vars.PYPI_TARGET }} # PYPI_TARGET is defined in the GHA environment
 
  # Wait for gitlint package to be available in PyPI for installation
  wait-for-package:
    timeout-minutes: 30
    environment: ${{ inputs.environment }}
    needs:
      - publish
    runs-on: "ubuntu-latest"
    steps:
      - name: Install gitlint
        uses: nick-fields/retry@v2.8.3
        with:
          timeout_minutes: 1
          max_attempts: 10
          # We need to add the --extra-index-url to the pip install command to deal with PYPI_TARGET=https://test.pypi.org/legacy,
          # because gitlint's dependencies are not available on Test PyPI
          command: |
            python -m pip install --no-cache-dir -i ${{ vars.PYPI_TARGET }} --extra-index-url https://pypi.org/simple gitlint==${{ needs.publish.outputs.gitlint_version }}
 
      - name: gitlint --version
        run: |
          gitlint --version
          [ "$(gitlint --version)" == "gitlint, version ${{ needs.publish.outputs.gitlint_version }}" ]
 
      # Unfortunately, it's not because the newly published package installation worked once that replication
      # has finished amongst all PyPI servers (subsequent installations might still fail). We sleep for 10 min here
      # to increase the odds that replication has finished.
      - name: Sleep
        run: sleep 600
 
  test-release:
    timeout-minutes: 30
    needs:
      - publish
      - wait-for-package
    uses: ./.github/workflows/test-release.yml
    with:
      gitlint_version: ${{ needs.publish.outputs.gitlint_version }}
      environment: ${{ inputs.environment }}
      repo_test_ref: ${{ inputs.repo_release_ref }}
 
  publish-docker:
    timeout-minutes: 30
    needs:
      - publish
      - test-release
    uses: ./.github/workflows/publish-docker.yml
    secrets: inherit # pass all secrets (required to access secrets in a called workflow)
    with:
      gitlint_version: ${{ needs.publish.outputs.gitlint_version }}
      docker_image_tag: ${{ inputs.docker_image_tag }}
      push_to_dockerhub: true
 

What changed

2 third-party actions are referenced by a movable tag. Pin them to the commit SHA (Latchkey resolves and applies this automatically) so a repointed tag cannot change what runs.

What Latchkey heals here

This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:

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

Actions used in this workflow