#!/usr/bin/bash
# Update a tree of svn directories checked out with checkout-all-specs
#
# Copyright © 2014–2022 Daniel Fandrich.
# This program is free software; you can redistribute it and/or modify
# Licensed under GNU General Public License 2.0 or later.
# Some rights reserved. See COPYING.
#
# Usage: update-all-specs [--update-only] [--max-delete N]
# where --update-only skips the delete and checkout new steps.
# and --max-delete N sets the maximum deletions allowed

set -e

readonly SPEC_TREE="${SPEC_TREE:-.}"
readonly BASE_SVN='svn://svn.mageia.org/svn/packages/cauldron/'
# The svn server has a limited number of connections, and they run out
# and updates fail when run with 12 in parallel. 8 provides a margin for error.
readonly PARALLEL_SVN=8
# Maximum number to delete, to prevent accidental mass deletion
MAX_DELETE_FAILSAFE=30

UPDATE_ONLY=
if [[ "$1" = "--update-only" ]] ; then
    UPDATE_ONLY=1
    shift
fi
if [[ "$1" = "--max-delete" ]] ; then
    MAX_DELETE_FAILSAFE="$2"
    shift 2
fi

# Update all svn repos in directories in the current path
update_specs () {
  find "${SPEC_TREE}" -maxdepth 1 -type d -print0 | xargs -0 -i{} -P"$PARALLEL_SVN"  sh -c 'cd "{}" && svn up' || \
      {
          local RC="$?"
          if [[ "$RC" -eq 123 || "$RC" -eq 1 ]] ; then
              echo "Warning: At least one update failed (error $RC)"
          elif [[ "$RC" -ne 0 ]]; then
              echo "Error: problem with an update (error $RC)"
              return "$?"
          fi;
      }
}

# Delete spec files that no longer exist in SVN.
# This will do a recursive deletion of the entire directory.
# It will NOT check if there are outstanding changes in the directory first
# but will unilaterally delete them.
delete_obsolete_specs () {
    local readonly SPECSLIST="$(mktemp)"
    local readonly HAVELIST="$(mktemp)"
    local readonly DELETELIST="$(mktemp)"
    trap 'rm -f "${SPECSLIST}" "${HAVELIST}" "${DELETELIST}"' RETURN

    svn ls "$BASE_SVN" | sed 's@/$@@' | sort >"${SPECSLIST}"
    echo $(wc -l "${SPECSLIST}" | awk '{print $1}') packages in SVN
    ls "${SPEC_TREE}" | sort >"${HAVELIST}"
    echo $(wc -l "${HAVELIST}" | awk '{print $1}') packages locally

    comm -2 -3 "${HAVELIST}" "${SPECSLIST}" > "${DELETELIST}"
    readonly NUMDELETE="$(wc -l "${DELETELIST}" | awk '{print $1}')"
    echo "$NUMDELETE" packages to delete
    if [[ "$NUMDELETE" -gt "$MAX_DELETE_FAILSAFE" ]]; then
        echo "That's a lot of packages! Something may have gone wrong. Aborting to be safe."
        echo "Use --max-delete $NUMDELETE to bypass this check."
        exit 1
    fi
    if [[ "$NUMDELETE" -eq 0 ]]; then
        echo "Nothing to delete."
        return
    fi
    echo Deleting...
    cat "${DELETELIST}"
    cd "${SPEC_TREE}"
    xargs -i{} rm -r {} < "${DELETELIST}"
}

# Check out all spec files that haven't already been checked out
# into subdirectories under the current directory.
checkout_new_specs () {
    local readonly SPECSLIST="$(mktemp)"
    trap 'rm -f "${SPECSLIST}"' RETURN

    svn ls "$BASE_SVN" >"${SPECSLIST}"
    echo $(wc -l "${SPECSLIST}" | awk '{print $1}') packages to check/download
    cd "${SPEC_TREE}"
    xargs -i{} -P4 sh -c "test -d {} || svn co '$BASE_SVN'{}current/SPECS {}" <"${SPECSLIST}"
}

test -z "$UPDATE_ONLY" && { echo Deleting obsolete directories; delete_obsolete_specs; }
echo Updating existing directories; update_specs
test -z "$UPDATE_ONLY" && { echo Checking out new directories; checkout_new_specs; }
echo Done
