Commit 7063dcf3 authored by Dom Sekotill's avatar Dom Sekotill
Browse files

Add git-change-commit

git-change-commit has been around for a while outside of version
control.
parent a5af52f9
Loading
Loading
Loading
Loading

git-change-commit

0 → 100755
+146 −0
Original line number Diff line number Diff line
#!/bin/bash

OPTS_SPEC="\
git change-commit [-h] [--start|--continue|--reset|--abort] [REVISION] [[--] PATH [PATH]]

change-commit does an interactive rebase keeping the current changes in the
working directory, allowing edits to be made to commits using changes made
in the working directory.

If more than one revision is given, only the first will be used.

This comes with the standard warning:
  DO NOT CHANGE COMMITS THAT HAVE BEEN (OR COULD HAVE BEEN) PUSHED OR FETCHED
  TO OTHER REPOSITORIES OUTSIDE OF YOUR CONTROL.
  (Unless you really, *really* know what you are doing.)
--
h,help  show this message and exit

start  begin the rebase, this is the default action and is optional. Requires: REVISION
reset  reset the working directory to the state it was in before starting. Optional: REVISION, PATH...
continue  continue to the next commit marked for editing
abort  quit the changes and return to the originally checked out commit/head
"

parse() { eval "$(git rev-parse --parseopt -- "$@" <<< "$OPTS_SPEC" || echo return $?)"; }

die() {
	EXIT_STATUS=${EXIT_STATUE-$?}
	[ "x$1" = x--usage ] && parse --help
	echo "$*"
	exit $EXIT_STATUS
} >&2

error() { printf "WARNING: %s\n" "$*"; } >&2

REPO_DIR=`git rev-parse --git-dir` || die
STASH_FILE=$REPO_DIR/change-commit/stash
STASH_FILE_BACKUP=${STASH_FILE}_pref

is_active() { [ -d $REPO_DIR/rebase-merge ]; }
is_ours() { [ -e $STASH_FILE ]; }

confirm()
{
	local prompt=$1 ans
	while true; do
		read -p "$prompt (y|N) " ans
		case $ans in
			y*|Y*) return 0 ;;
			n*|N*|'') return 1 ;;
			*) prompt="Sorry, what did you mean?" ;;
		esac
	done
}

action_start()
{
	local stash=$(git stash create) || die
	[ -n "$REVISION" ] || die "need a revision to edit"
	git reset --hard
	git rebase -i $REVISION~ 2>/dev/null
	mkdir -p $(dirname $STASH_FILE)
	if ! echo "$stash" > $STASH_FILE; then
		error "could not write stash ID to $STASH_FILE, aborting"
		git rebase --abort
	fi
	cp $STASH_FILE $STASH_FILE_BACKUP
	[ -n "$stash" ] && git stash apply $stash
}

action_continue()
{
	local stash=$(git stash create) || die
	echo "$stash" >> $STASH_FILE || die
	git reset --hard
	git rebase --continue || die
	[ -n "$stash" ] && git stash apply $stash
	is_active || rm $STASH_FILE
}

action_reset()
{
	local stash=$(head -n1 $STASH_FILE_BACKUP) || die
	if [ ${#PATHS[*]} -gt 0 ]; then
		git checkout ${REVISION:-$stash} -- "${PATHS[@]}"
	elif [ -n "$REVISION" ]; then
		error "This will reset the working directory to match $REVISION, are" \
		      "you sure this is what you intended?"
		error "You will still be able to get the original working directory" \
		      "by running --reset without any further arguments."
		confirm "Reset to ${REVISION::10}?" || return 0
		local saved_head=$(git symbolic-ref -q HEAD || cat $REPO_DIR/HEAD)
		git reset --hard HEAD
		git checkout $REVISION
		git reset $saved_head
	else
		git reset --hard
		[ -n "$stash" ] && git stash apply $stash
	fi
}

action_abort()
{
	local stash=$(head -n1 $STASH_FILE)
	git rebase --abort
	[ -n "$stash" ] && git stash apply $stash
	rm $STASH_FILE
}

# normalise cmdline arguments
parse "$@" || exit $?

ACTION=action_start

while [ $# -gt 0 ]; do
	case $1 in
		--continue)
			is_active && is_ours || \
				die "--continue given, but no active changes being made"
			ACTION=action_continue
			;;
		--reset)
			is_ours || \
				die "--reset given, but no stashed changes available"
			ACTION=action_reset
			;;
		--abort)
			is_ours || \
				die "--abort given, but no active changes being made"
			ACTION=action_abort
			;;
		--start)
			is_active && die "already doing a rebase or change"
			ACTION=action_start
			;;
		*)
			REVISION=$(git rev-parse --revs-only "$@" | head -n1)
			PATHS=( $(git rev-parse --sq --no-revs --no-flags "$@") )
			break
			;;
	esac
	shift
done

$ACTION
exit $?