bzr 0.4.4

A CLI for Bugzilla, inspired by gh
Documentation
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

permissions:
  contents: read

env:
  CARGO_TERM_COLOR: always

jobs:
  msrv:
    name: MSRV
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6
      - name: Install libdbus-1-dev
        run: sudo apt-get update && sudo apt-get install -y libdbus-1-dev pkg-config
      - uses: dtolnay/rust-toolchain@193d6aa1dbbc28bd2c0a6b0e327cfdce68baaf6e  # 1.89.0
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4  # v2
        with:
          key: msrv-1.89.0
      - run: cargo test --locked --features test-helpers

  fmt:
    name: Format
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6
      - uses: dtolnay/rust-toolchain@193d6aa1dbbc28bd2c0a6b0e327cfdce68baaf6e  # stable
        with:
          components: rustfmt
      - run: cargo fmt --check

  test-layout:
    name: Test Layout
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6
      - name: Install ripgrep
        run: sudo apt-get update && sudo apt-get install -y ripgrep
      - run: make check-test-layout
      - run: make check-no-spawn

  clippy:
    name: Clippy
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6
      - name: Install libdbus-1-dev
        run: sudo apt-get update && sudo apt-get install -y libdbus-1-dev pkg-config
      - uses: dtolnay/rust-toolchain@193d6aa1dbbc28bd2c0a6b0e327cfdce68baaf6e  # stable
        with:
          components: clippy
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4  # v2
      - run: cargo clippy --locked --features test-helpers -- -D warnings

  test:
    name: Test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6
      - name: Install libdbus-1-dev
        run: sudo apt-get update && sudo apt-get install -y libdbus-1-dev pkg-config
      - uses: dtolnay/rust-toolchain@193d6aa1dbbc28bd2c0a6b0e327cfdce68baaf6e  # stable
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4  # v2
      - run: cargo test --locked --features test-helpers

  build:
    name: Build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6
      - name: Install libdbus-1-dev
        run: sudo apt-get update && sudo apt-get install -y libdbus-1-dev pkg-config
      - uses: dtolnay/rust-toolchain@193d6aa1dbbc28bd2c0a6b0e327cfdce68baaf6e  # stable
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4  # v2
      - run: cargo build --release --locked
      - name: Verify vendored-keyring drops the libdbus dependency
        run: |
          set -euo pipefail
          # The release workflow builds the x86_64/aarch64 Linux artifacts with
          # --features vendored-keyring so the Homebrew bottle binary is
          # self-contained. Prove here, on native Linux, that the feature both
          # builds and statically links libdbus (no NEEDED libdbus-1.so entry).
          cargo build --release --locked --features vendored-keyring
          if readelf -d target/release/bzr | grep -i 'NEEDED.*dbus'; then
            echo "::error::vendored-keyring binary still links libdbus dynamically; vendoring did not take effect"
            exit 1
          fi
          echo "vendored-keyring: no libdbus NEEDED entry — OK"

  manpages:
    name: Manpages
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6
      - uses: dtolnay/rust-toolchain@193d6aa1dbbc28bd2c0a6b0e327cfdce68baaf6e  # stable
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4  # v2
        with:
          key: manpages
      - name: Generate manpages
        run: cargo run -p xtask --no-default-features --release --locked -- man --out man/man1
      - name: Verify expected pages exist
        run: |
          set -euo pipefail
          test -s man/man1/bzr.1
          test -s man/man1/bzr-bug.1
          test -s man/man1/bzr-bug-list.1
          test -s man/man1/bzr-config-set-server.1
      - name: Render with groff (catches malformed roff)
        run: |
          set -euo pipefail
          sudo apt-get update && sudo apt-get install -y groff
          for page in man/man1/*.1; do
            groff -man -Tutf8 "$page" > /dev/null
          done

  cross-check:
    name: Cross-check ${{ matrix.target }}
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        target:
          - aarch64-unknown-linux-gnu
          - powerpc64le-unknown-linux-gnu
          - s390x-unknown-linux-gnu
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6
      - uses: dtolnay/rust-toolchain@193d6aa1dbbc28bd2c0a6b0e327cfdce68baaf6e  # stable
        with:
          targets: ${{ matrix.target }}
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4  # v2
        with:
          key: ${{ matrix.target }}
      - name: Install cross
        run: cargo install cross --locked
      - name: Check
        env:
          PKG_CONFIG_ALLOW_CROSS: "1"
        run: |
          # aarch64 ships its release binary with vendored libdbus (it feeds a
          # Homebrew bottle), so check that target the same way -- this also
          # exercises the vendored libdbus C cross-compile that only otherwise
          # runs at release time.
          FEATURES=()
          if [ "${{ matrix.target }}" = "aarch64-unknown-linux-gnu" ]; then
            FEATURES=(--features vendored-keyring)
          fi
          cross check --locked --target ${{ matrix.target }} "${FEATURES[@]}"

  windows-cross-check:
    name: Cross-check aarch64-pc-windows-msvc
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6
      - uses: dtolnay/rust-toolchain@193d6aa1dbbc28bd2c0a6b0e327cfdce68baaf6e  # stable
        with:
          targets: aarch64-pc-windows-msvc
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4  # v2
        with:
          key: aarch64-pc-windows-msvc
      - name: Check
        run: cargo check --locked --target aarch64-pc-windows-msvc
  sonarqube:
    name: SonarQube
    # Skip on forked PRs and Dependabot PRs (neither has access to SONAR_TOKEN).
    # Clippy (above) provides unconditional SAST coverage on all PRs including
    # Dependabot, satisfying OpenSSF Scorecard's SAST check.
    if: |
      (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
      && github.actor != 'dependabot[bot]'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6
        with:
          fetch-depth: 0  # Shallow clones should be disabled for a better relevancy of analysis
      - name: Install libdbus-1-dev
        run: sudo apt-get update && sudo apt-get install -y libdbus-1-dev pkg-config
      - uses: dtolnay/rust-toolchain@193d6aa1dbbc28bd2c0a6b0e327cfdce68baaf6e  # stable
        with:
          components: llvm-tools-preview
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4  # v2
        with:
          key: sonarqube
      - uses: taiki-e/install-action@0631aa6515c7d545823c67cfae7ef4fc7f490154  # v2.81.8
        with:
          tool: cargo-llvm-cov
      - name: Generate coverage report
        run: cargo llvm-cov --locked --workspace --all-features --lcov --output-path lcov.info
      - name: SonarQube Scan
        uses: SonarSource/sonarqube-scan-action@7006c4492b2e0ee0f816d36501671557c97f5995  # v8.1.0
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

  installer-lint:
    name: Lint installer scripts
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6

      - name: Install shellcheck and shfmt
        run: |
          sudo apt-get update
          sudo apt-get install -y shellcheck
          curl -fsSL https://github.com/mvdan/sh/releases/download/v3.7.0/shfmt_v3.7.0_linux_amd64 -o /tmp/shfmt
          sudo install -m 0755 /tmp/shfmt /usr/local/bin/shfmt

      - name: Run shellcheck
        run: shellcheck -s sh install.sh tests/installer/smoke.sh

      - name: Run shfmt (check only)
        run: shfmt -d -ln posix -i 2 install.sh tests/installer/smoke.sh

      - name: Run PSScriptAnalyzer
        shell: pwsh
        run: |
          Install-Module -Name PSScriptAnalyzer -Force -Scope CurrentUser
          $diagnostics = @()
          $diagnostics += Invoke-ScriptAnalyzer -Path install.ps1 -Severity Warning
          $diagnostics += Invoke-ScriptAnalyzer -Path tests/installer/smoke.ps1 -Severity Warning
          if ($diagnostics) {
            $diagnostics | Format-Table
            exit 1
          }

  installer-test:
    name: Smoke ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-14, windows-latest]
    runs-on: ${{ matrix.os }}
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6

      - name: Smoke (bash)
        if: matrix.os != 'windows-latest'
        run: sh tests/installer/smoke.sh

      - name: Smoke (powershell)
        if: matrix.os == 'windows-latest'
        shell: pwsh
        run: ./tests/installer/smoke.ps1