|  | # git-gui index (add/remove) support | 
|  | # Copyright (C) 2006, 2007 Shawn Pearce | 
|  |  | 
|  | proc _delete_indexlock {} { | 
|  | if {[catch {file delete -- [gitdir index.lock]} err]} { | 
|  | error_popup [strcat [mc "Unable to unlock the index."] "\n\n$err"] | 
|  | } | 
|  | } | 
|  |  | 
|  | proc close_and_unlock_index {fd after} { | 
|  | if {![catch {_close_updateindex $fd} err]} { | 
|  | unlock_index | 
|  | uplevel #0 $after | 
|  | } else { | 
|  | rescan_on_error $err $after | 
|  | } | 
|  | } | 
|  |  | 
|  | proc _close_updateindex {fd} { | 
|  | fconfigure $fd -blocking 1 | 
|  | close $fd | 
|  | } | 
|  |  | 
|  | proc rescan_on_error {err {after {}}} { | 
|  | global use_ttk NS | 
|  |  | 
|  | set w .indexfried | 
|  | Dialog $w | 
|  | wm withdraw $w | 
|  | wm title $w [strcat "[appname] ([reponame]): " [mc "Index Error"]] | 
|  | wm geometry $w "+[winfo rootx .]+[winfo rooty .]" | 
|  | set s [mc "Updating the Git index failed.  A rescan will be automatically started to resynchronize git-gui."] | 
|  | text $w.msg -yscrollcommand [list $w.vs set] \ | 
|  | -width [string length $s] -relief flat \ | 
|  | -borderwidth 0 -highlightthickness 0 \ | 
|  | -background [get_bg_color $w] | 
|  | $w.msg tag configure bold -font font_uibold -justify center | 
|  | ${NS}::scrollbar $w.vs -command [list $w.msg yview] | 
|  | $w.msg insert end $s bold \n\n$err {} | 
|  | $w.msg configure -state disabled | 
|  |  | 
|  | ${NS}::button $w.continue \ | 
|  | -text [mc "Continue"] \ | 
|  | -command [list destroy $w] | 
|  | ${NS}::button $w.unlock \ | 
|  | -text [mc "Unlock Index"] \ | 
|  | -command "destroy $w; _delete_indexlock" | 
|  | grid $w.msg - $w.vs -sticky news | 
|  | grid $w.unlock $w.continue - -sticky se -padx 2 -pady 2 | 
|  | grid columnconfigure $w 0 -weight 1 | 
|  | grid rowconfigure $w 0 -weight 1 | 
|  |  | 
|  | wm protocol $w WM_DELETE_WINDOW update | 
|  | bind $w.continue <Visibility> " | 
|  | grab $w | 
|  | focus %W | 
|  | " | 
|  | wm deiconify $w | 
|  | tkwait window $w | 
|  |  | 
|  | $::main_status stop_all | 
|  | unlock_index | 
|  | rescan [concat $after {ui_ready;}] 0 | 
|  | } | 
|  |  | 
|  | proc update_indexinfo {msg path_list after} { | 
|  | global update_index_cp | 
|  |  | 
|  | if {![lock_index update]} return | 
|  |  | 
|  | set update_index_cp 0 | 
|  | set path_list [lsort $path_list] | 
|  | set total_cnt [llength $path_list] | 
|  | set batch [expr {int($total_cnt * .01) + 1}] | 
|  | if {$batch > 25} {set batch 25} | 
|  |  | 
|  | set status_bar_operation [$::main_status start $msg [mc "files"]] | 
|  | set fd [git_write update-index -z --index-info] | 
|  | fconfigure $fd \ | 
|  | -blocking 0 \ | 
|  | -buffering full \ | 
|  | -buffersize 512 \ | 
|  | -encoding binary \ | 
|  | -translation binary | 
|  | fileevent $fd writable [list \ | 
|  | write_update_indexinfo \ | 
|  | $fd \ | 
|  | $path_list \ | 
|  | $total_cnt \ | 
|  | $batch \ | 
|  | $status_bar_operation \ | 
|  | $after \ | 
|  | ] | 
|  | } | 
|  |  | 
|  | proc write_update_indexinfo {fd path_list total_cnt batch status_bar_operation \ | 
|  | after} { | 
|  | global update_index_cp | 
|  | global file_states current_diff_path | 
|  |  | 
|  | if {$update_index_cp >= $total_cnt} { | 
|  | $status_bar_operation stop | 
|  | close_and_unlock_index $fd $after | 
|  | return | 
|  | } | 
|  |  | 
|  | for {set i $batch} \ | 
|  | {$update_index_cp < $total_cnt && $i > 0} \ | 
|  | {incr i -1} { | 
|  | set path [lindex $path_list $update_index_cp] | 
|  | incr update_index_cp | 
|  |  | 
|  | set s $file_states($path) | 
|  | switch -glob -- [lindex $s 0] { | 
|  | A? {set new _O} | 
|  | MT - | 
|  | TM - | 
|  | T_ {set new _T} | 
|  | M? {set new _M} | 
|  | TD - | 
|  | D_ {set new _D} | 
|  | D? {set new _?} | 
|  | ?? {continue} | 
|  | } | 
|  | set info [lindex $s 2] | 
|  | if {$info eq {}} continue | 
|  |  | 
|  | puts -nonewline $fd "$info\t[encoding convertto utf-8 $path]\0" | 
|  | display_file $path $new | 
|  | } | 
|  |  | 
|  | $status_bar_operation update $update_index_cp $total_cnt | 
|  | } | 
|  |  | 
|  | proc update_index {msg path_list after} { | 
|  | global update_index_cp | 
|  |  | 
|  | if {![lock_index update]} return | 
|  |  | 
|  | set update_index_cp 0 | 
|  | set path_list [lsort $path_list] | 
|  | set total_cnt [llength $path_list] | 
|  | set batch [expr {int($total_cnt * .01) + 1}] | 
|  | if {$batch > 25} {set batch 25} | 
|  |  | 
|  | set status_bar_operation [$::main_status start $msg [mc "files"]] | 
|  | set fd [git_write update-index --add --remove -z --stdin] | 
|  | fconfigure $fd \ | 
|  | -blocking 0 \ | 
|  | -buffering full \ | 
|  | -buffersize 512 \ | 
|  | -encoding binary \ | 
|  | -translation binary | 
|  | fileevent $fd writable [list \ | 
|  | write_update_index \ | 
|  | $fd \ | 
|  | $path_list \ | 
|  | $total_cnt \ | 
|  | $batch \ | 
|  | $status_bar_operation \ | 
|  | $after \ | 
|  | ] | 
|  | } | 
|  |  | 
|  | proc write_update_index {fd path_list total_cnt batch status_bar_operation \ | 
|  | after} { | 
|  | global update_index_cp | 
|  | global file_states current_diff_path | 
|  |  | 
|  | if {$update_index_cp >= $total_cnt} { | 
|  | $status_bar_operation stop | 
|  | close_and_unlock_index $fd $after | 
|  | return | 
|  | } | 
|  |  | 
|  | for {set i $batch} \ | 
|  | {$update_index_cp < $total_cnt && $i > 0} \ | 
|  | {incr i -1} { | 
|  | set path [lindex $path_list $update_index_cp] | 
|  | incr update_index_cp | 
|  |  | 
|  | switch -glob -- [lindex $file_states($path) 0] { | 
|  | AD {set new __} | 
|  | ?D {set new D_} | 
|  | _O - | 
|  | AT - | 
|  | AM {set new A_} | 
|  | TM - | 
|  | MT - | 
|  | _T {set new T_} | 
|  | _U - | 
|  | U? { | 
|  | if {[file exists $path]} { | 
|  | set new M_ | 
|  | } else { | 
|  | set new D_ | 
|  | } | 
|  | } | 
|  | ?M {set new M_} | 
|  | ?? {continue} | 
|  | } | 
|  | puts -nonewline $fd "[encoding convertto utf-8 $path]\0" | 
|  | display_file $path $new | 
|  | } | 
|  |  | 
|  | $status_bar_operation update $update_index_cp $total_cnt | 
|  | } | 
|  |  | 
|  | proc checkout_index {msg path_list after capture_error} { | 
|  | global update_index_cp | 
|  |  | 
|  | if {![lock_index update]} return | 
|  |  | 
|  | set update_index_cp 0 | 
|  | set path_list [lsort $path_list] | 
|  | set total_cnt [llength $path_list] | 
|  | set batch [expr {int($total_cnt * .01) + 1}] | 
|  | if {$batch > 25} {set batch 25} | 
|  |  | 
|  | set status_bar_operation [$::main_status start $msg [mc "files"]] | 
|  | set fd [git_write checkout-index \ | 
|  | --index \ | 
|  | --quiet \ | 
|  | --force \ | 
|  | -z \ | 
|  | --stdin \ | 
|  | ] | 
|  | fconfigure $fd \ | 
|  | -blocking 0 \ | 
|  | -buffering full \ | 
|  | -buffersize 512 \ | 
|  | -encoding binary \ | 
|  | -translation binary | 
|  | fileevent $fd writable [list \ | 
|  | write_checkout_index \ | 
|  | $fd \ | 
|  | $path_list \ | 
|  | $total_cnt \ | 
|  | $batch \ | 
|  | $status_bar_operation \ | 
|  | $after \ | 
|  | $capture_error \ | 
|  | ] | 
|  | } | 
|  |  | 
|  | proc write_checkout_index {fd path_list total_cnt batch status_bar_operation \ | 
|  | after capture_error} { | 
|  | global update_index_cp | 
|  | global file_states current_diff_path | 
|  |  | 
|  | if {$update_index_cp >= $total_cnt} { | 
|  | $status_bar_operation stop | 
|  |  | 
|  | # We do not unlock the index directly here because this | 
|  | # operation expects to potentially run in parallel with file | 
|  | # deletions scheduled by revert_helper. We're done with the | 
|  | # update index, so we close it, but actually unlocking the index | 
|  | # and dealing with potential errors is deferred to the chord | 
|  | # body that runs when all async operations are completed. | 
|  | # | 
|  | # (See after_chord in revert_helper.) | 
|  |  | 
|  | if {[catch {_close_updateindex $fd} err]} { | 
|  | uplevel #0 $capture_error [list $err] | 
|  | } | 
|  |  | 
|  | uplevel #0 $after | 
|  |  | 
|  | return | 
|  | } | 
|  |  | 
|  | for {set i $batch} \ | 
|  | {$update_index_cp < $total_cnt && $i > 0} \ | 
|  | {incr i -1} { | 
|  | set path [lindex $path_list $update_index_cp] | 
|  | incr update_index_cp | 
|  | switch -glob -- [lindex $file_states($path) 0] { | 
|  | U? {continue} | 
|  | ?M - | 
|  | ?T - | 
|  | ?D { | 
|  | puts -nonewline $fd "[encoding convertto utf-8 $path]\0" | 
|  | display_file $path ?_ | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | $status_bar_operation update $update_index_cp $total_cnt | 
|  | } | 
|  |  | 
|  | proc unstage_helper {txt paths} { | 
|  | global file_states current_diff_path | 
|  |  | 
|  | if {![lock_index begin-update]} return | 
|  |  | 
|  | set path_list [list] | 
|  | set after {} | 
|  | foreach path $paths { | 
|  | switch -glob -- [lindex $file_states($path) 0] { | 
|  | A? - | 
|  | M? - | 
|  | T? - | 
|  | D? { | 
|  | lappend path_list $path | 
|  | if {$path eq $current_diff_path} { | 
|  | set after {reshow_diff;} | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | if {$path_list eq {}} { | 
|  | unlock_index | 
|  | } else { | 
|  | update_indexinfo \ | 
|  | $txt \ | 
|  | $path_list \ | 
|  | [concat $after {ui_ready;}] | 
|  | } | 
|  | } | 
|  |  | 
|  | proc do_unstage_selection {} { | 
|  | global current_diff_path selected_paths | 
|  |  | 
|  | if {[array size selected_paths] > 0} { | 
|  | unstage_helper \ | 
|  | [mc "Unstaging selected files from commit"] \ | 
|  | [array names selected_paths] | 
|  | } elseif {$current_diff_path ne {}} { | 
|  | unstage_helper \ | 
|  | [mc "Unstaging %s from commit" [short_path $current_diff_path]] \ | 
|  | [list $current_diff_path] | 
|  | } | 
|  | } | 
|  |  | 
|  | proc add_helper {txt paths} { | 
|  | global file_states current_diff_path | 
|  |  | 
|  | if {![lock_index begin-update]} return | 
|  |  | 
|  | set path_list [list] | 
|  | set after {} | 
|  | foreach path $paths { | 
|  | switch -glob -- [lindex $file_states($path) 0] { | 
|  | _U - | 
|  | U? { | 
|  | if {$path eq $current_diff_path} { | 
|  | unlock_index | 
|  | merge_stage_workdir $path | 
|  | return | 
|  | } | 
|  | } | 
|  | _O - | 
|  | ?M - | 
|  | ?D - | 
|  | ?T { | 
|  | lappend path_list $path | 
|  | if {$path eq $current_diff_path} { | 
|  | set after {reshow_diff;} | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | if {$path_list eq {}} { | 
|  | unlock_index | 
|  | } else { | 
|  | update_index \ | 
|  | $txt \ | 
|  | $path_list \ | 
|  | [concat $after {ui_status [mc "Ready to commit."];}] | 
|  | } | 
|  | } | 
|  |  | 
|  | proc do_add_selection {} { | 
|  | global current_diff_path selected_paths | 
|  |  | 
|  | if {[array size selected_paths] > 0} { | 
|  | add_helper \ | 
|  | [mc "Adding selected files"] \ | 
|  | [array names selected_paths] | 
|  | } elseif {$current_diff_path ne {}} { | 
|  | add_helper \ | 
|  | [mc "Adding %s" [short_path $current_diff_path]] \ | 
|  | [list $current_diff_path] | 
|  | } | 
|  | } | 
|  |  | 
|  | proc do_add_all {} { | 
|  | global file_states | 
|  |  | 
|  | set paths [list] | 
|  | set untracked_paths [list] | 
|  | foreach path [array names file_states] { | 
|  | switch -glob -- [lindex $file_states($path) 0] { | 
|  | U? {continue} | 
|  | ?M - | 
|  | ?T - | 
|  | ?D {lappend paths $path} | 
|  | ?O {lappend untracked_paths $path} | 
|  | } | 
|  | } | 
|  | if {[llength $untracked_paths]} { | 
|  | set reply 0 | 
|  | switch -- [get_config gui.stageuntracked] { | 
|  | no { | 
|  | set reply 0 | 
|  | } | 
|  | yes { | 
|  | set reply 1 | 
|  | } | 
|  | ask - | 
|  | default { | 
|  | set reply [ask_popup [mc "Stage %d untracked files?" \ | 
|  | [llength $untracked_paths]]] | 
|  | } | 
|  | } | 
|  | if {$reply} { | 
|  | set paths [concat $paths $untracked_paths] | 
|  | } | 
|  | } | 
|  | add_helper [mc "Adding all changed files"] $paths | 
|  | } | 
|  |  | 
|  | # Copied from TclLib package "lambda". | 
|  | proc lambda {arguments body args} { | 
|  | return [list ::apply [list $arguments $body] {*}$args] | 
|  | } | 
|  |  | 
|  | proc revert_helper {txt paths} { | 
|  | global file_states current_diff_path | 
|  |  | 
|  | if {![lock_index begin-update]} return | 
|  |  | 
|  | # Common "after" functionality that waits until multiple asynchronous | 
|  | # operations are complete (by waiting for them to activate their notes | 
|  | # on the chord). | 
|  | # | 
|  | # The asynchronous operations are each indicated below by a comment | 
|  | # before the code block that starts the async operation. | 
|  | set after_chord [SimpleChord::new { | 
|  | if {[string trim $err] != ""} { | 
|  | rescan_on_error $err | 
|  | } else { | 
|  | unlock_index | 
|  | if {$should_reshow_diff} { reshow_diff } | 
|  | ui_ready | 
|  | } | 
|  | }] | 
|  |  | 
|  | $after_chord eval { set should_reshow_diff 0 } | 
|  |  | 
|  | # This function captures an error for processing when after_chord is | 
|  | # completed. (The chord is curried into the lambda function.) | 
|  | set capture_error [lambda \ | 
|  | {chord error} \ | 
|  | { $chord eval [list set err $error] } \ | 
|  | $after_chord] | 
|  |  | 
|  | # We don't know how many notes we're going to create (it's dynamic based | 
|  | # on conditional paths below), so create a common note that will delay | 
|  | # the chord's completion until we activate it, and then activate it | 
|  | # after all the other notes have been created. | 
|  | set after_common_note [$after_chord add_note] | 
|  |  | 
|  | set path_list [list] | 
|  | set untracked_list [list] | 
|  |  | 
|  | foreach path $paths { | 
|  | switch -glob -- [lindex $file_states($path) 0] { | 
|  | U? {continue} | 
|  | ?O { | 
|  | lappend untracked_list $path | 
|  | } | 
|  | ?M - | 
|  | ?T - | 
|  | ?D { | 
|  | lappend path_list $path | 
|  | if {$path eq $current_diff_path} { | 
|  | $after_chord eval { set should_reshow_diff 1 } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | set path_cnt [llength $path_list] | 
|  | set untracked_cnt [llength $untracked_list] | 
|  |  | 
|  | # Asynchronous operation: revert changes by checking them out afresh | 
|  | # from the index. | 
|  | if {$path_cnt > 0} { | 
|  | # Split question between singular and plural cases, because | 
|  | # such distinction is needed in some languages. Previously, the | 
|  | # code used "Revert changes in" for both, but that can't work | 
|  | # in languages where 'in' must be combined with word from | 
|  | # rest of string (in different way for both cases of course). | 
|  | # | 
|  | # FIXME: Unfortunately, even that isn't enough in some languages | 
|  | # as they have quite complex plural-form rules. Unfortunately, | 
|  | # msgcat doesn't seem to support that kind of string | 
|  | # translation. | 
|  | # | 
|  | if {$path_cnt == 1} { | 
|  | set query [mc \ | 
|  | "Revert changes in file %s?" \ | 
|  | [short_path [lindex $path_list]] \ | 
|  | ] | 
|  | } else { | 
|  | set query [mc \ | 
|  | "Revert changes in these %i files?" \ | 
|  | $path_cnt] | 
|  | } | 
|  |  | 
|  | set reply [tk_dialog \ | 
|  | .confirm_revert \ | 
|  | "[appname] ([reponame])" \ | 
|  | "$query | 
|  |  | 
|  | [mc "Any unstaged changes will be permanently lost by the revert."]" \ | 
|  | question \ | 
|  | 1 \ | 
|  | [mc "Do Nothing"] \ | 
|  | [mc "Revert Changes"] \ | 
|  | ] | 
|  |  | 
|  | if {$reply == 1} { | 
|  | set note [$after_chord add_note] | 
|  | checkout_index \ | 
|  | $txt \ | 
|  | $path_list \ | 
|  | [list $note activate] \ | 
|  | $capture_error | 
|  | } | 
|  | } | 
|  |  | 
|  | # Asynchronous operation: Deletion of untracked files. | 
|  | if {$untracked_cnt > 0} { | 
|  | # Split question between singular and plural cases, because | 
|  | # such distinction is needed in some languages. | 
|  | # | 
|  | # FIXME: Unfortunately, even that isn't enough in some languages | 
|  | # as they have quite complex plural-form rules. Unfortunately, | 
|  | # msgcat doesn't seem to support that kind of string | 
|  | # translation. | 
|  | # | 
|  | if {$untracked_cnt == 1} { | 
|  | set query [mc \ | 
|  | "Delete untracked file %s?" \ | 
|  | [short_path [lindex $untracked_list]] \ | 
|  | ] | 
|  | } else { | 
|  | set query [mc \ | 
|  | "Delete these %i untracked files?" \ | 
|  | $untracked_cnt \ | 
|  | ] | 
|  | } | 
|  |  | 
|  | set reply [tk_dialog \ | 
|  | .confirm_revert \ | 
|  | "[appname] ([reponame])" \ | 
|  | "$query | 
|  |  | 
|  | [mc "Files will be permanently deleted."]" \ | 
|  | question \ | 
|  | 1 \ | 
|  | [mc "Do Nothing"] \ | 
|  | [mc "Delete Files"] \ | 
|  | ] | 
|  |  | 
|  | if {$reply == 1} { | 
|  | $after_chord eval { set should_reshow_diff 1 } | 
|  |  | 
|  | set note [$after_chord add_note] | 
|  | delete_files $untracked_list [list $note activate] | 
|  | } | 
|  | } | 
|  |  | 
|  | # Activate the common note. If no other notes were created, this | 
|  | # completes the chord. If other notes were created, then this common | 
|  | # note prevents a race condition where the chord might complete early. | 
|  | $after_common_note activate | 
|  | } | 
|  |  | 
|  | # Delete all of the specified files, performing deletion in batches to allow the | 
|  | # UI to remain responsive and updated. | 
|  | proc delete_files {path_list after} { | 
|  | # Enable progress bar status updates | 
|  | set status_bar_operation [$::main_status \ | 
|  | start \ | 
|  | [mc "Deleting"] \ | 
|  | [mc "files"]] | 
|  |  | 
|  | set path_index 0 | 
|  | set deletion_errors [list] | 
|  | set batch_size 50 | 
|  |  | 
|  | delete_helper \ | 
|  | $path_list \ | 
|  | $path_index \ | 
|  | $deletion_errors \ | 
|  | $batch_size \ | 
|  | $status_bar_operation \ | 
|  | $after | 
|  | } | 
|  |  | 
|  | # Helper function to delete a list of files in batches. Each call deletes one | 
|  | # batch of files, and then schedules a call for the next batch after any UI | 
|  | # messages have been processed. | 
|  | proc delete_helper {path_list path_index deletion_errors batch_size \ | 
|  | status_bar_operation after} { | 
|  | global file_states | 
|  |  | 
|  | set path_cnt [llength $path_list] | 
|  |  | 
|  | set batch_remaining $batch_size | 
|  |  | 
|  | while {$batch_remaining > 0} { | 
|  | if {$path_index >= $path_cnt} { break } | 
|  |  | 
|  | set path [lindex $path_list $path_index] | 
|  |  | 
|  | set deletion_failed [catch {file delete -- $path} deletion_error] | 
|  |  | 
|  | if {$deletion_failed} { | 
|  | lappend deletion_errors [list "$deletion_error"] | 
|  | } else { | 
|  | remove_empty_directories [file dirname $path] | 
|  |  | 
|  | # Don't assume the deletion worked. Remove the file from | 
|  | # the UI, but only if it no longer exists. | 
|  | if {![path_exists $path]} { | 
|  | unset file_states($path) | 
|  | display_file $path __ | 
|  | } | 
|  | } | 
|  |  | 
|  | incr path_index 1 | 
|  | incr batch_remaining -1 | 
|  | } | 
|  |  | 
|  | # Update the progress bar to indicate that this batch has been | 
|  | # completed. The update will be visible when this procedure returns | 
|  | # and allows the UI thread to process messages. | 
|  | $status_bar_operation update $path_index $path_cnt | 
|  |  | 
|  | if {$path_index < $path_cnt} { | 
|  | # The Tcler's Wiki lists this as the best practice for keeping | 
|  | # a UI active and processing messages during a long-running | 
|  | # operation. | 
|  |  | 
|  | after idle [list after 0 [list \ | 
|  | delete_helper \ | 
|  | $path_list \ | 
|  | $path_index \ | 
|  | $deletion_errors \ | 
|  | $batch_size \ | 
|  | $status_bar_operation \ | 
|  | $after | 
|  | ]] | 
|  | } else { | 
|  | # Finish the status bar operation. | 
|  | $status_bar_operation stop | 
|  |  | 
|  | # Report error, if any, based on how many deletions failed. | 
|  | set deletion_error_cnt [llength $deletion_errors] | 
|  |  | 
|  | if {($deletion_error_cnt > 0) | 
|  | && ($deletion_error_cnt <= [MAX_VERBOSE_FILES_IN_DELETION_ERROR])} { | 
|  | set error_text [mc "Encountered errors deleting files:\n"] | 
|  |  | 
|  | foreach deletion_error $deletion_errors { | 
|  | append error_text "* [lindex $deletion_error 0]\n" | 
|  | } | 
|  |  | 
|  | error_popup $error_text | 
|  | } elseif {$deletion_error_cnt == $path_cnt} { | 
|  | error_popup [mc \ | 
|  | "None of the %d selected files could be deleted." \ | 
|  | $path_cnt \ | 
|  | ] | 
|  | } elseif {$deletion_error_cnt > 1} { | 
|  | error_popup [mc \ | 
|  | "%d of the %d selected files could not be deleted." \ | 
|  | $deletion_error_cnt \ | 
|  | $path_cnt \ | 
|  | ] | 
|  | } | 
|  |  | 
|  | uplevel #0 $after | 
|  | } | 
|  | } | 
|  |  | 
|  | proc MAX_VERBOSE_FILES_IN_DELETION_ERROR {} { return 10; } | 
|  |  | 
|  | # This function is from the TCL documentation: | 
|  | # | 
|  | #   https://wiki.tcl-lang.org/page/file+exists | 
|  | # | 
|  | # [file exists] returns false if the path does exist but is a symlink to a path | 
|  | # that doesn't exist. This proc returns true if the path exists, regardless of | 
|  | # whether it is a symlink and whether it is broken. | 
|  | proc path_exists {name} { | 
|  | expr {![catch {file lstat $name finfo}]} | 
|  | } | 
|  |  | 
|  | # Remove as many empty directories as we can starting at the specified path, | 
|  | # walking up the directory tree. If we encounter a directory that is not | 
|  | # empty, or if a directory deletion fails, then we stop the operation and | 
|  | # return to the caller. Even if this procedure fails to delete any | 
|  | # directories at all, it does not report failure. | 
|  | proc remove_empty_directories {directory_path} { | 
|  | set parent_path [file dirname $directory_path] | 
|  |  | 
|  | while {$parent_path != $directory_path} { | 
|  | set contents [glob -nocomplain -dir $directory_path *] | 
|  |  | 
|  | if {[llength $contents] > 0} { break } | 
|  | if {[catch {file delete -- $directory_path}]} { break } | 
|  |  | 
|  | set directory_path $parent_path | 
|  | set parent_path [file dirname $directory_path] | 
|  | } | 
|  | } | 
|  |  | 
|  | proc do_revert_selection {} { | 
|  | global current_diff_path selected_paths | 
|  |  | 
|  | if {[array size selected_paths] > 0} { | 
|  | revert_helper \ | 
|  | [mc "Reverting selected files"] \ | 
|  | [array names selected_paths] | 
|  | } elseif {$current_diff_path ne {}} { | 
|  | revert_helper \ | 
|  | [mc "Reverting %s" [short_path $current_diff_path]] \ | 
|  | [list $current_diff_path] | 
|  | } | 
|  | } | 
|  |  | 
|  | proc do_select_commit_type {} { | 
|  | global commit_type commit_type_is_amend | 
|  |  | 
|  | if {$commit_type_is_amend == 0 | 
|  | && [string match amend* $commit_type]} { | 
|  | create_new_commit | 
|  | } elseif {$commit_type_is_amend == 1 | 
|  | && ![string match amend* $commit_type]} { | 
|  | load_last_commit | 
|  |  | 
|  | # The amend request was rejected... | 
|  | # | 
|  | if {![string match amend* $commit_type]} { | 
|  | set commit_type_is_amend 0 | 
|  | } | 
|  | } | 
|  | } |