From 4f762c2e55ea55dc67406b54809b2fa62ef6c961 Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Thu, 28 Jan 2021 17:27:40 +0100 Subject: Initial commit --- gitmirror.sh | 148 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100755 gitmirror.sh diff --git a/gitmirror.sh b/gitmirror.sh new file mode 100755 index 0000000..29503d9 --- /dev/null +++ b/gitmirror.sh @@ -0,0 +1,148 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +msg() { + echo "==> $1" +} + +read_branches() { + local prefix="refs/$1/" + local -n read_branches_target="$2" + local refname + local objname + local -i name_length + local -i max_length=0 + while IFS=$'\t' read -r refname objname; do + local branch_name="${refname#"${prefix}"}" + read_branches_target["${branch_name}"]="${objname}" + name_length=${#branch_name} + if (( name_length > max_length )); then + max_length=$name_length + fi + done < <(git for-each-ref "${prefix}**" \ + --format=$'%(refname)\t%(objectname)' \ + --color=never) + for branch_name in "${!read_branches_target[@]}"; do + printf " %*s = %s\n" \ + $((-max_length)) \ + "${branch_name}" \ + "${read_branches_target["${branch_name}"]}" + done +} + +get_or_empty() { + local -n get_or_empty_array="$1" + local key="$2" + local -n get_or_empty_target="$3" + if [[ -v get_or_empty_array["${key}"] ]]; then + get_or_empty_target="${get_or_empty_array["${key}"]}" + else + # shellcheck disable=SC2034 + get_or_empty_target='' + fi +} + +archive_if_needed() { + local archive_prefix="$1" + local branch_name="$2" + local old_tip="$3" + local new_tip="$4" + if ! git merge-base --is-ancestor "${old_tip}" "${new_tip}"; then + local archive_name="${archive_prefix}${branch_name}" + msg "Refusing to clobber ${branch_name}, archiving as ${archive_name}" + git tag -a -m "Archive before synchronization" "${archive_name}" "${old_tip}" + fi +} + +sync_mirror() { + if (( $# == 0 || $# > 2 )); then + echo "Usage: $0 REPOSITORY [REMOTE]" >&2 + exit 1 + fi + + local repository="$1" + local remote + if (( $# == 1 )); then + remote="origin" + else + remote="$2" + fi + local archive_prefix + archive_prefix="$(TZ='UTC' printf 'archives/%(%Y%m%dT%H%M%S)TZ/')" + + msg "Synchronizing ${repository} with ${remote}" + pushd "${repository}" > /dev/null + + msg "Remote tracking branches before fetch" + # shellcheck disable=SC2034 + local -A old_tracking + read_branches "remotes/${remote}" old_tracking + + msg "Local branches" + # shellcheck disable=SC2034 + local -A local_branches + read_branches "heads" local_branches + + msg "Fetch from ${remote}" + git fetch --tags "${remote}" + + msg "Remote tracking branches after fetch" + local -A new_tracking + read_branches "remotes/${remote}" new_tracking + + local -a to_patch=() + + local branch_name + + for branch_name in "${!new_tracking[@]}"; do + local new_tip="${new_tracking["${branch_name}"]}" + local old_tip + get_or_empty old_tracking "${branch_name}" old_tip + local local_tip + get_or_empty local_branches "${branch_name}" local_tip + local patch_tip + get_or_empty local_branches "patch-for/${branch_name}" patch_tip + + if [[ -n "${old_tip}" ]]; then + archive_if_needed "${archive_prefix}" "${remote}/${branch_name}" \ + "${old_tip}" "${new_tip}" + fi + + if [[ -z "${local_tip}" ]]; then + msg "Create local branch ${branch_name} at ${new_tip}" + git branch --no-track "${branch_name}" "${patch_tip:-"${new_tip}"}" + git branch --set-upstream-to="${remote}/${branch_name}" "${branch_name}" + if [[ -n "${patch_tip}" ]]; then + msg "Will patch newly created local branch ${branch_name}" + to_patch+=("${branch_name}") + fi + else + if [[ -z "${patch_tip}" ]]; then + if [[ "${local_tip}" != "${new_tip}" ]]; then + archive_if_needed "${archive_prefix}" "${branch_name}" \ + "${local_tip}" "${old_tip:-"${new_tip}"}" + msg "Update local branch ${branch_name} to ${new_tip}" + git update-ref "refs/heads/${branch_name}" "${new_tip}" "${local_tip}" + fi + else + if ! git merge-base --is-ancestor "${new_tip}" "${local_tip}"; then + msg "Will update local branch ${branch_name} to ${new_tip} by patching" + to_patch+=("${branch_name}") + fi + fi + fi + done + + local job_name="${repository}-apply-patch" + + for branch_name in "${to_patch[@]}"; do + msg "Triggering Jenkings job ${job_name} for branch ${branch_name}" + # TODO + done + + popd > /dev/null +} + +if [[ "$0" == "${BASH_SOURCE[0]}" ]]; then + sync_mirror "$@" +fi -- cgit v1.2.3-54-g00ecf