#!/bin/bash set -euo pipefail IFS=$'\n\t' # ╭─────────────────────────────────────╮ # │ CONFIGURATION │ # ╰─────────────────────────────────────╮ REPO_PATH=$(git rev-parse --show-toplevel 2>/dev/null) || error "Not inside a Git repository" REPO_NAME=$(basename "$REPO_PATH") GITFIELD_DIR="$REPO_PATH/.gitfield" WELL_KNOWN_DIR="$REPO_PATH/.well-known" LOG_FILE="$GITFIELD_DIR/pushed.log" GITFIELD_MD="$REPO_PATH/GITFIELD.md" CANONICAL_META="$GITFIELD_DIR/canonical.meta" CANONICAL_MD="$GITFIELD_DIR/canonical.md" INDEX_JSON="$GITFIELD_DIR/index.json" WELL_KNOWN_JSON="$WELL_KNOWN_DIR/gitfield.json" TIMESTAMP=$(date -u '+%Y-%m-%dT%H:%M:%SZ') SCRIPT_VERSION="1.1" # Updated version for metadata enhancements PRESERVE_META=${PRESERVE_META:-false} # Flag to preserve existing metadata (default: false) # URLs for each platform CANONICAL_URL="https://remember.thefoldwithin.earth/mrhavens/$REPO_NAME" GITHUB_URL="https://github.com/mrhavens/$REPO_NAME" GITLAB_URL="https://gitlab.com/mrhavens/$REPO_NAME" BITBUCKET_URL="https://bitbucket.org/thefoldwithin/$REPO_NAME" FORGEJO_URL="https://remember.thefoldwithin.earth/mrhavens/$REPO_NAME" CODEBERG_URL="https://codeberg.org/mrhavens/$REPO_NAME" GITEA_URL="https://gitea.com/mrhavens/$REPO_NAME" RADICLE_RID="rad:z3FEj7rF8gZw9eFksCuiN43qjzrex" RADICLE_PEER_ID="z6Mkw5s3ppo26C7y7tGK5MD8n2GqTHS582PPpeX5Xqbu2Mpz" # Metadata configuration MIRRORS=( "$GITHUB_URL" "$GITLAB_URL" "$BITBUCKET_URL" "$FORGEJO_URL" "$CODEBERG_URL" "$GITEA_URL" "rad:$RADICLE_RID" ) COMMIT_HASH=$(git -C "$REPO_PATH" rev-parse --short HEAD 2>/dev/null || echo "unknown") TREE_HASH=$(git -C "$REPO_PATH" rev-parse HEAD^{tree} 2>/dev/null || echo "unknown") SYNC_CYCLES=0 # ╭─────────────────────────────────────╮ # │ LOGGING UTILS │ # ╰─────────────────────────────────────╮ info() { echo -e "\e[1;34m[INFO]\e[0m $*" >&2; } warn() { echo -e "\e[1;33m[WARN]\e[0m $*" >&2; } error() { echo -e "\e[1;31m[ERROR]\e[0m $*" >&2; exit 1; } # ╭─────────────────────────────────────╮ # │ SCRIPT LOOKUP FUNCTION │ # ╰─────────────────────────────────────╮ find_script() { local script_name=$1 local search_paths=( "$HOME/.local/gitfieldbin" "$HOME/.local/bin" "$HOME/.local/gitfield" "$HOME/.local/bin/gitfield" "$HOME/.local/bin/gitfieldbin" "$REPO_PATH/bin" ) for path in "${search_paths[@]}"; do if [ -f "$path/$script_name" ]; then if [ -x "$path/$script_name" ]; then if [[ "$path" != "$HOME"* && "$path" != "$REPO_PATH"* ]]; then info "Using script: \e[1;31m$path/$script_name\e[0m (outside home or repo)" else info "Using script: $path/$script_name" fi echo "$path/$script_name" return 0 else warn "Found $path/$script_name but it is not executable" fi fi done error "Script $script_name not found in any search path" } # ╭─────────────────────────────────────╮ # │ METADATA GENERATION │ # ╰─────────────────────────────────────╮ generate_canonical_meta() { info "Generating $CANONICAL_META..." if [ "$PRESERVE_META" = "true" ] && [ -f "$CANONICAL_META" ]; then info "Preserving existing $CANONICAL_META (--preserve-meta enabled)" return fi cat > "$CANONICAL_META" < "$CANONICAL_MD" < "$INDEX_JSON" < "$WELL_KNOWN_JSON" < "$readme_file" < "$LOG_FILE" echo "# Generated by gitfield-sync" >> "$LOG_FILE" echo "" >> "$LOG_FILE" fi # ╭─────────────────────────────────────╮ # │ GENERATE GITFIELD.MD │ # ╰─────────────────────────────────────╮ generate_gitfield_md() { info "Generating $GITFIELD_MD..." cat > "$GITFIELD_MD" </dev/null || echo "unknown") local diff_summary=$(git -C "$REPO_PATH" diff --stat HEAD^ HEAD 2>/dev/null || echo "No diff available") if [ "$platform" = "Radicle" ]; then echo "[$timestamp] $platform: RID=$rid, Peer ID=$peer_id, Branch=$branch, Commit=$COMMIT_HASH" >> "$LOG_FILE" echo " CLI: rad inspect $rid # View project details" >> "$LOG_FILE" echo " CLI: git ls-tree -r --name-only HEAD # View file structure" >> "$LOG_FILE" if [ -n "$diff_summary" ]; then echo " Diff Summary:" >> "$LOG_FILE" echo "$diff_summary" | sed 's/^/ /' >> "$LOG_FILE" fi info "Logged push to $LOG_FILE: [$timestamp] $platform: RID=$rid, Peer ID=$peer_id, Branch=$branch, Commit=$COMMIT_HASH" else echo "[$timestamp] $platform: $url, Branch=$branch, Commit=$COMMIT_HASH" >> "$LOG_FILE" if [ -n "$diff_summary" ]; then echo " Diff Summary:" >> "$LOG_FILE" echo "$diff_summary" | sed 's/^/ /' >> "$LOG_FILE" fi info "Logged push to $LOG_FILE: [$timestamp] $platform: $url, Branch=$branch, Commit=$COMMIT_HASH" fi } # ╭─────────────────────────────────────╮ # │ EXECUTE PUSH SCRIPT │ # ╰─────────────────────────────────────╮ execute_push() { local script_name=$1 local platform=$2 local url=$3 local rid=$4 local peer_id=$5 local script_path script_path=$(find_script "$script_name") || error "Failed to find $script_name" info "Executing $platform push with script: $script_path" if [ -x "$script_path" ]; then pushd "$REPO_PATH" >/dev/null "$script_path" || warn "Execution of $script_path failed, continuing..." log_url "$platform" "$url" "$rid" "$peer_id" git add . || warn "Nothing to add after $script_path" git commit -m "Post-$platform sync at $TIMESTAMP" || warn "No changes to commit after $script_path" popd >/dev/null else error "Script $script_path is not executable" fi } # ╭─────────────────────────────────────╮ # │ RECURSIVE PUSH LOOP │ # ╰─────────────────────────────────────╮ run_push_cycle() { local cycle_number=$1 info "Starting push cycle $cycle_number..." SYNC_CYCLES=$cycle_number execute_push "gitfield-local" "Local" "" "" "" execute_push "gitfield-radicle" "Radicle" "" "$RADICLE_RID" "$RADICLE_PEER_ID" execute_push "gitfield-remember" "Forgejo" "$FORGEJO_URL" "" "" execute_push "gitfield-codeberg" "Codeberg" "$CODEBERG_URL" "" "" execute_push "gitfield-gitea" "Gitea" "$GITEA_URL" "" "" execute_push "gitfield-gitlab" "GitLab" "$GITLAB_URL" "" "" execute_push "gitfield-bitbucket" "Bitbucket" "$BITBUCKET_URL" "" "" execute_push "gitfield-github" "GitHub" "$GITHUB_URL" "" "" # Regenerate metadata after each cycle to update sync_cycles generate_canonical_meta generate_canonical_md generate_index_json generate_well_known_json generate_gitfield_readme } # ╭─────────────────────────────────────╮ # │ MAIN EXECUTION │ # ╰─────────────────────────────────────╮ info "Starting gitfield-sync for $REPO_NAME..." # Parse --preserve-meta flag while [ $# -gt 0 ]; do case "$1" in --preserve-meta) PRESERVE_META=true info "Preserve metadata flag enabled" shift ;; *) warn "Unknown argument: $1" shift ;; esac done if [ ! -d "$REPO_PATH/.git" ]; then pushd "$REPO_PATH" >/dev/null git init git add . git commit -m "Initial commit" || warn "Nothing to commit" popd >/dev/null fi # Generate initial metadata generate_canonical_meta generate_canonical_md generate_index_json generate_well_known_json generate_gitfield_readme # Run push cycles run_push_cycle 1 generate_gitfield_md run_push_cycle 2 run_push_cycle 3 info "✅ gitfield-sync completed successfully." info "🔗 View logs: $LOG_FILE" info "🔗 View multi-repo manifest: $GITFIELD_MD" info "🔗 View canonical metadata: $CANONICAL_META" info "🔗 View canonical declaration: $CANONICAL_MD" info "🔗 View index manifest: $INDEX_JSON" info "🔗 View well-known metadata: $WELL_KNOWN_JSON"