clickhouse 0.15.2

Official Rust client for ClickHouse DB
Documentation
name: 'Sync docs to ClickHouse/ClickHouse'

# Opens (or refreshes) a pull request on the aggregator docs repo
#
# It fires on three events:
#   * a merged PR carrying the `sync-docs` label  -> ship docs immediately,
#     without waiting for a tag
#   * a pushed version tag                        -> ship docs at the tag, gated
#     by TAG_SCOPE below (major-only by default);
#   * manual dispatch                             -> force a sync.
#
# A single stable branch is force-pushed each run, so the bot keeps exactly one
# open PR that is always one commit off the target branch. The PR is labelled
# `pr-autogenerated-docs`, which the aggregator's docs check recognizes for the
# autogenerated-region edit guard. The generated body includes the changelog
# metadata required for the target repository's CI to initialize.

on:
  pull_request_target:
    types: [closed]
  push:
    tags:
      - 'v[0-9]+.[0-9]+.[0-9]+'
  workflow_dispatch:

# ---------------------------------------------------------------------------
# Configuration -- edit these to reuse this workflow in another client repo.
# ---------------------------------------------------------------------------
env:
  # Repo that receives the docs PR, and the branch that PR targets.
  TARGET_REPO: ClickHouse/ClickHouse
  TARGET_BRANCH: master
  # Path inside TARGET_REPO that mirrors this repo's docs. Wiped and replaced on
  # every sync so deletions and renames propagate.
  TARGET_DOCS_PATH: docs/integrations/language-clients/rust
  # This repo's docs file or folder (the source of truth).
  SOURCE_DOCS_PATH: docs
  # Stable branch on TARGET_REPO, force-pushed each run.
  SYNC_BRANCH: robot/docs-sync-clickhouse-rs
  # Label the sync PR gets on TARGET_REPO.
  PR_LABEL: pr-autogenerated-docs
  # Label on a merged PR in THIS repo that triggers the expedited path.
  SYNC_LABEL: sync-docs
  # Which version tags trigger a sync: major (X.0.0), minor (X.Y.0), or all.
  TAG_SCOPE: all

# The cross-repo work is done with a GitHub App token (see the sync job); this
# workflow only needs to read its own repository.
permissions:
  contents: read

jobs:
  # Gate: decide whether this event should produce a sync, and why. Kept in its
  # own job so the (large) checkout/clone in `sync` only runs when needed.
  decide:
    runs-on: ubuntu-latest
    outputs:
      run: ${{ steps.gate.outputs.run }}
      reason: ${{ steps.gate.outputs.reason }}
    steps:
      - name: Decide whether to sync
        id: gate
        env:
          EVENT_NAME: ${{ github.event_name }}
          PR_MERGED: ${{ github.event.pull_request.merged }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
          PR_LABELS: ${{ toJSON(github.event.pull_request.labels.*.name) }}
          TAG_NAME: ${{ github.ref_name }}
        run: |
          set -euo pipefail
          run=false
          reason=""
          case "$EVENT_NAME" in
            workflow_dispatch)
              # Manual runs always sync -- the operator explicitly asked for it.
              run=true
              reason="manual dispatch"
              ;;
            pull_request_target)
              # Expedited path: a merged PR that carries the sync label.
              if [[ "$PR_MERGED" == "true" ]] \
                  && printf '%s' "$PR_LABELS" | jq -e --arg l "$SYNC_LABEL" 'index($l) != null' >/dev/null; then
                run=true
                reason="merged PR #${PR_NUMBER} labeled '${SYNC_LABEL}'"
              fi
              ;;
            push)
              if [[ "${TAG_NAME#v}" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
                minor="${BASH_REMATCH[2]}"
                patch="${BASH_REMATCH[3]}"
                case "$TAG_SCOPE" in
                  all)            run=true ;;
                  minor)          [[ "$patch" == "0" ]] && run=true ;;
                  major|*)        [[ "$minor" == "0" && "$patch" == "0" ]] && run=true ;;
                esac
                [[ "$run" == "true" ]] && reason="tag ${TAG_NAME} (scope=${TAG_SCOPE})"
              else
                echo "Could not parse version tag '${TAG_NAME}'; skipping."
              fi
              ;;
          esac
          echo "Decision: run=${run} reason='${reason}'"
          {
            echo "run=${run}"
            echo "reason=${reason}"
          } >> "$GITHUB_OUTPUT"

  sync:
    needs: decide
    if: needs.decide.outputs.run == 'true'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout docs source
        uses: actions/checkout@v5
        with:
          # On a tag push, take the docs as of the pushed tag; otherwise take
          # the default checkout (post-merge default branch / dispatched ref).
          ref: ${{ github.event_name == 'push' && github.ref_name || '' }}

      - name: Parse target repo
        id: parse
        run: |
          set -euo pipefail
          echo "owner=${TARGET_REPO%%/*}" >> "$GITHUB_OUTPUT"
          echo "name=${TARGET_REPO##*/}" >> "$GITHUB_OUTPUT"

      - name: Generate token for target repo
        id: app-token
        uses: actions/create-github-app-token@v3
        with:
          app-id: ${{ secrets.WORKFLOW_AUTH_PUBLIC_APP_ID }}
          private-key: ${{ secrets.WORKFLOW_AUTH_PUBLIC_PRIVATE_KEY }}
          owner: ${{ steps.parse.outputs.owner }}
          repositories: ${{ steps.parse.outputs.name }}

      - name: Sync docs and open/refresh PR
        env:
          GH_TOKEN: ${{ steps.app-token.outputs.token }}
          REASON: ${{ needs.decide.outputs.reason }}
          SOURCE_REPO: ${{ github.repository }}
          SOURCE_REF: ${{ github.ref_name }}
          SOURCE_SHA: ${{ github.sha }}
        run: |
          set -euo pipefail

          # Resolve the absolute source path before we cd elsewhere.
          src="$(realpath "$SOURCE_DOCS_PATH")"

          workdir="$(mktemp -d)"
          git clone --depth 1 --branch "$TARGET_BRANCH" \
            "https://x-access-token:${GH_TOKEN}@github.com/${TARGET_REPO}.git" "$workdir"
          cd "$workdir"

          # Replace the target file or folder with this repo's docs. Wipe first
          # so deletions and renames on our side propagate.
          target="${workdir:?}/${TARGET_DOCS_PATH}"
          rm -rf "$target"
          mkdir -p "$(dirname "$target")"
          if [[ -d "$src" ]]; then
            mkdir -p "$target"
            cp -R "${src}/." "${target}/"
          else
            cp "$src" "$target"
          fi

          git config user.name "clickhouse-docs-bot"
          git config user.email "clickhouse-docs-bot@users.noreply.github.com"

          # Build the single-commit-off-target branch fresh from the target
          # branch so the PR is always exactly one commit ahead.
          git checkout -B "$SYNC_BRANCH"
          git add -A -- "$TARGET_DOCS_PATH"
          if git diff --cached --quiet; then
            echo "No docs changes to sync; nothing to do."
            exit 0
          fi

          git commit -m "Sync ${SOURCE_REPO} docs (${REASON})"
          git push --force origin "$SYNC_BRANCH"

          title="Docs: Sync ${SOURCE_REPO} docs"
          body=$(cat <<EOF
          Automated one-way docs sync from [\`${SOURCE_REPO}\`](https://github.com/${SOURCE_REPO}).

          - **Source ref:** \`${SOURCE_REF}\` (\`${SOURCE_SHA}\`)
          - **Trigger:** ${REASON}
          - **Replaces:** \`${TARGET_DOCS_PATH}\`

          This PR is generated by the \`Sync docs to ClickHouse/ClickHouse\` workflow in \`${SOURCE_REPO}\`.
          The source repo is the source of truth for these docs; edit them there, not here.

          ### Changelog category (leave one):
          - Documentation (changelog entry is not required)

          ### Changelog entry (a [user-readable short description](https://github.com/ClickHouse/ClickHouse/blob/master/docs/changelog_entry_guidelines.md) of the changes that goes into CHANGELOG.md):
          Sync language client documentation from \`${SOURCE_REPO}\`.
          EOF
          )

          existing="$(gh pr list --repo "$TARGET_REPO" --head "$SYNC_BRANCH" \
            --state open --json number --jq '.[0].number // empty')"
          if [[ -n "$existing" ]]; then
            echo "PR #${existing} already open; force-push refreshed it."
            gh pr edit "$existing" --repo "$TARGET_REPO" \
              --title "$title" --body "$body" --add-label "$PR_LABEL"
          else
            gh pr create --repo "$TARGET_REPO" \
              --base "$TARGET_BRANCH" --head "$SYNC_BRANCH" \
              --title "$title" --body "$body" --label "$PR_LABEL"
          fi