π Sync documentation from source-docs #7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Reverse Sync on Push | |
env: | |
TARGET_REPO: alaudadevops/tektoncd-operator | |
# Ignoring the files or folders in this prefix (uses comma to split) | |
IGNORE_PATHS: .github/,README.md | |
# will check these files change to create a new patch | |
SYNCED_PATHS: "docs/ theme/ .yarn/ doom.config.yml yarn.lock tsconfig.json package.json sites.yaml" | |
on: | |
push: | |
branches: | |
- main | |
- release-* | |
# Limit token capabilities to what the job really needs | |
permissions: | |
contents: read # checkout / git diff | |
pull-requests: write # create PR in target repo | |
# (Optional) Prevent multiple syncs of the same ref running in parallel | |
concurrency: | |
group: reverse-sync-${{ github.ref }} | |
cancel-in-progress: true | |
jobs: | |
reverse-sync: | |
runs-on: ubuntu-latest | |
steps: | |
- name: Checkout devops-pipelines-docs repository | |
uses: actions/checkout@v4 | |
with: | |
token: ${{ secrets.GH_TOKEN }} | |
fetch-depth: 0 | |
- name: Check if commit is from merged PR | |
id: check_pr_commit | |
run: | | |
# Get the latest commit | |
commit_sha="${{ github.sha }}" | |
echo "commit_sha=$commit_sha" >> $GITHUB_OUTPUT | |
# Get commit message | |
commit_message=$(git log -1 --pretty=format:"%s" $commit_sha) | |
echo "commit_message=$commit_message" >> $GITHUB_OUTPUT | |
# Get commit author | |
commit_author=$(git log -1 --pretty=format:"%an" $commit_sha) | |
commit_author_email=$(git log -1 --pretty=format:"%ae" $commit_sha) | |
echo "commit_author=$commit_author" >> $GITHUB_OUTPUT | |
echo "commit_author_email=$commit_author_email" >> $GITHUB_OUTPUT | |
echo "=> Commit: $commit_sha" | |
echo "=> Message: $commit_message" | |
echo "=> Author: $commit_author <$commit_author_email>" | |
# Check if this is a merge commit from GitHub (squash merge creates a single commit) | |
# Look for PR number in commit message (GitHub automatically adds this) | |
if [[ "$commit_message" =~ \(#([0-9]+)\)$ ]]; then | |
pr_number="${BASH_REMATCH[1]}" | |
echo "pr_number=$pr_number" >> $GITHUB_OUTPUT | |
echo "is_pr_commit=true" >> $GITHUB_OUTPUT | |
echo "β Detected commit from PR #$pr_number" | |
else | |
echo "is_pr_commit=false" >> $GITHUB_OUTPUT | |
echo "βΉοΈ Not a PR commit - skipping reverse sync" | |
fi | |
# Skip if the commit is from our sync bot | |
if [[ "$commit_author_email" == "[email protected]" ]] && [[ "$commit_message" == *"[reverse-sync]"* ]]; then | |
echo "skip_sync=true" >> $GITHUB_OUTPUT | |
echo "π€ Commit is from sync bot - skipping reverse sync" | |
elif [[ "$commit_message" == *"Sync documentation"* ]] || [[ "$commit_message" == *"sync-docs"* ]]; then | |
echo "skip_sync=true" >> $GITHUB_OUTPUT | |
echo "π€ Commit appears to be from sync process - skipping reverse sync" | |
else | |
echo "skip_sync=false" >> $GITHUB_OUTPUT | |
echo "π₯ Commit is from external contributor - proceeding with reverse sync" | |
fi | |
- name: Get PR information | |
if: steps.check_pr_commit.outputs.is_pr_commit == 'true' && steps.check_pr_commit.outputs.skip_sync == 'false' | |
id: get_pr_info | |
run: | | |
pr_number="${{ steps.check_pr_commit.outputs.pr_number }}" | |
echo "π Looking for PR $pr_number ..." | |
# Get PR information using GitHub API | |
pr_info=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ | |
"https://api.github.com/repos/${{ github.repository }}/pulls/$pr_number") | |
echo "π PR data $pr_info ..." | |
pr_title=$(echo "$pr_info" | jq -r '.title') | |
pr_author=$(echo "$pr_info" | jq -r '.user.login') | |
pr_url=$(echo "$pr_info" | jq -r '.html_url') | |
pr_base_ref=$(echo "$pr_info" | jq -r '.base.ref') | |
echo "pr_title=$pr_title" >> $GITHUB_OUTPUT | |
echo "pr_author=$pr_author" >> $GITHUB_OUTPUT | |
echo "pr_url=$pr_url" >> $GITHUB_OUTPUT | |
echo "pr_base_ref=$pr_base_ref" >> $GITHUB_OUTPUT | |
echo "=> PR #$pr_number: $pr_title" | |
echo "=> Author: $pr_author" | |
echo "=> URL: $pr_url" | |
- name: Get commit changes | |
if: steps.check_pr_commit.outputs.is_pr_commit == 'true' && steps.check_pr_commit.outputs.skip_sync == 'false' | |
id: get_changes | |
run: | | |
commit_sha="${{ steps.check_pr_commit.outputs.commit_sha }}" | |
# Get the parent commit (previous commit before this one) | |
parent_commit=$(git rev-parse ${commit_sha}^) | |
echo "parent_commit=$parent_commit" >> $GITHUB_OUTPUT | |
# Get list of changed files in the commit, excluding ignored paths | |
ignore_pattern=$(echo "$IGNORE_PATHS" | sed 's/,/|/g' | sed 's|/$||g') | |
echo "π Ignored paths: $ignore_pattern" | |
git diff --name-only $parent_commit $commit_sha | grep -v -E "^($ignore_pattern)" > changed_files.txt || true | |
echo "π Changed files in commit:" | |
cat changed_files.txt | |
# Check if any relevant files were changed | |
if [ -s changed_files.txt ]; then | |
echo "has_doc_changes=true" >> $GITHUB_OUTPUT | |
echo "β Documentation changes detected" | |
else | |
echo "has_doc_changes=false" >> $GITHUB_OUTPUT | |
echo "βΉοΈ No documentation changes detected" | |
fi | |
- name: Checkout target repository | |
if: steps.check_pr_commit.outputs.is_pr_commit == 'true' && steps.check_pr_commit.outputs.skip_sync == 'false' && steps.get_changes.outputs.has_doc_changes == 'true' | |
uses: actions/checkout@v4 | |
with: | |
repository: ${{env.TARGET_REPO}} | |
token: ${{ secrets.GH_TOKEN }} | |
path: target-docs | |
fetch-depth: 0 | |
ref: ${{ steps.get_pr_info.outputs.pr_base_ref }} | |
- name: Create reverse sync branch | |
if: steps.check_pr_commit.outputs.is_pr_commit == 'true' && steps.check_pr_commit.outputs.skip_sync == 'false' && steps.get_changes.outputs.has_doc_changes == 'true' | |
id: create_branch | |
run: | | |
cd target-docs | |
# Create a unique branch name | |
pr_number="${{ steps.check_pr_commit.outputs.pr_number }}" | |
branch_name="reverse-sync/pr-$pr_number-$(date +%s)" | |
echo "branch_name=$branch_name" >> $GITHUB_OUTPUT | |
git checkout -b "$branch_name" | |
# Configure git | |
git config user.name "Documentation Sync Bot" | |
git config user.email "[email protected]" | |
echo "π Created branch: $branch_name" | |
- name: Apply changes from devops-pipelines-docs | |
if: steps.check_pr_commit.outputs.is_pr_commit == 'true' && steps.check_pr_commit.outputs.skip_sync == 'false' && steps.get_changes.outputs.has_doc_changes == 'true' | |
run: | | |
commit_sha="${{ steps.check_pr_commit.outputs.commit_sha }}" | |
parent_commit="${{ steps.get_changes.outputs.parent_commit }}" | |
# Create a patch with only the synced paths | |
echo "π Will only sync these paths: $SYNCED_PATHS" | |
git format-patch $parent_commit..$commit_sha --stdout -- $SYNCED_PATHS > changes.patch | |
echo "π Generated Patch: " | |
cat changes.patch | |
cd target-docs | |
# Apply the patch | |
if [ -s ../changes.patch ]; then | |
echo "π¦ Applying changes from devops-pipelines-docs..." | |
git apply ../changes.patch || { | |
echo "β οΈ Patch application failed, trying manual copy..." | |
# Fallback: manual copy of changed files | |
while IFS= read -r file; do | |
if [ -f "../$file" ]; then | |
mkdir -p "$(dirname "$file")" | |
cp "../$file" "$file" | |
echo "β Copied: $file" | |
fi | |
done < ../changed_files.txt | |
} | |
else | |
echo "β οΈ No patch generated, using manual copy..." | |
# Manual copy approach | |
while IFS= read -r file; do | |
if [ -f "../$file" ]; then | |
mkdir -p "$(dirname "$file")" | |
cp "../$file" "$file" | |
echo "β Copied: $file" | |
fi | |
done < ../changed_files.txt | |
fi | |
- name: Commit changes | |
if: steps.check_pr_commit.outputs.is_pr_commit == 'true' && steps.check_pr_commit.outputs.skip_sync == 'false' && steps.get_changes.outputs.has_doc_changes == 'true' | |
id: commit_changes | |
run: | | |
cd target-docs | |
echo "π Checking for changes..." | |
git status | |
echo "=> Adding changes..." | |
git add . | |
echo "==> Checking if there are staged changes..." | |
if git diff --staged --quiet; then | |
echo "has_changes=false" >> $GITHUB_OUTPUT | |
echo "βΉοΈ No changes to commit" | |
else | |
echo "=> Changes detected..." | |
echo "has_changes=true" >> $GITHUB_OUTPUT | |
pr_number="${{ steps.check_pr_commit.outputs.pr_number }}" | |
pr_title="${{ steps.get_pr_info.outputs.pr_title }}" | |
pr_author="${{ steps.get_pr_info.outputs.pr_author }}" | |
pr_url="${{ steps.get_pr_info.outputs.pr_url }}" | |
commit_sha="${{ steps.check_pr_commit.outputs.commit_sha }}" | |
echo "=> Creating commit message..." | |
echo " - PR Number: $pr_number" | |
echo " - PR Title: $pr_title" | |
echo " - PR Author: $pr_author" | |
echo " - PR URL: $pr_url" | |
echo " - Commit SHA: $commit_sha" | |
# Create commit message with reverse sync marker | |
echo "[reverse-sync] Sync documentation changes from devops-pipelines-docs PR #$pr_number" > commit_message.txt | |
echo "" >> commit_message.txt | |
echo "- Title: $pr_title" >> commit_message.txt | |
echo "- Author: $pr_author" >> commit_message.txt | |
echo "- URL: $pr_url" >> commit_message.txt | |
echo "- Commit: $commit_sha" >> commit_message.txt | |
echo "=> Commit message:" | |
cat commit_message.txt | |
echo "=> Committing changes..." | |
git commit -F commit_message.txt | |
rm commit_message.txt | |
echo "β Changes committed successfully" | |
fi | |
- name: Push branch and create PR | |
if: steps.check_pr_commit.outputs.is_pr_commit == 'true' && steps.check_pr_commit.outputs.skip_sync == 'false' && steps.get_changes.outputs.has_doc_changes == 'true' && steps.commit_changes.outputs.has_changes == 'true' | |
run: | | |
cd target-docs | |
branch_name="${{ steps.create_branch.outputs.branch_name }}" | |
pr_base_ref="${{ steps.get_pr_info.outputs.pr_base_ref }}" | |
pr_number="${{ steps.check_pr_commit.outputs.pr_number }}" | |
pr_title="${{ steps.get_pr_info.outputs.pr_title }}" | |
pr_author="${{ steps.get_pr_info.outputs.pr_author }}" | |
pr_url="${{ steps.get_pr_info.outputs.pr_url }}" | |
# Push the branch | |
git push origin "$branch_name" | |
# Create PR body with proper JSON escaping | |
pr_body=$(cat << 'EOF' | |
### π Reverse Sync from devops-pipelines-docs | |
This PR incorporates documentation changes from external contributors to the devops-pipelines-docs repository. | |
#### Original PR Details | |
- **Repository**: danielfbm/devops-pipelines-docs | |
- **PR**: #%PR_NUMBER% - %PR_TITLE% | |
- **Author**: @%PR_AUTHOR% | |
- **URL**: %PR_URL% | |
#### Changes | |
This PR includes changes to documentation files that were contributed by external contributors to the public devops-pipelines-docs repository. | |
#### Important Notes | |
- β οΈ This PR contains the `[reverse-sync]` marker to prevent infinite sync loops | |
- β Once merged, this will NOT trigger a forward sync back to devops-pipelines-docs | |
- π Please review the changes to ensure they align with internal documentation standards | |
--- | |
*This PR was automatically created by the reverse sync workflow.* | |
EOF) | |
# Replace placeholders in the PR body | |
pr_body=$(echo "$pr_body" | sed "s/%PR_NUMBER%/$pr_number/g") | |
pr_body=$(echo "$pr_body" | sed "s/%PR_TITLE%/$pr_title/g") | |
pr_body=$(echo "$pr_body" | sed "s/%PR_AUTHOR%/$pr_author/g") | |
pr_body=$(echo "$pr_body" | sed "s|%PR_URL%|$pr_url|g") | |
# Create JSON payload with proper escaping | |
json_payload=$(jq -n \ | |
--arg title "[reverse-sync] Documentation changes from devops-pipelines-docs PR #$pr_number" \ | |
--arg head "$branch_name" \ | |
--arg base "$pr_base_ref" \ | |
--arg body "$pr_body" \ | |
'{title: $title, head: $head, base: $base, body: $body}') | |
# Create the PR using GitHub API | |
curl -X POST \ | |
-H "Authorization: token ${{ secrets.GH_TOKEN }}" \ | |
-H "Accept: application/vnd.github.v3+json" \ | |
-H "Content-Type: application/json" \ | |
https://api.github.com/repos/${{env.TARGET_REPO}}/pulls \ | |
-d "$json_payload" | |
- name: Create workflow summary | |
run: | | |
if [ "${{ steps.check_pr_commit.outputs.is_pr_commit }}" == "false" ]; then | |
echo "## βΉοΈ Not a PR Commit" >> $GITHUB_STEP_SUMMARY | |
echo "" >> $GITHUB_STEP_SUMMARY | |
echo "This commit was not created from a merged PR, so no reverse sync was performed." >> $GITHUB_STEP_SUMMARY | |
elif [ "${{ steps.check_pr_commit.outputs.skip_sync }}" == "true" ]; then | |
echo "## π€ Sync Bot Commit - Reverse Sync Skipped" >> $GITHUB_STEP_SUMMARY | |
echo "" >> $GITHUB_STEP_SUMMARY | |
echo "This commit was created by the sync bot, so reverse sync was skipped to prevent loops." >> $GITHUB_STEP_SUMMARY | |
elif [ "${{ steps.get_changes.outputs.has_doc_changes }}" == "false" ]; then | |
echo "## βΉοΈ No Documentation Changes" >> $GITHUB_STEP_SUMMARY | |
echo "" >> $GITHUB_STEP_SUMMARY | |
echo "This commit didn't contain any documentation changes, so no reverse sync was needed." >> $GITHUB_STEP_SUMMARY | |
elif [ "${{ steps.commit_changes.outputs.has_changes }}" == "false" ]; then | |
echo "## βΉοΈ No Changes to Sync" >> $GITHUB_STEP_SUMMARY | |
echo "" >> $GITHUB_STEP_SUMMARY | |
echo "The documentation changes were already present in target-docs." >> $GITHUB_STEP_SUMMARY | |
else | |
echo "## π Reverse Sync Completed" >> $GITHUB_STEP_SUMMARY | |
echo "" >> $GITHUB_STEP_SUMMARY | |
echo "Successfully created a PR in target-docs with the documentation changes." >> $GITHUB_STEP_SUMMARY | |
echo "" >> $GITHUB_STEP_SUMMARY | |
echo "### Details:" >> $GITHUB_STEP_SUMMARY | |
echo "- **Source PR**: #${{ steps.check_pr_commit.outputs.pr_number }} by @${{ steps.get_pr_info.outputs.pr_author }}" >> $GITHUB_STEP_SUMMARY | |
echo "- **Branch**: ${{ steps.create_branch.outputs.branch_name }}" >> $GITHUB_STEP_SUMMARY | |
echo "- **Base ref**: ${{ steps.get_pr_info.outputs.pr_base_ref }}" >> $GITHUB_STEP_SUMMARY | |
echo "- **Target repository**: ${{env.TARGET_REPO}}" >> $GITHUB_STEP_SUMMARY | |
fi |