#!/bin/sh

# Compares the currently installed Git for Windows against latest available
# release.  If versions differ, the bit matched installer is downloaded and run
# when confirmation to do so is given.


# Counts how many Bash instances are running, apart from the current one (if
# any: `git update` might have been called from a CMD window, in which case no
# Git Bash might be running at all).
#
# This is a little tricky, as the /usr/bin/sh process (as which `ps` reports the
# process running this script) is an MSYS2 one, but the calling `git.exe`
# process is a pure Win32 one. As a consequence, the former process' PPID will
# be reported as 1 (!!!) and its PGID will refer to the latter, while the
# latter's PGID will be identical to its PID and its PPID refers to the calling
# Bash (or is 1, if `git.exe` was not called by an MSYS2 program).
#
# So we have to employ a little sed fu to parse `ps` output of the form:
#
#     PID    PPID    PGID     WINPID   TTY         UID    STIME COMMAND
#   19864   15640   19864      27996  pty0     4853009 15:58:05 /usr/bin/bash
#   15640       1   15640      15640  ?        4853009 15:58:05 /usr/bin/mintty
#   28128   13048   21176      28716  pty0     4853009 16:01:08 /usr/bin/ps
#   13048       1   21176      13048  pty0     4853009 16:01:08 /usr/bin/sh
#   21176   19864   21176      11996  pty0     4853009 16:01:08 /mingw64/bin/git
#
# Essentially, we are looking for the /usr/bin/sh line (in the example, PID
# 13048), follow its PGID to the /mingw64/bin/git line (in the example, PID
# 21176), and record the PPID of the latter as the pid of the current Bash, if
# any. As we do not know in which order the `sh` and the `git` line appear, we
# have to handle both orders.
#
# Then, we filter the `ps` output first by dropping the line with the current
# Bash, then finally counting the remaining lines referring to a bash process.

count_other_bashes () {
	mypid=$$ && nl='\n *' && s='  *' && p='[1-9][0-9]*' &&
	mypid="$(ps | sed -n ":1;N;
		s/.*$nl$mypid$s$p$s\\($p\\) .*$nl\\1$s\\($p\\) .*/\\2/p;
		s/.*$nl\\($p\\)$s\\($p\\) .*$nl$mypid$s$p$s\\1 .*/\\2/p;
		b1")"
	ps |
	if test -z "$mypid"; then cat; else grep -v "^ *$mypid "; fi |
	grep ' /usr/bin/bash$' |
	wc -l
}

# The main function of this script

git_update () {
	proxy=$(git config --get http.proxy)
	if test -n "$proxy"
	then
		export https_proxy="$proxy"
		echo "Using proxy server $https_proxy detected from git http.proxy" >&2
	fi

	yn=
	use_gui=
	while test $# -gt 0
	do
		case "$1" in
		-\?|--?\?|-h|--help) ;;
		-y|--yes) yn=y; shift; continue;;
		-g|--gui) use_gui=t; shift; continue;;
		*) echo "Unknown option: $1" >&2;;
		esac
		printf >&2 '%s\n%s\n\t%s\n' \
			"Usage: git-update [options]" \
			"Options:" \
			"-y, --yes Automatic yes to download and install prompt"
		return 1
	done

	case "$(uname -m)" in
	x86_64) bit=64;;
	*) bit=32;;
	esac

	releases_url=https://api.github.com/repos/git-for-windows/git/releases
	releases=$(curl --silent $releases_url/latest) || return

	latest=$(echo "$releases" |
		grep '"tag_name": "v' |
		sed -E 's/.*"tag_name": "v([^"]*).*/\1/')
	version=$(git --version | sed "s/git version //")
	echo "Git for Windows $version (${bit}bit)" >&2
	if test "$latest" = "$version"
	then
		echo "Up to date" >&2
		return
	fi

	echo "Update $latest is available" >&2
	download=$(echo "$releases" |
		grep '"browser_download_url": "' |
		grep "$bit\-bit\.exe" |
		sed -E 's/.*": "([^"]*).*/\1/')
	filename=$(echo "$download" | sed -E 's/.*\/([^\/]*)$/\1/')
	name="$(echo "$releases" | sed -n 's/^  "name": "\(.*\)",$/\1/p')"
	installer=$(mktemp -t gfw-install-XXXXXXXX.exe)
	if test -z "$yn"
	then
		other_bashes=$(count_other_bashes)
		if test $other_bashes -le 0
		then
			warn=
		elif test $other_bashes -eq 1
		then
			warn=" (killing one Git Bash)"
		else
			warn=" (killing $other_bashes Git Bash instances)"
		fi
		if test -n "$use_gui"
		then
			git gui--askyesno --title "Git Update Available" \
				"Download and install $name$warn?" ||
			return 1
		else
			read -p "Download and install $name$warn [N/y]? " \
				yn >&2
			case "$yn" in
			[Yy]*) ;;
			*) return 1;;
			esac
		fi
	else
		echo "Downloading $filename" >&2
	fi
	curl -# -L -o $installer $download || return
	start "" "$installer" /SILENT

	# Kill all Bash processes (which will let MinTTY quit, too)"
	#
	# `ps` without `-W` will automatically only catch MSYS2 processes
	# that link to *this* MSYS2 runtime, i.e. no processes from other Git
	# installations (e.g. Git for Windows' SDK) will be killed.
	ps | grep ' /usr/bin/bash$' | awk '{print "kill -9 " $1 ";" }' | sh
}

git_update "$@"
