#!/usr/bin/env bash
# Keep /var/fossil/repos/<name>.fossil as a full-history mirror of a GitHub repo.
#
# git-mirror.sh init <name> <git-url> "<project description>"
# git-mirror.sh sync [<name>...] # default: every mirror under /var/fossil/mirror
#
# State per mirror lives in /var/fossil/mirror/<name>/: a bare git clone plus the
# git and fossil marks files that make `fossil import --git --incremental` resume
# where the last run stopped. The crontab entry runs `sync` hourly.
set -euo pipefail
REPOS=/var/fossil/repos
MIRROR=/var/fossil/mirror
LOG=$MIRROR/sync.log
export_git() { # name, extra fast-export args...
local name=$1; shift
git -C "$MIRROR/$name/git" fast-export --all --signed-tags=strip \
--tag-of-filtered-object=drop "$@"
}
init() {
local name=$1 url=$2 desc=${3:-}
local dir=$MIRROR/$name repo=$REPOS/$name.fossil
[ -e "$dir" ] && { echo "mirror $name already exists at $dir" >&2; exit 1; }
mkdir -p "$dir"
git clone --quiet --bare "$url" "$dir/git"
if [ -e "$repo" ]; then
# Keep the old repo's wiki pages and move the old file out of the served directory.
mkdir -p "$dir/wiki-from-old"
fossil wiki list -R "$repo" | while IFS= read -r page; do
fossil wiki export "$page" "$dir/wiki-from-old/$page.md" -R "$repo"
done
mkdir -p "$MIRROR/retired"
mv "$repo" "$MIRROR/retired/$name.$(date +%Y%m%d%H%M%S).fossil"
fi
export_git "$name" --export-marks="$dir/git.marks" \
| fossil import --git --export-marks="$dir/fossil.marks" "$repo" > /dev/null
local now; now=$(date +%s)
fossil sql -R "$repo" "REPLACE INTO config(name,value,mtime) VALUES('project-name','$name',$now)"
[ -n "$desc" ] && fossil sql -R "$repo" \
"REPLACE INTO config(name,value,mtime) VALUES('project-description','$(printf %s "$desc" | sed "s/'/''/g")',$now)"
if [ -d "$dir/wiki-from-old" ]; then
for f in "$dir/wiki-from-old"/*.md; do
[ -e "$f" ] || continue
page=$(basename "$f" .md)
fossil wiki create "$page" "$f" --mimetype text/x-markdown -R "$repo" > /dev/null
done
fi
echo "$url" > "$dir/url"
echo "initialised $name: $(fossil info -R "$repo" | grep check-ins)"
}
sync_one() {
local name=$1
local dir=$MIRROR/$name repo=$REPOS/$name.fossil
git -C "$dir/git" fetch --quiet --prune origin '+refs/heads/*:refs/heads/*' '+refs/tags/*:refs/tags/*'
local before after
before=$(fossil info -R "$repo" | awk '/check-ins/{print $2}')
export_git "$name" --import-marks="$dir/git.marks" --export-marks="$dir/git.marks" \
| fossil import --git --incremental --import-marks="$dir/fossil.marks" \
--export-marks="$dir/fossil.marks" "$repo" > /dev/null
after=$(fossil info -R "$repo" | awk '/check-ins/{print $2}')
echo "$(date -u +%FT%TZ) $name: $before -> $after check-ins" >> "$LOG"
}
case ${1:-} in
init) shift; init "$@" ;;
sync) shift
if [ $# -eq 0 ]; then
names=()
for d in "$MIRROR"/*/git; do [ -d "$d" ] && names+=("$(basename "$(dirname "$d")")"); done
set -- "${names[@]}"
fi
for n in "$@"; do sync_one "$n"; done ;;
*) sed -n '2,9p' "$0"; exit 2 ;;
esac