#!/bin/bash
set -o errexit
[[ -v AUR_DEBUG ]] && set -o xtrace
#argv0=fetch--mirror
XDG_CACHE_HOME=${XDG_CACHE_HOME:-$HOME/.cache}
AUR_MIRROR=${AUR_MIRROR:-https://github.com/archlinux/aur}
AUR_MIRROR_TTL=${AUR_MIRROR_TTL:-300}
AUR_MIRROR_BRANCH=${AUR_MIRROR_BRANCH:-main}
AUR_ROOT=${AUR_ROOT:-$XDG_CACHE_HOME/aur}
startdir=$PWD
local_clones=0

# Avoid CDPATH screwing with cd (#1047)
unset -v CDPATH

if [[ $1 = '--lclone' ]]; then
    local_clones=1
    shift
fi
mkdir -p -- "$AUR_ROOT"

# --------------------------------------------------
# Step 1. Create a sparse checkout of the AUR mirror
git_active_remotes() {
    git config --get-all remote.origin.fetch | awk -F'[/:]' '{print $3}'
}

# XXX: this includes refs/heads (which we do not use) but unlike
# git-config this accepts multiple arguments in one call
git_set_branches() {
    git remote set-branches origin --add "$AUR_MIRROR_BRANCH" # ensure at least 1 remote
    sort -u | xargs -d '\n' git remote set-branches origin
}

git_ls_remote_ttl() {
    local cachetime now ttl=$AUR_MIRROR_TTL
    printf -v now '%(%s)T' -1

    if ! cachetime=$(stat -c %Y "$1" 2>/dev/null) || (( now > (cachetime + ttl) )); then
        git ls-remote origin 'refs/heads/*' | awk -F/ '{print $3}' >"$1"
    fi
}

exec {fd}< "$AUR_ROOT"
{ flock --wait 5 "$fd" || exit 1
  cd -- "$AUR_ROOT"

  # Shallow clone
  if [[ ! $(git rev-parse --is-inside-git-dir 2>/dev/null) ]]; then
      git init --bare --initial-branch="$AUR_MIRROR_BRANCH"
      git remote add origin "$AUR_MIRROR"
  fi

  # Keep a refcache to filter out packages which are not in AUR. This is
  # includes unlisted packages (unlike `aur pkglist --pkgbase`)
  git_ls_remote_ttl 'remote-pkgbase'

  # Retrieve list of active remotes
  mapfile -t active_remotes < <(git_active_remotes | grep -v "$AUR_MIRROR_BRANCH")

  # Only consider AUR targets
  mapfile -t target_remotes < <(printf '%s\n' "$@" | grep -Fxf 'remote-pkgbase')

  # Set remote branches as union of active remotes and arguments. If '*' is set
  # instead, `git fetch origin` in $AUR_ROOT will pull in the entire history.
  printf '%s\n' "${target_remotes[@]}" "${active_remotes[@]}" | git_set_branches

  # Retrieve objects
  if (( ${#target_remotes[@]} )); then
      git fetch origin "${target_remotes[@]}"
  fi

  # Resetting branches is only needed when .git/config contains both refs/heads
  # and refs/remotes/origin, i.e. when using git-clone. `--lclone` uses git-init
  # and refspec manipulation instead.
  # for b in "${target_remotes[@]}"; do
  #     git branch --force "$b" origin/"$b"
  # done
} >&2

# ------------------------------------------------------------
# Step 2. Propagate each branch in the mirror to a local clone
(( ! local_clones )) && exit
results=()

git_srcdest_origin() {
    local refspec=refs/remotes/origin/$1

    git config remote.origin.fetch "+$refspec:$refspec"
    git fetch --quiet
    git checkout --quiet -B master "$refspec"
}

for b in "${target_remotes[@]}"; do
    cd -- "$startdir"

    # XXX: "Cloning into an existing directory is only allowed if the
    # directory is empty"
    if [[ ! -d $b/.git ]] && mkdir -- "$b"; then
        cd -- "$b"
        git init --quiet
        git remote add origin "$AUR_ROOT"

        # Compared to git clone --local, using $refspec as both source and
        # destination allows updates from `git-fetch` in $AUR_ROOT to be
        # directly visible in local clones.  $AUR_ROOT does not require
        # local branches or refs/heads for this to work.
        git_srcdest_origin "$b"

        # Export results to aur-fetch
        head=$(git rev-parse --verify --quiet HEAD)
        results+=("$b:$head")
    fi >&2
done

# The lock is only released here, because we do not want an instance to modify
# AUR_ROOT while local git-clones are still underway. Additionally, local clones
# use cp(1) and are thus a cheap operation. See --local in git-clone(1).
exec {fd}<&-

if (( ${#results[@]} )); then
    printf '%s\n' "${results[@]}"
fi
