Merge branch 'jk/revision-pruning-optim'
[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                 MT -
107                 TM -
108                 T_ {set new _T}
109                 M? {set new _M}
110                 TD -
111                 D_ {set new _D}
112                 D? {set new _?}
113                 ?? {continue}
114                 }
115                 set info [lindex $s 2]
116                 if {$info eq {}} continue
117
118                 puts -nonewline $fd "$info\t[encoding convertto utf-8 $path]\0"
119                 display_file $path $new
120         }
121
122         $::main_status update $update_index_cp $totalCnt
123 }
124
125 proc update_index {msg pathList after} {
126         global update_index_cp
127
128         if {![lock_index update]} return
129
130         set update_index_cp 0
131         set pathList [lsort $pathList]
132         set totalCnt [llength $pathList]
133         set batch [expr {int($totalCnt * .01) + 1}]
134         if {$batch > 25} {set batch 25}
135
136         $::main_status start $msg [mc "files"]
137         set fd [git_write update-index --add --remove -z --stdin]
138         fconfigure $fd \
139                 -blocking 0 \
140                 -buffering full \
141                 -buffersize 512 \
142                 -encoding binary \
143                 -translation binary
144         fileevent $fd writable [list \
145                 write_update_index \
146                 $fd \
147                 $pathList \
148                 $totalCnt \
149                 $batch \
150                 $after \
151                 ]
152 }
153
154 proc write_update_index {fd pathList totalCnt batch after} {
155         global update_index_cp
156         global file_states current_diff_path
157
158         if {$update_index_cp >= $totalCnt} {
159                 _close_updateindex $fd $after
160                 return
161         }
162
163         for {set i $batch} \
164                 {$update_index_cp < $totalCnt && $i > 0} \
165                 {incr i -1} {
166                 set path [lindex $pathList $update_index_cp]
167                 incr update_index_cp
168
169                 switch -glob -- [lindex $file_states($path) 0] {
170                 AD {set new __}
171                 ?D {set new D_}
172                 _O -
173                 AT -
174                 AM {set new A_}
175                 TM -
176                 MT -
177                 _T {set new T_}
178                 _U -
179                 U? {
180                         if {[file exists $path]} {
181                                 set new M_
182                         } else {
183                                 set new D_
184                         }
185                 }
186                 ?M {set new M_}
187                 ?? {continue}
188                 }
189                 puts -nonewline $fd "[encoding convertto utf-8 $path]\0"
190                 display_file $path $new
191         }
192
193         $::main_status update $update_index_cp $totalCnt
194 }
195
196 proc checkout_index {msg pathList after} {
197         global update_index_cp
198
199         if {![lock_index update]} return
200
201         set update_index_cp 0
202         set pathList [lsort $pathList]
203         set totalCnt [llength $pathList]
204         set batch [expr {int($totalCnt * .01) + 1}]
205         if {$batch > 25} {set batch 25}
206
207         $::main_status start $msg [mc "files"]
208         set fd [git_write checkout-index \
209                 --index \
210                 --quiet \
211                 --force \
212                 -z \
213                 --stdin \
214                 ]
215         fconfigure $fd \
216                 -blocking 0 \
217                 -buffering full \
218                 -buffersize 512 \
219                 -encoding binary \
220                 -translation binary
221         fileevent $fd writable [list \
222                 write_checkout_index \
223                 $fd \
224                 $pathList \
225                 $totalCnt \
226                 $batch \
227                 $after \
228                 ]
229 }
230
231 proc write_checkout_index {fd pathList totalCnt batch after} {
232         global update_index_cp
233         global file_states current_diff_path
234
235         if {$update_index_cp >= $totalCnt} {
236                 _close_updateindex $fd $after
237                 return
238         }
239
240         for {set i $batch} \
241                 {$update_index_cp < $totalCnt && $i > 0} \
242                 {incr i -1} {
243                 set path [lindex $pathList $update_index_cp]
244                 incr update_index_cp
245                 switch -glob -- [lindex $file_states($path) 0] {
246                 U? {continue}
247                 ?M -
248                 ?T -
249                 ?D {
250                         puts -nonewline $fd "[encoding convertto utf-8 $path]\0"
251                         display_file $path ?_
252                 }
253                 }
254         }
255
256         $::main_status update $update_index_cp $totalCnt
257 }
258
259 proc unstage_helper {txt paths} {
260         global file_states current_diff_path
261
262         if {![lock_index begin-update]} return
263
264         set pathList [list]
265         set after {}
266         foreach path $paths {
267                 switch -glob -- [lindex $file_states($path) 0] {
268                 A? -
269                 M? -
270                 T? -
271                 D? {
272                         lappend pathList $path
273                         if {$path eq $current_diff_path} {
274                                 set after {reshow_diff;}
275                         }
276                 }
277                 }
278         }
279         if {$pathList eq {}} {
280                 unlock_index
281         } else {
282                 update_indexinfo \
283                         $txt \
284                         $pathList \
285                         [concat $after [list ui_ready]]
286         }
287 }
288
289 proc do_unstage_selection {} {
290         global current_diff_path selected_paths
291
292         if {[array size selected_paths] > 0} {
293                 unstage_helper \
294                         [mc "Unstaging selected files from commit"] \
295                         [array names selected_paths]
296         } elseif {$current_diff_path ne {}} {
297                 unstage_helper \
298                         [mc "Unstaging %s from commit" [short_path $current_diff_path]] \
299                         [list $current_diff_path]
300         }
301 }
302
303 proc add_helper {txt paths} {
304         global file_states current_diff_path
305
306         if {![lock_index begin-update]} return
307
308         set pathList [list]
309         set after {}
310         foreach path $paths {
311                 switch -glob -- [lindex $file_states($path) 0] {
312                 _U -
313                 U? {
314                         if {$path eq $current_diff_path} {
315                                 unlock_index
316                                 merge_stage_workdir $path
317                                 return
318                         }
319                 }
320                 _O -
321                 ?M -
322                 ?D -
323                 ?T {
324                         lappend pathList $path
325                         if {$path eq $current_diff_path} {
326                                 set after {reshow_diff;}
327                         }
328                 }
329                 }
330         }
331         if {$pathList eq {}} {
332                 unlock_index
333         } else {
334                 update_index \
335                         $txt \
336                         $pathList \
337                         [concat $after {ui_status [mc "Ready to commit."]}]
338         }
339 }
340
341 proc do_add_selection {} {
342         global current_diff_path selected_paths
343
344         if {[array size selected_paths] > 0} {
345                 add_helper \
346                         [mc "Adding selected files"] \
347                         [array names selected_paths]
348         } elseif {$current_diff_path ne {}} {
349                 add_helper \
350                         [mc "Adding %s" [short_path $current_diff_path]] \
351                         [list $current_diff_path]
352         }
353 }
354
355 proc do_add_all {} {
356         global file_states
357
358         set paths [list]
359         set untracked_paths [list]
360         foreach path [array names file_states] {
361                 switch -glob -- [lindex $file_states($path) 0] {
362                 U? {continue}
363                 ?M -
364                 ?T -
365                 ?D {lappend paths $path}
366                 ?O {lappend untracked_paths $path}
367                 }
368         }
369         if {[llength $untracked_paths]} {
370                 set reply 0
371                 switch -- [get_config gui.stageuntracked] {
372                 no {
373                         set reply 0
374                 }
375                 yes {
376                         set reply 1
377                 }
378                 ask -
379                 default {
380                         set reply [ask_popup [mc "Stage %d untracked files?" \
381                                                                           [llength $untracked_paths]]]
382                 }
383                 }
384                 if {$reply} {
385                         set paths [concat $paths $untracked_paths]
386                 }
387         }
388         add_helper [mc "Adding all changed files"] $paths
389 }
390
391 proc revert_helper {txt paths} {
392         global file_states current_diff_path
393
394         if {![lock_index begin-update]} return
395
396         set pathList [list]
397         set after {}
398         foreach path $paths {
399                 switch -glob -- [lindex $file_states($path) 0] {
400                 U? {continue}
401                 ?M -
402                 ?T -
403                 ?D {
404                         lappend pathList $path
405                         if {$path eq $current_diff_path} {
406                                 set after {reshow_diff;}
407                         }
408                 }
409                 }
410         }
411
412
413         # Split question between singular and plural cases, because
414         # such distinction is needed in some languages. Previously, the
415         # code used "Revert changes in" for both, but that can't work
416         # in languages where 'in' must be combined with word from
417         # rest of string (in different way for both cases of course).
418         #
419         # FIXME: Unfortunately, even that isn't enough in some languages
420         # as they have quite complex plural-form rules. Unfortunately,
421         # msgcat doesn't seem to support that kind of string translation.
422         #
423         set n [llength $pathList]
424         if {$n == 0} {
425                 unlock_index
426                 return
427         } elseif {$n == 1} {
428                 set query [mc "Revert changes in file %s?" [short_path [lindex $pathList]]]
429         } else {
430                 set query [mc "Revert changes in these %i files?" $n]
431         }
432
433         set reply [tk_dialog \
434                 .confirm_revert \
435                 "[appname] ([reponame])" \
436                 "$query
437
438 [mc "Any unstaged changes will be permanently lost by the revert."]" \
439                 question \
440                 1 \
441                 [mc "Do Nothing"] \
442                 [mc "Revert Changes"] \
443                 ]
444         if {$reply == 1} {
445                 checkout_index \
446                         $txt \
447                         $pathList \
448                         [concat $after [list ui_ready]]
449         } else {
450                 unlock_index
451         }
452 }
453
454 proc do_revert_selection {} {
455         global current_diff_path selected_paths
456
457         if {[array size selected_paths] > 0} {
458                 revert_helper \
459                         [mc "Reverting selected files"] \
460                         [array names selected_paths]
461         } elseif {$current_diff_path ne {}} {
462                 revert_helper \
463                         [mc "Reverting %s" [short_path $current_diff_path]] \
464                         [list $current_diff_path]
465         }
466 }
467
468 proc do_select_commit_type {} {
469         global commit_type selected_commit_type
470
471         if {$selected_commit_type eq {new}
472                 && [string match amend* $commit_type]} {
473                 create_new_commit
474         } elseif {$selected_commit_type eq {amend}
475                 && ![string match amend* $commit_type]} {
476                 load_last_commit
477
478                 # The amend request was rejected...
479                 #
480                 if {![string match amend* $commit_type]} {
481                         set selected_commit_type new
482                 }
483         }
484 }