For all you darcs lovers: git-hunk-commit
Hi,
I was inspired by Han-Wen. This script allows you to commit selected hunks from the current modifications.
It has two modes: darcs mode (--darcs) and normal mode (without arguments).
In darcs mode, all hunks are presented one by one, and you are asked if you want to commit this or not. If you make a mistake: ^C and back to start. I do not really know darcs, so this might not be how it works -- I did not find any good documentation how a "darcs record" looks like.
Normal mode shows you the list of modified files, and lets you choose one. Then it shows you how many hunks there are, and lets you pick one for inspection, after which you are asked if you want it or not.
Normal mode is chattier, but if you know which (of those thousands) hunk you want to commit, it is faster. Besides, it was easier to debug.
Note that this script uses no temporary files, but rather bash array variables.
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
# the index must match the HEAD if [ -n "$(git diff --index --name-only HEAD)" ]; 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 while read -d $'\0' file; do modified[$filenr]="$file" filenr=$(($filenr+1)) done < <(git ls-files --modified -z)
if [ ${#modified[*]} = 0 ]; then
echo "No modified files."
exit 1
fideclare -a hunks
# interactively show the hunks of a file and ask if they should be committed. # 1st parameter is the index into the modified file list. # 2nd parameter should be "true" for darcs mode, empty otherwise. # 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 darcs_mode=$2
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 while read line; do diff[$lineno]="$line" case "$line" in @@*) hunk_start[$hunkno]=$lineno hunkno=$(($hunkno+1)) ;; esac lineno=$(($lineno+1)) done < <(git diff "$filename")
hunk_start[$hunkno]=$lineno
action=""
while [ "$action" != commit -a "$action" != abort ]; do
case "$darcs_mode" in
'')
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
;;
[1-9]*)
darcs_mode=$(($darcs_mode+1))
if [ $darcs_mode -gt $hunkno ]; then
action=commit
else
action=$darcs_mode
fi
;;
*)
darcs_mode=1
action=1
;;
esac
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
if [ -n "$darcs_mode" -a $i = ${hunk_start[0]} ]; then
echo "File: $filename"
fi
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 }
# 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
while read hunkno; do
current_hunks[$i]=$hunkno
i=$(($i+1))
done < <(echo ${hunks[$index]} | tr , '\n') linediff=0
hunkno=0
i=0
active=true
while read 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 < <(git diff "$filename")
}darcs_mode= case "$1" in --darcs) darcs_mode=true;; esac
IFS=''
action=
i=
while [ "$action" != commit -a "$action" != abort ]; do
case "$darcs_mode" in
'')
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
;;
true)
if [ -z "$i" ]; then
i=1
else
i=$(($i+1))
fi
if [ $i -ge $filenr ]; then
action=commit
else
action=$i
fi
;;
esac
case "$action" in
c) action=commit;;
q|a) action=abort;;
commit|abort) ;;
[0-9]*) select_hunks "$action" "$darcs_mode";;
*) echo "Unknown command." ;;
esac
doneif [ "$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
git commit
fi