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