#!/usr/bin/env sh
#
# Keeps a git directory synced by automatically committing every x seconds.
# After y number of runs without changes automatically pushes the directory to a remote.

# time in seconds to check for changes to commit
TIME_TO_COMMIT=${GS_TIME_TO_COMMIT:-120}
# amount of times to check for changes *without changes occurring* to push to origin
UNCHANGED_RUNS_TO_PUSH=${GS_UNCHANGED_RUNS_TO_PUSH:-30}

# logging verbosity level -- 0=error, 1=info, 2=debug
VERBOSITY=${GS_LOG_VERBOSITY:-1}

set_target() {
  [ -z "$GS_TARGETDIR" ] && [ -z "$1" ] && {
    msg "ERROR: git-sync requires a target directory to keep in sync to be passed in." 0
    exit 1
  }
  DIR="${GS_TARGETDIR:-$1}"
}

pull() {
  msg "Pulling upstream changes into $DIR"
  run_git pull --ff-only --ff
}

commit() {
  msg "Committing changes to $DIR"
  run_git add .
  # shellcheck disable=2039
  run_git commit --no-gpg-sign -m "Git sync: $(date +%F_%R) from ${HOSTNAME:-"${HOST:-undefined}"}"
}

push() {
  msg "No changes for $((UNCHANGED_RUNS_TO_PUSH * TIME_TO_COMMIT)) seconds, pushing to origin"
  run_git push
}

should_commit() {
  if [ "$(run_git diff-files --name-only | wc -l)" -eq 0 ]; then
    msg "No changes to commit in $DIR" 2
    false
  else
    msg "Found changes to commit in $DIR" 2
    true
  fi
}

should_push() {
  if [ "$no_change_cycle" -ge "$UNCHANGED_RUNS_TO_PUSH" ]; then
    true
  else
    false
  fi
}

should_pull() {
  run_git fetch
  # shellcheck disable=1083
  if [ "$(run_git rev-parse HEAD)" = "$(run_git rev-parse @{u})" ]; then
    false
  else
    true
  fi
}

run_git() {
  git -C "$DIR" "$@"
}

# echos its first argument
# verbosity level optionally set through second argument
# default verbosity 1 (info), can be set to 2 (debug), or 0 (error)
msg() {
  lvl=${2:-1}
  [ "$lvl" -gt "$VERBOSITY" ] && return 0

  echo "$1"
}

watch_changes() {
  no_change_cycle=0
  while true; do
    if should_pull; then
      pull
    fi

    if should_commit; then
      commit
      no_change_cycle=0
    fi

    if should_push; then
      push
      no_change_cycle=-9999
    fi

    msg "No changes for $((no_change_cycle * TIME_TO_COMMIT)) of $((UNCHANGED_RUNS_TO_PUSH * TIME_TO_COMMIT)) seconds, going back to sleep for $((TIME_TO_COMMIT)) seconds." 2

    no_change_cycle=$((no_change_cycle + 1))
    sleep "$TIME_TO_COMMIT"
  done
}

main() {
  set_target "$1"

  watch_changes
}

main "$1"
