mobile-wallet/sync-dirs.sh

587 lines
19 KiB
Bash
Raw Normal View History

#!/bin/bash
# sync-dirs.sh
# Script to sync directories and files from upstream repository
# Colors and formatting
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
BOLD='\033[1m'
NC='\033[0m' # No Color
CHECKMARK='\xE2\x9C\x94'
CROSS='\xE2\x9C\x98'
# Default upstream URL
DEFAULT_UPSTREAM_URL="https://github.com/openMF/kmp-project-template.git"
# Script options
DRY_RUN=false
FORCE=false
LOG_FILE="sync-$(date +%d%m%Y-%H%M%S).log"
# Directories and files to sync
SYNC_DIRS=(
"cmp-android"
"cmp-desktop"
"cmp-ios"
"cmp-web"
"cmp-shared"
"core-base"
"build-logic"
"fastlane"
"scripts"
"config"
".github"
".run"
)
SYNC_FILES=(
"Gemfile"
"Gemfile.lock"
"ci-prepush.bat"
"ci-prepush.sh"
)
# Define exclusions for directories and files
# Format: "path/to/exclude:type"
# type can be 'dir' or 'file'
# Use "root" key for files in the root directory
declare -A EXCLUSIONS=(
["cmp-android"]="src/main/res:dir dependencies:dir src/main/ic_launcher-playstore.png:file google-services.json:file"
["cmp-web"]="src/jsMain/resources:dir src/wasmJsMain/resources:dir"
["cmp-desktop"]="icons:dir"
["cmp-ios"]="iosApp/Assets.xcassets:dir"
["root"]="secrets.env:file"
)
# Display help information
show_help() {
echo -e "${BOLD}Usage:${NC} ./sync-dirs.sh [options]"
echo
echo -e "${BOLD}Description:${NC}"
echo " This script syncs directories and files from an upstream repository."
echo " It preserves excluded files and directories as defined in the script."
echo
echo -e "${BOLD}Options:${NC}"
echo " -h, --help Display this help message and exit"
echo " --dry-run Show what would be done without making changes"
echo " -f, --force Skip confirmation prompts and proceed automatically"
echo
echo -e "${BOLD}Examples:${NC}"
echo " ./sync-dirs.sh # Run with interactive prompts"
echo " ./sync-dirs.sh --dry-run # Test run without changes"
echo " ./sync-dirs.sh --force # Run with no prompts"
}
# Logging function
log_message() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
echo -e "$1"
}
# Error handling function
handle_error() {
log_message "${RED}${CROSS} Error: $1${NC}"
exit 1
}
# Print error message
print_error() {
log_message "${RED}${CROSS} Error: $1${NC}"
}
# Simple progress indicator function
show_progress() {
if [ "$DRY_RUN" = false ]; then
echo -ne "${BLUE}[ ]${NC}\r"
echo -ne "${BLUE}[===== ]${NC}\r"
sleep 0.1
echo -ne "${BLUE}[========== ]${NC}\r"
sleep 0.1
echo -ne "${BLUE}[=============== ]${NC}\r"
sleep 0.1
echo -ne "${BLUE}[====================]${NC}"
echo
fi
}
# Fancy banner
print_banner() {
echo -e "${BLUE}╔════════════════════════════════════════════╗${NC}"
echo -e "${BLUE}${BOLD} Project Directory Sync Tool ${NC}${BLUE}${NC}"
echo -e "${BLUE}╚════════════════════════════════════════════╝${NC}"
echo
}
# Print step with color and symbol
print_step() {
log_message "${GREEN}${CHECKMARK} $1${NC}"
}
# Print warning with color
print_warning() {
log_message "${YELLOW}$1${NC}"
}
# Function to generate unique branch name
get_sync_branch_name() {
local date_suffix=$(date +%Y%m%d-%H%M%S)
echo "sync/upstream-${date_suffix}"
}
# Print directories and files to be synced
print_items() {
echo -e "${BLUE}Items to sync:${NC}"
echo -e "${BOLD}Directories:${NC}"
for dir in "${SYNC_DIRS[@]}"; do
echo -e " ${BOLD}${NC} $dir"
done
echo -e "\n${BOLD}Files:${NC}"
for file in "${SYNC_FILES[@]}"; do
echo -e " ${BOLD}${NC} $file"
done
echo
}
# Function to check if a path is excluded
is_excluded() {
local check_dir=$1
local full_path=$2
local check_type=$3 # 'file' or 'dir'
# Remove ./ from the beginning of the path if it exists
full_path="${full_path#./}"
# Check for root-level exclusions
if [ -n "${EXCLUSIONS["root"]}" ] && [[ "$check_type" == "file" ]]; then
local IFS=' '
read -ra ROOT_EXCLUDE_ITEMS <<< "${EXCLUSIONS["root"]}"
for item in "${ROOT_EXCLUDE_ITEMS[@]}"; do
local IFS=':'
read -ra PARTS <<< "$item"
local exclude_path="${PARTS[0]}"
local exclude_type="${PARTS[1]}"
if [ "$exclude_type" = "$check_type" ] && [ "$full_path" = "$exclude_path" ]; then
return 0 # Path is excluded
fi
done
fi
# Check directory-specific exclusions
for dir in "${!EXCLUSIONS[@]}"; do
# Skip the root key as we've already checked it
if [ "$dir" = "root" ]; then
continue
fi
# Check if the path starts with the directory we're looking at
if [[ "$full_path" == "$dir"* ]]; then
local IFS=' '
read -ra EXCLUDE_ITEMS <<< "${EXCLUSIONS[$dir]}"
for item in "${EXCLUDE_ITEMS[@]}"; do
local IFS=':'
read -ra PARTS <<< "$item"
local exclude_path="$dir/${PARTS[0]}"
local exclude_type="${PARTS[1]}"
# Remove any duplicate slashes
exclude_path=$(echo "$exclude_path" | sed 's#/\+#/#g')
full_path=$(echo "$full_path" | sed 's#/\+#/#g')
if [ "$exclude_type" = "$check_type" ] && [ "$full_path" = "$exclude_path" ]; then
return 0 # Path is excluded
fi
done
fi
done
return 1 # Path is not excluded
}
cleanup_temp_dirs() {
print_step "Cleaning up temporary directories..."
find . -type d -name "temp_*" -exec rm -rf {} +
show_progress
}
# Function to preserve excluded paths
preserve_excluded_paths() {
local dir=$1
local destination=$2
if [ -n "${EXCLUSIONS[$dir]}" ]; then
local IFS=' '
read -ra EXCLUDE_ITEMS <<< "${EXCLUSIONS[$dir]}"
for item in "${EXCLUDE_ITEMS[@]}"; do
local IFS=':'
read -ra PARTS <<< "$item"
local exclude_path="${PARTS[0]}"
local exclude_type="${PARTS[1]}"
local full_source_path="$dir/$exclude_path"
local full_dest_path="$destination/$exclude_path"
if [ -e "$full_source_path" ]; then
print_step "Preserving excluded ${exclude_type}: ${BOLD}$exclude_path${NC}"
mkdir -p "$(dirname "$full_dest_path")"
cp -r "$full_source_path" "$(dirname "$full_dest_path")"
fi
done
fi
}
# Function to sync directory with exclusions
sync_directory() {
local dir=$1
local temp_branch=$2
if [ -d "$dir" ]; then
print_step "Syncing ${BOLD}$dir${NC}..."
if [ "$DRY_RUN" = false ]; then
# Create temporary directory for original content
mkdir -p "temp_$dir"
# Store original directory for excluded items
if [ -d "$dir" ]; then
# First handle directory exclusions
if [ -n "${EXCLUSIONS[$dir]}" ]; then
local IFS=' '
read -ra EXCLUDE_ITEMS <<< "${EXCLUSIONS[$dir]}"
for item in "${EXCLUDE_ITEMS[@]}"; do
local IFS=':'
read -ra PARTS <<< "$item"
local exclude_path="$dir/${PARTS[0]}"
local exclude_type="${PARTS[1]}"
if [ "$exclude_type" = "dir" ] && [ -e "$exclude_path" ]; then
print_step "Preserving excluded directory: ${BOLD}${PARTS[0]}${NC}"
mkdir -p "$(dirname "temp_$exclude_path")"
cp -r "$exclude_path" "$(dirname "temp_$exclude_path")"
elif [ "$exclude_type" = "file" ] && [ -f "$exclude_path" ]; then
print_step "Preserving excluded file: ${BOLD}${PARTS[0]}${NC}"
mkdir -p "$(dirname "temp_$exclude_path")"
cp "$exclude_path" "temp_$exclude_path"
fi
done
fi
fi
# Checkout from upstream
git checkout "$temp_branch" -- "$dir" || {
print_error "Failed to sync $dir"
rm -rf "temp_$dir"
return 1
}
# Restore excluded files and directories
if [ -n "${EXCLUSIONS[$dir]}" ]; then
local IFS=' '
read -ra EXCLUDE_ITEMS <<< "${EXCLUSIONS[$dir]}"
for item in "${EXCLUDE_ITEMS[@]}"; do
local IFS=':'
read -ra PARTS <<< "$item"
local exclude_path="$dir/${PARTS[0]}"
local exclude_type="${PARTS[1]}"
local temp_path="temp_$exclude_path"
if [ -e "$temp_path" ]; then
print_step "Restoring excluded ${exclude_type}: ${BOLD}${PARTS[0]}${NC}"
mkdir -p "$(dirname "$exclude_path")"
if [ "$exclude_type" = "dir" ]; then
rm -rf "$exclude_path"
cp -r "$temp_path" "$(dirname "$exclude_path")"
else
cp "$temp_path" "$exclude_path"
fi
fi
done
fi
fi
else
print_warning "Directory ${BOLD}$dir${NC} not found. Creating it..."
if [ "$DRY_RUN" = false ]; then
mkdir -p "$dir"
git checkout "$temp_branch" -- "$dir" || {
handle_error "Failed to sync $dir"
cleanup_temp_dirs
}
fi
fi
show_progress
}
# Function to sync individual file with exclusions
sync_file() {
local file=$1
local temp_branch=$2
# Check if file should be excluded (root-level or directory-specific)
if is_excluded "$(dirname "$file")" "$file" "file"; then
print_step "Skipping excluded file: ${BOLD}$file${NC}"
return
fi
print_step "Syncing ${BOLD}$file${NC}..."
if [ "$DRY_RUN" = false ]; then
if [ -f "$file" ]; then
# Create directory for excluded files if it doesn't exist
mkdir -p "temp_files"
# Store original file if it exists
cp "$file" "temp_files/$(basename "$file")"
fi
if ! git checkout "$temp_branch" -- "$file"; then
if [ -f "temp_files/$(basename "$file")" ]; then
# Restore original file if checkout fails
cp "temp_files/$(basename "$file")" "$file"
fi
print_error "Failed to sync $file"
return 1
fi
fi
show_progress
}
# Function to get default branch name
get_default_branch() {
local default_branch
default_branch=$(git remote show origin | grep 'HEAD branch' | cut -d' ' -f5)
echo "$default_branch"
}
# Function to preserve root-level excluded files
preserve_root_files() {
if [ -n "${EXCLUSIONS["root"]}" ] && [ "$DRY_RUN" = false ]; then
print_step "Preserving root-level excluded files..."
mkdir -p "temp_root"
local IFS=' '
read -ra ROOT_EXCLUDE_ITEMS <<< "${EXCLUSIONS["root"]}"
for item in "${ROOT_EXCLUDE_ITEMS[@]}"; do
local IFS=':'
read -ra PARTS <<< "$item"
local exclude_path="${PARTS[0]}"
local exclude_type="${PARTS[1]}"
if [ "$exclude_type" = "file" ] && [ -f "$exclude_path" ]; then
print_step "Preserving root file: ${BOLD}$exclude_path${NC}"
cp "$exclude_path" "temp_root/"
fi
done
fi
}
# Function to restore root-level excluded files
restore_root_files() {
if [ -n "${EXCLUSIONS["root"]}" ] && [ "$DRY_RUN" = false ]; then
print_step "Restoring root-level excluded files..."
local IFS=' '
read -ra ROOT_EXCLUDE_ITEMS <<< "${EXCLUSIONS["root"]}"
for item in "${ROOT_EXCLUDE_ITEMS[@]}"; do
local IFS=':'
read -ra PARTS <<< "$item"
local exclude_path="${PARTS[0]}"
local exclude_type="${PARTS[1]}"
if [ "$exclude_type" = "file" ] && [ -f "temp_root/$(basename "$exclude_path")" ]; then
print_step "Restoring root file: ${BOLD}$exclude_path${NC}"
cp "temp_root/$(basename "$exclude_path")" "$exclude_path"
fi
done
rm -rf "temp_root"
fi
}
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit 0
;;
--dry-run)
DRY_RUN=true
shift
;;
-f|--force)
FORCE=true
shift
;;
*)
handle_error "Unknown option: $1. Use --help for usage information."
;;
esac
done
# Check git configuration
if ! git config user.name > /dev/null || ! git config user.email > /dev/null; then
handle_error "Git user.name or user.email not configured"
fi
# Main script
clear
print_banner
print_items
# Print configured exclusions
echo -e "${BLUE}Configured Exclusions:${NC}"
OLD_IFS="$IFS" # Save original IFS
for dir in "${!EXCLUSIONS[@]}"; do
echo -e " ${BOLD}${dir}${NC}:"
IFS=' '
read -ra EXCLUDE_ITEMS <<< "${EXCLUSIONS[$dir]}"
for item in "${EXCLUDE_ITEMS[@]}"; do
IFS=':'
read -ra PARTS <<< "$item"
if [ "$dir" = "root" ]; then
echo -e "${PARTS[0]} (${PARTS[1]}) [root level]"
else
echo -e "${PARTS[0]} (${PARTS[1]})"
fi
done
done
IFS="$OLD_IFS" # Restore original IFS
echo
# Check if upstream remote exists
if ! git remote | grep -q '^upstream$'; then
print_warning "Upstream remote not found."
if [ "$DRY_RUN" = false ]; then
echo -e "${YELLOW}Default upstream URL:${NC} ${BOLD}$DEFAULT_UPSTREAM_URL${NC}"
if [ "$FORCE" = false ]; then
echo -e "${YELLOW}Press Enter to use default URL or input a different one:${NC}"
read -r custom_url
else
custom_url=""
fi
upstream_url=${custom_url:-$DEFAULT_UPSTREAM_URL}
print_step "Adding upstream remote: ${BOLD}$upstream_url${NC}"
git remote add upstream "$upstream_url" || handle_error "Failed to add upstream remote"
show_progress
fi
fi
# Fetch from upstream
print_step "Fetching from upstream..."
if ! git fetch upstream; then
handle_error "Failed to fetch from upstream"
fi
show_progress
# Get default branch if dev doesn't exist
DEFAULT_BRANCH=$(get_default_branch)
BASE_BRANCH="dev"
if ! git rev-parse --verify "origin/dev" >/dev/null 2>&1; then
print_warning "dev branch not found, using default branch: ${BOLD}$DEFAULT_BRANCH${NC}"
BASE_BRANCH="$DEFAULT_BRANCH"
fi
# Create sync branch
SYNC_BRANCH=$(get_sync_branch_name)
print_step "Creating sync branch: ${BOLD}$SYNC_BRANCH${NC}"
if [ "$DRY_RUN" = false ]; then
# Create sync branch from base branch
if ! git checkout -b "$SYNC_BRANCH" "$BASE_BRANCH"; then
handle_error "Failed to create sync branch"
fi
show_progress
# Create temporary branch for upstream changes
TEMP_BRANCH="temp-${SYNC_BRANCH}"
print_step "Creating temporary branch: ${BOLD}$TEMP_BRANCH${NC}"
if ! git checkout -b "$TEMP_BRANCH" "upstream/$BASE_BRANCH"; then
handle_error "Failed to create temporary branch"
fi
show_progress
# Switch back to sync branch
print_step "Switching back to sync branch..."
if ! git checkout "$SYNC_BRANCH"; then
handle_error "Failed to switch to sync branch"
fi
show_progress
# Preserve root-level excluded files
preserve_root_files
fi
# Sync directories
echo -e "\n${BLUE}${BOLD}Syncing directories...${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"
for dir in "${SYNC_DIRS[@]}"; do
sync_directory "$dir" "$TEMP_BRANCH"
done
# Sync files
echo -e "\n${BLUE}${BOLD}Syncing files...${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"
for file in "${SYNC_FILES[@]}"; do
sync_file "$file" "$TEMP_BRANCH"
done
if [ "$DRY_RUN" = false ]; then
# Restore root-level excluded files
restore_root_files
cleanup_temp_dirs
rm -rf temp_files
# Cleanup temporary branch
print_step "Cleaning up temporary branch..."
git branch -D "$TEMP_BRANCH" || handle_error "Failed to delete temporary branch"
show_progress
# Check for changes
if ! git diff --quiet --exit-code --cached; then
print_step "Committing changes..."
git add "${SYNC_DIRS[@]}" "${SYNC_FILES[@]}"
git commit -m "sync: Update directories and files from upstream
This PR syncs the following items with upstream:
- Directories: ${SYNC_DIRS[*]}
- Files: ${SYNC_FILES[*]}" || handle_error "Failed to commit changes"
show_progress
if [ "$FORCE" = false ]; then
echo -e "\n${YELLOW}${BOLD}Would you like to push the sync branch? (y/n)${NC}"
read -r response
if [[ "$response" =~ ^[Yy]$ ]]; then
print_step "Pushing sync branch..."
git push -u origin "$SYNC_BRANCH" || handle_error "Failed to push sync branch"
show_progress
echo -e "\n${GREEN}${BOLD}✨ Sync branch pushed successfully! ✨${NC}"
echo -e "${YELLOW}Please create a pull request from branch ${BOLD}$SYNC_BRANCH${NC}${YELLOW} to ${BOLD}$BASE_BRANCH${NC}${YELLOW} in your repository.${NC}\n"
else
echo -e "\n${YELLOW}Changes committed but not pushed. You can push later with:${NC}"
echo -e "${BOLD}git push -u origin $SYNC_BRANCH${NC}"
echo -e "${YELLOW}Then create a pull request from ${BOLD}$SYNC_BRANCH${NC}${YELLOW} to ${BOLD}$BASE_BRANCH${NC}\n"
fi
else
print_step "Pushing sync branch..."
git push -u origin "$SYNC_BRANCH" || handle_error "Failed to push sync branch"
show_progress
echo -e "\n${GREEN}${BOLD}✨ Sync branch pushed successfully! ✨${NC}"
echo -e "${YELLOW}Please create a pull request from branch ${BOLD}$SYNC_BRANCH${NC}${YELLOW} to ${BOLD}$BASE_BRANCH${NC}${YELLOW} in your repository.${NC}\n"
fi
else
print_warning "No changes to commit"
git checkout "$BASE_BRANCH"
git branch -D "$SYNC_BRANCH"
fi
else
echo -e "\n${YELLOW}${BOLD}Dry run completed. No changes were made.${NC}\n"
fi