gitsnitch 0.4.6

Lints your Git commit history against a declarative ruleset
name: Release
on:
  push:
    tags:
      - "v*"
  workflow_dispatch:

permissions:
  contents: write

jobs:
  release:
    strategy:
      fail-fast: false
      matrix:
        platform:
          - runs-on: ubuntu-24.04
            target: x86_64-unknown-linux-musl
          - runs-on: ubuntu-24.04
            target: aarch64-unknown-linux-musl
          - runs-on: ubuntu-24.04
            target: armv7-unknown-linux-musleabihf
          - runs-on: macos-latest
            target: x86_64-apple-darwin
          - runs-on: macos-latest
            target: aarch64-apple-darwin
          - runs-on: windows-latest
            target: aarch64-pc-windows-msvc
          - runs-on: windows-latest
            target: x86_64-pc-windows-msvc
    runs-on: ${{ matrix.platform.runs-on }}
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Generate changelog
        uses: orhun/git-cliff-action@v4
        with:
          config: cliff.toml
          args: --output CHANGELOG.md

      - name: Build
        uses: houseabsolute/actions-rust-cross@v1
        with:
          target: ${{ matrix.platform.target }}
          args: "--release"

      - name: Import GPG key
        id: import_gpg
        uses: crazy-max/ghaction-import-gpg@v7
        with:
          gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
          passphrase: ${{ secrets.GPG_PASSPHRASE }}

      - name: Sign binaries
        shell: bash
        run: |
          target_dir="target/${{ matrix.platform.target }}/release"
          binary_name="gitsnitch"

          case "${{ matrix.platform.target }}" in
            *windows*) binary_name="${binary_name}.exe" ;;
          esac

          gpg --batch --sign --detach-sign \
            --local-user "${{ steps.import_gpg.outputs.keyid }}" \
            "${target_dir}/${binary_name}"

          mv \
            "${target_dir}/${binary_name}.sig" \
            "gitsnitch-${{ matrix.platform.target }}.sig"

      - name: Release
        uses: houseabsolute/actions-rust-release@v0.0.7
        with:
          changes-file: CHANGELOG.md
          executable-name: gitsnitch
          target: ${{ matrix.platform.target }}
          action-gh-release-parameters: '{"token":"${{ secrets.GITHUB_TOKEN }}"}'

      - name: Upload detached signature
        uses: softprops/action-gh-release@v3
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          files: gitsnitch-${{ matrix.platform.target }}.sig

  release-macos-universal2:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Generate changelog
        uses: orhun/git-cliff-action@v4
        with:
          config: cliff.toml
          args: --output CHANGELOG.md

      - name: Build x86_64 macOS binary
        uses: houseabsolute/actions-rust-cross@v1
        with:
          target: x86_64-apple-darwin
          args: "--release"

      - name: Build arm64 macOS binary
        uses: houseabsolute/actions-rust-cross@v1
        with:
          target: aarch64-apple-darwin
          args: "--release"

      - name: Create universal2 binary
        shell: bash
        run: |
          mkdir -p target/release
          lipo -create \
            target/x86_64-apple-darwin/release/gitsnitch \
            target/aarch64-apple-darwin/release/gitsnitch \
            -output target/release/gitsnitch
          chmod 755 target/release/gitsnitch

      - name: Import GPG key
        id: import_gpg_universal2
        uses: crazy-max/ghaction-import-gpg@v7
        with:
          gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
          passphrase: ${{ secrets.GPG_PASSPHRASE }}

      - name: Sign universal2 binary
        shell: bash
        run: |
          gpg --batch --sign --detach-sign \
            --local-user "${{ steps.import_gpg_universal2.outputs.keyid }}" \
            target/release/gitsnitch

          mv target/release/gitsnitch.sig gitsnitch-universal2-apple-darwin.sig

      - name: Release universal2 archive
        uses: houseabsolute/actions-rust-release@v0
        with:
          changes-file: CHANGELOG.md
          executable-name: gitsnitch
          archive-name: gitsnitch-macOS-universal2
          action-gh-release-parameters: '{"token":"${{ secrets.GITHUB_TOKEN }}"}'

      - name: Upload detached signature
        uses: softprops/action-gh-release@v3
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          files: gitsnitch-universal2-apple-darwin.sig

  notify-homebrew-tap:
    name: Notify homebrew-tap
    runs-on: ubuntu-latest
    needs:
      - release
      - release-macos-universal2
    if: startsWith(github.ref, 'refs/tags/v')
    steps:
      - name: Notify homebrew-tap
        env:
          TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
          VERSION: ${{ github.ref_name }}
        run: |
          set -euo pipefail

          VERSION="${VERSION#v}"
          REPOSITORY="${GITHUB_REPOSITORY#*/}"

          fetch_sha() {
            local name="$1"
            local attempts="${SHA_RETRY_ATTEMPTS:-5}"
            local delay_seconds="${SHA_RETRY_DELAY_SECONDS:-8}"
            local i=1
            local sha=""
            local url="https://github.com/${GITHUB_REPOSITORY}/releases/download/v${VERSION}/${name}.sha256"

            while [[ "$i" -le "$attempts" ]]; do
              sha="$(curl -fsSL "$url" 2>/dev/null \
                | tr -d '\r' \
                | awk 'NR==1 {print $1}' || true)"

              if [[ "$sha" =~ ^[0-9a-fA-F]{64}$ ]]; then
                printf '%s\n' "$sha"
                return 0
              fi

              echo "Attempt ${i}/${attempts} failed for ${name}; retrying in ${delay_seconds}s..."
              sleep "$delay_seconds"
              i=$((i + 1))
            done

            echo "Failed to resolve valid SHA256 from sidecar after ${attempts} attempts: ${name}"
            return 1
          }

          SHA_MACOS="$(fetch_sha "gitsnitch-macOS-universal2.tar.gz")"
          SHA_LINUX_X86="$(fetch_sha "gitsnitch-Linux-musl-x86_64.tar.gz")"
          SHA_LINUX_ARM64="$(fetch_sha "gitsnitch-Linux-musl-arm64.tar.gz")"
          SHA_LINUX_ARMV7="$(fetch_sha "gitsnitch-Linux-musleabihf-armv7.tar.gz")"

          payload="$(jq -n \
            --arg version "$VERSION" \
            --arg sha_macos "$SHA_MACOS" \
            --arg sha_x86 "$SHA_LINUX_X86" \
            --arg sha_arm64 "$SHA_LINUX_ARM64" \
            --arg sha_armv7 "$SHA_LINUX_ARMV7" \
            --arg repository "$REPOSITORY" \
            '{
              event_type: "formula-update",
              client_payload: {
                repository: $repository,
                version: $version,
                sha256_macos_universal2: $sha_macos,
                sha256_linux_x86_64: $sha_x86,
                sha256_linux_arm64: $sha_arm64,
                sha256_linux_armv7: $sha_armv7
              }
            }')"

          curl --fail-with-body -sS \
            -X POST \
            -H "Accept: application/vnd.github+json" \
            -H "Authorization: Bearer ${TAP_TOKEN}" \
            -H "X-GitHub-Api-Version: 2022-11-28" \
            https://api.github.com/repos/iilei/homebrew-tap/dispatches \
            -d "$payload"

  publish-nuget:
    name: Publish to NuGet / Chocolatey
    runs-on: windows-latest
    needs:
      - release
      - release-macos-universal2
    if: startsWith(github.ref, 'refs/tags/v')
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 1

      - name: Detect dry-run mode
        id: dry_run
        shell: bash
        run: |
          git fetch origin master --depth=1 2>/dev/null || true
          if [[ "$GITHUB_REF" == refs/heads/master ]] || \
             git merge-base --is-ancestor "$GITHUB_SHA" origin/master 2>/dev/null; then
            echo "DRY_RUN=false" >> "$GITHUB_ENV"
            echo "value=false" >> "$GITHUB_OUTPUT"
          else
            echo "DRY_RUN=true" >> "$GITHUB_ENV"
            echo "value=true" >> "$GITHUB_OUTPUT"
            echo "Commit not reachable from master — dry-run mode."
          fi

      - name: Resolve version and SHA256
        id: resolve
        shell: bash
        env:
          VERSION: ${{ github.ref_name }}
        run: |
          VERSION="${VERSION#v}"
          echo "VERSION=${VERSION}" >> "$GITHUB_ENV"
          echo "version=${VERSION}" >> "$GITHUB_OUTPUT"

          SHA=$(curl -sL \
            "https://github.com/iilei/gitsnitch/releases/download/v${VERSION}/gitsnitch-Windows-msvc-x86_64.zip.sha256" \
            | awk '{print $1}')

          if [[ -z "$SHA" ]]; then
            echo "Failed to fetch SHA256 for Windows release asset."
            exit 1
          fi

          echo "SHA256_WIN_X64=${SHA}" >> "$GITHUB_ENV"

      - name: Stamp install script
        shell: pwsh
        run: |
          $script = Get-Content packaging\choco\tools\chocolateyInstall.ps1 -Raw
          $script = $script -replace '{{VERSION}}',      $env:VERSION
          $script = $script -replace '{{SHA256_WIN_X64}}', $env:SHA256_WIN_X64
          Set-Content packaging\choco\tools\chocolateyInstall.ps1 $script

      - name: Pack Chocolatey package
        uses: crazy-max/ghaction-chocolatey@v4
        with:
          args: pack packaging\choco\gitsnitch.nuspec --version ${{ steps.resolve.outputs.version }} --outputdirectory out\choco

      - name: Stage NuGet package assets
        shell: pwsh
        run: |
          New-Item -ItemType Directory -Force -Path packaging\nuget\docs   | Out-Null
          New-Item -ItemType Directory -Force -Path packaging\nuget\images | Out-Null
          Copy-Item README.md         packaging\nuget\docs\README.md
          Copy-Item gitsnitch_banner.png packaging\nuget\images\icon.png

      - name: Pack NuGet package
        shell: pwsh
        run: |
          New-Item -ItemType Directory -Force -Path out\nuget | Out-Null
          nuget pack packaging\nuget\gitsnitch.nuspec `
            -Version ${{ steps.resolve.outputs.version }} `
            -OutputDirectory out\nuget `
            -NoDefaultExcludes

      - name: Push to NuGet.org
        shell: pwsh
        env:
          NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
        run: |
          $pkg = Get-ChildItem out\nuget\*.nupkg | Select-Object -First 1
          dotnet nuget push $pkg.FullName `
            --api-key $env:NUGET_API_KEY `
            --source https://api.nuget.org/v3/index.json `
            --skip-duplicate

      - name: Push to Chocolatey Community
        continue-on-error: true
        uses: crazy-max/ghaction-chocolatey@v4
        with:
          args: push out\choco\gitsnitch.${{ steps.resolve.outputs.version }}.nupkg --source https://push.chocolatey.org/ --api-key ${{ secrets.CHOCOLATEY_API_KEY }}