Merge branch 'js/no-cherry-pick-head-after-punted'
[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 $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 $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 $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                         {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                         {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 unknown_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 unknown_paths $path}
367                 }
368         }
369         if {[llength $unknown_paths]} {
370                 set reply [ask_popup [mc "There are unknown files do you also want
371 to stage those?"]]
372                 if {$reply} {
373                         set paths [concat $paths $unknown_paths]
374                 }
375         }
376         add_helper {Adding all changed files} $paths
377 }
378
379 proc revert_helper {txt paths} {
380         global file_states current_diff_path
381
382         if {![lock_index begin-update]} return
383
384         set pathList [list]
385         set after {}
386         foreach path $paths {
387                 switch -glob -- [lindex $file_states($path) 0] {
388                 U? {continue}
389                 ?M -
390                 ?T -
391                 ?D {
392                         lappend pathList $path
393                         if {$path eq $current_diff_path} {
394                                 set after {reshow_diff;}
395                         }
396                 }
397                 }
398         }
399
400
401         # Split question between singular and plural cases, because
402         # such distinction is needed in some languages. Previously, the
403         # code used "Revert changes in" for both, but that can't work
404         # in languages where 'in' must be combined with word from
405         # rest of string (in diffrent way for both cases of course).
406         #
407         # FIXME: Unfortunately, even that isn't enough in some languages
408         # as they have quite complex plural-form rules. Unfortunately,
409         # msgcat doesn't seem to support that kind of string translation.
410         #
411         set n [llength $pathList]
412         if {$n == 0} {
413                 unlock_index
414                 return
415         } elseif {$n == 1} {
416                 set query [mc "Revert changes in file %s?" [short_path [lindex $pathList]]]
417         } else {
418                 set query [mc "Revert changes in these %i files?" $n]
419         }
420
421         set reply [tk_dialog \
422                 .confirm_revert \
423                 "[appname] ([reponame])" \
424                 "$query
425
426 [mc "Any unstaged changes will be permanently lost by the revert."]" \
427                 question \
428                 1 \
429                 [mc "Do Nothing"] \
430                 [mc "Revert Changes"] \
431                 ]
432         if {$reply == 1} {
433                 checkout_index \
434                         $txt \
435                         $pathList \
436                         [concat $after [list ui_ready]]
437         } else {
438                 unlock_index
439         }
440 }
441
442 proc do_revert_selection {} {
443         global current_diff_path selected_paths
444
445         if {[array size selected_paths] > 0} {
446                 revert_helper \
447                         [mc "Reverting selected files"] \
448                         [array names selected_paths]
449         } elseif {$current_diff_path ne {}} {
450                 revert_helper \
451                         [mc "Reverting %s" [short_path $current_diff_path]] \
452                         [list $current_diff_path]
453         }
454 }
455
456 proc do_select_commit_type {} {
457         global commit_type selected_commit_type
458
459         if {$selected_commit_type eq {new}
460                 && [string match amend* $commit_type]} {
461                 create_new_commit
462         } elseif {$selected_commit_type eq {amend}
463                 && ![string match amend* $commit_type]} {
464                 load_last_commit
465
466                 # The amend request was rejected...
467                 #
468                 if {![string match amend* $commit_type]} {
469                         set selected_commit_type new
470                 }
471         }
472 }