name: 'Sync docs to ClickHouse/ClickHouse'
on:
pull_request_target:
types: [closed]
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
workflow_dispatch:
env:
TARGET_REPO: ClickHouse/ClickHouse
TARGET_BRANCH: master
TARGET_DOCS_PATH: docs/integrations/language-clients/rust
SOURCE_DOCS_PATH: docs
SYNC_BRANCH: robot/docs-sync-clickhouse-rs
PR_LABEL: pr-autogenerated-docs
SYNC_LABEL: sync-docs
TAG_SCOPE: all
permissions:
contents: read
jobs:
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:
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