#!/usr/bin/env bash # Copyright © 2014, 2016 Luke Shumaker # # This work is free. You can redistribute it and/or modify it under the # terms of the Do What The Fuck You Want To Public License, Version 2, # as published by Sam Hocevar. See the COPYING file for more details. # Depends on the 'gitget' and 'libremessages' commands. On Parabola # GNU/Linux-libre, those are the 'gitget' and 'librelib' packages, # respectively. # # For other systems, they both live at: # https://git.parabola.nu/packages/libretools.git/ set -o pipefail -euE . libremessages usage() { print 'Usage %s CONFIG-FILE' "${0##*/}" } main() { if [[ $# != 1 ]]; then usage exit fi declare -g cfg_file="$1" while read -r repo; do handle-repo "$repo" done < <(cfg-list-repos) } handle-repo() { [[ $# == 1 ]] || panic local repo=$1 local local upstream downstreams downstream # read configuration local="$(cfg-get "repo.$repo.local")" upstream="$(cfg-get "repo.$repo.upstream")" || true downstreams=($(cfg-get-all "repo.$repo.downstream")) || true # download if [[ -n "$upstream" ]]; then download "$repo" "$upstream" "$local" fi # ensure that $local exists test -f "$local"/HEAD # upload for downstream in "${downstreams[@]}"; do upload "$repo" "$local" "$downstream" done } download() { [[ $# == 3 ]] || panic local repo=$1 local _remote=$2 local local=$3 local IFS='&' local remote=${_remote%%#*} local params=(${_remote#"$remote"}) msg 'Downloading %s <- %s' "$repo" "${remote%%#*}" # download the repository local url url="$(remote "$remote" pull-url)" gitget -f -n "$repo" bare "$url" "$local" # download the metadata get-meta "$remote" > "$local/git-mirror.tmp" git config --file "$local/config" --rename-section git-mirror git-mirror-bak 2>/dev/null || true local IFS='=' while read -r key val; do git config --file "$local/config" --add git-mirror."$key" "$val" done < "$local/git-mirror.tmp" rm -f "$local/git-mirror.tmp" git config --file "$local/config" --remove-section git-mirror-bak 2>/dev/null || true } upload() ( [[ $# == 3 ]] || panic local repo=$1 local local=$2 local remote=$3 msg 'Uploading %s -> %s' "$repo" "${remote%%#*}" # push metadata local meta mapfile meta < <(git config --file "$local/config" --list|sed -n 's/^git-mirror[.]//p') meta=("${meta[@]%$'\n'}") set-meta "$remote" "${meta[@]}" # push repository local repo_mode repo_mode=$(remote "$remote" repo-mode) if [[ $repo_mode == passive ]]; then msg2 "Pushing repo %s" "$repo" local push_url push_url="$(remote "$remote" push-url)" cd "$local" && git push --mirror "$push_url" fi ) get-meta() ( [[ $# = 1 ]] || panic local IFS remote _params params IFS='#' read remote _params <<<"$1" IFS='&' read -a params <<<"$_params" remote "$remote" get-meta [[ ${#params[@]} = 0 ]] || printf '%s\n' "${params[@]}" ) set-meta() { [[ $# -ge 1 ]] || panic local IFS remote _params params IFS='#' read remote _params <<<"$1" IFS='&' read -a params <<<"$_params" local args=("${@:2}") remote "$remote" set-meta "${args[@]}" "${params[@]}" } # Spawn an 'account.type' helper. It will read commands from stdin. account() { [[ $# == 1 ]] || panic local account=$1 local type type="$(cfg-get "account.$account.type")" { cfg --list|sed -n "s=^account[.]$account[.]==p"|grep -v '^type='|xargs -r printf 'config %q\n' cat } | git mirror-"$type" "$account" } # `account` is awkward to use; so let's wrap it. remote() { [[ $# > 1 ]] || panic local remote=${1%%#*} [[ $remote = *:* ]] local account="${remote%%:*}" local path="${remote#*:}" printf '%q ' "$2" "$path" "${@:3}" | account "$account" } cfg() { git config --file "$cfg_file" "$@" } cfg-get() { [[ $# == 1 ]] || panic cfg --get-all "$1" } cfg-get-all() { [[ $# == 1 ]] || panic cfg --get-all "$1" } cfg-list-repos() { [[ $# == 0 ]] || panic cfg --name-only -z --get-regexp '^repo[.].*[.].*$' |sed -z -e 's|^repo[.]||' -e 's|[.][^.]*$||'|sort -zu|xargs -r0 printf '%s\n' } main "$@"