name-rev --stdin: handle tags, too
[git/dscho.git] / my-hunk-commit.bash
blobc675811790a3af78f71774ecfd854ff6e69ebcdd
1 #!/bin/bash
3 # Copyright (C) 2006 Johannes E. Schindelin
4 # Distributed under the same license as git.
6 # Use this command to commit just a few hunks of the current output
7 # of "git diff". For your security, it only works when the index matches
8 # HEAD.
10 # ensure that this is a git repository
11 . "$(git --exec-path)"/git-sh-setup
13 TMP_FILE=$GIT_DIR/tmp.$$.txt
15 trap "rm -f $TMP_FILE; exit" 0 1 2 3 15
17 # the index must match the HEAD
18 if [ -n "$(git diff --cached --name-only HEAD)" ]; then
19 echo "The staging area (AKA index) is already dirty."
20 exit 1
23 # read the names of all modified files into the array "modified"
25 declare -a modified
26 filenr=1
27 git ls-files --modified -z > $TMP_FILE
28 while read -d $'\0' file; do
29 modified[$filenr]="$file"
30 filenr=$(($filenr+1))
31 done < $TMP_FILE
33 if [ ${#modified[*]} = 0 ]; then
34 echo "No modified files."
35 exit 1
38 declare -a hunks
40 # interactively show the hunks of a file and ask if they should be committed.
41 # 1st parameter is the index into the modified file list.
42 # 2nd parameter should be "true" for darcs mode, empty otherwise.
43 # Darcs mode means that all hunks are presented one after another.
44 # Normal mode means user can specify hunks interactively.
46 select_hunks () {
47 local index=$1
48 local darcs_mode=$2
49 local filename=${modified[$index]}
50 local -a diff
51 local -a hunk_start
52 local current_hunks=${hunks[$index]}
53 local lineno
54 local hunkno
55 local action
56 local i
57 local active
59 lineno=1
60 hunkno=0
61 git diff "$filename" > $TMP_FILE
62 while read line; do
63 diff[$lineno]="$line"
64 case "$line" in
65 @@*)
66 hunk_start[$hunkno]=$lineno
67 hunkno=$(($hunkno+1))
69 esac
70 lineno=$(($lineno+1))
71 done < $TMP_FILE
73 hunk_start[$hunkno]=$lineno
75 action=""
76 while [ "$action" != commit -a "$action" != abort ]; do
77 case "$darcs_mode" in
78 '')
79 echo
80 echo "Current hunks: ($current_hunks) of $hunkno hunks"
81 echo "To show (and decide on) a hunk type in the number."
82 echo "To commit the current hunks, say 'commit', else 'abort'."
83 echo
84 echo -n "Your choice? "
85 read action
87 [1-9]*)
88 darcs_mode=$(($darcs_mode+1))
89 if [ $darcs_mode -gt $hunkno ]; then
90 action=commit
91 else
92 action=$darcs_mode
96 darcs_mode=1
97 action=1
99 esac
100 case "$action" in
101 c) action=commit;;
102 q|a) action=abort;;
103 commit|abort) ;;
104 [1-9]*)
105 echo
106 for ((i=${hunk_start[$(($action-1))]}; i<${hunk_start[$action]}; i++)); do
107 if [ -n "$darcs_mode" -a $i = ${hunk_start[0]} ]; then
108 echo "File: $filename"
110 echo ${diff[$i]}
111 done | less -FSX
112 active=$(echo $current_hunks,$action | tr , '\n' | sort | uniq -u | tr '\n' , | sed -e "s/^,//" -e "s/,$//")
113 if [ ${#active} -lt ${#current_hunks} ]; then
114 i=yes
115 else
116 i=no
118 echo
119 while [ -n "$action" -a "$action" != yes -a "$action" != no -a -n "$action" ]; do
120 echo -n "Commit this hunk (default is $i)? "
121 read action
122 case "$action" in
123 y) action=yes;;
124 n) action=no;;
125 esac
126 done
127 if [ -n "$action" -a $i != "$action" ]; then
128 current_hunks=$active
131 *) echo "Unknown command: $action";;
132 esac
133 done
135 if [ "$action" = commit ]; then
136 hunks[$index]=$current_hunks
140 # Apply the hunks saved in the array hunks for the specified file.
141 # This means that the diff is rewritten to skip the unwanted hunks.
143 apply_hunks () {
144 local index=$1
145 local filename=${modified[$index]}
146 local -a current_hunks
147 local lineno
148 local lineno2
149 local linediff
150 local hunkno
151 local i
152 local active
155 echo ${hunks[$index]} | tr , '\n' > $TMP_FILE
156 while read hunkno; do
157 current_hunks[$i]=$hunkno
158 i=$(($i+1))
159 done < $TMP_FILE
161 linediff=0
162 hunkno=0
164 active=true
165 git diff "$filename" > $TMP_FILE
166 while read line
168 case "$line" in
169 @@*)
170 hunkno=$(($hunkno+1))
171 if [ $hunkno = "${current_hunks[$i]}" ]; then
172 active=true
173 i=$(($i+1))
174 if [ $linediff -ne 0 ]; then
175 lineno=$(echo "$line" | sed "s/^.*+\([0-9]*\)[, ].*$/\1/")
176 lineno2=$(($lineno+$linediff))
177 line="$(echo "$line" | sed "s/+$lineno/+$lineno2/")"
179 else
180 active=
181 lineno=$(echo "$line" | sed -n "s/^.*-[0-9]*,\([0-9]*\) .*$/\1/p")
182 if [ -z "$lineno" ]; then
183 lineno=1
185 lineno2=$(echo "$line" | sed -n "s/^.*+[0-9]*,\([0-9]*\) .*$/\1/p")
186 if [ -z "$lineno2" ]; then
187 lineno2=1
189 linediff=$(($linediff+$lineno-$lineno2))
192 esac
193 if [ -n "$active" ]; then
194 echo "$line"
196 done < $TMP_FILE
199 darcs_mode=
200 case "$1" in
201 --darcs) darcs_mode=true;;
202 esac
204 IFS=''
205 action=
207 while [ "$action" != commit -a "$action" != abort ]; do
208 case "$darcs_mode" in
210 echo
211 for ((i=1; i<$filenr; i++)); do
212 echo -n "$i ${modified[$i]}"
213 if [ -n "${hunks[$i]}" ]; then
214 echo " (${hunks[$i]})"
215 else
216 echo
218 done | less -FSX
219 echo
220 echo "To put one or more hunks of a file into the staging area (AKA"
221 echo "index), type in the number of the file."
222 echo "To commit, say 'commit', to abort, say 'abort'."
223 echo
224 echo -n "Your choice? "
225 read action
227 true)
228 if [ -z "$i" ]; then
230 else
231 i=$(($i+1))
233 if [ $i -ge $filenr ]; then
234 action=commit
235 else
236 action=$i
239 esac
240 case "$action" in
241 c) action=commit;;
242 q|a) action=abort;;
243 commit|abort) ;;
244 [0-9]*) select_hunks "$action" "$darcs_mode";;
245 *) echo "Unknown command." ;;
246 esac
247 done
249 if [ "$action" = commit ]; then
250 for ((i=1; i<$filenr; i++)); do
251 if [ -n "${hunks[$i]}" ]; then
252 apply_hunks $i
254 done | tee a123 | git apply --cached || exit
255 git commit