#!/bin/sh
set -euC

##/// git-gitolite 1.0
#////
#//// Manage the counterpart of a local Git repository on a remote gitolite
#//// host.
#////
###// usage:
#////   git-gitolite help
#////   git-gitolite info
#////   git-gitolite init <license> [<description> [<references>]]
#////   git-gitolite remote
#////   git-gitolite head
#////   git-gitolite publish
#////   git-gitolite unpublish
#////   git-gitolite rm
#////   git-gitolite <command> [<args>...]
#////   git-gitolite --help|-h
#////   git-gitolite --version
#////
###// commands:
####/   help
#////     Runs the gitolite 'help' command.
####/   info
#////     Runs the gitolite 'info' command.
####/   init
#////     Creates and commits, separately, readme and license files.
#////     A note about the <license> (which should be an SPDX identifier), as
#////     well as <description> and <references> if given, is inserted into the
#////     readme. The readme is opened in the editor configured for git
#////     for review before each commit. This command then (re)sets the remote
#////     URL, creates the remote repository and pushes.
####/   remote
#////     (Re)sets the remote URL.
####/   head
#////     Resets the remote HEAD to point to the same ref as the local HEAD,
#////     making it the default view on a web server.
####/   publish
#////     Grants read permissions to 'gitweb', making it viewable through a
#////     web server.
####/   unpublish
#////     Revokes read permissions from 'gitweb', preventing it from being
#////     viewable through a web server.
####/   rm
#////     (Unlocks and) removes the remote repository.
####/   <command>
#////     Perform a gitolite command, filling in the host and repository name
#////     automatically. See `git-gitolite help` for a list of commands.
#////
###// environment variables:
####/   GIT_GITOLITE_USER
#////     The gitolite user under which repositories are found.
#////     [default: $USER]
####/   GIT_GITOLITE_HOST
#////     The host to connect to.
#////     [default: git.$(dnsdomainname)]
####/   GIT_GITOLITE_REMOTE
#////     The git remote used to identify the gitolite server.
#////     [default: origin]

## Messages
prog="$(basename "$0")"
help()    { sed -n 's|^#[#/]*/ \?||p' "$0";                 exit 0; }
version() { help | awk '/^$/{++p;next}p==0';                exit 0; }
usage()   { help | awk '/^$/{++p;next}p==2';                exit 0; }
parse()   { printf '%s: error: %s\n'   "$prog" "$1"; usage; exit 1; } >&2
error()   { printf '%s: error: %s\n'   "$prog" "$1";        exit 1; } >&2
warning() { printf '%s: warning: %s\n' "$prog" "$1";                } >&2
opt()     { [ $# -gt 1 ] || parse "option '$1' value not provided"; }
arg()     { [ $# -gt 1 ] || parse "argument '$1' not provided";     }
argend()  { [ $# -eq 0 ] || parse "unrecognized argument '$1'";     }

## Parse special options
case "${1:-}"
in
  '-h'|'--help') help; ;;
  '--version') version; ;;
esac

## Parse required arguments
arg 'command' "$@"; command="$1"; shift;

case "$command" in
  'init')
    ## Parse required arguments
    arg 'license' "$@"; license="$1"; shift;

    ## Parse optional arguments
    description='<DESCRIPTION>'
    references=''
    [ $# -eq 0 ] || { description="$1"; shift; }
    [ $# -eq 0 ] || { references="$1";  shift; }
    ;;
esac

## Parse unrecognized arguments
case "$command" in
  'help'|'info'|'init'|'remote'|'head'|'publish'|'unpublish'|'rm')
    argend
    ;;
esac

## Parse environment variables
user="${GIT_GITOLITE_USER:-"$USER"}"
host="${GIT_GITOLITE_HOST:-"git.$(dnsdomainname)"}"
remote="${GIT_GITOLITE_REMOTE:-"origin"}"

## ssh
ssh()
{
  command ssh -T "git@$host" "$@"
}

## Repository-independent commands
case "$command" in
  'info'|'help') ssh "$command" "$@"; exit; ;;
esac

## Helpers
toplevel="$(git rev-parse --show-toplevel)"
project="$(basename "$toplevel")"
repo="$user/$project"
url="https://$host/$repo"
remote_url="$(git remote get-url "$remote" 2> /dev/null || true)"
remote_repo="$(printf '%s\n' "$remote_url" | sed 's#^\(.\+://\)\?[^/]\+/##')"
head="$(git rev-parse --abbrev-ref HEAD 2> /dev/null || true)"
licenses='https://github.com/github/choosealicense.com/raw/gh-pages/_licenses'

remote()
{
  git remote remove "$remote" 2> /dev/null || true
  git remote add    "$remote" "$url"
}

commit()
{
  message="$1"; shift;
  file="$1"; shift;
  printf '%s\n' "$@" >> "$file"
  eval "$(git var GIT_EDITOR)" '"$file"'
  git add "$file"
  git commit -m "$message"
}

fmt()
{
  printf '%s\n' "$@" \
  | command fmt --width=79 --goal=79
}

## Repository initialization commands
[ "$remote_url" = "" ] || [ "$remote_url" = "$url" ] || \
  warning "remote '$remote_url' does not match '$url'"
case "$command" in
  'init')
    # Readme.
    commit 'Add readme' "$toplevel/README.md" \
      "# [\`$project\`][]" \
      '' \
      ${description:+"$description"} \
      ${description:+""} \
      "[\`$project\`]: $url" \
      ${references:+"$references"}
    # License.
    license_raw="$(curl -sL "$licenses/$license.txt")"
    license_title="$(
      printf '%s' "$license_raw" \
      | awk -F': ' '/^title:/{print $2; exit; }'
    )"
    license_body="$(
      printf '%s' "$license_raw" \
      | awk 's>=2&&!/^$/{b++};b;/^---$/{s++}' \
      | sed \
        -e 's/\[year\]/'"$(date +%Y)"'/' \
        -e 's/\[fullname\]/'"$(git config user.name)"'/' \
    )"
    printf '%s\n' "$license_body" >| "$toplevel/LICENSE"
    git add "$toplevel/LICENSE"
    commit 'Add license' "$toplevel/README.md" \
      '' \
      '## License' \
      '' \
      "$(fmt \
        "Licensed under the [$license_title][] unless otherwise noted, see" \
        'the [`LICENSE`][] file.' \
      )" \
      '' \
      "[$license_title]: https://choosealicense.com/licenses/$license" \
      '[`LICENSE`]: LICENSE'
    # Remote.
    remote
    ssh create "$repo"
    git push
    exit
    ;;
  'remote')
    remote
    exit
    ;;
esac

## Repository-dependent commands
[ "$remote_repo" ] || \
  error "remote repo for '$remote' is not defined (run 'git-gitolite init'?)"
case "$command" in
  'head')      ssh symbolic-ref "$remote_repo" HEAD "refs/heads/$head"; ;;
  'publish')   ssh perms        "$remote_repo" + READERS 'gitweb';      ;;
  'unpublish') ssh perms        "$remote_repo" - READERS 'gitweb';      ;;
  'rm')        ssh D unlock     "$remote_repo";
               ssh D rm         "$remote_repo";                         ;;
  *)           ssh "$command"   "$remote_repo" "$@";                    ;;
esac