#!/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 . set -euo pipefail IFS=$'\n' tor=true if [[ "$1" == "--notor" ]]; then tor=false shift 1 fi if [[ $# -lt 3 ]]; then echo "Usage: $0 [--notor] " 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