#!/usr/bin/env bash
#
# Switch your current local branch.
# Copyright (c) Yann Dirson, Petr Baudis  2005
#
# `cg-switch` can switch your current local branch (and working copy)
# to an existing branch, or create a new branch based on a given commit.
#
# Terminology note: This command concerns local branches (also called
# "heads"), not remote branches (those managed by `cg-branch-add`).
#
# Note that `cg-switch` is meant for permanent switching of your current
# local branch (permanent in the sense that you are going to work on it;
# you can obviously `cg-switch` again later). If you want to just causually
# explore the current state of a particular branch of commit, use `cg-seek`.
#
# OPTIONS
# -------
# -f:: Enable overwriting of existing branches
#	Force the branch's head pointer to be updated to whatever you
#	passed as the '-r' argument even if the branch already exists.
#	WARNING: The pointer to the original contents of the branch will
#	be lost! The contents itself will not be deleted right away,
#	`git-fsck-objects --unreachable` might help you to find it.
#	Besides, this can get very troublesome if you are pushing the
#	branch out - please refer to the documentation of a close
#	relative, `cg-admin-uncommit`.
#
# -n:: No switch; only create and update the branch
#	Do not switch your current branch to the given branch. This
#	will make cg-switch to only create or update the branch, but
#	leave your working copy alone.
#
# -r COMMIT_ID:: Branch off the given COMMIT_ID, create branch if non-existing
#	Point the branch at the given commit. Required when creating
#	a new branch (use "HEAD" if you want to just base the new
#	head on the current commit). When switching to an existing
#	branch, the branch pointer is modified if '-r' is passed
#	and confirmed by '-f'.
#
# EXAMPLE USAGE
# -------------
# To create a "v1.x" branch based on the commit "v1.0" and switch the
# working copy to it, making it your current branch, do:
#
# 	$ cg-switch -r v1.0 v1.x
#
# If you want to create the branch (let's say based on the current
# commit) but don't switch your working copy to it (so that your
# current branch stays the same as before), do:
#
# 	$ cg-switch -n -r HEAD v1.x
#
# If you want to go back to the 'master' branch, just do:
#
# 	$ cg-switch master
#
# To change the "v1.x" branch to refer to the latest commit on the
# "testing" branch, do (WARNING: you will lose the pointer to the
# original contents of the "v1.x" branch, be careful!):
#
# 	$ cg-switch -f -r testing v1.x

USAGE="cg-switch [-f] [-n] [-r COMMIT_ID] BRANCH"
_git_requires_root=1

. "${COGITO_LIB}"cg-Xlib || exit 1

set -e


repoint_head() {
	# $oldcommit is optional
	local dsthead="$1" dstcommit="$2" oldcommit="$3"
	git-update-ref "refs/heads/$dsthead" "$dstcommit" $oldcommit
}


force=
seek=1
dstcommit=
while optparse; do
	if optparse -f; then
		force=1
	elif optparse -n; then
		seek=
	elif optparse -r=; then
		dstcommit="$(cg-object-id -c "$OPTARG")" || exit 1
	else
		optfail
	fi
done

[ "${#ARGS[@]}" -eq "1" ] || usage

dsthead="${ARGS[0]}"
[ -s "$_git/branches/$dsthead" ] &&
	die "refusing to switch to a remote branch - see README for lengthy explanation; use cg-seek to just quickly inspect it"

[ "$(git-symbolic-ref HEAD)" != "refs/heads/$dsthead" ] || [ -n "$dstcommit" ] ||
	die "already on branch $dsthead"

if [ "$seek" ]; then
	[ -s "$_git/blocked" ] && die "switch blocked: $(cat "$_git/blocked")"
fi


curcommit="$(cg-object-id -c)"

if [ -s "$_git/refs/heads/$dsthead" ]; then
	# Existing branch
	[ -r "$_git/refs/heads/$dsthead" ] ||
		die "reference '$_git/refs/heads/$dsthead' is unreadable"
	if [ -n "$dstcommit" ]; then
		[ "$force" ] ||
			die "branch $dsthead already exists - use -f to force the switch"
		srccommit="$(cg-object-id -c "$dsthead")"
		echo "Repointing branch $dsthead: $srccommit -> $dstcommit"
		repoint_head "$dsthead" "$dstcommit" "$srccommit"
	fi

else
	# New branch
	[ "$dstcommit" ] || die "branch $dsthead does not exist - you must pass -r if you want to create a new branch"
	echo "Creating new branch $dsthead: $dstcommit"
	repoint_head "$dsthead" "$dstcommit"
fi


if [ "$seek" ]; then
	[ -n "$dstcommit" ] || dstcommit="$(cg-object-id -c "$dsthead")"
	echo "Switching to branch $dsthead..."
	if [ "x$curcommit" != "x$dstcommit" ]; then
		# tree_timewarp returns false on local modifications
		tree_timewarp --no-head-update "along" "please rollback" "$curcommit" "$dstcommit" || :
	fi
	git-symbolic-ref HEAD "refs/heads/$dsthead"
fi
