#!/bin/sh # shellcheck disable=2034 disable=2059 set -eu MSG_USAGE_EN='Usage: @NAME@ -i -o DIRECTORY REPOSITORY... @NAME@ -o DIRECTORY -u CLONE_URL REPOSITORY @NAME@ [-hV] ' MSG_HELP_EN='Options: -i build the index page of the repositories -u CLONE_URL address to be shown alongside "git clone" -o DIRECTORY output where to place the generated files -h, --help show this help -V, --version print the version information See "man @NAME@" for more information.' MSG_MISSING_CLIARG_EN='Missing %s\n' MSG_INCOMPATIBLE_OPTIONS_EN='Incompatible options -u and -i' MSG_MISSING_ARGS_EN='Missing [PATH | [PATHS]]' MSG_LANGNAME_EN='en' MSG_INDEX_DESCRIPTION_EN='Index of repositories' MSG_DEFAULT_TITLE_EN='Repositories' MSG_LOGO_ALT_INDEX_EN='Logo image of the repository list' MSG_LOGO_ALT_REPOSITORY_EN='Logo image of the repository' MSG_NAME_EN='Name' MSG_DESCRIPTION_EN='Description' MSG_LAST_COMMIT_EN='Last commit' MSG_FOOTER_TEMPLATE_EN='Generated with @NAME@.' MSG_COMMIT_FEED_EN='commit feed' MSG_TAGS_FEED_EN='tags feed' MSG_NAV_FILES_EN='files' MSG_NAV_LOG_EN='log' MSG_NAV_REFS_EN='refs' MSG_THEAD_BRANCH_EN='Branch' MSG_THEAD_COMMITMSG_EN='Commit message' MSG_THEAD_AUTHOR_EN='Author' MSG_THEAD_DATE_EN='Date' MSG_THEAD_TAG_EN='Tag' MSG_TITLE_REFS_EN='refs' set_lang() { lang="$1" eval " MSG_USAGE=\$MSG_USAGE_$lang MSG_HELP=\$MSG_HELP_$lang MSG_MISSING_CLIARG=\$MSG_MISSING_CLIARG_$lang MSG_INCOMPATIBLE_OPTIONS=\$MSG_INCOMPATIBLE_OPTIONS_$lang MSG_MISSING_ARGS=\$MSG_MISSING_ARGS_$lang MSG_LANGNAME=\$MSG_LANGNAME_$lang MSG_INDEX_DESCRIPTION=\$MSG_INDEX_DESCRIPTION_$lang MSG_DEFAULT_TITLE=\$MSG_DEFAULT_TITLE_$lang MSG_LOGO_ALT_INDEX=\$MSG_LOGO_ALT_INDEX_$lang MSG_LOGO_ALT_REPOSITORY=\$MSG_LOGO_ALT_REPOSITORY_$lang MSG_NAME=\$MSG_NAME_$lang MSG_DESCRIPTION=\$MSG_DESCRIPTION_$lang MSG_LAST_COMMIT=\$MSG_LAST_COMMIT_$lang MSG_FOOTER_TEMPLATE=\$MSG_FOOTER_TEMPLATE_$lang MSG_COMMIT_FEED=\$MSG_COMMIT_FEED_$lang MSG_TAGS_FEED=\$MSG_TAGS_FEED_$lang MSG_NAV_FILES=\$MSG_NAV_FILES_$lang MSG_NAV_LOG=\$MSG_NAV_LOG_$lang MSG_NAV_REFS=\$MSG_NAV_REFS_$lang MSG_THEAD_BRANCH=\$MSG_THEAD_BRANCH_$lang MSG_THEAD_COMMITMSG=\$MSG_THEAD_COMMITMSG_$lang MSG_THEAD_AUTHOR=\$MSG_THEAD_AUTHOR_$lang MSG_THEAD_DATE=\$MSG_THEAD_DATE_$lang MSG_THEAD_TAG=\$MSG_THEAD_TAG_$lang MSG_TITLE_REFS=\$MSG_TITLE_REFS_$lang " } get_lang() { # LC_MESSAGES="ll_CC.CODESET@modifier" -> ll_CC, where quotes # are optional locale 2>/dev/null | grep LC_MESSAGES | cut -d. -f1 | cut -d\" -f2 | cut -d= -f2 } case "$(get_lang)" in *) set_lang EN ;; esac # # Utilities # escape() { echo "$1" | sed -e 's|&|\&|g;s|<|\<|g;s|>|\>|g;s|"|\"|g' \ -e "s|'|\'|g" } realpath() { mkdir -p "$1" cd "$1" pwd } # # Documentation functions # usage() { printf '%s\n' "$MSG_USAGE" } help() { printf '%s\n' "$MSG_HELP" } version() { printf '@NAME@-@VERSION@ @DATE@\n' } # # Template strings # print_logo() { cat < EOF } print_style() { cat < $(escape "$TITLE")"
$(escape

$(escape "$TITLE")


EOF } print_index_row() { repo="$(basename "$(realpath "${1%.git}")")" description="$(cat "$1/description" 2>/dev/null ||:)" last_commit_date="$(git -C "$1" log -1 --format=%cd --date='format:%Y-%m:%d %H:%M' 2>/dev/null ||:)" cat < EOF } print_index_footer() { cat <
$(escape "$MSG_NAME") $(escape "$MSG_DESCRIPTION") $(escape "$MSG_LAST_COMMIT")
$(escape "$repo") $(escape "$description") $(escape "$last_commit_date")

$(escape "$MSG_FOOTER_TEMPLATE")

EOF } index_write() { rm -f "$OUTDIR/index.html" print_index_header >> "$OUTDIR/index.html" for r in "$@"; do print_index_row "$r" >> "$OUTDIR/index.html" done print_index_footer >> "$OUTDIR/index.html" print_logo > "$OUTDIR/logo.svg" print_style > "$OUTDIR/style.css" } print_repo_refs() { repo="$1" description="$2" cat < $(escape "$repo") - $(escape "$MSG_TITLE_REFS")

$(escape "$repo")

$(escape "$description")

git clone $(escape "$CLONE_URL")

EOF for branch in $(git branch --format '%(refname:lstrip=2)'); do sha="$(git rev-parse "$branch")" cat < EOF done cat <
$(escape "$MSG_THEAD_BRANCH") $(escape "$MSG_THEAD_COMMITMSG") $(escape "$MSG_THEAD_AUTHOR") $(escape "$MSG_THEAD_DATE")
$(escape "$branch") $(escape "$(git log -1 --format=%B "$sha")") $(escape "$(git log -1 --format=%an "$sha")") $(escape "$(git log -1 --format=%cd --date='format:%Y-%m:%d %H:%M' "$sha")")
EOF for tag in $(git tag | sed '1!G;h;$!d'); do if [ -e "$CACHE_DIR/tarballs/$repo-$tag.tar.gz.asc" ]; then tarball_link="(tarball, sig)" else tarball_link="(tarball)" fi cat < EOF done cat <
$(escape "$MSG_THEAD_TAG") $(escape "$MSG_THEAD_AUTHOR") $(escape "$MSG_THEAD_DATE")
$(escape "$tag") $tarball_link $(escape "$(git log -1 --format=%an "$tag")") $(escape "$(git log -1 --format=%cd --date='format:%Y-%m:%d %H:%M' "$tag")")

$(escape "$MSG_FOOTER_TEMPLATE")

EOF } print_formatted_diff() { sha="$1" printf '
'
	git show -p "$sha"
	printf '
\n' } print_repo_commit_page() { sha="$2" summary="$(git log -1 --format=%B "$sha")" cat < $(escape "$repo")@$(escape "$sha") - $(escape "$summary")

$(escape "$repo")

$(escape "$description")

git clone $(escape "$CLONE_URL")

$(print_formatted_diff "$sha")
EOF } cached_run() { TARGET_PATH="$1" shift # drop $TARGET_PATH shift # drop -- if [ -e "$OUTDIR/$TARGET_PATH" ]; then echo "$OUTDIR/$TARGET_PATH" return fi if [ ! -e "$CACHE_DIR/$TARGET_PATH" ]; then "$@" fi cp "$CACHE_DIR/$TARGET_PATH" "$OUTDIR/$TARGET_PATH" } repo_tarballs_write() { repo="$1" mkdir -p "$OUTDIR/tarballs" "$CACHE_DIR/tarballs" for branch in $(git branch --format '%(refname:lstrip=2)'); do git archive --prefix "$repo-$branch/" "$branch" \ -o "$OUTDIR/tarballs/$repo-$branch.tar.xz" done for tag in $(git tag); do TARBALL_PATH="tarballs/$repo-$tag.tar.gz" cached_run "$TARBALL_PATH" -- \ git archive --prefix "$repo-$tag/" "$tag" \ -o "$CACHE_DIR/$TARBALL_PATH" SIGNATURE_PATH="tarballs/$repo-$tag.tar.gz.asc" if git notes --ref=refs/notes/signatures/tar.gz show "$tag" \ 1>/dev/null 2>&1; then git notes --ref=refs/notes/signatures/tar.gz show "$tag" \ > "$OUTDIR/$SIGNATURE_PATH" fi done } repo_commits_write() { mkdir -p "$OUTDIR/commit" "$CACHE_DIR/commit" for branch in $(git branch --format '%(refname:lstrip=2)'); do for commit in $(git rev-list "$branch"); do COMMIT_PATH="commit/$commit.html" if [ -e "$OUTDIR/$COMMIT_PATH" ]; then # Assume all previous history exists, too break fi if [ ! -e "CACHE_DIR/$COMMIT_PATH" ]; then print_repo_commit_page "$repo" "$commit" \ > "$CACHE_DIR/$COMMIT_PATH" fi cp "$CACHE_DIR/$COMMIT_PATH" "$OUTDIR/$COMMIT_PATH" # FIXME: WIP return done done } repo_write() { cd "$1" repo="$(basename "$(realpath "${1%.git}")")" description="$(cat "$1/description" 2>/dev/null ||:)" CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/gistatic/$repo" repo_tarballs_write "$repo" print_repo_refs "$repo" "$description" > "$OUTDIR/refs.html" repo_commits_write "$repo" print_logo > "$OUTDIR/logo.svg" print_style > "$OUTDIR/style.css" cd - > /dev/null } for flag in "$@"; do case "$flag" in --) break ;; --help) usage help exit ;; --version) version exit ;; *) ;; esac done INDEX=false TITLE="$MSG_DEFAULT_TITLE" OUTDIR= CLONE_URL= CACHE_DIR= while getopts 'o:t:u:ihV' flag; do case "$flag" in i) INDEX=true ;; t) TITLE="$OPTARG" ;; o) OUTDIR="$(realpath "$OPTARG")" ;; u) CLONE_URL="$OPTARG" ;; h) usage help exit ;; V) version exit ;; *) usage >&2 exit 2 ;; esac done shift $((OPTIND - 1)) assert_arg() { if [ -z "$1" ]; then printf "$MSG_MISSING_CLIARG" "$2" >&2 exit 2 fi } assert_arg "$OUTDIR" '-o DIRECTORY' if [ "$INDEX" = false ]; then assert_arg "$CLONE_URL" '-u CLONE_URL' elif [ -n "$CLONE_URL" ]; then printf '%s\n' "$MSG_INCOMPATIBLE_OPTIONS" >&2 exit 2 fi if [ -z "${1:-}" ]; then printf '%s\n' "$MSG_MISSING_ARGS" >&2 exit 2 fi mkdir -p "$OUTDIR" if [ "$INDEX" = true ]; then index_write "$@" else repo_write "$1" fi