Merge git://git.kernel.org/pub/scm/gitk/gitk
[git] / git-gui / lib / index.tcl
1 # git-gui index (add/remove) support
2 # Copyright (C) 2006, 2007 Shawn Pearce
3
4 proc _delete_indexlock {} {
5         if {[catch {file delete -- [gitdir index.lock]} err]} {
6                 error_popup [strcat [mc "Unable to unlock the index."] "\n\n$err"]
7         }
8 }
9
10 proc _close_updateindex {fd after} {
11         fconfigure $fd -blocking 1
12         if {[catch {close $fd} err]} {
13                 set w .indexfried
14                 toplevel $w
15                 wm title $w [strcat "[appname] ([reponame]): " [mc "Index Error"]]
16                 wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
17                 pack [label $w.msg \
18                         -justify left \
19                         -anchor w \
20                         -text [strcat \
21                                 [mc "Updating the Git index failed.  A rescan will be automatically started to resynchronize git-gui."] \
22                                 "\n\n$err"] \
23                         ] -anchor w
24
25                 frame $w.buttons
26                 button $w.buttons.continue \
27                         -text [mc "Continue"] \
28                         -command [list destroy $w]
29                 pack $w.buttons.continue -side right -padx 5
30                 button $w.buttons.unlock \
31                         -text [mc "Unlock Index"] \
32                         -command "destroy $w; _delete_indexlock"
33                 pack $w.buttons.unlock -side right
34                 pack $w.buttons -side bottom -fill x -pady 10 -padx 10
35
36                 wm protocol $w WM_DELETE_WINDOW update
37                 bind $w.buttons.continue <Visibility> "
38                         grab $w
39                         focus $w.buttons.continue
40                 "
41                 tkwait window $w
42
43                 $::main_status stop
44                 unlock_index
45                 rescan $after 0
46                 return
47         }
48
49         $::main_status stop
50         unlock_index
51         uplevel #0 $after
52 }
53
54 proc update_indexinfo {msg pathList after} {
55         global update_index_cp
56
57         if {![lock_index update]} return
58
59         set update_index_cp 0
60         set pathList [lsort $pathList]
61         set totalCnt [llength $pathList]
62         set batch [expr {int($totalCnt * .01) + 1}]
63         if {$batch > 25} {set batch 25}
64
65         $::main_status start $msg [mc "files"]
66         set fd [git_write update-index -z --index-info]
67         fconfigure $fd \
68                 -blocking 0 \
69                 -buffering full \
70                 -buffersize 512 \
71                 -encoding binary \
72                 -translation binary
73         fileevent $fd writable [list \
74                 write_update_indexinfo \
75                 $fd \
76                 $pathList \
77                 $totalCnt \
78                 $batch \
79                 $after \
80                 ]
81 }
82
83 proc write_update_indexinfo {fd pathList totalCnt batch after} {
84         global update_index_cp
85         global file_states current_diff_path
86
87         if {$update_index_cp >= $totalCnt} {
88                 _close_updateindex $fd $after
89                 return
90         }
91
92         for {set i $batch} \
93                 {$update_index_cp < $totalCnt && $i > 0} \
94                 {incr i -1} {
95                 set path [lindex $pathList $update_index_cp]
96                 incr update_index_cp
97
98                 set s $file_states($path)
99                 switch -glob -- [lindex $s 0] {
100                 A? {set new _O}
101                 M? {set new _M}
102                 D_ {set new _D}
103                 D? {set new _?}
104                 ?? {continue}
105                 }
106                 set info [lindex $s 2]
107                 if {$info eq {}} continue
108
109                 puts -nonewline $fd "$info\t[encoding convertto $path]\0"
110                 display_file $path $new
111         }
112
113         $::main_status update $update_index_cp $totalCnt
114 }
115
116 proc update_index {msg pathList after} {
117         global update_index_cp
118
119         if {![lock_index update]} return
120
121         set update_index_cp 0
122         set pathList [lsort $pathList]
123         set totalCnt [llength $pathList]
124         set batch [expr {int($totalCnt * .01) + 1}]
125         if {$batch > 25} {set batch 25}
126
127         $::main_status start $msg [mc "files"]
128         set fd [git_write update-index --add --remove -z --stdin]
129         fconfigure $fd \
130                 -blocking 0 \
131                 -buffering full \
132                 -buffersize 512 \
133                 -encoding binary \
134                 -translation binary
135         fileevent $fd writable [list \
136                 write_update_index \
137                 $fd \
138                 $pathList \
139                 $totalCnt \
140                 $batch \
141                 $after \
142                 ]
143 }
144
145 proc write_update_index {fd pathList totalCnt batch after} {
146         global update_index_cp
147         global file_states current_diff_path
148
149         if {$update_index_cp >= $totalCnt} {
150                 _close_updateindex $fd $after
151                 return
152         }
153
154         for {set i $batch} \
155                 {$update_index_cp < $totalCnt && $i > 0} \
156                 {incr i -1} {
157                 set path [lindex $pathList $update_index_cp]
158                 incr update_index_cp
159
160                 switch -glob -- [lindex $file_states($path) 0] {
161                 AD {set new __}
162                 ?D {set new D_}
163                 _O -
164                 AM {set new A_}
165                 U? {
166                         if {[file exists $path]} {
167                                 set new M_
168                         } else {
169                                 set new D_
170                         }
171                 }
172                 ?M {set new M_}
173                 ?? {continue}
174                 }
175                 puts -nonewline $fd "[encoding convertto $path]\0"
176                 display_file $path $new
177         }
178
179         $::main_status update $update_index_cp $totalCnt
180 }
181
182 proc checkout_index {msg pathList after} {
183         global update_index_cp
184
185         if {![lock_index update]} return
186
187         set update_index_cp 0
188         set pathList [lsort $pathList]
189         set totalCnt [llength $pathList]
190         set batch [expr {int($totalCnt * .01) + 1}]
191         if {$batch > 25} {set batch 25}
192
193         $::main_status start $msg [mc "files"]
194         set fd [git_write checkout-index \
195                 --index \
196                 --quiet \
197                 --force \
198                 -z \
199                 --stdin \
200                 ]
201         fconfigure $fd \
202                 -blocking 0 \
203                 -buffering full \
204                 -buffersize 512 \
205                 -encoding binary \
206                 -translation binary
207         fileevent $fd writable [list \
208                 write_checkout_index \
209                 $fd \
210                 $pathList \
211                 $totalCnt \
212                 $batch \
213                 $after \
214                 ]
215 }
216
217 proc write_checkout_index {fd pathList totalCnt batch after} {
218         global update_index_cp
219         global file_states current_diff_path
220
221         if {$update_index_cp >= $totalCnt} {
222                 _close_updateindex $fd $after
223                 return
224         }
225
226         for {set i $batch} \
227                 {$update_index_cp < $totalCnt && $i > 0} \
228                 {incr i -1} {
229                 set path [lindex $pathList $update_index_cp]
230                 incr update_index_cp
231                 switch -glob -- [lindex $file_states($path) 0] {
232                 U? {continue}
233                 ?M -
234                 ?D {
235                         puts -nonewline $fd "[encoding convertto $path]\0"
236                         display_file $path ?_
237                 }
238                 }
239         }
240
241         $::main_status update $update_index_cp $totalCnt
242 }
243
244 proc unstage_helper {txt paths} {
245         global file_states current_diff_path
246
247         if {![lock_index begin-update]} return
248
249         set pathList [list]
250         set after {}
251         foreach path $paths {
252                 switch -glob -- [lindex $file_states($path) 0] {
253                 A? -
254                 M? -
255                 D? {
256                         lappend pathList $path
257                         if {$path eq $current_diff_path} {
258                                 set after {reshow_diff;}
259                         }
260                 }
261                 }
262         }
263         if {$pathList eq {}} {
264                 unlock_index
265         } else {
266                 update_indexinfo \
267                         $txt \
268                         $pathList \
269                         [concat $after [list ui_ready]]
270         }
271 }
272
273 proc do_unstage_selection {} {
274         global current_diff_path selected_paths
275
276         if {[array size selected_paths] > 0} {
277                 unstage_helper \
278                         {Unstaging selected files from commit} \
279                         [array names selected_paths]
280         } elseif {$current_diff_path ne {}} {
281                 unstage_helper \
282                         [mc "Unstaging %s from commit" [short_path $current_diff_path]] \
283                         [list $current_diff_path]
284         }
285 }
286
287 proc add_helper {txt paths} {
288         global file_states current_diff_path
289
290         if {![lock_index begin-update]} return
291
292         set pathList [list]
293         set after {}
294         foreach path $paths {
295                 switch -glob -- [lindex $file_states($path) 0] {
296                 _O -
297                 ?M -
298                 ?D -
299                 U? {
300                         lappend pathList $path
301                         if {$path eq $current_diff_path} {
302                                 set after {reshow_diff;}
303                         }
304                 }
305                 }
306         }
307         if {$pathList eq {}} {
308                 unlock_index
309         } else {
310                 update_index \
311                         $txt \
312                         $pathList \
313                         [concat $after {ui_status {Ready to commit.}}]
314         }
315 }
316
317 proc do_add_selection {} {
318         global current_diff_path selected_paths
319
320         if {[array size selected_paths] > 0} {
321                 add_helper \
322                         {Adding selected files} \
323                         [array names selected_paths]
324         } elseif {$current_diff_path ne {}} {
325                 add_helper \
326                         [mc "Adding %s" [short_path $current_diff_path]] \
327                         [list $current_diff_path]
328         }
329 }
330
331 proc do_add_all {} {
332         global file_states
333
334         set paths [list]
335         foreach path [array names file_states] {
336                 switch -glob -- [lindex $file_states($path) 0] {
337                 U? {continue}
338                 ?M -
339                 ?D {lappend paths $path}
340                 }
341         }
342         add_helper {Adding all changed files} $paths
343 }
344
345 proc revert_helper {txt paths} {
346         global file_states current_diff_path
347
348         if {![lock_index begin-update]} return
349
350         set pathList [list]
351         set after {}
352         foreach path $paths {
353                 switch -glob -- [lindex $file_states($path) 0] {
354                 U? {continue}
355                 ?M -
356                 ?D {
357                         lappend pathList $path
358                         if {$path eq $current_diff_path} {
359                                 set after {reshow_diff;}
360                         }
361                 }
362                 }
363         }
364
365
366         # Split question between singular and plural cases, because
367         # such distinction is needed in some languages. Previously, the
368         # code used "Revert changes in" for both, but that can't work
369         # in languages where 'in' must be combined with word from
370         # rest of string (in diffrent way for both cases of course).
371         #
372         # FIXME: Unfortunately, even that isn't enough in some languages
373         # as they have quite complex plural-form rules. Unfortunately,
374         # msgcat doesn't seem to support that kind of string translation.
375         #
376         set n [llength $pathList]
377         if {$n == 0} {
378                 unlock_index
379                 return
380         } elseif {$n == 1} {
381                 set query [mc "Revert changes in file %s?" [short_path [lindex $pathList]]]
382         } else {
383                 set query [mc "Revert changes in these %i files?" $n]
384         }
385
386         set reply [tk_dialog \
387                 .confirm_revert \
388                 "[appname] ([reponame])" \
389                 [mc "Any unstaged changes will be permanently lost by the revert."] \
390                 question \
391                 1 \
392                 [mc "Do Nothing"] \
393                 [mc "Revert Changes"] \
394                 ]
395         if {$reply == 1} {
396                 checkout_index \
397                         $txt \
398                         $pathList \
399                         [concat $after [list ui_ready]]
400         } else {
401                 unlock_index
402         }
403 }
404
405 proc do_revert_selection {} {
406         global current_diff_path selected_paths
407
408         if {[array size selected_paths] > 0} {
409                 revert_helper \
410                         {Reverting selected files} \
411                         [array names selected_paths]
412         } elseif {$current_diff_path ne {}} {
413                 revert_helper \
414                         "Reverting [short_path $current_diff_path]" \
415                         [list $current_diff_path]
416         }
417 }
418
419 proc do_select_commit_type {} {
420         global commit_type selected_commit_type
421
422         if {$selected_commit_type eq {new}
423                 && [string match amend* $commit_type]} {
424                 create_new_commit
425         } elseif {$selected_commit_type eq {amend}
426                 && ![string match amend* $commit_type]} {
427                 load_last_commit
428
429                 # The amend request was rejected...
430                 #
431                 if {![string match amend* $commit_type]} {
432                         set selected_commit_type new
433                 }
434         }
435 }