|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
} |
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 } |
|
|
|
|
|
|
|
set capture_error [lambda \ |
|
{chord error} \ |
|
{ $chord eval [list set err $error] } \ |
|
$after_chord] |
|
|
|
|
|
|
|
|
|
|
|
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] |
|
|
|
|
|
|
|
if {$path_cnt > 0} { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
} |
|
} |
|
|
|
|
|
if {$untracked_cnt > 0} { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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] |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
$after_common_note activate |
|
} |
|
|
|
|
|
|
|
proc delete_files {path_list after} { |
|
|
|
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 |
|
} |
|
|
|
|
|
|
|
|
|
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] |
|
|
|
|
|
|
|
if {![path_exists $path]} { |
|
unset file_states($path) |
|
display_file $path __ |
|
} |
|
} |
|
|
|
incr path_index 1 |
|
incr batch_remaining -1 |
|
} |
|
|
|
|
|
|
|
|
|
$status_bar_operation update $path_index $path_cnt |
|
|
|
if {$path_index < $path_cnt} { |
|
|
|
|
|
|
|
|
|
after idle [list after 0 [list \ |
|
delete_helper \ |
|
$path_list \ |
|
$path_index \ |
|
$deletion_errors \ |
|
$batch_size \ |
|
$status_bar_operation \ |
|
$after |
|
]] |
|
} else { |
|
|
|
$status_bar_operation stop |
|
|
|
|
|
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; } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
proc path_exists {name} { |
|
expr {![catch {file lstat $name finfo}]} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
if {![string match amend* $commit_type]} { |
|
set commit_type_is_amend 0 |
|
} |
|
} |
|
} |
|
|