Updated version of git-hunk-commit.bash

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi,

on IRC, "cehteh" requested that I send an updated version of the script. 
It was submitted when a short-lived option of git-diff was used, so the 
first publicised version no longer works.

This has seen a tremendous amount of work by Jason Chu, which made it work 
more like darcs when called as "bash git-hunk-commit.bash --darcs".

It still relies heavily on bash arrays.

Ciao,
Dscho
#!/bin/bash

# Copyright (C) 2006 Johannes E. Schindelin
# Distributed under the same license as git.

# Use this command to commit just a few hunks of the current output
# of "git diff". For your security, it only works when the index matches
# HEAD.

# ensure that this is a git repository
. git-sh-setup

TMP_FILE=$GIT_DIR/tmp.$$.txt

trap "rm -f $TMP_FILE; exit" 0 1 2 3 15

# the index must match the HEAD
index_check="$(git diff --index --name-only HEAD 2>&1)"
if [ $? -ne 0 ]; then
	index_check="$(git diff --cached --name-only HEAD 2>&1)"
fi
if [ -n "$index_check" ]; then
	echo "The staging area (AKA index) is already dirty."
	exit 1
fi

# read the names of all modified files into the array "modified"

declare -a modified
filenr=1
git ls-files --modified -z > $TMP_FILE
while read -d $'\0' file; do
	modified[$filenr]="$file"
	filenr=$(($filenr+1))
done < $TMP_FILE

if [ ${#modified[*]} = 0 ]; then
	echo "No modified files."
	exit 1
fi

declare -a hunks

declare -a darcs_hunk_state

# interactively show the hunks of a file and ask if they should be committed.
# 1st parameter is the index into the modified file list.
#	Darcs mode means that all hunks are presented one after another.
#	Normal mode means user can specify hunks interactively.

select_hunks () {
	local index=$1
	local filename=${modified[$index]}
	local -a diff
	local -a hunk_start
	local current_hunks=${hunks[$index]}
	local lineno
	local hunkno
	local action
	local i
	local active

	lineno=1
	hunkno=0
	git diff "$filename" > $TMP_FILE
	while read -r line; do
		diff[$lineno]="$line"
		case "$line" in
		@@*)
			hunk_start[$hunkno]=$lineno
			hunkno=$(($hunkno+1))
			;;
		esac
		lineno=$(($lineno+1))
	done < $TMP_FILE

	hunk_start[$hunkno]=$lineno

	action=""
	while [ "$action" != commit -a "$action" != abort ]; do
		echo
		echo "Current hunks: ($current_hunks) of $hunkno hunks"
		echo "To show (and decide on) a hunk type in the number."
		echo "To commit the current hunks, say 'commit', else 'abort'."
		echo
		echo -n "Your choice? "
		read action
		case "$action" in
		c) action=commit;;
		q|a) action=abort;;
		commit|abort) ;;
		[1-9]*)
			echo
			for ((i=${hunk_start[$(($action-1))]}; i<${hunk_start[$action]}; i++)); do
				echo ${diff[$i]}
			done | less -FS
			active=$(echo $current_hunks,$action | tr , '\n' | sort | uniq -u | tr '\n' , | sed -e "s/^,//" -e "s/,$//")
			if [ ${#active} -lt ${#current_hunks} ]; then
				i=yes
			else
				i=no
			fi
			echo
			while [ -n "$action" -a "$action" != yes -a "$action" != no -a -n "$action" ]; do
				echo -n "Commit this hunk (default is $i)? "
				read action
				case "$action" in
				y) action=yes;;
				n) action=no;;
				esac
			done
			if [ -n "$action" -a $i != "$action" ]; then
				current_hunks=$active
			fi
			;;
		*) echo "Unknown command: $action";;
		esac
	done

	if [ "$action" = commit ]; then
		hunks[$index]=$current_hunks
	fi
}

darcs_select_hunks () {
	local index=1
	local filename=${modified[$index]}
	local -a diff
	local -a hunk_start
	local lineno
	local hunkno
	local i
	local current_hunkno=1
	local current_hunk_state
	local input=''
	local done=0

	# Hunk number over all files
	local absolute_hunkno=1

	# Was the input we just had something that would finish recording
	# if we hit the end of the patches?
	local quiter

	lineno=1
	hunkno=0
	git diff "$filename" > $TMP_FILE
	while read -r line; do
		diff[$lineno]="$line"
		case "$line" in
		@@*)
			hunk_start[$hunkno]=$lineno
			hunkno=$(($hunkno+1))
			;;
		esac
		lineno=$(($lineno+1))
	done < $TMP_FILE

	hunk_start[$hunkno]=$lineno

	while [ $done -eq 0 ]; do
		quiter=0
		# Darcs hunk preamble
		echo "hunk $filename"
		# Display hunk
		for ((i=${hunk_start[$(($current_hunkno-1))]}; i<${hunk_start[$current_hunkno]}; i++)); do
			echo ${diff[$i]}
		done
		current_hunk_state=$(get_hunk_state $index $current_hunkno)
		echo -n "Shall I record this change? ($absolute_hunkno/?)  ["
		case "$current_hunk_state" in
		y) echo -n Ynwsfqadjkc ;;
		n) echo -n yNwsfqadjkc ;;
		w) echo -n ynWsfqadjkc ;;
		esac
		echo -n "], or ? for help: "
		read -n 1 input
		echo
		case "$input" in
		n|y|w) set_hunk_state $index $current_hunkno $input
		       current_hunkno=$((current_hunkno+1))
		       absolute_hunkno=$((absolute_hunkno+1))
		       quiter=1 ;;
		j) current_hunkno=$((current_hunkno+1))
		   absolute_hunkno=$((absolute_hunkno+1)) ;;
		k) current_hunkno=$((current_hunkno-1))
		   absolute_hunkno=$((absolute_hunkno-1)) ;;
		q) done=2
		   continue ;;
		' ') current_hunkno=$((current_hunkno+1))
		     absolute_hunkno=$((absolute_hunkno+1))
		     quiter=1 ;;
		d) done=1
		   continue ;;
		?|h) print_darcs_help
		   continue ;;
		esac
		if [ $((current_hunkno+1)) -gt ${#hunk_start[@]} ]; then
			index=$(($index+1))
			if [ $index -gt ${#modified[@]} ]; then
				if [ $quiter -eq 1 ]; then
					done=1
				else
					current_hunkno=$((current_hunkno-1))
					absolute_hunkno=$((absolute_hunkno-1))
					index=$(($index-1))
				fi
				continue
			fi
			filename=${modified[$index]}

			unset hunk_start
			lineno=1
			hunkno=0
			git diff "$filename" > $TMP_FILE
			while read -r line; do
				diff[$lineno]="$line"
				case "$line" in
				@@*)
					hunk_start[$hunkno]=$lineno
					hunkno=$(($hunkno+1))
					;;
				esac
				lineno=$(($lineno+1))
			done < $TMP_FILE

			hunk_start[$hunkno]=$lineno

			current_hunkno=1
		elif [ $current_hunkno -lt 1 ]; then
			index=$(($index-1))
			if [ $index -lt 1 ]; then
				index=1
				current_hunkno=1
				absolute_hunkno=1
				continue
			fi
			filename=${modified[$index]}

			unset hunk_start
			lineno=1
			hunkno=0
			git diff "$filename" > $TMP_FILE
			while read -r line; do
				diff[$lineno]="$line"
				case "$line" in
				@@*)
					hunk_start[$hunkno]=$lineno
					hunkno=$(($hunkno+1))
					;;
				esac
				lineno=$(($lineno+1))
			done < $TMP_FILE

			hunk_start[$hunkno]=$lineno

			current_hunkno=$((hunkno))
		fi
	done

	case $done in
	# q -- don't do anything
	2) echo "Commit cancelled."
	   exit 0 ;;
	# d -- commit everything that was a y
	1) set_darcs_hunks
	   return ;;
	esac
}

set_darcs_hunks () {
	local i
	local active

	for ((i=1; i<$filenr; i++)); do
		active=$(echo ${darcs_hunk_state[$i]} | tr , '\n' | grep :y$ | sed -e 's/:y$//' | sort | tr '\n' , | sed -e "s/^,//" -e "s/,$//")
		hunks[$i]=$active
	done
}

print_darcs_help () {
cat << EOF
How to use git-hunk-commit...
y: record this patch
n: don't record it
w: wait and decide later, defaulting to no

s: don't record the rest of the changes to this file
f: record the rest of the changes to this file

d: record selected patches, skipping all the remaining patches
a: record all the remaining patches
q: cancel record

j: skip to next patch
k: back up to previous patch
c: calculate number of patches
h or ?: show this help

<Space>: accept the current default (which is capitalized)
EOF
}

# Given the index ($1) and hunkno ($2) return the current darcs choice

get_hunk_state () {
	local index=$1
	local hunkno=$2
	local current_darcs_state=${darcs_hunk_state[$index]}
	local value=$(echo $current_darcs_state | sed -e "s/.*\(^\|,\)$hunkno:\([^,]*\)\(,\|$\).*/\2/")

	if [ "$value" = "$current_darcs_state" ]; then
		echo w
	else
		echo $value
	fi
}

# Given the index ($1), hunkno ($2), and new state ($3) set the entry in darcs_hunk_state
set_hunk_state () {
	local index=$1
	local hunkno=$2
	local state=$3
	local current_darcs_state=${darcs_hunk_state[$index]}
	local newstates=''

	newstates=$(echo $current_darcs_state | sed -e "s/\(^\|,\)$hunkno:[^,]*\(,\|$\)//")
	newstates="$newstates,$hunkno:$state"
	darcs_hunk_state[$index]=$(echo $newstates | sed -e "s/^,//" -e "s/,$//")
}

# Apply the hunks saved in the array hunks for the specified file.
# This means that the diff is rewritten to skip the unwanted hunks.

apply_hunks () {
	local index=$1
	local filename=${modified[$index]}
	local -a current_hunks
	local lineno
	local lineno2
	local linediff
	local hunkno
	local i
	local active

	i=0
	echo ${hunks[$index]} | tr , '\n' > $TMP_FILE
	while read hunkno; do
		current_hunks[$i]=$hunkno
		i=$(($i+1))
	done < $TMP_FILE

	linediff=0
	hunkno=0
	i=0
	active=true
	git diff "$filename" > $TMP_FILE
	while read -r line
	do
		case "$line" in
		@@*)
			hunkno=$(($hunkno+1))
			if [ $hunkno = "${current_hunks[$i]}" ]; then
				active=true
				i=$(($i+1))
				if [ $linediff -ne 0 ]; then
					lineno=$(echo "$line" | sed "s/^.*+\([0-9]*\)[, ].*$/\1/")
					lineno2=$(($lineno+$linediff))
					line="$(echo "$line" | sed "s/+$lineno/+$lineno2/")"
				fi
			else
				active=
				lineno=$(echo "$line" | sed -n "s/^.*-[0-9]*,\([0-9]*\) .*$/\1/p")
				if [ -z "$lineno" ]; then
					lineno=1
				fi
				lineno2=$(echo "$line" | sed -n "s/^.*+[0-9]*,\([0-9]*\) .*$/\1/p")
				if [ -z "$lineno2" ]; then
					lineno2=1
				fi
				linediff=$(($linediff+$lineno-$lineno2))
			fi
			;;
		esac
		if [ -n "$active" ]; then
			echo "$line"
		fi
	done < $TMP_FILE
}

darcs_mode=
case "$1" in
--darcs) darcs_mode=true;;
esac

IFS=''
action=
i=
if [ ! -n "$darcs_mode" ]; then
	while [ "$action" != commit -a "$action" != abort ]; do
		echo
		for ((i=1; i<$filenr; i++)); do
			echo -n "$i ${modified[$i]}"
			if [ -n "${hunks[$i]}" ]; then
				echo " (${hunks[$i]})"
			else
				echo
			fi
		done | less -FS
		echo
		echo "To put one or more hunks of a file into the staging area (AKA"
		echo "index), type in the number of the file."
		echo "To commit, say 'commit', to abort, say 'abort'."
		echo
		echo -n "Your choice? "
		read action
		case "$action" in
		c) action=commit;;
		q|a) action=abort;;
		commit|abort) ;;
		[0-9]*) select_hunks "$action";;
		*) echo "Unknown command." ;;
		esac
	done
else
	darcs_select_hunks
	action="commit"
fi

if [ "$action" = commit ]; then
	for ((i=1; i<$filenr; i++)); do
		if [ -n "${hunks[$i]}" ]; then
			apply_hunks $i
		fi
	done | tee a123 | git apply --cached || exit
	echo git commit
fi


[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]