From 6e2f0ddfc020608609814c8866be7a8771556f3c Mon Sep 17 00:00:00 2001 From: Petr Kovar Date: Tue, 11 Nov 2025 17:23:07 +0100 Subject: [PATCH] Add backport_docs.sh A script to backport changes (added, modified, and removed files) from 'docs/' and 'sidebars.js' to their versioned counterparts. --- backport_docs.sh | 320 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 320 insertions(+) create mode 100755 backport_docs.sh diff --git a/backport_docs.sh b/backport_docs.sh new file mode 100755 index 00000000000..a0400e93f32 --- /dev/null +++ b/backport_docs.sh @@ -0,0 +1,320 @@ +#!/bin/bash + +# backport_docs.sh +# +# A script to backport changes (added, modified, and removed files) +# from 'docs/' and 'sidebars.js' to their versioned counterparts. + +# --- Configuration --- +DOCS_DIR="docs" +VERSIONED_DIR="versioned_docs" +SIDEBARS_SOURCE_FILE="sidebars.js" +SIDEBARS_VERSIONED_DIR="versioned_sidebars" + +# --- Colors --- +GREEN="\033[0;32m" +RED="\033[0;31m" +YELLOW="\033[0;33m" +BLUE="\033[0;34m" +RESET="\033[0m" + +# --- Help Message --- +usage() { + echo -e "${GREEN}Usage:${RESET} $0 [version1] [version2] ..." + echo + echo -e "${YELLOW}Description:${RESET}" + echo " Backports changes (added, modified, removed) from the '$DOCS_DIR/' directory" + echo " and modifications from '$SIDEBARS_SOURCE_FILE' to specified versioned directories." + echo + echo -e "${YELLOW}Arguments:${RESET}" + echo " [version] (Optional) One or more specific versions to backport to." + echo " If no versions are provided, the script attempts to backport" + echo " to all valid versions found in '$VERSIONED_DIR/'." + echo + echo -e "${YELLOW}Options:${RESET}" + echo " -h, --help Show this help message." + echo + echo -e "${YELLOW}Example (all versions):${RESET}" + echo " $0" + echo + echo -e "${YELLOW}Example (specific versions):${RESET}" + echo " $0 version-2.12 version-2.13" +} + +# --- Argument Parsing --- +if [[ "$1" == "-h" || "$1" == "--help" ]]; then + usage + exit 0 +fi + +TARGET_VERSIONS=("$@") + +# --- Sanity Checks --- +if [ ! -d "$DOCS_DIR" ]; then + echo -e "${RED}Error:${RESET} Directory '$DOCS_DIR' not found." + echo "Please run this script from the repository root." + exit 1 +fi + +if [ ! -d "$VERSIONED_DIR" ]; then + echo -e "${RED}Error:${RESET} Directory '$VERSIONED_DIR' not found." + echo "Please run this script from the repository root." + exit 1 +fi + +if [ ! -f "$SIDEBARS_SOURCE_FILE" ]; then + echo -e "${RED}Error:${RESET} Source file '$SIDEBARS_SOURCE_FILE' not found." + echo "Please run this script from the repository root." + exit 1 +fi + +if [ ! -d "$SIDEBARS_VERSIONED_DIR" ]; then + echo -e "${RED}Error:${RESET} Directory '$SIDEBARS_VERSIONED_DIR' not found." + echo "Please run this script from the repository root." + exit 1 +fi + +# --- Validate Provided Versions --- +# This block only runs if arguments were passed +if [ ${#TARGET_VERSIONS[@]} -gt 0 ]; then + echo -e "${BLUE}Validating provided versions...${RESET}" + for version in "${TARGET_VERSIONS[@]}"; do + # Fail if the argument looks like an option (starts with -) + if [[ "$version" == -* ]]; then + echo -e "${RED}Error:${RESET} Unknown option '$version'." + echo "Use -h or --help for usage." + exit 1 + fi + + if [ ! -d "$VERSIONED_DIR/$version" ]; then + echo -e "${RED}Error:${RESET} Provided version '$version' is not a valid directory under '$VERSIONED_DIR/'." + exit 1 + fi + done + echo -e "${GREEN}All provided versions are valid.${RESET}" + echo # Newline +fi + +# --- Find Target Versions (if not specified) --- +# This block only runs if NO arguments were passed +if [ ${#TARGET_VERSIONS[@]} -eq 0 ]; then + echo -e "${BLUE}No versions specified. Detecting all valid versions in '$VERSIONED_DIR'...${RESET}" + # Find all subdirectories + for dir in "$VERSIONED_DIR"/*/; do + # Check if the directory exists and is a directory + if [ -d "$dir" ]; then + # Check if the directory contains any file *other* than notice.md + # We use -print -quit to stop find immediately after the first match + if find "$dir" -type f -not -name "notice.md" -print -quit | grep -q .; then + # It's a valid version, add its basename + TARGET_VERSIONS+=("$(basename "$dir")") + fi + fi + done +fi + +if [ ${#TARGET_VERSIONS[@]} -eq 0 ]; then + echo -e "${RED}Error:${RESET} No target versions found or specified." + exit 1 +fi + +echo -e "${GREEN}Target versions:${RESET} ${TARGET_VERSIONS[*]}" +echo # Newline + +# --- Find Files to Backport (Docs) --- +echo -e "${BLUE}Finding modified/new/removed files in '$DOCS_DIR'...${RESET}" +# Find staged (cached) and unstaged (HEAD) changes (Added, Modified, or Deleted) +FILES_TO_BACKPORT=$( (git diff --name-only --diff-filter=AMD HEAD -- "$DOCS_DIR"; \ + git diff --name-only --diff-filter=AMD --cached -- "$DOCS_DIR") \ + | sort -u) + +if [ -z "$FILES_TO_BACKPORT" ]; then + echo -e "${GREEN}No modified, new, or removed files found in '$DOCS_DIR'. Nothing to do.${RESET}" +else + echo -e "${GREEN}Files to backport:${RESET}" + echo -e "${FILES_TO_BACKPORT}" + echo # Newline + + # --- Main Logic (Docs) --- + echo -e "${BLUE}Starting backport process for docs...${RESET}" + + # Use 'read' to handle file paths + echo "$FILES_TO_BACKPORT" | while IFS= read -r file; do + if [ -z "$file" ]; then # Skip empty lines + continue + fi + + echo -e "${YELLOW}Processing file:${RESET} $file" + + # Get the relative path inside 'docs/' + # e.g., 'docs/guides/intro.md' -> 'guides/intro.md' + REL_PATH="${file#$DOCS_DIR/}" + if [ "$REL_PATH" == "$file" ]; then + echo -e " ${RED}Error:${RESET} Could not determine relative path for '$file'. Skipping." + continue + fi + + # Check if the file exists in the source. + # If it does, it's ADDED or MODIFIED. + # If it doesn't, it's DELETED. + if [ -f "$file" ]; then + # --- LOGIC FOR ADDED/MODIFIED FILES --- + + PATCH_DATA_STAGED=$(git diff --cached -- "$file") + PATCH_DATA_UNSTAGED=$(git diff -- "$file") + + # Loop through each target version + for version in "${TARGET_VERSIONS[@]}"; do + TARGET_FILE="$VERSIONED_DIR/$version/$REL_PATH" + TARGET_DIR=$(dirname "$TARGET_FILE") + + # Check if the target *versioned file* exists + if [ ! -f "$TARGET_FILE" ]; then + # File doesn't exist in the versioned directory. + # This is a NEW file. Copy it directly. + echo -e " ${BLUE}Backporting as new file to version:${RESET} $version" + # Ensure target directory exists + mkdir -p "$TARGET_DIR" + if [ $? -ne 0 ]; then + echo -e " ${RED}Error:${RESET} Could not create directory '$TARGET_DIR'. Skipping." + continue + fi + # Copy the new file + cp "$file" "$TARGET_FILE" + if [ $? -eq 0 ]; then + echo -e " ${GREEN}Copied:${RESET} $file -> $TARGET_FILE" + else + echo -e " ${RED}Error:${RESET} Failed to copy '$file' to '$TARGET_FILE'." + fi + else + # File *does* exist. Patch it using the deltas. + echo -e " ${BLUE}Backporting changes to version:${RESET} $version" + + patch_applied=false + patch_failed=false + + if [ -n "$PATCH_DATA_STAGED" ]; then + echo "$PATCH_DATA_STAGED" | patch --no-backup "$TARGET_FILE" - > /dev/null 2>&1 + if [ $? -eq 0 ]; then + echo -e " ${GREEN}Patched (staged):${RESET} $TARGET_FILE" + patch_applied=true + else + echo -e " ${RED}Error (staged):${RESET} Failed to patch '$TARGET_FILE'." + patch_failed=true + fi + fi + + if [ -n "$PATCH_DATA_UNSTAGED" ]; then + echo "$PATCH_DATA_UNSTAGED" | patch --no-backup "$TARGET_FILE" - > /dev/null 2>&1 + if [ $? -eq 0 ]; then + echo -e " ${GREEN}Patched (unstaged):${RESET} $TARGET_FILE" + patch_applied=true + else + echo -e " ${RED}Error (unstaged):${RESET} Failed to patch '$TARGET_FILE'." + patch_failed=true + fi + fi + + if [ "$patch_failed" = true ]; then + echo -e " ${YELLOW}Tip:${RESET} This file may have diverged. Please review manually." + elif [ "$patch_applied" = false ]; then + echo -e " ${GREEN}Skipped:${RESET} $TARGET_FILE (already in sync)" + fi + fi + done # end version loop + + else + # --- LOGIC FOR DELETED FILES --- + echo -e " ${BLUE}File was deleted. Propagating deletion...${RESET}" + + # Loop through each target version + for version in "${TARGET_VERSIONS[@]}"; do + TARGET_FILE="$VERSIONED_DIR/$version/$REL_PATH" + + if [ -f "$TARGET_FILE" ]; then + rm "$TARGET_FILE" + if [ $? -eq 0 ]; then + echo -e " ${GREEN}Deleted:${RESET} $TARGET_FILE" + else + echo -e " ${RED}Error:${RESET} Failed to delete '$TARGET_FILE'." + fi + else + echo -e " ${GREEN}Skipped:${RESET} $TARGET_FILE (already deleted)" + fi + done # end version loop + fi # end add/modify/delete check + + echo # Add a newline for readability + done # end file loop +fi + +echo # Newline + +# --- Main Logic (Sidebars) --- +echo -e "${BLUE}Checking for sidebars changes...${RESET}" + +# Check for staged or unstaged MODIFICATIONS to the source sidebars file +# We only care about modifications (M), not additions or deletions. +SIDEBARS_CHANGED=$( (git diff --name-only --diff-filter=M HEAD -- "$SIDEBARS_SOURCE_FILE"; \ + git diff --name-only --diff-filter=M --cached -- "$SIDEBARS_SOURCE_FILE") \ + | sort -u) + +if [ -z "$SIDEBARS_CHANGED" ]; then + echo -e "${GREEN}No modifications found in '$SIDEBARS_SOURCE_FILE'. Skipping sidebars backport.${RESET}" +else + echo -e "${YELLOW}Processing file:${RESET} $SIDEBARS_SOURCE_FILE" + + # Get the patch data for STAGED changes (vs. HEAD) + PATCH_DATA_STAGED=$(git diff --cached -- "$SIDEBARS_SOURCE_FILE") + # Get the patch data for UNSTAGED changes (vs. Index) + PATCH_DATA_UNSTAGED=$(git diff -- "$SIDEBARS_SOURCE_FILE") + + for version in "${TARGET_VERSIONS[@]}"; do + + # The $version variable already contains "version-X.Y" + TARGET_FILE="$SIDEBARS_VERSIONED_DIR/${version}-sidebars.json" + + # Check if the corresponding versioned sidebars file exists + if [ ! -f "$TARGET_FILE" ]; then + echo -e " ${YELLOW}Skipped:${RESET} $TARGET_FILE (not found)" + continue + fi + + echo -e " ${BLUE}Backporting changes to:${RESET} $TARGET_FILE" + + patch_applied=false + patch_failed=false + + if [ -n "$PATCH_DATA_STAGED" ]; then + echo "$PATCH_DATA_STAGED" | patch --no-backup "$TARGET_FILE" - > /dev/null 2>&1 + if [ $? -eq 0 ]; then + echo -e " ${GREEN}Patched (staged):${RESET} $TARGET_FILE" + patch_applied=true + else + echo -e " ${RED}Error (staged):${RESET} Failed to patch '$TARGET_FILE'." + patch_failed=true + fi + fi + + if [ -n "$PATCH_DATA_UNSTAGED" ]; then + echo "$PATCH_DATA_UNSTAGED" | patch --no-backup "$TARGET_FILE" - > /dev/null 2>&1 + if [ $? -eq 0 ]; then + echo -e " ${GREEN}Patched (unstaged):${RESET} $TARGET_FILE" + patch_applied=true + else + echo -e " ${RED}Error (unstaged):${RESET} Failed to patch '$TARGET_FILE'." + patch_failed=true + fi + fi + + if [ "$patch_failed" = true ]; then + echo -e " ${YELLOW}Tip:${RESET} This file may have diverged. Please review manually." + elif [ "$patch_applied" = false ]; then + echo -e " ${GREEN}Skipped:${RESET} $TARGET_FILE (already in sync)" + fi + echo # Add a newline + done # end version loop +fi + +echo # Newline +echo -e "${GREEN}Backport complete.${RESET}"