git-mirror/git-mirror.sh

138 lines
2.8 KiB
Bash
Executable File

#!/bin/bash
# git-mirror: Mirror a git repo in leader-followers configuration
# Copyright © 2019-2022 Midgard
#
# This program is free software: you can redistribute it and/or modify it under the terms of the
# GNU Affero General Public License as published by the Free Software Foundation, either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
# even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License along with this program.
# If not, see <http://www.gnu.org/licenses/>.
set -euo pipefail
IFS=$'\n'
tor=true
if [[ "$1" == "--notor" ]]; then
tor=false
shift 1
fi
if [[ $# -lt 3 ]]; then
echo "Usage: $0 [--notor] <workdir> <leader> <follower...>"
exit 1
fi
cd "$1"
leader="$2"
shift 2
followers=( "$@" )
echo "Working directory: $PWD"
echo "Leader remote: $leader"
echo "Follower remotes: ${followers[*]}" | tr '\n' ' '
echo; echo
if [[ $tor == true ]]; then
torify git fetch --prune --multiple "$leader"
else
git fetch --prune --multiple "$leader"
fi
git fetch --prune --multiple "${followers[@]}"
echo
echo "Leader remote information:"
git remote show -n "$leader"
echo
echo "Follower remotes information:"
git remote show -n "${followers[@]}"
echo
branches=( $(git show-ref | sed -n 's| refs/remotes/'"$leader"'/|\t|p') )
echo -e "${#branches[*]} branches at leader:"
echo "${branches[*]}"
IFS=$'\t'
up_to_date=( )
errors=( )
error() {
tput bold
tput setaf 1
echo -n "XXX"
tput sgr0
echo " $1" >&2
errors[${#errors[*]}]="$1"
}
ref_exists() {
git show-ref --verify --quiet "$1"
}
is_ref_at_sha() {
sha1="$(git show-ref --verify --hash "$1")"
sha2="$2"
[[ $sha1 == $sha2 ]]
}
for branchspec in "${branches[@]}"; do
set $branchspec
leader_sha="$1"
branch="$2"
if [ "$branch" = HEAD ]; then continue; fi
for remote in "${followers[@]}"; do
follower_ref="refs/remotes/$remote/$branch"
msg=""
if ! ref_exists "$follower_ref"; then
msg="Creating $remote/$branch"
elif ! is_ref_at_sha "$follower_ref" "$leader_sha"; then
msg="Updating $remote/$branch"
else
up_to_date[${#up_to_date[*]}]="$remote/$branch"
fi
if [[ -n $msg ]]; then
echo
tput bold
echo "$msg"
tput sgr0
git push "$remote" "+$leader_sha:refs/heads/$branch" || { error "$remote $branch: failed to push"; continue; }
fi
done
done
if [[ "${#up_to_date[*]}" -gt 0 ]]; then
echo
echo "Already up to date:"
echo "${up_to_date[@]}"
fi
echo
if [[ "${#errors[*]}" -gt 0 ]]; then
tput bold
tput setaf 1
echo "Errors:"
tput sgr0
tput bold
for (( i=0; i < ${#errors[*]}; i++ )); do
echo "${errors[$i]}"
done
tput sgr0
exit 1
else
echo "No errors."
fi