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