#!/bin/bash set -e # config options declare -A keep REMOTE="origin" BASE_BRANCH="main" PACKAGE_BASE_URL= PACKAGE_RELATIVE_URL="../../rpm" DATE="$(date "+%s %z")" PACKAGES_KEEP= # command line only packages_from= single= verbose=0 cfg_file= ################### # constant needed to aid quoting nl=$'\n' declare -A packages declare -A new declare -A drop declare -A revs gitmodules_rev= helpandquit() { cat <<-EOF Usage: $0 [OPTIONS] [ ...] OPTIONS: --packages-from=FILE read list of packges to sync from FILE --single create single commit for all changes -h help screen EOF exit 0 } show_status() { git for-each-ref 'refs/pq/*' exit 0 } clear_queue() { while read -r ref; do git update-ref -d "$ref" done < <(git for-each-ref 'refs/pq/*' --format '%(refname)') exit 0 } isnew() { local p="${1:?}" [ -n "${new[$p]}" ] } todrop() { local p="${1:?}" [ -n "${drop[$p]}" ] } log_info() { [ "$verbose" -gt 0 ] || return 0 echo "$@" } makedict() { local dict="$1" shift mapfile -t a < <("$@") for k in "${a[@]}"; do eval "$dict"["$k"]=1 done } getopttmp=$(getopt -o hc:v --long help,single,branch:,config:,date:,remote:,status,verbose,clear,packages-from: -n "${0##*/}" -- "$@") eval set -- "$getopttmp" while true ; do case "$1" in -h|--help) helpandquit; shift ;; -v|--verbose) verbose=$((++verbose)); shift ;; -c|--config) cfg_file="$2"; shift 2 ;; --single) single=1; shift ;; --packages-from) packages_from="$2"; shift 2 ;; --remote) REMOTE="$2"; shift 2 ;; --branch) BASE_BRANCH="$2"; shift 2 ;; --status) show_status; exit 0 ;; --clear) clear_queue; exit 0 ;; --date) DATE="$2"; shift 2 ;; --) shift ; break ;; *) echo "Internal error!" ; exit 1 ;; esac done modules=("$@") PACKAGE_BASE_URL="$(git config --get remote."$REMOTE".url)" # shellcheck disable=SC1090 . "${cfg_file:-.settings}" for p in $PACKAGES_KEEP; do keep["$p"]=1 done if [ "${DATE:0:1}" = '@' ]; then DATE="$(stat -c %Y "${DATE:1}") +0100" fi export GIT_AUTHOR_NAME="Auto" export GIT_AUTHOR_EMAIL="auto@suse.de" export GIT_AUTHOR_DATE="$DATE" export GIT_COMMITTER_NAME="Auto" export GIT_COMMITTER_EMAIL="auto@suse.de" export GIT_COMMITTER_DATE="$DATE" tmpfile=$(mktemp updatemodules.XXXXXX) tmpfile2=$(mktemp updatemodules.XXXXXX) cleanup() { rm -f "$tmpfile" "$tmpfile2" } trap cleanup EXIT # read all submodules while read -r m t cid p; do if [ "$t" = blob ] && [ "$p" = ".gitmodules" ]; then gitmodules_rev="$cid" fi [ "$t" = "commit" ] || continue revs["$p"]="$cid" done < <(git cat-file -p "$REMOTE/$BASE_BRANCH^{tree}") if [ -n "$packages_from" ]; then while read -r p; do packages["$p"]=1 [ -n "${revs[$p]}" ] || new["$p"]=1 done < "$packages_from" for m in "${!revs[@]}"; do [ -n "${packages[$m]}" -o -n "${keep[$m]}" ] || drop["$m"]=1 done fi [ -z "${new[*]}" ] || log_info "new: ${!new[*]}" [ -z "${drop[*]}" ] || log_info "drop: ${!drop[*]}" if [ "${#modules[@]}" = 0 ]; then modules=("${!revs[@]}" "${!new[@]}") fi # push queue declare -A pq makedict pq git for-each-ref 'refs/pq/*' --format '%(refname)' for r in "${!pq[@]}"; do m="${r##*/}" if [ -z "${revs[$m]}" ] && ! isnew "$m" && ! todrop "$m"; then log_info "remove stale update entry for $m" git update-ref -d "$r" fi done # check remotes for updates declare -A commits treetext=$(git cat-file -p "$REMOTE/$BASE_BRANCH^{tree}") git cat-file -p "$REMOTE/$BASE_BRANCH":.gitmodules > "$tmpfile" cat "$tmpfile" > "$tmpfile2" for m in "${modules[@]}"; do if isnew "$m"; then path="$m" url="$PACKAGE_RELATIVE_URL/$m" smbranch= git config -f "$tmpfile" --add "submodule.$m.path" "$path" git config -f "$tmpfile" --add "submodule.$m.url" "$url" else path="$(git config -f "$tmpfile" --get "submodule.$m.path")" url="$(git config -f "$tmpfile" --get "submodule.$m.url")" smbranch="$(git config -f "$tmpfile" --get "submodule.$m.branch" || :)" fi if [ -z "$path" ] || [ -z "$url" ]; then echo "$m unknown" >&2 continue fi if [ "${url:0:3}" = '../' ]; then url="$PACKAGE_BASE_URL/$url" fi cid= if ! todrop "$m"; then read -r cid _d < <(GIT_ASKPASS=/bin/true git ls-remote "$url" "${smbranch:-HEAD}" 2>/dev/null) || true if [ -z "$cid" ]; then echo "Warning: $path not in pool, ignored" >&2 continue fi fi # create a new commit for this package if [ "${revs[$path]}" != "$cid" ]; then log_info "Needs update: $path@${revs[$path]:-NEW} -> ${cid:-DROP}" if isnew "$m"; then newtreetext="$treetext${nl}160000 commit $cid $m" elif todrop "$m"; then newtreetext="${treetext/160000 commit ${revs[$path]} $path$nl/}" git config -f "$tmpfile" --remove-section "submodule.$m" else newtreetext="${treetext/${revs[$path]} $path/$cid $path}" fi if isnew "$m" || todrop "$m"; then nh=$(git hash-object -w --path .gitmodules "$tmpfile") newtreetext="${newtreetext/$gitmodules_rev .gitmodules/$nh .gitmodules}" fi if [ "$single" = 1 ]; then if isnew "$m" || todrop "$m"; then gitmodules_rev="$nh" fi treetext="$newtreetext" # fall through else newtree=$(echo "$newtreetext" | git mktree) msg="Update $m" isnew "$m" && msg="Add $m" todrop "$m" && msg="Remove $m" newcid="$(git commit-tree -p "$REMOTE/$BASE_BRANCH" -m "$msg" "$newtree")" commits["$m"]="$newcid" cat "$tmpfile2" > "$tmpfile" continue fi # fall through fi if [ -n "${pq[refs/pq/$m]}" ]; then log_info "remove pq for $m" git update-ref -d "refs/pq/$m" fi done if [ "$single" = 1 ]; then newtree="$(echo "$treetext" | git mktree)" newcid="$(git commit-tree -p "$REMOTE/$BASE_BRANCH" -m "Update all" "$newtree")" commits["all"]="$newcid" elif [ -n "${pq[refs/pq/all]}" ]; then log_info "remove pq for single commit" git update-ref -d "refs/pq/all" fi for m in "${!commits[@]}"; do ref="refs/pq/$m" if [ -n "${pq[$ref]}" ]; then cid="$(git rev-parse "$ref")" [ "$cid" = "${commits[$m]}" ] || echo "Warning: previous commit $cid for $m" >&2 fi git update-ref "$ref" "${commits[$m]}" done