123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117 | #!/bin/sh
# Usage: new-repo [-c CONFIG] [-p PROGRAM] [-b BRANCH] [-d DESCRIPTION] [-m MIRROR]... DIR
#
# Sets up the bare repository DIR (for example /var/git/foo.git) for
# Repo Renderer, creating it if it is not there. Run it as the user that
# owns the repositories. On an existing repository it wires it up again.
#
# -c CONFIG render pushes with the repo-render config CONFIG
# -p PROGRAM the repo-render to run (default: the one on PATH)
# -b BRANCH the default branch of a new repository (default main)
# -d DESCRIPTION the one-line description the site shows
# -m MIRROR a git URL to push to after every push; repeatable
#
# It refuses branch deletions (force pushes are still allowed), links
# the repository's post-receive hook to the one beside this script (or
# installed under share/repo-render), and records CONFIG, PROGRAM and the
# mirrors in the repository's git config, where the hook reads them. Every
# repository runs that one hook, so installing a newer one upgrades them
# all. Remove a mirror with
# git -C DIR config --unset repo-render.mirror URL
# and give mirrors an ssh key with
# git -C DIR config core.sshCommand "ssh -o BatchMode=yes -i KEY"
set -eu
die() { echo "new-repo: $*" >&2; exit 1; }
usage() { die "usage: new-repo [-c CONFIG] [-p PROGRAM] [-b BRANCH] [-d DESCRIPTION] [-m MIRROR]... DIR"; }
# The hook ships beside this script, or under share/ when installed. The
# link names it by its absolute path, wherever this runs from.
here=$(cd "$(dirname "$0")" && pwd -P)
hook=""
for d in "$here" "$here/../share/repo-render"; do
if [ -f "$d/post-receive" ]; then
hook=$(cd "$d" && pwd -P)/post-receive
break
fi
done
[ -n "$hook" ] || die "no post-receive hook beside $0"
[ -x "$hook" ] || die "$hook is not executable"
config="" program="" branch="" description="" mirrors=""
while getopts c:p:b:d:m: opt; do
case $opt in
c) config=$OPTARG ;;
p) program=$OPTARG ;;
b) branch=$OPTARG ;;
d) description=$OPTARG ;;
m)
case $OPTARG in
""|-*|*[[:space:][:cntrl:]]*) die "mirror $OPTARG is not a usable git URL (no spaces, no leading -)" ;;
esac
mirrors="$mirrors
$OPTARG"
;;
*) usage ;;
esac
done
shift $((OPTIND - 1))
[ $# -eq 1 ] || usage
dir=$1
case $(basename "$dir") in
[A-Za-z0-9]*.git) ;;
*) die "$dir: the repository's directory must be NAME.git" ;;
esac
case $(basename "$dir" .git) in
*[!A-Za-z0-9._-]*) die "$dir: a name has only letters, digits and . _ -" ;;
esac
if [ -n "$config" ]; then
[ -f "$config" ] || die "no config $config"
config=$(cd "$(dirname "$config")" && pwd -P)/$(basename "$config")
if [ -z "$program" ]; then
program=$(command -v repo-render) || die "no repo-render on PATH: give -p PROGRAM"
fi
fi
if [ -n "$program" ]; then
case $program in
/*) ;;
*) program=$(command -v "$program") || die "no $program" ;;
esac
[ -x "$program" ] || die "$program is not executable"
fi
if [ -e "$dir" ]; then
[ -z "$branch" ] || die "$dir already exists, and -b only applies to a new repository"
[ "$(git -C "$dir" rev-parse --is-bare-repository 2>/dev/null)" = true ] || die "$dir is not a bare git repository"
verb=rewired
else
git init --quiet --bare --initial-branch="${branch:-main}" "$dir"
verb=created
fi
git -C "$dir" config receive.denyDeletes true
git -C "$dir" config receive.denyNonFastForwards false
# Older versions pointed core.hooksPath at a shared directory, which
# would hide the hook installed here.
git -C "$dir" config --unset-all core.hooksPath || [ $? -eq 5 ]
[ -z "$config" ] || git -C "$dir" config repo-render.config "$config"
[ -z "$program" ] || git -C "$dir" config repo-render.program "$program"
printf '%s\n' "$mirrors" | while read -r m; do
[ -n "$m" ] || continue
git -C "$dir" config --get-all repo-render.mirror | grep -qxF -- "$m" || git -C "$dir" config --add repo-render.mirror "$m"
done
[ -z "$description" ] || printf '%s\n' "$description" >"$dir/description"
# The link is made beside the hook and renamed over it (an older
# version's copy, or a link), so a push meanwhile finds one or the other.
mkdir -p "$dir/hooks"
tmp=$dir/hooks/.post-receive.$$
rm -f "$tmp"
ln -s "$hook" "$tmp"
mv -f "$tmp" "$dir/hooks/post-receive"
echo "$verb $dir"
if [ -z "$(git -C "$dir" config repo-render.config || true)" ]; then
echo "new-repo: $dir has no repo-render.config, so pushes are not rendered: give -c CONFIG" >&2
fi
|