Updated version of git-hunk-commit.bash
- From
Johannes Schindelin <johannes.schindelin@gmx.de>
- Date
- Mar 1, 2007, 02:52 UTC
- Message-ID
- <Pine.LNX.4.63.0703010350010.22628@wbgn013.biozentrum.uni-wuerzburg.de>
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
fideclare -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 activelineno=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
doneif [ "$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"
fiif [ "$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