From 2f1a955b99954fd18c512244e1321dc4cff856b4 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 28 Apr 2007 20:49:22 -0400 Subject: git-gui: Include the subject in the status bar after commit Now that the command line git-commit has made displaying the subject (first line) of the newly created commit popular we can easily do the same thing here in git-gui, without the ugly part of forking off a child process to obtain that first line. Signed-off-by: Shawn O. Pearce diff --git a/git-gui.sh b/git-gui.sh index 0ad2815..f6c6d44 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1317,10 +1317,11 @@ A rescan will be automatically started now. } set i [string first "\n" $msg] if {$i >= 0} { - append reflogm {: } [string range $msg 0 [expr {$i - 1}]] + set subject [string range $msg 0 [expr {$i - 1}]] } else { - append reflogm {: } $msg + set subject $msg } + append reflogm {: } $subject set cmd [list git update-ref -m $reflogm HEAD $cmt_id $curHEAD] if {[catch {eval exec $cmd} err]} { error_popup "update-ref failed:\n\n$err" @@ -1414,7 +1415,7 @@ A rescan will be automatically started now. unlock_index reshow_diff set ui_status_value \ - "Changes committed as [string range $cmt_id 0 7]." + "Created commit [string range $cmt_id 0 7]: $subject" } ###################################################################### -- cgit v0.10.2-6-g49f6 From 1afd1ec1077a6d43b9492e19ea88f5928204d4cc Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 28 Apr 2007 21:26:27 -0400 Subject: git-gui: Warn users before making an octopus merge A coworker who was new to git-gui recently tried to make an octopus merge when he did not quite mean to. Unfortunately in his case the branches had file level conflicts and failed to merge with the octopus strategy, and he didn't quite know why this happened. Since most users really don't want to perform an octopus merge this additional safety valve in front of the merge process is a good thing. Signed-off-by: Shawn O. Pearce diff --git a/git-gui.sh b/git-gui.sh index f6c6d44..dd8a408 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2886,6 +2886,19 @@ proc start_local_merge_action {w} { set unit branch } elseif {$revcnt <= 15} { set unit branches + + if {[tk_dialog \ + $w.confirm_octopus \ + [wm title $w] \ + "Use octopus merge strategy? + +You are merging $revcnt branches at once. This requires using the octopus merge driver, which may not succeed if there are file-level conflicts. +" \ + question \ + 0 \ + {Cancel} \ + {Use octopus} \ + ] != 1} return } else { tk_messageBox \ -icon error \ -- cgit v0.10.2-6-g49f6 From d45b52b540b13a6dae816b65f0f5431b56583570 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 28 Apr 2007 21:32:27 -0400 Subject: git-gui: Correct line wrapping for too many branch message Since Tk automatically wraps lines for us in tk_messageBox widgets we don't need to try to wrap them ourselves. Its actually worse that we linewrapped this here in the script, as not all fonts will render this dialog nicely. Signed-off-by: Shawn O. Pearce diff --git a/git-gui.sh b/git-gui.sh index dd8a408..ba2ee98 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2907,12 +2907,9 @@ You are merging $revcnt branches at once. This requires using the octopus merge -parent $w \ -message "Too many branches selected. -You have requested to merge $revcnt branches -in an octopus merge. This exceeds Git's -internal limit of 15 branches per merge. +You have requested to merge $revcnt branches in an octopus merge. This exceeds Git's internal limit of 15 branches per merge. -Please select fewer branches. To merge more -than 15 branches, merge the branches in batches. +Please select fewer branches. To merge more than 15 branches, merge the branches in batches. " return } -- cgit v0.10.2-6-g49f6 From 2739291b77fefe7cfae16481756716f11a01e654 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 28 Apr 2007 22:00:02 -0400 Subject: git-gui: Cleanup common font handling for font_ui An earlier change tossed these optionMenu font configurations all over the code, when really we can just rename the proc to a hidden internal name and provide our own wrapper to install the font configuration we really want. We also don't need to set these option database entries in all of the procedures that open dialogs; instead we should just set one time, them after we have the font configuration ready for use. Signed-off-by: Shawn O. Pearce diff --git a/git-gui.sh b/git-gui.sh index ba2ee98..aa232f0 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -242,8 +242,6 @@ proc error_popup {msg} { if {[reponame] ne {}} { append title " ([reponame])" } - option add *Dialog.msg.font font_ui - option add *Button.font font_ui set cmd [list tk_messageBox \ -icon error \ -type ok \ @@ -260,8 +258,6 @@ proc warn_popup {msg} { if {[reponame] ne {}} { append title " ([reponame])" } - option add *Dialog.msg.font font_ui - option add *Button.font font_ui set cmd [list tk_messageBox \ -icon warning \ -type ok \ @@ -278,8 +274,6 @@ proc info_popup {msg {parent .}} { if {[reponame] ne {}} { append title " ([reponame])" } - option add *Dialog.msg.font font_ui - option add *Button.font font_ui tk_messageBox \ -parent $parent \ -icon info \ @@ -293,8 +287,6 @@ proc ask_popup {msg} { if {[reponame] ne {}} { append title " ([reponame])" } - option add *Dialog.msg.font font_ui - option add *Button.font font_ui return [tk_messageBox \ -parent . \ -icon question \ @@ -303,6 +295,15 @@ proc ask_popup {msg} { -message $msg] } +auto_load tk_optionMenu +rename tk_optionMenu real__tkOptionMenu +proc tk_optionMenu {w varName args} { + set m [eval real__tkOptionMenu $w $varName $args] + $m configure -font font_ui + $w configure -font font_ui + return $m +} + ###################################################################### ## ## version check @@ -2114,10 +2115,7 @@ proc do_create_branch {} { -value head \ -variable create_branch_revtype \ -font font_ui - set lbranchm [eval tk_optionMenu $w.from.head_m create_branch_head \ - $all_heads] - $lbranchm configure -font font_ui - $w.from.head_m configure -font font_ui + eval tk_optionMenu $w.from.head_m create_branch_head $all_heads grid $w.from.head_r $w.from.head_m -sticky w set all_trackings [all_tracking_branches] if {$all_trackings ne {}} { @@ -2127,11 +2125,9 @@ proc do_create_branch {} { -value tracking \ -variable create_branch_revtype \ -font font_ui - set tbranchm [eval tk_optionMenu $w.from.tracking_m \ + eval tk_optionMenu $w.from.tracking_m \ create_branch_trackinghead \ - $all_trackings] - $tbranchm configure -font font_ui - $w.from.tracking_m configure -font font_ui + $all_trackings grid $w.from.tracking_r $w.from.tracking_m -sticky w } set all_tags [load_all_tags] @@ -2142,11 +2138,7 @@ proc do_create_branch {} { -value tag \ -variable create_branch_revtype \ -font font_ui - set tagsm [eval tk_optionMenu $w.from.tag_m \ - create_branch_tag \ - $all_tags] - $tagsm configure -font font_ui - $w.from.tag_m configure -font font_ui + eval tk_optionMenu $w.from.tag_m create_branch_tag $all_tags grid $w.from.tag_r $w.from.tag_m -sticky w } radiobutton $w.from.exp_r \ @@ -2340,11 +2332,7 @@ proc do_delete_branch {} { -value head \ -variable delete_branch_checktype \ -font font_ui - set mergedlocalm [eval tk_optionMenu $w.validate.head_m \ - delete_branch_head \ - $all_heads] - $mergedlocalm configure -font font_ui - $w.validate.head_m configure -font font_ui + eval tk_optionMenu $w.validate.head_m delete_branch_head $all_heads grid $w.validate.head_r $w.validate.head_m -sticky w set all_trackings [all_tracking_branches] if {$all_trackings ne {}} { @@ -2354,11 +2342,9 @@ proc do_delete_branch {} { -value tracking \ -variable delete_branch_checktype \ -font font_ui - set mergedtrackm [eval tk_optionMenu $w.validate.tracking_m \ + eval tk_optionMenu $w.validate.tracking_m \ delete_branch_trackinghead \ - $all_trackings] - $mergedtrackm configure -font font_ui - $w.validate.tracking_m configure -font font_ui + $all_trackings grid $w.validate.tracking_r $w.validate.tracking_m -sticky w } radiobutton $w.validate.always_r \ @@ -2729,10 +2715,7 @@ proc do_push_anywhere {} { -value remote \ -variable push_urltype \ -font font_ui - set remmenu [eval tk_optionMenu $w.dest.remote_m push_remote \ - $all_remotes] - $remmenu configure -font font_ui - $w.dest.remote_m configure -font font_ui + eval tk_optionMenu $w.dest.remote_m push_remote $all_remotes grid $w.dest.remote_r $w.dest.remote_m -sticky w if {[lsearch -sorted -exact $all_remotes origin] != -1} { set push_remote origin @@ -4715,11 +4698,9 @@ proc do_options {} { frame $w.global.$name label $w.global.$name.l -text "$text:" -font font_ui pack $w.global.$name.l -side left -anchor w -fill x - set fontmenu [eval tk_optionMenu $w.global.$name.family \ + eval tk_optionMenu $w.global.$name.family \ global_config_new(gui.$font^^family) \ - $all_fonts] - $w.global.$name.family configure -font font_ui - $fontmenu configure -font font_ui + $all_fonts spinbox $w.global.$name.size \ -textvariable global_config_new(gui.$font^^size) \ -from 2 -to 80 -increment 1 \ @@ -5068,6 +5049,8 @@ set font_descs { } load_config 0 apply_config +option add *Dialog.msg.font font_ui +option add *Button.font font_ui ###################################################################### ## -- cgit v0.10.2-6-g49f6 From 7416bbc65cbcc3db9cafe50b92c14193ac295dbe Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 28 Apr 2007 23:14:08 -0400 Subject: git-gui: Use option database defaults to set the font Rather than passing "-font font_ui" to every widget that we create we can instead reconfigure the option database for all widget classes to use our font_ui as the default widget font. This way Tk will automatically setup their defaults for us, and we can reduce the size of the application. Signed-off-by: Shawn O. Pearce diff --git a/git-gui.sh b/git-gui.sh index aa232f0..94f80bb 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1918,8 +1918,7 @@ proc populate_branch_menu {} { -label $b \ -command [list switch_branch $b] \ -variable current_branch \ - -value $b \ - -font font_ui + -value $b lappend disable_on_lock \ [list $m entryconf [$m index last] -state] } @@ -2078,26 +2077,21 @@ proc do_create_branch {} { frame $w.buttons button $w.buttons.create -text Create \ - -font font_ui \ -default active \ -command [list do_create_branch_action $w] pack $w.buttons.create -side right button $w.buttons.cancel -text {Cancel} \ - -font font_ui \ -command [list destroy $w] pack $w.buttons.cancel -side right -padx 5 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - labelframe $w.desc \ - -text {Branch Description} \ - -font font_ui - label $w.desc.name_l -text {Name:} -font font_ui + labelframe $w.desc -text {Branch Description} + label $w.desc.name_l -text {Name:} entry $w.desc.name_t \ -borderwidth 1 \ -relief sunken \ -width 40 \ -textvariable create_branch_name \ - -font font_ui \ -validate key \ -validatecommand { if {%d == 1 && [regexp {[~^:?*\[\0- ]} %S]} {return 0} @@ -2107,14 +2101,11 @@ proc do_create_branch {} { grid columnconfigure $w.desc 1 -weight 1 pack $w.desc -anchor nw -fill x -pady 5 -padx 5 - labelframe $w.from \ - -text {Starting Revision} \ - -font font_ui + labelframe $w.from -text {Starting Revision} radiobutton $w.from.head_r \ -text {Local Branch:} \ -value head \ - -variable create_branch_revtype \ - -font font_ui + -variable create_branch_revtype eval tk_optionMenu $w.from.head_m create_branch_head $all_heads grid $w.from.head_r $w.from.head_m -sticky w set all_trackings [all_tracking_branches] @@ -2123,8 +2114,7 @@ proc do_create_branch {} { radiobutton $w.from.tracking_r \ -text {Tracking Branch:} \ -value tracking \ - -variable create_branch_revtype \ - -font font_ui + -variable create_branch_revtype eval tk_optionMenu $w.from.tracking_m \ create_branch_trackinghead \ $all_trackings @@ -2136,22 +2126,19 @@ proc do_create_branch {} { radiobutton $w.from.tag_r \ -text {Tag:} \ -value tag \ - -variable create_branch_revtype \ - -font font_ui + -variable create_branch_revtype eval tk_optionMenu $w.from.tag_m create_branch_tag $all_tags grid $w.from.tag_r $w.from.tag_m -sticky w } radiobutton $w.from.exp_r \ -text {Revision Expression:} \ -value expression \ - -variable create_branch_revtype \ - -font font_ui + -variable create_branch_revtype entry $w.from.exp_t \ -borderwidth 1 \ -relief sunken \ -width 50 \ -textvariable create_branch_revexp \ - -font font_ui \ -validate key \ -validatecommand { if {%d == 1 && [regexp {\s} %S]} {return 0} @@ -2164,13 +2151,10 @@ proc do_create_branch {} { grid columnconfigure $w.from 1 -weight 1 pack $w.from -anchor nw -fill x -pady 5 -padx 5 - labelframe $w.postActions \ - -text {Post Creation Actions} \ - -font font_ui + labelframe $w.postActions -text {Post Creation Actions} checkbutton $w.postActions.checkout \ -text {Checkout after creation} \ - -variable create_branch_checkout \ - -font font_ui + -variable create_branch_checkout pack $w.postActions.checkout -anchor nw pack $w.postActions -anchor nw -fill x -pady 5 -padx 5 @@ -2296,24 +2280,19 @@ proc do_delete_branch {} { frame $w.buttons button $w.buttons.create -text Delete \ - -font font_ui \ -command [list do_delete_branch_action $w] pack $w.buttons.create -side right button $w.buttons.cancel -text {Cancel} \ - -font font_ui \ -command [list destroy $w] pack $w.buttons.cancel -side right -padx 5 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - labelframe $w.list \ - -text {Local Branches} \ - -font font_ui + labelframe $w.list -text {Local Branches} listbox $w.list.l \ -height 10 \ -width 70 \ -selectmode extended \ - -yscrollcommand [list $w.list.sby set] \ - -font font_ui + -yscrollcommand [list $w.list.sby set] foreach h $all_heads { if {$h ne $current_branch} { $w.list.l insert end $h @@ -2324,14 +2303,11 @@ proc do_delete_branch {} { pack $w.list.l -side left -fill both -expand 1 pack $w.list -fill both -expand 1 -pady 5 -padx 5 - labelframe $w.validate \ - -text {Delete Only If} \ - -font font_ui + labelframe $w.validate -text {Delete Only If} radiobutton $w.validate.head_r \ -text {Merged Into Local Branch:} \ -value head \ - -variable delete_branch_checktype \ - -font font_ui + -variable delete_branch_checktype eval tk_optionMenu $w.validate.head_m delete_branch_head $all_heads grid $w.validate.head_r $w.validate.head_m -sticky w set all_trackings [all_tracking_branches] @@ -2340,8 +2316,7 @@ proc do_delete_branch {} { radiobutton $w.validate.tracking_r \ -text {Merged Into Tracking Branch:} \ -value tracking \ - -variable delete_branch_checktype \ - -font font_ui + -variable delete_branch_checktype eval tk_optionMenu $w.validate.tracking_m \ delete_branch_trackinghead \ $all_trackings @@ -2350,8 +2325,7 @@ proc do_delete_branch {} { radiobutton $w.validate.always_r \ -text {Always (Do not perform merge checks)} \ -value always \ - -variable delete_branch_checktype \ - -font font_ui + -variable delete_branch_checktype grid $w.validate.always_r -columnspan 2 -sticky w grid columnconfigure $w.validate 1 -weight 1 pack $w.validate -anchor nw -fill x -pady 5 -padx 5 @@ -2578,8 +2552,7 @@ proc populate_fetch_menu {} { if {$enable} { $m add command \ -label "Fetch from $r..." \ - -command [list fetch_from $r] \ - -font font_ui + -command [list fetch_from $r] } } } @@ -2614,8 +2587,7 @@ proc populate_push_menu {} { } $m add command \ -label "Push to $r..." \ - -command [list push_to $r] \ - -font font_ui + -command [list push_to $r] incr fast_count } } @@ -2675,26 +2647,21 @@ proc do_push_anywhere {} { frame $w.buttons button $w.buttons.create -text Push \ - -font font_ui \ -default active \ -command [list start_push_anywhere_action $w] pack $w.buttons.create -side right button $w.buttons.cancel -text {Cancel} \ - -font font_ui \ -default normal \ -command [list destroy $w] pack $w.buttons.cancel -side right -padx 5 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - labelframe $w.source \ - -text {Source Branches} \ - -font font_ui + labelframe $w.source -text {Source Branches} listbox $w.source.l \ -height 10 \ -width 70 \ -selectmode extended \ - -yscrollcommand [list $w.source.sby set] \ - -font font_ui + -yscrollcommand [list $w.source.sby set] foreach h $all_heads { $w.source.l insert end $h if {$h eq $current_branch} { @@ -2706,15 +2673,12 @@ proc do_push_anywhere {} { pack $w.source.l -side left -fill both -expand 1 pack $w.source -fill both -expand 1 -pady 5 -padx 5 - labelframe $w.dest \ - -text {Destination Repository} \ - -font font_ui + labelframe $w.dest -text {Destination Repository} if {$all_remotes ne {}} { radiobutton $w.dest.remote_r \ -text {Remote:} \ -value remote \ - -variable push_urltype \ - -font font_ui + -variable push_urltype eval tk_optionMenu $w.dest.remote_m push_remote $all_remotes grid $w.dest.remote_r $w.dest.remote_m -sticky w if {[lsearch -sorted -exact $all_remotes origin] != -1} { @@ -2729,14 +2693,12 @@ proc do_push_anywhere {} { radiobutton $w.dest.url_r \ -text {Arbitrary URL:} \ -value url \ - -variable push_urltype \ - -font font_ui + -variable push_urltype entry $w.dest.url_t \ -borderwidth 1 \ -relief sunken \ -width 50 \ -textvariable push_url \ - -font font_ui \ -validate key \ -validatecommand { if {%d == 1 && [regexp {\s} %S]} {return 0} @@ -2749,18 +2711,14 @@ proc do_push_anywhere {} { grid columnconfigure $w.dest 1 -weight 1 pack $w.dest -anchor nw -fill x -pady 5 -padx 5 - labelframe $w.options \ - -text {Transfer Options} \ - -font font_ui + labelframe $w.options -text {Transfer Options} checkbutton $w.options.thin \ -text {Use thin pack (for slow network connections)} \ - -variable push_thin \ - -font font_ui + -variable push_thin grid $w.options.thin -columnspan 2 -sticky w checkbutton $w.options.tags \ -text {Include tags} \ - -variable push_tags \ - -font font_ui + -variable push_tags grid $w.options.tags -columnspan 2 -sticky w grid columnconfigure $w.options 1 -weight 1 pack $w.options -anchor nw -fill x -pady 5 -padx 5 @@ -2950,28 +2908,22 @@ proc do_local_merge {} { frame $w.buttons button $w.buttons.visualize -text Visualize \ - -font font_ui \ -command [list visualize_local_merge $w] pack $w.buttons.visualize -side left button $w.buttons.create -text Merge \ - -font font_ui \ -command [list start_local_merge_action $w] pack $w.buttons.create -side right button $w.buttons.cancel -text {Cancel} \ - -font font_ui \ -command [list destroy $w] pack $w.buttons.cancel -side right -padx 5 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - labelframe $w.source \ - -text {Source Branches} \ - -font font_ui + labelframe $w.source -text {Source Branches} listbox $w.source.l \ -height 10 \ -width 70 \ -selectmode extended \ - -yscrollcommand [list $w.source.sby set] \ - -font font_ui + -yscrollcommand [list $w.source.sby set] scrollbar $w.source.sby -command [list $w.source.l yview] pack $w.source.sby -side right -fill y pack $w.source.l -side left -fill both -expand 1 @@ -3106,8 +3058,7 @@ proc new_browser {commit} { -height 20 \ -width 70 \ -xscrollcommand [list $w.list.sbx set] \ - -yscrollcommand [list $w.list.sby set] \ - -font font_ui + -yscrollcommand [list $w.list.sby set] $w_list tag conf in_sel \ -background [$w_list cget -foreground] \ -foreground [$w_list cget -background] @@ -3122,8 +3073,7 @@ proc new_browser {commit} { -anchor w \ -justify left \ -borderwidth 1 \ - -relief sunken \ - -font font_ui + -relief sunken pack $w.status -anchor w -side bottom -fill x bind $w_list "browser_click 0 $w_list @%x,%y;break" @@ -3403,8 +3353,7 @@ proc show_blame {commit path} { -anchor w \ -justify left \ -borderwidth 1 \ - -relief sunken \ - -font font_ui + -relief sunken pack $w.status -side bottom -fill x frame $w.cm @@ -3426,7 +3375,6 @@ proc show_blame {commit path} { menu $w.ctxm -tearoff 0 $w.ctxm add command -label "Copy Commit" \ - -font font_ui \ -command "blame_copycommit $w \$cursorW @\$cursorX,\$cursorY" foreach i [list \ @@ -3928,7 +3876,6 @@ proc hook_failed_popup {hook msg} { button $w.ok -text OK \ -width 15 \ - -font font_ui \ -command "destroy $w" pack $w.ok -side bottom -anchor e -pady 10 -padx 10 @@ -3977,13 +3924,10 @@ proc console_init {w} { menu $w.ctxm -tearoff 0 $w.ctxm add command -label "Copy" \ - -font font_ui \ -command "tk_textCopy $w.m.t" $w.ctxm add command -label "Select All" \ - -font font_ui \ -command "focus $w.m.t;$w.m.t tag add sel 0.0 end" $w.ctxm add command -label "Copy All" \ - -font font_ui \ -command " $w.m.t tag add sel 0.0 end tk_textCopy $w.m.t @@ -3991,7 +3935,6 @@ proc console_init {w} { " button $w.ok -text {Close} \ - -font font_ui \ -state disabled \ -command "destroy $w" pack $w.ok -side bottom -anchor e -pady 10 -padx 10 @@ -4179,17 +4122,14 @@ proc do_stats {} { toplevel $w wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - label $w.header -text {Database Statistics} \ - -font font_uibold + label $w.header -text {Database Statistics} pack $w.header -side top -fill x frame $w.buttons -border 1 button $w.buttons.close -text Close \ - -font font_ui \ -default active \ -command [list destroy $w] button $w.buttons.gc -text {Compress Database} \ - -font font_ui \ -default normal \ -command "destroy $w;do_gc" pack $w.buttons.close -side right @@ -4213,8 +4153,8 @@ proc do_stats {} { set value "$value[lindex $s 2]" } - label $w.stat.l_$name -text "$label:" -anchor w -font font_ui - label $w.stat.v_$name -text $value -anchor w -font font_ui + label $w.stat.l_$name -text "$label:" -anchor w + label $w.stat.v_$name -text $value -anchor w grid $w.stat.l_$name $w.stat.v_$name -sticky we -padx {0 5} } pack $w.stat -pady 10 -padx 10 @@ -4515,7 +4455,6 @@ proc do_about {} { frame $w.buttons button $w.buttons.close -text {Close} \ - -font font_ui \ -default active \ -command [list destroy $w] pack $w.buttons.close -side right @@ -4528,8 +4467,7 @@ $copyright" \ -justify left \ -anchor w \ -borderwidth 1 \ - -relief solid \ - -font font_ui + -relief solid pack $w.desc -side top -fill x -padx 5 -pady 5 set v {} @@ -4549,14 +4487,12 @@ $copyright" \ -justify left \ -anchor w \ -borderwidth 1 \ - -relief solid \ - -font font_ui + -relief solid pack $w.vers -side top -fill x -padx 5 -pady 5 menu $w.ctxm -tearoff 0 $w.ctxm add command \ -label {Copy} \ - -font font_ui \ -command " clipboard clear clipboard append -format STRING -type STRING -- \[$w.vers cget -text\] @@ -4600,26 +4536,21 @@ proc do_options {} { frame $w.buttons button $w.buttons.restore -text {Restore Defaults} \ - -font font_ui \ -default normal \ -command do_restore_defaults pack $w.buttons.restore -side left button $w.buttons.save -text Save \ - -font font_ui \ -default active \ -command [list do_save_config $w] pack $w.buttons.save -side right button $w.buttons.cancel -text {Cancel} \ - -font font_ui \ -default normal \ -command [list destroy $w] pack $w.buttons.cancel -side right -padx 5 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - labelframe $w.repo -text "[reponame] Repository" \ - -font font_ui - labelframe $w.global -text {Global (All Repositories)} \ - -font font_ui + labelframe $w.repo -text "[reponame] Repository" + labelframe $w.global -text {Global (All Repositories)} pack $w.repo -side left -fill both -expand 1 -pady 5 -padx 5 pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5 @@ -4645,35 +4576,32 @@ proc do_options {} { checkbutton $w.$f.$optid -text $text \ -variable ${f}_config_new($name) \ -onvalue true \ - -offvalue false \ - -font font_ui + -offvalue false pack $w.$f.$optid -side top -anchor w } i-* { regexp -- {-(\d+)\.\.(\d+)$} $type _junk min max frame $w.$f.$optid - label $w.$f.$optid.l -text "$text:" -font font_ui + label $w.$f.$optid.l -text "$text:" pack $w.$f.$optid.l -side left -anchor w -fill x spinbox $w.$f.$optid.v \ -textvariable ${f}_config_new($name) \ -from $min \ -to $max \ -increment 1 \ - -width [expr {1 + [string length $max]}] \ - -font font_ui + -width [expr {1 + [string length $max]}] bind $w.$f.$optid.v {%W selection range 0 end} pack $w.$f.$optid.v -side right -anchor e -padx 5 pack $w.$f.$optid -side top -anchor w -fill x } t { frame $w.$f.$optid - label $w.$f.$optid.l -text "$text:" -font font_ui + label $w.$f.$optid.l -text "$text:" entry $w.$f.$optid.v \ -borderwidth 1 \ -relief sunken \ -width 20 \ - -textvariable ${f}_config_new($name) \ - -font font_ui + -textvariable ${f}_config_new($name) pack $w.$f.$optid.l -side left -anchor w pack $w.$f.$optid.v -side left -anchor w \ -fill x -expand 1 \ @@ -4696,7 +4624,7 @@ proc do_options {} { [font configure $font -size] frame $w.global.$name - label $w.global.$name.l -text "$text:" -font font_ui + label $w.global.$name.l -text "$text:" pack $w.global.$name.l -side left -anchor w -fill x eval tk_optionMenu $w.global.$name.family \ global_config_new(gui.$font^^family) \ @@ -4704,8 +4632,7 @@ proc do_options {} { spinbox $w.global.$name.size \ -textvariable global_config_new(gui.$font^^size) \ -from 2 -to 80 -increment 1 \ - -width 3 \ - -font font_ui + -width 3 bind $w.global.$name.size {%W selection range 0 end} pack $w.global.$name.size -side right -anchor e pack $w.global.$name.family -side right -anchor e @@ -5002,6 +4929,13 @@ catch { font create font_uibold font create font_diffbold +foreach class {Button Checkbutton Entry Label + Labelframe Listbox Menu Message + Radiobutton Text} { + option add *$class.font font_ui +} +unset class + if {[is_Windows]} { set M1B Control set M1T Ctrl @@ -5049,8 +4983,6 @@ set font_descs { } load_config 0 apply_config -option add *Dialog.msg.font font_ui -option add *Button.font font_ui ###################################################################### ## @@ -5098,18 +5030,18 @@ set ui_comm {} # -- Menu Bar # menu .mbar -tearoff 0 -.mbar add cascade -label Repository -menu .mbar.repository -font font_ui -.mbar add cascade -label Edit -menu .mbar.edit -font font_ui +.mbar add cascade -label Repository -menu .mbar.repository +.mbar add cascade -label Edit -menu .mbar.edit if {[is_enabled branch]} { - .mbar add cascade -label Branch -menu .mbar.branch -font font_ui + .mbar add cascade -label Branch -menu .mbar.branch } if {[is_enabled multicommit] || [is_enabled singlecommit]} { - .mbar add cascade -label Commit -menu .mbar.commit -font font_ui + .mbar add cascade -label Commit -menu .mbar.commit } if {[is_enabled transport]} { - .mbar add cascade -label Merge -menu .mbar.merge -font font_ui - .mbar add cascade -label Fetch -menu .mbar.fetch -font font_ui - .mbar add cascade -label Push -menu .mbar.push -font font_ui + .mbar add cascade -label Merge -menu .mbar.merge + .mbar add cascade -label Fetch -menu .mbar.fetch + .mbar add cascade -label Push -menu .mbar.push } . configure -menu .mbar @@ -5119,93 +5051,76 @@ menu .mbar.repository .mbar.repository add command \ -label {Browse Current Branch} \ - -command {new_browser $current_branch} \ - -font font_ui + -command {new_browser $current_branch} trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Browse \$current_branch\" ;#" .mbar.repository add separator .mbar.repository add command \ -label {Visualize Current Branch} \ - -command {do_gitk $current_branch} \ - -font font_ui + -command {do_gitk $current_branch} trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Visualize \$current_branch\" ;#" .mbar.repository add command \ -label {Visualize All Branches} \ - -command {do_gitk --all} \ - -font font_ui + -command {do_gitk --all} .mbar.repository add separator if {[is_enabled multicommit]} { .mbar.repository add command -label {Database Statistics} \ - -command do_stats \ - -font font_ui + -command do_stats .mbar.repository add command -label {Compress Database} \ - -command do_gc \ - -font font_ui + -command do_gc .mbar.repository add command -label {Verify Database} \ - -command do_fsck_objects \ - -font font_ui + -command do_fsck_objects .mbar.repository add separator if {[is_Cygwin]} { .mbar.repository add command \ -label {Create Desktop Icon} \ - -command do_cygwin_shortcut \ - -font font_ui + -command do_cygwin_shortcut } elseif {[is_Windows]} { .mbar.repository add command \ -label {Create Desktop Icon} \ - -command do_windows_shortcut \ - -font font_ui + -command do_windows_shortcut } elseif {[is_MacOSX]} { .mbar.repository add command \ -label {Create Desktop Icon} \ - -command do_macosx_app \ - -font font_ui + -command do_macosx_app } } .mbar.repository add command -label Quit \ -command do_quit \ - -accelerator $M1T-Q \ - -font font_ui + -accelerator $M1T-Q # -- Edit Menu # menu .mbar.edit .mbar.edit add command -label Undo \ -command {catch {[focus] edit undo}} \ - -accelerator $M1T-Z \ - -font font_ui + -accelerator $M1T-Z .mbar.edit add command -label Redo \ -command {catch {[focus] edit redo}} \ - -accelerator $M1T-Y \ - -font font_ui + -accelerator $M1T-Y .mbar.edit add separator .mbar.edit add command -label Cut \ -command {catch {tk_textCut [focus]}} \ - -accelerator $M1T-X \ - -font font_ui + -accelerator $M1T-X .mbar.edit add command -label Copy \ -command {catch {tk_textCopy [focus]}} \ - -accelerator $M1T-C \ - -font font_ui + -accelerator $M1T-C .mbar.edit add command -label Paste \ -command {catch {tk_textPaste [focus]; [focus] see insert}} \ - -accelerator $M1T-V \ - -font font_ui + -accelerator $M1T-V .mbar.edit add command -label Delete \ -command {catch {[focus] delete sel.first sel.last}} \ - -accelerator Del \ - -font font_ui + -accelerator Del .mbar.edit add separator .mbar.edit add command -label {Select All} \ -command {catch {[focus] tag add sel 0.0 end}} \ - -accelerator $M1T-A \ - -font font_ui + -accelerator $M1T-A # -- Branch Menu # @@ -5214,20 +5129,17 @@ if {[is_enabled branch]} { .mbar.branch add command -label {Create...} \ -command do_create_branch \ - -accelerator $M1T-N \ - -font font_ui + -accelerator $M1T-N lappend disable_on_lock [list .mbar.branch entryconf \ [.mbar.branch index last] -state] .mbar.branch add command -label {Delete...} \ - -command do_delete_branch \ - -font font_ui + -command do_delete_branch lappend disable_on_lock [list .mbar.branch entryconf \ [.mbar.branch index last] -state] .mbar.branch add command -label {Reset...} \ - -command do_reset_hard \ - -font font_ui + -command do_reset_hard lappend disable_on_lock [list .mbar.branch entryconf \ [.mbar.branch index last] -state] } @@ -5241,8 +5153,7 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} { -label {New Commit} \ -command do_select_commit_type \ -variable selected_commit_type \ - -value new \ - -font font_ui + -value new lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] @@ -5250,8 +5161,7 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} { -label {Amend Last Commit} \ -command do_select_commit_type \ -variable selected_commit_type \ - -value amend \ - -font font_ui + -value amend lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] @@ -5259,33 +5169,28 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} { .mbar.commit add command -label Rescan \ -command do_rescan \ - -accelerator F5 \ - -font font_ui + -accelerator F5 lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] .mbar.commit add command -label {Add To Commit} \ - -command do_add_selection \ - -font font_ui + -command do_add_selection lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] .mbar.commit add command -label {Add Existing To Commit} \ -command do_add_all \ - -accelerator $M1T-I \ - -font font_ui + -accelerator $M1T-I lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] .mbar.commit add command -label {Unstage From Commit} \ - -command do_unstage_selection \ - -font font_ui + -command do_unstage_selection lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] .mbar.commit add command -label {Revert Changes} \ - -command do_revert_selection \ - -font font_ui + -command do_revert_selection lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] @@ -5293,13 +5198,11 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} { .mbar.commit add command -label {Sign Off} \ -command do_signoff \ - -accelerator $M1T-S \ - -font font_ui + -accelerator $M1T-S .mbar.commit add command -label Commit \ -command do_commit \ - -accelerator $M1T-Return \ - -font font_ui + -accelerator $M1T-Return lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] } @@ -5309,13 +5212,11 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} { if {[is_enabled branch]} { menu .mbar.merge .mbar.merge add command -label {Local Merge...} \ - -command do_local_merge \ - -font font_ui + -command do_local_merge lappend disable_on_lock \ [list .mbar.merge entryconf [.mbar.merge index last] -state] .mbar.merge add command -label {Abort Merge...} \ - -command do_reset_hard \ - -font font_ui + -command do_reset_hard lappend disable_on_lock \ [list .mbar.merge entryconf [.mbar.merge index last] -state] @@ -5328,8 +5229,7 @@ if {[is_enabled transport]} { menu .mbar.push .mbar.push add command -label {Push...} \ - -command do_push_anywhere \ - -font font_ui + -command do_push_anywhere } if {[is_MacOSX]} { @@ -5339,18 +5239,15 @@ if {[is_MacOSX]} { menu .mbar.apple .mbar.apple add command -label "About [appname]" \ - -command do_about \ - -font font_ui + -command do_about .mbar.apple add command -label "Options..." \ - -command do_options \ - -font font_ui + -command do_options } else { # -- Edit Menu # .mbar.edit add separator .mbar.edit add command -label {Options...} \ - -command do_options \ - -font font_ui + -command do_options # -- Tools Menu # @@ -5376,8 +5273,7 @@ if {[is_MacOSX]} { .mbar add cascade -label Tools -menu .mbar.tools menu .mbar.tools .mbar.tools add command -label "Migrate" \ - -command do_miga \ - -font font_ui + -command do_miga lappend disable_on_lock \ [list .mbar.tools entryconf [.mbar.tools index last] -state] } @@ -5385,13 +5281,12 @@ if {[is_MacOSX]} { # -- Help Menu # -.mbar add cascade -label Help -menu .mbar.help -font font_ui +.mbar add cascade -label Help -menu .mbar.help menu .mbar.help if {![is_MacOSX]} { .mbar.help add command -label "About [appname]" \ - -command do_about \ - -font font_ui + -command do_about } set browser {} @@ -5428,8 +5323,7 @@ if {[file isfile $doc_path]} { if {$browser ne {}} { .mbar.help add command -label {Online Documentation} \ - -command [list exec $browser $doc_url &] \ - -font font_ui + -command [list exec $browser $doc_url &] } unset browser doc_path doc_url @@ -5488,13 +5382,11 @@ frame .branch \ label .branch.l1 \ -text {Current Branch:} \ -anchor w \ - -justify left \ - -font font_ui + -justify left label .branch.cb \ -textvariable current_branch \ -anchor w \ - -justify left \ - -font font_ui + -justify left pack .branch.l1 -side left pack .branch.cb -side left -fill x pack .branch -side top -fill x @@ -5510,12 +5402,10 @@ pack .vpane -anchor n -side top -fill both -expand 1 # frame .vpane.files.index -height 100 -width 200 label .vpane.files.index.title -text {Changes To Be Committed} \ - -background green \ - -font font_ui + -background green text $ui_index -background white -borderwidth 0 \ -width 20 -height 10 \ -wrap none \ - -font font_ui \ -cursor $cursor_ptr \ -xscrollcommand {.vpane.files.index.sx set} \ -yscrollcommand {.vpane.files.index.sy set} \ @@ -5532,12 +5422,10 @@ pack $ui_index -side left -fill both -expand 1 # frame .vpane.files.workdir -height 100 -width 200 label .vpane.files.workdir.title -text {Changed But Not Updated} \ - -background red \ - -font font_ui + -background red text $ui_workdir -background white -borderwidth 0 \ -width 20 -height 10 \ -wrap none \ - -font font_ui \ -cursor $cursor_ptr \ -xscrollcommand {.vpane.files.workdir.sx set} \ -yscrollcommand {.vpane.files.workdir.sy set} \ @@ -5572,33 +5460,28 @@ pack .vpane.lower.diff -side bottom -fill both -expand 1 frame .vpane.lower.commarea.buttons label .vpane.lower.commarea.buttons.l -text {} \ -anchor w \ - -justify left \ - -font font_ui + -justify left pack .vpane.lower.commarea.buttons.l -side top -fill x pack .vpane.lower.commarea.buttons -side left -fill y button .vpane.lower.commarea.buttons.rescan -text {Rescan} \ - -command do_rescan \ - -font font_ui + -command do_rescan pack .vpane.lower.commarea.buttons.rescan -side top -fill x lappend disable_on_lock \ {.vpane.lower.commarea.buttons.rescan conf -state} button .vpane.lower.commarea.buttons.incall -text {Add Existing} \ - -command do_add_all \ - -font font_ui + -command do_add_all pack .vpane.lower.commarea.buttons.incall -side top -fill x lappend disable_on_lock \ {.vpane.lower.commarea.buttons.incall conf -state} button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \ - -command do_signoff \ - -font font_ui + -command do_signoff pack .vpane.lower.commarea.buttons.signoff -side top -fill x button .vpane.lower.commarea.buttons.commit -text {Commit} \ - -command do_commit \ - -font font_ui + -command do_commit pack .vpane.lower.commarea.buttons.commit -side top -fill x lappend disable_on_lock \ {.vpane.lower.commarea.buttons.commit conf -state} @@ -5613,22 +5496,19 @@ radiobutton .vpane.lower.commarea.buffer.header.new \ -text {New Commit} \ -command do_select_commit_type \ -variable selected_commit_type \ - -value new \ - -font font_ui + -value new lappend disable_on_lock \ [list .vpane.lower.commarea.buffer.header.new conf -state] radiobutton .vpane.lower.commarea.buffer.header.amend \ -text {Amend Last Commit} \ -command do_select_commit_type \ -variable selected_commit_type \ - -value amend \ - -font font_ui + -value amend lappend disable_on_lock \ [list .vpane.lower.commarea.buffer.header.amend conf -state] label $ui_coml \ -anchor w \ - -justify left \ - -font font_ui + -justify left proc trace_commit_type {varname args} { global ui_coml commit_type switch -glob -- $commit_type { @@ -5667,28 +5547,22 @@ set ctxm .vpane.lower.commarea.buffer.ctxm menu $ctxm -tearoff 0 $ctxm add command \ -label {Cut} \ - -font font_ui \ -command {tk_textCut $ui_comm} $ctxm add command \ -label {Copy} \ - -font font_ui \ -command {tk_textCopy $ui_comm} $ctxm add command \ -label {Paste} \ - -font font_ui \ -command {tk_textPaste $ui_comm} $ctxm add command \ -label {Delete} \ - -font font_ui \ -command {$ui_comm delete sel.first sel.last} $ctxm add separator $ctxm add command \ -label {Select All} \ - -font font_ui \ -command {focus $ui_comm;$ui_comm tag add sel 0.0 end} $ctxm add command \ -label {Copy All} \ - -font font_ui \ -command { $ui_comm tag add sel 0.0 end tk_textCopy $ui_comm @@ -5697,7 +5571,6 @@ $ctxm add command \ $ctxm add separator $ctxm add command \ -label {Sign Off} \ - -font font_ui \ -command do_signoff bind_button3 $ui_comm "tk_popup $ctxm %X %Y" @@ -5732,18 +5605,15 @@ label .vpane.lower.diff.header.status \ -background orange \ -width $max_status_desc \ -anchor w \ - -justify left \ - -font font_ui + -justify left label .vpane.lower.diff.header.file \ -background orange \ -anchor w \ - -justify left \ - -font font_ui + -justify left label .vpane.lower.diff.header.path \ -background orange \ -anchor w \ - -justify left \ - -font font_ui + -justify left pack .vpane.lower.diff.header.status -side left pack .vpane.lower.diff.header.file -side left pack .vpane.lower.diff.header.path -fill x @@ -5751,7 +5621,6 @@ set ctxm .vpane.lower.diff.header.ctxm menu $ctxm -tearoff 0 $ctxm add command \ -label {Copy} \ - -font font_ui \ -command { clipboard clear clipboard append \ @@ -5820,22 +5689,18 @@ set ctxm .vpane.lower.diff.body.ctxm menu $ctxm -tearoff 0 $ctxm add command \ -label {Refresh} \ - -font font_ui \ -command reshow_diff lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] $ctxm add command \ -label {Copy} \ - -font font_ui \ -command {tk_textCopy $ui_diff} lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] $ctxm add command \ -label {Select All} \ - -font font_ui \ -command {focus $ui_diff;$ui_diff tag add sel 0.0 end} lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] $ctxm add command \ -label {Copy All} \ - -font font_ui \ -command { $ui_diff tag add sel 0.0 end tk_textCopy $ui_diff @@ -5845,25 +5710,21 @@ lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] $ctxm add separator $ctxm add command \ -label {Apply/Reverse Hunk} \ - -font font_ui \ -command {apply_hunk $cursorX $cursorY} set ui_diff_applyhunk [$ctxm index last] lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state] $ctxm add separator $ctxm add command \ -label {Decrease Font Size} \ - -font font_ui \ -command {incr_font_size font_diff -1} lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] $ctxm add command \ -label {Increase Font Size} \ - -font font_ui \ -command {incr_font_size font_diff 1} lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] $ctxm add separator $ctxm add command \ -label {Show Less Context} \ - -font font_ui \ -command {if {$repo_config(gui.diffcontext) >= 2} { incr repo_config(gui.diffcontext) -1 reshow_diff @@ -5871,7 +5732,6 @@ $ctxm add command \ lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] $ctxm add command \ -label {Show More Context} \ - -font font_ui \ -command { incr repo_config(gui.diffcontext) reshow_diff @@ -5879,7 +5739,6 @@ $ctxm add command \ lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] $ctxm add separator $ctxm add command -label {Options...} \ - -font font_ui \ -command do_options bind_button3 $ui_diff " set cursorX %x @@ -5899,8 +5758,7 @@ label .status -textvariable ui_status_value \ -anchor w \ -justify left \ -borderwidth 1 \ - -relief sunken \ - -font font_ui + -relief sunken pack .status -anchor w -side bottom -fill x # -- Load geometry -- cgit v0.10.2-6-g49f6 From dc6716b83d3f90f8b1b2c611fe4f9f7c9fb14f5e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 29 Apr 2007 01:54:57 -0400 Subject: git-gui: Refactor to use our git proc more often Whenever we want to execute a git subcommand from the plumbing layer (and on rare occasion, the more porcelain-ish layer) we tend to use our proc wrapper, just to make the code slightly cleaner at the call sites. I wasn't doing that in a couple of places, so this is a simple cleanup to correct that. Signed-off-by: Shawn O. Pearce diff --git a/git-gui.sh b/git-gui.sh index 94f80bb..4c01b91 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1298,12 +1298,12 @@ A rescan will be automatically started now. # -- Create the commit. # - set cmd [list git commit-tree $tree_id] + set cmd [list commit-tree $tree_id] foreach p [concat $PARENT $MERGE_HEAD] { lappend cmd -p $p } lappend cmd <$msg_p - if {[catch {set cmt_id [eval exec $cmd]} err]} { + if {[catch {set cmt_id [eval git $cmd]} err]} { error_popup "commit-tree failed:\n\n$err" set ui_status_value {Commit failed.} unlock_index @@ -1323,8 +1323,9 @@ A rescan will be automatically started now. set subject $msg } append reflogm {: } $subject - set cmd [list git update-ref -m $reflogm HEAD $cmt_id $curHEAD] - if {[catch {eval exec $cmd} err]} { + if {[catch { + git update-ref -m $reflogm HEAD $cmt_id $curHEAD + } err]} { error_popup "update-ref failed:\n\n$err" set ui_status_value {Commit failed.} unlock_index @@ -2018,13 +2019,13 @@ proc do_create_branch_action {w} { -message "Invalid starting revision: $rev" return } - set cmd [list git update-ref] - lappend cmd -m - lappend cmd "branch: Created from $rev" - lappend cmd "refs/heads/$newbranch" - lappend cmd $cmt - lappend cmd $null_sha1 - if {[catch {eval exec $cmd} err]} { + if {[catch { + git update-ref \ + -m "branch: Created from $rev" \ + "refs/heads/$newbranch" \ + $cmt \ + $null_sha1 + } err]} { tk_messageBox \ -icon error \ -type ok \ -- cgit v0.10.2-6-g49f6 From c6a5e4030320c934bca70471d04aa9e7d8e2cd67 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 29 Apr 2007 03:02:10 -0400 Subject: git-gui: Track our own embedded values and rebuild when they change Like core-Git we now track the values that we embed into our shell script wrapper, and we "recompile" that wrapper if they are changed. This concept was lifted from git.git's Makefile, where a similar thing was done by Eygene Ryabinkin. Too bad it wasn't just done here in git-gui from the beginning, as the git.git Makefile support for GIT-GUI-VARS was really just because git-gui doesn't do it on its own. Signed-off-by: Shawn O. Pearce diff --git a/.gitignore b/.gitignore index c714d38..57cced8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ GIT-VERSION-FILE +GIT-GUI-VARS git-citool git-gui diff --git a/Makefile b/Makefile index b29d7d1..3b6282e 100644 --- a/Makefile +++ b/Makefile @@ -53,7 +53,19 @@ $(GITGUI_BUILT_INS): git-gui $(QUIET_BUILT_IN)rm -f $@ && ln git-gui $@ # These can record GITGUI_VERSION -$(patsubst %.sh,%,$(SCRIPT_SH)): GIT-VERSION-FILE +$(patsubst %.sh,%,$(SCRIPT_SH)): GIT-VERSION-FILE GIT-GUI-VARS + +TRACK_VARS = \ + $(subst ','\'',SHELL_PATH='$(SHELL_PATH_SQ)') \ + $(subst ','\'',TCLTK_PATH='$(TCLTK_PATH_SQ)') \ +#end TRACK_VARS + +GIT-GUI-VARS: .FORCE-GIT-GUI-VARS + @VARS='$(TRACK_VARS)'; \ + if test x"$$VARS" != x"`cat $@ 2>/dev/null`" ; then \ + echo 1>&2 " * new locations or Tcl/Tk interpreter"; \ + echo 1>$@ "$$VARS"; \ + fi all:: $(ALL_PROGRAMS) @@ -67,7 +79,8 @@ dist-version: @echo $(GITGUI_VERSION) > $(TARDIR)/version clean:: - rm -f $(ALL_PROGRAMS) GIT-VERSION-FILE + rm -f $(ALL_PROGRAMS) GIT-VERSION-FILE GIT-GUI-VARS .PHONY: all install dist-version clean .PHONY: .FORCE-GIT-VERSION-FILE +.PHONY: .FORCE-GIT-GUI-VARS -- cgit v0.10.2-6-g49f6 From f522c9b5ed367172f969397589ae3d686b867ac0 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 7 May 2007 23:35:48 -0400 Subject: git-gui: Refactor into multiple files to save my sanity I'm finding it difficult to work with a 6,000+ line Tcl script and not go insane while looking for a particular block of code. Since most of the program is organized into different units of functionality and not all users will need all units immediately on startup we can improve things by splitting procs out into multiple files and let auto_load handle things for us. This should help not only to better organize the source, but it may also improve startup times for some users as the Tcl parser does not need to read as much script before it can show the UI. In many cases the user can avoid reading at least half of git-gui now. Unfortunately we now need a library directory in our runtime location. This is currently assumed to be $(sharedir)/git-gui/lib and its expected that the Makefile invoker will setup some sort of reasonable sharedir value for us, or let us assume its going to be $(gitexecdir)/../share. We now also require a tclsh (in TCL_PATH) to just run the Makefile, as we use tclsh to generate the tclIndex for our lib directory. I'm hoping this is not an unncessary burden on end-users who are building from source. I haven't really made any functionality changes here, this is just a huge migration of code from one file to many smaller files. All of the new changes are to setup the library path and install the library files. Signed-off-by: Shawn O. Pearce diff --git a/.gitignore b/.gitignore index 57cced8..020b86d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ GIT-VERSION-FILE GIT-GUI-VARS git-citool git-gui +lib/tclIndex diff --git a/Makefile b/Makefile index 3b6282e..ba1e33b 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,7 @@ GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE SCRIPT_SH = git-gui.sh GITGUI_BUILT_INS = git-citool ALL_PROGRAMS = $(GITGUI_BUILT_INS) $(patsubst %.sh,%,$(SCRIPT_SH)) +ALL_LIBFILES = $(wildcard lib/*.tcl) ifndef SHELL_PATH SHELL_PATH = /bin/sh @@ -19,6 +20,10 @@ ifndef gitexecdir gitexecdir := $(shell git --exec-path) endif +ifndef sharedir + sharedir := $(dir $(gitexecdir))/share +endif + ifndef INSTALL INSTALL = install endif @@ -26,8 +31,10 @@ endif ifndef V QUIET_GEN = @echo ' ' GEN $@; QUIET_BUILT_IN = @echo ' ' BUILTIN $@; + QUIET_INDEX = @echo ' ' INDEX $(dir $@); endif +TCL_PATH ?= tclsh TCLTK_PATH ?= wish ifeq ($(findstring $(MAKEFLAGS),s),s) @@ -40,11 +47,15 @@ gitexecdir_SQ = $(subst ','\'',$(gitexecdir)) SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH)) +libdir ?= $(sharedir)/git-gui/lib +libdir_SQ = $(subst ','\'',$(libdir)) + $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh $(QUIET_GEN)rm -f $@ $@+ && \ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ -e 's|^exec wish "$$0"|exec $(subst |,'\|',$(TCLTK_PATH_SQ)) "$$0"|' \ -e 's/@@GITGUI_VERSION@@/$(GITGUI_VERSION)/g' \ + -e 's|@@GITGUI_LIBDIR@@|$(libdir_SQ)|' \ $@.sh >$@+ && \ chmod +x $@+ && \ mv $@+ $@ @@ -52,12 +63,16 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh $(GITGUI_BUILT_INS): git-gui $(QUIET_BUILT_IN)rm -f $@ && ln git-gui $@ +lib/tclIndex: $(ALL_LIBFILES) + $(QUIET_INDEX)echo auto_mkindex lib '*.tcl' | $(TCL_PATH) + # These can record GITGUI_VERSION $(patsubst %.sh,%,$(SCRIPT_SH)): GIT-VERSION-FILE GIT-GUI-VARS TRACK_VARS = \ $(subst ','\'',SHELL_PATH='$(SHELL_PATH_SQ)') \ $(subst ','\'',TCLTK_PATH='$(TCLTK_PATH_SQ)') \ + $(subst ','\'',libdir='$(libdir_SQ)') \ #end TRACK_VARS GIT-GUI-VARS: .FORCE-GIT-GUI-VARS @@ -67,19 +82,23 @@ GIT-GUI-VARS: .FORCE-GIT-GUI-VARS echo 1>$@ "$$VARS"; \ fi -all:: $(ALL_PROGRAMS) +all:: $(ALL_PROGRAMS) lib/tclIndex install: all $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(gitexecdir_SQ)' $(INSTALL) git-gui '$(DESTDIR_SQ)$(gitexecdir_SQ)' $(foreach p,$(GITGUI_BUILT_INS), rm -f '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' && ln '$(DESTDIR_SQ)$(gitexecdir_SQ)/git-gui' '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' ;) + $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(libdir_SQ)' + $(INSTALL) -m644 lib/tclIndex '$(DESTDIR_SQ)$(libdir_SQ)' + $(foreach p,$(ALL_LIBFILES), $(INSTALL) -m644 $p '$(DESTDIR_SQ)$(libdir_SQ)' ;) dist-version: @mkdir -p $(TARDIR) @echo $(GITGUI_VERSION) > $(TARDIR)/version clean:: - rm -f $(ALL_PROGRAMS) GIT-VERSION-FILE GIT-GUI-VARS + rm -f $(ALL_PROGRAMS) lib/tclIndex + rm -f GIT-VERSION-FILE GIT-GUI-VARS .PHONY: all install dist-version clean .PHONY: .FORCE-GIT-VERSION-FILE diff --git a/git-gui.sh b/git-gui.sh index 4c01b91..72673c6 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -22,6 +22,30 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA} ###################################################################### ## +## configure our library + +set oguilib {@@GITGUI_LIBDIR@@} +if {[string match @@* $oguilib]} { + set oguilib [file join [file dirname [file normalize $argv0]] lib] +} +set auto_path [concat [list $oguilib] $auto_path] + +if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} { + unset _verbose + rename auto_load real__auto_load + proc auto_load {name args} { + puts stderr "auto_load $name" + return [uplevel 1 real__auto_load $name $args] + } + rename source real__source + proc source {name} { + puts stderr "source $name" + uplevel 1 real__source $name + } +} + +###################################################################### +## ## read only globals set _appname [lindex [file split $argv0] end] @@ -179,56 +203,6 @@ proc load_config {include_global} { } } -proc save_config {} { - global default_config font_descs - global repo_config global_config - global repo_config_new global_config_new - - foreach option $font_descs { - set name [lindex $option 0] - set font [lindex $option 1] - font configure $font \ - -family $global_config_new(gui.$font^^family) \ - -size $global_config_new(gui.$font^^size) - font configure ${font}bold \ - -family $global_config_new(gui.$font^^family) \ - -size $global_config_new(gui.$font^^size) - set global_config_new(gui.$name) [font configure $font] - unset global_config_new(gui.$font^^family) - unset global_config_new(gui.$font^^size) - } - - foreach name [array names default_config] { - set value $global_config_new($name) - if {$value ne $global_config($name)} { - if {$value eq $default_config($name)} { - catch {git config --global --unset $name} - } else { - regsub -all "\[{}\]" $value {"} value - git config --global $name $value - } - set global_config($name) $value - if {$value eq $repo_config($name)} { - catch {git config --unset $name} - set repo_config($name) $value - } - } - } - - foreach name [array names default_config] { - set value $repo_config_new($name) - if {$value ne $repo_config($name)} { - if {$value eq $global_config($name)} { - catch {git config --unset $name} - } else { - regsub -all "\[{}\]" $value {"} value - git config $name $value - } - set repo_config($name) $value - } - } -} - ###################################################################### ## ## handy utils @@ -237,64 +211,6 @@ proc git {args} { return [eval exec git $args] } -proc error_popup {msg} { - set title [appname] - if {[reponame] ne {}} { - append title " ([reponame])" - } - set cmd [list tk_messageBox \ - -icon error \ - -type ok \ - -title "$title: error" \ - -message $msg] - if {[winfo ismapped .]} { - lappend cmd -parent . - } - eval $cmd -} - -proc warn_popup {msg} { - set title [appname] - if {[reponame] ne {}} { - append title " ([reponame])" - } - set cmd [list tk_messageBox \ - -icon warning \ - -type ok \ - -title "$title: warning" \ - -message $msg] - if {[winfo ismapped .]} { - lappend cmd -parent . - } - eval $cmd -} - -proc info_popup {msg {parent .}} { - set title [appname] - if {[reponame] ne {}} { - append title " ([reponame])" - } - tk_messageBox \ - -parent $parent \ - -icon info \ - -type ok \ - -title $title \ - -message $msg -} - -proc ask_popup {msg} { - set title [appname] - if {[reponame] ne {}} { - append title " ([reponame])" - } - return [tk_messageBox \ - -parent . \ - -icon question \ - -type yesno \ - -title $title \ - -message $msg] -} - auto_load tk_optionMenu rename tk_optionMenu real__tkOptionMenu proc tk_optionMenu {w varName args} { @@ -664,7 +580,7 @@ proc read_ls_others {fd after} { } proc rescan_done {fd buf after} { - global rescan_active + global rescan_active current_diff_path global file_states repo_config upvar $buf to_clear @@ -676,7 +592,7 @@ proc rescan_done {fd buf after} { prune_selection unlock_index display_all_files - reshow_diff + if {$current_diff_path ne {}} reshow_diff uplevel #0 $after } @@ -692,2983 +608,195 @@ proc prune_selection {} { ###################################################################### ## -## diff +## ui helpers -proc clear_diff {} { - global ui_diff current_diff_path current_diff_header - global ui_index ui_workdir +proc mapicon {w state path} { + global all_icons - $ui_diff conf -state normal - $ui_diff delete 0.0 end - $ui_diff conf -state disabled + if {[catch {set r $all_icons($state$w)}]} { + puts "error: no icon for $w state={$state} $path" + return file_plain + } + return $r +} - set current_diff_path {} - set current_diff_header {} +proc mapdesc {state path} { + global all_descs - $ui_index tag remove in_diff 0.0 end - $ui_workdir tag remove in_diff 0.0 end + if {[catch {set r $all_descs($state)}]} { + puts "error: no desc for state={$state} $path" + return $state + } + return $r } -proc reshow_diff {} { - global ui_status_value file_states file_lists - global current_diff_path current_diff_side - - set p $current_diff_path - if {$p eq {}} { - # No diff is being shown. - } elseif {$current_diff_side eq {} - || [catch {set s $file_states($p)}] - || [lsearch -sorted -exact $file_lists($current_diff_side) $p] == -1} { - clear_diff - } else { - show_diff $p $current_diff_side - } +proc escape_path {path} { + regsub -all {\\} $path "\\\\" path + regsub -all "\n" $path "\\n" path + return $path } -proc handle_empty_diff {} { - global current_diff_path file_states file_lists +proc short_path {path} { + return [escape_path [lindex [file split $path] end]] +} - set path $current_diff_path - set s $file_states($path) - if {[lindex $s 0] ne {_M}} return +set next_icon_id 0 +set null_sha1 [string repeat 0 40] - info_popup "No differences detected. +proc merge_state {path new_state {head_info {}} {index_info {}}} { + global file_states next_icon_id null_sha1 -[short_path $path] has no changes. + set s0 [string index $new_state 0] + set s1 [string index $new_state 1] -The modification date of this file was updated by another application, but the content within the file was not changed. + if {[catch {set info $file_states($path)}]} { + set state __ + set icon n[incr next_icon_id] + } else { + set state [lindex $info 0] + set icon [lindex $info 1] + if {$head_info eq {}} {set head_info [lindex $info 2]} + if {$index_info eq {}} {set index_info [lindex $info 3]} + } -A rescan will be automatically started to find other files which may have the same state." + if {$s0 eq {?}} {set s0 [string index $state 0]} \ + elseif {$s0 eq {_}} {set s0 _} - clear_diff - display_file $path __ - rescan {set ui_status_value {Ready.}} 0 -} + if {$s1 eq {?}} {set s1 [string index $state 1]} \ + elseif {$s1 eq {_}} {set s1 _} -proc show_diff {path w {lno {}}} { - global file_states file_lists - global is_3way_diff diff_active repo_config - global ui_diff ui_status_value ui_index ui_workdir - global current_diff_path current_diff_side current_diff_header + if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} { + set head_info [list 0 $null_sha1] + } elseif {$s0 ne {_} && [string index $state 0] eq {_} + && $head_info eq {}} { + set head_info $index_info + } + + set file_states($path) [list $s0$s1 $icon \ + $head_info $index_info \ + ] + return $state +} - if {$diff_active || ![lock_index read]} return +proc display_file_helper {w path icon_name old_m new_m} { + global file_lists - clear_diff - if {$lno == {}} { + if {$new_m eq {_}} { set lno [lsearch -sorted -exact $file_lists($w) $path] if {$lno >= 0} { + set file_lists($w) [lreplace $file_lists($w) $lno $lno] incr lno + $w conf -state normal + $w delete $lno.0 [expr {$lno + 1}].0 + $w conf -state disabled } + } elseif {$old_m eq {_} && $new_m ne {_}} { + lappend file_lists($w) $path + set file_lists($w) [lsort -unique $file_lists($w)] + set lno [lsearch -sorted -exact $file_lists($w) $path] + incr lno + $w conf -state normal + $w image create $lno.0 \ + -align center -padx 5 -pady 1 \ + -name $icon_name \ + -image [mapicon $w $new_m $path] + $w insert $lno.1 "[escape_path $path]\n" + $w conf -state disabled + } elseif {$old_m ne $new_m} { + $w conf -state normal + $w image conf $icon_name -image [mapicon $w $new_m $path] + $w conf -state disabled } - if {$lno >= 1} { - $w tag add in_diff $lno.0 [expr {$lno + 1}].0 - } +} +proc display_file {path state} { + global file_states selected_paths + global ui_index ui_workdir + + set old_m [merge_state $path $state] set s $file_states($path) - set m [lindex $s 0] - set is_3way_diff 0 - set diff_active 1 - set current_diff_path $path - set current_diff_side $w - set current_diff_header {} - set ui_status_value "Loading diff of [escape_path $path]..." - - # - Git won't give us the diff, there's nothing to compare to! - # - if {$m eq {_O}} { - set max_sz [expr {128 * 1024}] - if {[catch { - set fd [open $path r] - set content [read $fd $max_sz] - close $fd - set sz [file size $path] - } err ]} { - set diff_active 0 - unlock_index - set ui_status_value "Unable to display [escape_path $path]" - error_popup "Error loading file:\n\n$err" - return - } - $ui_diff conf -state normal - if {![catch {set type [exec file $path]}]} { - set n [string length $path] - if {[string equal -length $n $path $type]} { - set type [string range $type $n end] - regsub {^:?\s*} $type {} type - } - $ui_diff insert end "* $type\n" d_@ - } - if {[string first "\0" $content] != -1} { - $ui_diff insert end \ - "* Binary file (not showing content)." \ - d_@ - } else { - if {$sz > $max_sz} { - $ui_diff insert end \ -"* Untracked file is $sz bytes. -* Showing only first $max_sz bytes. -" d_@ - } - $ui_diff insert end $content - if {$sz > $max_sz} { - $ui_diff insert end " -* Untracked file clipped here by [appname]. -* To see the entire file, use an external editor. -" d_@ - } - } - $ui_diff conf -state disabled - set diff_active 0 - unlock_index - set ui_status_value {Ready.} - return - } + set new_m [lindex $s 0] + set icon_name [lindex $s 1] - set cmd [list | git] - if {$w eq $ui_index} { - lappend cmd diff-index - lappend cmd --cached - } elseif {$w eq $ui_workdir} { - if {[string index $m 0] eq {U}} { - lappend cmd diff - } else { - lappend cmd diff-files - } + set o [string index $old_m 0] + set n [string index $new_m 0] + if {$o eq {U}} { + set o _ } - - lappend cmd -p - lappend cmd --no-color - if {$repo_config(gui.diffcontext) > 0} { - lappend cmd "-U$repo_config(gui.diffcontext)" + if {$n eq {U}} { + set n _ } - if {$w eq $ui_index} { - lappend cmd [PARENT] + display_file_helper $ui_index $path $icon_name $o $n + + if {[string index $old_m 0] eq {U}} { + set o U + } else { + set o [string index $old_m 1] } - lappend cmd -- - lappend cmd $path - - if {[catch {set fd [open $cmd r]} err]} { - set diff_active 0 - unlock_index - set ui_status_value "Unable to display [escape_path $path]" - error_popup "Error loading diff:\n\n$err" - return + if {[string index $new_m 0] eq {U}} { + set n U + } else { + set n [string index $new_m 1] } + display_file_helper $ui_workdir $path $icon_name $o $n - fconfigure $fd \ - -blocking 0 \ - -encoding binary \ - -translation binary - fileevent $fd readable [list read_diff $fd] -} - -proc read_diff {fd} { - global ui_diff ui_status_value diff_active - global is_3way_diff current_diff_header - - $ui_diff conf -state normal - while {[gets $fd line] >= 0} { - # -- Cleanup uninteresting diff header lines. - # - if { [string match {diff --git *} $line] - || [string match {diff --cc *} $line] - || [string match {diff --combined *} $line] - || [string match {--- *} $line] - || [string match {+++ *} $line]} { - append current_diff_header $line "\n" - continue - } - if {[string match {index *} $line]} continue - if {$line eq {deleted file mode 120000}} { - set line "deleted symlink" - } - - # -- Automatically detect if this is a 3 way diff. - # - if {[string match {@@@ *} $line]} {set is_3way_diff 1} - - if {[string match {mode *} $line] - || [string match {new file *} $line] - || [string match {deleted file *} $line] - || [string match {Binary files * and * differ} $line] - || $line eq {\ No newline at end of file} - || [regexp {^\* Unmerged path } $line]} { - set tags {} - } elseif {$is_3way_diff} { - set op [string range $line 0 1] - switch -- $op { - { } {set tags {}} - {@@} {set tags d_@} - { +} {set tags d_s+} - { -} {set tags d_s-} - {+ } {set tags d_+s} - {- } {set tags d_-s} - {--} {set tags d_--} - {++} { - if {[regexp {^\+\+([<>]{7} |={7})} $line _g op]} { - set line [string replace $line 0 1 { }] - set tags d$op - } else { - set tags d_++ - } - } - default { - puts "error: Unhandled 3 way diff marker: {$op}" - set tags {} - } - } - } else { - set op [string index $line 0] - switch -- $op { - { } {set tags {}} - {@} {set tags d_@} - {-} {set tags d_-} - {+} { - if {[regexp {^\+([<>]{7} |={7})} $line _g op]} { - set line [string replace $line 0 0 { }] - set tags d$op - } else { - set tags d_+ - } - } - default { - puts "error: Unhandled 2 way diff marker: {$op}" - set tags {} - } - } - } - $ui_diff insert end $line $tags - if {[string index $line end] eq "\r"} { - $ui_diff tag add d_cr {end - 2c} - } - $ui_diff insert end "\n" $tags + if {$new_m eq {__}} { + unset file_states($path) + catch {unset selected_paths($path)} } - $ui_diff conf -state disabled +} - if {[eof $fd]} { - close $fd - set diff_active 0 - unlock_index - set ui_status_value {Ready.} +proc display_all_files_helper {w path icon_name m} { + global file_lists - if {[$ui_diff index end] eq {2.0}} { - handle_empty_diff - } - } + lappend file_lists($w) $path + set lno [expr {[lindex [split [$w index end] .] 0] - 1}] + $w image create end \ + -align center -padx 5 -pady 1 \ + -name $icon_name \ + -image [mapicon $w $m $path] + $w insert end "[escape_path $path]\n" } -proc apply_hunk {x y} { - global current_diff_path current_diff_header current_diff_side - global ui_diff ui_index file_states - - if {$current_diff_path eq {} || $current_diff_header eq {}} return - if {![lock_index apply_hunk]} return +proc display_all_files {} { + global ui_index ui_workdir + global file_states file_lists + global last_clicked - set apply_cmd {git apply --cached --whitespace=nowarn} - set mi [lindex $file_states($current_diff_path) 0] - if {$current_diff_side eq $ui_index} { - set mode unstage - lappend apply_cmd --reverse - if {[string index $mi 0] ne {M}} { - unlock_index - return - } - } else { - set mode stage - if {[string index $mi 1] ne {M}} { - unlock_index - return - } - } + $ui_index conf -state normal + $ui_workdir conf -state normal - set s_lno [lindex [split [$ui_diff index @$x,$y] .] 0] - set s_lno [$ui_diff search -backwards -regexp ^@@ $s_lno.0 0.0] - if {$s_lno eq {}} { - unlock_index - return - } + $ui_index delete 0.0 end + $ui_workdir delete 0.0 end + set last_clicked {} - set e_lno [$ui_diff search -forwards -regexp ^@@ "$s_lno + 1 lines" end] - if {$e_lno eq {}} { - set e_lno end - } + set file_lists($ui_index) [list] + set file_lists($ui_workdir) [list] - if {[catch { - set p [open "| $apply_cmd" w] - fconfigure $p -translation binary -encoding binary - puts -nonewline $p $current_diff_header - puts -nonewline $p [$ui_diff get $s_lno $e_lno] - close $p} err]} { - error_popup "Failed to $mode selected hunk.\n\n$err" - unlock_index - return - } + foreach path [lsort [array names file_states]] { + set s $file_states($path) + set m [lindex $s 0] + set icon_name [lindex $s 1] - $ui_diff conf -state normal - $ui_diff delete $s_lno $e_lno - $ui_diff conf -state disabled + set s [string index $m 0] + if {$s ne {U} && $s ne {_}} { + display_all_files_helper $ui_index $path \ + $icon_name $s + } - if {[$ui_diff get 1.0 end] eq "\n"} { - set o _ - } else { - set o ? + if {[string index $m 0] eq {U}} { + set s U + } else { + set s [string index $m 1] + } + if {$s ne {_}} { + display_all_files_helper $ui_workdir $path \ + $icon_name $s + } } - if {$current_diff_side eq $ui_index} { - set mi ${o}M - } elseif {[string index $mi 0] eq {_}} { - set mi M$o - } else { - set mi ?$o - } - unlock_index - display_file $current_diff_path $mi - if {$o eq {_}} { - clear_diff - } -} - -###################################################################### -## -## commit - -proc load_last_commit {} { - global HEAD PARENT MERGE_HEAD commit_type ui_comm - global repo_config - - if {[llength $PARENT] == 0} { - error_popup {There is nothing to amend. - -You are about to create the initial commit. There is no commit before this to amend. -} - return - } - - repository_state curType curHEAD curMERGE_HEAD - if {$curType eq {merge}} { - error_popup {Cannot amend while merging. - -You are currently in the middle of a merge that has not been fully completed. You cannot amend the prior commit unless you first abort the current merge activity. -} - return - } - - set msg {} - set parents [list] - if {[catch { - set fd [open "| git cat-file commit $curHEAD" r] - fconfigure $fd -encoding binary -translation lf - if {[catch {set enc $repo_config(i18n.commitencoding)}]} { - set enc utf-8 - } - while {[gets $fd line] > 0} { - if {[string match {parent *} $line]} { - lappend parents [string range $line 7 end] - } elseif {[string match {encoding *} $line]} { - set enc [string tolower [string range $line 9 end]] - } - } - set msg [encoding convertfrom $enc [read $fd]] - set msg [string trim $msg] - close $fd - } err]} { - error_popup "Error loading commit data for amend:\n\n$err" - return - } - - set HEAD $curHEAD - set PARENT $parents - set MERGE_HEAD [list] - switch -- [llength $parents] { - 0 {set commit_type amend-initial} - 1 {set commit_type amend} - default {set commit_type amend-merge} - } - - $ui_comm delete 0.0 end - $ui_comm insert end $msg - $ui_comm edit reset - $ui_comm edit modified false - rescan {set ui_status_value {Ready.}} -} - -proc create_new_commit {} { - global commit_type ui_comm - - set commit_type normal - $ui_comm delete 0.0 end - $ui_comm edit reset - $ui_comm edit modified false - rescan {set ui_status_value {Ready.}} -} - -set GIT_COMMITTER_IDENT {} - -proc committer_ident {} { - global GIT_COMMITTER_IDENT - - if {$GIT_COMMITTER_IDENT eq {}} { - if {[catch {set me [git var GIT_COMMITTER_IDENT]} err]} { - error_popup "Unable to obtain your identity:\n\n$err" - return {} - } - if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \ - $me me GIT_COMMITTER_IDENT]} { - error_popup "Invalid GIT_COMMITTER_IDENT:\n\n$me" - return {} - } - } - - return $GIT_COMMITTER_IDENT -} - -proc commit_tree {} { - global HEAD commit_type file_states ui_comm repo_config - global ui_status_value pch_error - - if {[committer_ident] eq {}} return - if {![lock_index update]} return - - # -- Our in memory state should match the repository. - # - repository_state curType curHEAD curMERGE_HEAD - if {[string match amend* $commit_type] - && $curType eq {normal} - && $curHEAD eq $HEAD} { - } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} { - info_popup {Last scanned state does not match repository state. - -Another Git program has modified this repository since the last scan. A rescan must be performed before another commit can be created. - -The rescan will be automatically started now. -} - unlock_index - rescan {set ui_status_value {Ready.}} - return - } - - # -- At least one file should differ in the index. - # - set files_ready 0 - foreach path [array names file_states] { - switch -glob -- [lindex $file_states($path) 0] { - _? {continue} - A? - - D? - - M? {set files_ready 1} - U? { - error_popup "Unmerged files cannot be committed. - -File [short_path $path] has merge conflicts. You must resolve them and add the file before committing. -" - unlock_index - return - } - default { - error_popup "Unknown file state [lindex $s 0] detected. - -File [short_path $path] cannot be committed by this program. -" - } - } - } - if {!$files_ready && ![string match *merge $curType]} { - info_popup {No changes to commit. - -You must add at least 1 file before you can commit. -} - unlock_index - return - } - - # -- A message is required. - # - set msg [string trim [$ui_comm get 1.0 end]] - regsub -all -line {[ \t\r]+$} $msg {} msg - if {$msg eq {}} { - error_popup {Please supply a commit message. - -A good commit message has the following format: - -- First line: Describe in one sentance what you did. -- Second line: Blank -- Remaining lines: Describe why this change is good. -} - unlock_index - return - } - - # -- Run the pre-commit hook. - # - set pchook [gitdir hooks pre-commit] - - # On Cygwin [file executable] might lie so we need to ask - # the shell if the hook is executable. Yes that's annoying. - # - if {[is_Cygwin] && [file isfile $pchook]} { - set pchook [list sh -c [concat \ - "if test -x \"$pchook\";" \ - "then exec \"$pchook\" 2>&1;" \ - "fi"]] - } elseif {[file executable $pchook]} { - set pchook [list $pchook |& cat] - } else { - commit_writetree $curHEAD $msg - return - } - - set ui_status_value {Calling pre-commit hook...} - set pch_error {} - set fd_ph [open "| $pchook" r] - fconfigure $fd_ph -blocking 0 -translation binary - fileevent $fd_ph readable \ - [list commit_prehook_wait $fd_ph $curHEAD $msg] -} - -proc commit_prehook_wait {fd_ph curHEAD msg} { - global pch_error ui_status_value - - append pch_error [read $fd_ph] - fconfigure $fd_ph -blocking 1 - if {[eof $fd_ph]} { - if {[catch {close $fd_ph}]} { - set ui_status_value {Commit declined by pre-commit hook.} - hook_failed_popup pre-commit $pch_error - unlock_index - } else { - commit_writetree $curHEAD $msg - } - set pch_error {} - return - } - fconfigure $fd_ph -blocking 0 -} - -proc commit_writetree {curHEAD msg} { - global ui_status_value - - set ui_status_value {Committing changes...} - set fd_wt [open "| git write-tree" r] - fileevent $fd_wt readable \ - [list commit_committree $fd_wt $curHEAD $msg] -} - -proc commit_committree {fd_wt curHEAD msg} { - global HEAD PARENT MERGE_HEAD commit_type - global all_heads current_branch - global ui_status_value ui_comm selected_commit_type - global file_states selected_paths rescan_active - global repo_config - - gets $fd_wt tree_id - if {$tree_id eq {} || [catch {close $fd_wt} err]} { - error_popup "write-tree failed:\n\n$err" - set ui_status_value {Commit failed.} - unlock_index - return - } - - # -- Verify this wasn't an empty change. - # - if {$commit_type eq {normal}} { - set old_tree [git rev-parse "$PARENT^{tree}"] - if {$tree_id eq $old_tree} { - info_popup {No changes to commit. - -No files were modified by this commit and it was not a merge commit. - -A rescan will be automatically started now. -} - unlock_index - rescan {set ui_status_value {No changes to commit.}} - return - } - } - - # -- Build the message. - # - set msg_p [gitdir COMMIT_EDITMSG] - set msg_wt [open $msg_p w] - if {[catch {set enc $repo_config(i18n.commitencoding)}]} { - set enc utf-8 - } - fconfigure $msg_wt -encoding binary -translation binary - puts -nonewline $msg_wt [encoding convertto $enc $msg] - close $msg_wt - - # -- Create the commit. - # - set cmd [list commit-tree $tree_id] - foreach p [concat $PARENT $MERGE_HEAD] { - lappend cmd -p $p - } - lappend cmd <$msg_p - if {[catch {set cmt_id [eval git $cmd]} err]} { - error_popup "commit-tree failed:\n\n$err" - set ui_status_value {Commit failed.} - unlock_index - return - } - - # -- Update the HEAD ref. - # - set reflogm commit - if {$commit_type ne {normal}} { - append reflogm " ($commit_type)" - } - set i [string first "\n" $msg] - if {$i >= 0} { - set subject [string range $msg 0 [expr {$i - 1}]] - } else { - set subject $msg - } - append reflogm {: } $subject - if {[catch { - git update-ref -m $reflogm HEAD $cmt_id $curHEAD - } err]} { - error_popup "update-ref failed:\n\n$err" - set ui_status_value {Commit failed.} - unlock_index - return - } - - # -- Cleanup after ourselves. - # - catch {file delete $msg_p} - catch {file delete [gitdir MERGE_HEAD]} - catch {file delete [gitdir MERGE_MSG]} - catch {file delete [gitdir SQUASH_MSG]} - catch {file delete [gitdir GITGUI_MSG]} - - # -- Let rerere do its thing. - # - if {[file isdirectory [gitdir rr-cache]]} { - catch {git rerere} - } - - # -- Run the post-commit hook. - # - set pchook [gitdir hooks post-commit] - if {[is_Cygwin] && [file isfile $pchook]} { - set pchook [list sh -c [concat \ - "if test -x \"$pchook\";" \ - "then exec \"$pchook\";" \ - "fi"]] - } elseif {![file executable $pchook]} { - set pchook {} - } - if {$pchook ne {}} { - catch {exec $pchook &} - } - - $ui_comm delete 0.0 end - $ui_comm edit reset - $ui_comm edit modified false - - if {[is_enabled singlecommit]} do_quit - - # -- Make sure our current branch exists. - # - if {$commit_type eq {initial}} { - lappend all_heads $current_branch - set all_heads [lsort -unique $all_heads] - populate_branch_menu - } - - # -- Update in memory status - # - set selected_commit_type new - set commit_type normal - set HEAD $cmt_id - set PARENT $cmt_id - set MERGE_HEAD [list] - - foreach path [array names file_states] { - set s $file_states($path) - set m [lindex $s 0] - switch -glob -- $m { - _O - - _M - - _D {continue} - __ - - A_ - - M_ - - D_ { - unset file_states($path) - catch {unset selected_paths($path)} - } - DO { - set file_states($path) [list _O [lindex $s 1] {} {}] - } - AM - - AD - - MM - - MD { - set file_states($path) [list \ - _[string index $m 1] \ - [lindex $s 1] \ - [lindex $s 3] \ - {}] - } - } - } - - display_all_files - unlock_index - reshow_diff - set ui_status_value \ - "Created commit [string range $cmt_id 0 7]: $subject" -} - -###################################################################### -## -## fetch push - -proc fetch_from {remote} { - set w [new_console \ - "fetch $remote" \ - "Fetching new changes from $remote"] - set cmd [list git fetch] - lappend cmd $remote - console_exec $w $cmd console_done -} - -proc push_to {remote} { - set w [new_console \ - "push $remote" \ - "Pushing changes to $remote"] - set cmd [list git push] - lappend cmd -v - lappend cmd $remote - console_exec $w $cmd console_done -} - -###################################################################### -## -## ui helpers - -proc mapicon {w state path} { - global all_icons - - if {[catch {set r $all_icons($state$w)}]} { - puts "error: no icon for $w state={$state} $path" - return file_plain - } - return $r -} - -proc mapdesc {state path} { - global all_descs - - if {[catch {set r $all_descs($state)}]} { - puts "error: no desc for state={$state} $path" - return $state - } - return $r -} - -proc escape_path {path} { - regsub -all {\\} $path "\\\\" path - regsub -all "\n" $path "\\n" path - return $path -} - -proc short_path {path} { - return [escape_path [lindex [file split $path] end]] -} - -set next_icon_id 0 -set null_sha1 [string repeat 0 40] - -proc merge_state {path new_state {head_info {}} {index_info {}}} { - global file_states next_icon_id null_sha1 - - set s0 [string index $new_state 0] - set s1 [string index $new_state 1] - - if {[catch {set info $file_states($path)}]} { - set state __ - set icon n[incr next_icon_id] - } else { - set state [lindex $info 0] - set icon [lindex $info 1] - if {$head_info eq {}} {set head_info [lindex $info 2]} - if {$index_info eq {}} {set index_info [lindex $info 3]} - } - - if {$s0 eq {?}} {set s0 [string index $state 0]} \ - elseif {$s0 eq {_}} {set s0 _} - - if {$s1 eq {?}} {set s1 [string index $state 1]} \ - elseif {$s1 eq {_}} {set s1 _} - - if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} { - set head_info [list 0 $null_sha1] - } elseif {$s0 ne {_} && [string index $state 0] eq {_} - && $head_info eq {}} { - set head_info $index_info - } - - set file_states($path) [list $s0$s1 $icon \ - $head_info $index_info \ - ] - return $state -} - -proc display_file_helper {w path icon_name old_m new_m} { - global file_lists - - if {$new_m eq {_}} { - set lno [lsearch -sorted -exact $file_lists($w) $path] - if {$lno >= 0} { - set file_lists($w) [lreplace $file_lists($w) $lno $lno] - incr lno - $w conf -state normal - $w delete $lno.0 [expr {$lno + 1}].0 - $w conf -state disabled - } - } elseif {$old_m eq {_} && $new_m ne {_}} { - lappend file_lists($w) $path - set file_lists($w) [lsort -unique $file_lists($w)] - set lno [lsearch -sorted -exact $file_lists($w) $path] - incr lno - $w conf -state normal - $w image create $lno.0 \ - -align center -padx 5 -pady 1 \ - -name $icon_name \ - -image [mapicon $w $new_m $path] - $w insert $lno.1 "[escape_path $path]\n" - $w conf -state disabled - } elseif {$old_m ne $new_m} { - $w conf -state normal - $w image conf $icon_name -image [mapicon $w $new_m $path] - $w conf -state disabled - } -} - -proc display_file {path state} { - global file_states selected_paths - global ui_index ui_workdir - - set old_m [merge_state $path $state] - set s $file_states($path) - set new_m [lindex $s 0] - set icon_name [lindex $s 1] - - set o [string index $old_m 0] - set n [string index $new_m 0] - if {$o eq {U}} { - set o _ - } - if {$n eq {U}} { - set n _ - } - display_file_helper $ui_index $path $icon_name $o $n - - if {[string index $old_m 0] eq {U}} { - set o U - } else { - set o [string index $old_m 1] - } - if {[string index $new_m 0] eq {U}} { - set n U - } else { - set n [string index $new_m 1] - } - display_file_helper $ui_workdir $path $icon_name $o $n - - if {$new_m eq {__}} { - unset file_states($path) - catch {unset selected_paths($path)} - } -} - -proc display_all_files_helper {w path icon_name m} { - global file_lists - - lappend file_lists($w) $path - set lno [expr {[lindex [split [$w index end] .] 0] - 1}] - $w image create end \ - -align center -padx 5 -pady 1 \ - -name $icon_name \ - -image [mapicon $w $m $path] - $w insert end "[escape_path $path]\n" -} - -proc display_all_files {} { - global ui_index ui_workdir - global file_states file_lists - global last_clicked - - $ui_index conf -state normal - $ui_workdir conf -state normal - - $ui_index delete 0.0 end - $ui_workdir delete 0.0 end - set last_clicked {} - - set file_lists($ui_index) [list] - set file_lists($ui_workdir) [list] - - foreach path [lsort [array names file_states]] { - set s $file_states($path) - set m [lindex $s 0] - set icon_name [lindex $s 1] - - set s [string index $m 0] - if {$s ne {U} && $s ne {_}} { - display_all_files_helper $ui_index $path \ - $icon_name $s - } - - if {[string index $m 0] eq {U}} { - set s U - } else { - set s [string index $m 1] - } - if {$s ne {_}} { - display_all_files_helper $ui_workdir $path \ - $icon_name $s - } - } - - $ui_index conf -state disabled - $ui_workdir conf -state disabled -} - -proc update_indexinfo {msg pathList after} { - global update_index_cp ui_status_value - - if {![lock_index update]} return - - set update_index_cp 0 - set pathList [lsort $pathList] - set totalCnt [llength $pathList] - set batch [expr {int($totalCnt * .01) + 1}] - if {$batch > 25} {set batch 25} - - set ui_status_value [format \ - "$msg... %i/%i files (%.2f%%)" \ - $update_index_cp \ - $totalCnt \ - 0.0] - set fd [open "| git update-index -z --index-info" w] - fconfigure $fd \ - -blocking 0 \ - -buffering full \ - -buffersize 512 \ - -encoding binary \ - -translation binary - fileevent $fd writable [list \ - write_update_indexinfo \ - $fd \ - $pathList \ - $totalCnt \ - $batch \ - $msg \ - $after \ - ] -} - -proc write_update_indexinfo {fd pathList totalCnt batch msg after} { - global update_index_cp ui_status_value - global file_states current_diff_path - - if {$update_index_cp >= $totalCnt} { - close $fd - unlock_index - uplevel #0 $after - return - } - - for {set i $batch} \ - {$update_index_cp < $totalCnt && $i > 0} \ - {incr i -1} { - set path [lindex $pathList $update_index_cp] - incr update_index_cp - - set s $file_states($path) - switch -glob -- [lindex $s 0] { - A? {set new _O} - M? {set new _M} - D_ {set new _D} - D? {set new _?} - ?? {continue} - } - set info [lindex $s 2] - if {$info eq {}} continue - - puts -nonewline $fd "$info\t[encoding convertto $path]\0" - display_file $path $new - } - - set ui_status_value [format \ - "$msg... %i/%i files (%.2f%%)" \ - $update_index_cp \ - $totalCnt \ - [expr {100.0 * $update_index_cp / $totalCnt}]] -} - -proc update_index {msg pathList after} { - global update_index_cp ui_status_value - - if {![lock_index update]} return - - set update_index_cp 0 - set pathList [lsort $pathList] - set totalCnt [llength $pathList] - set batch [expr {int($totalCnt * .01) + 1}] - if {$batch > 25} {set batch 25} - - set ui_status_value [format \ - "$msg... %i/%i files (%.2f%%)" \ - $update_index_cp \ - $totalCnt \ - 0.0] - set fd [open "| git update-index --add --remove -z --stdin" w] - fconfigure $fd \ - -blocking 0 \ - -buffering full \ - -buffersize 512 \ - -encoding binary \ - -translation binary - fileevent $fd writable [list \ - write_update_index \ - $fd \ - $pathList \ - $totalCnt \ - $batch \ - $msg \ - $after \ - ] -} - -proc write_update_index {fd pathList totalCnt batch msg after} { - global update_index_cp ui_status_value - global file_states current_diff_path - - if {$update_index_cp >= $totalCnt} { - close $fd - unlock_index - uplevel #0 $after - return - } - - for {set i $batch} \ - {$update_index_cp < $totalCnt && $i > 0} \ - {incr i -1} { - set path [lindex $pathList $update_index_cp] - incr update_index_cp - - switch -glob -- [lindex $file_states($path) 0] { - AD {set new __} - ?D {set new D_} - _O - - AM {set new A_} - U? { - if {[file exists $path]} { - set new M_ - } else { - set new D_ - } - } - ?M {set new M_} - ?? {continue} - } - puts -nonewline $fd "[encoding convertto $path]\0" - display_file $path $new - } - - set ui_status_value [format \ - "$msg... %i/%i files (%.2f%%)" \ - $update_index_cp \ - $totalCnt \ - [expr {100.0 * $update_index_cp / $totalCnt}]] -} - -proc checkout_index {msg pathList after} { - global update_index_cp ui_status_value - - if {![lock_index update]} return - - set update_index_cp 0 - set pathList [lsort $pathList] - set totalCnt [llength $pathList] - set batch [expr {int($totalCnt * .01) + 1}] - if {$batch > 25} {set batch 25} - - set ui_status_value [format \ - "$msg... %i/%i files (%.2f%%)" \ - $update_index_cp \ - $totalCnt \ - 0.0] - set cmd [list git checkout-index] - lappend cmd --index - lappend cmd --quiet - lappend cmd --force - lappend cmd -z - lappend cmd --stdin - set fd [open "| $cmd " w] - fconfigure $fd \ - -blocking 0 \ - -buffering full \ - -buffersize 512 \ - -encoding binary \ - -translation binary - fileevent $fd writable [list \ - write_checkout_index \ - $fd \ - $pathList \ - $totalCnt \ - $batch \ - $msg \ - $after \ - ] -} - -proc write_checkout_index {fd pathList totalCnt batch msg after} { - global update_index_cp ui_status_value - global file_states current_diff_path - - if {$update_index_cp >= $totalCnt} { - close $fd - unlock_index - uplevel #0 $after - return - } - - for {set i $batch} \ - {$update_index_cp < $totalCnt && $i > 0} \ - {incr i -1} { - set path [lindex $pathList $update_index_cp] - incr update_index_cp - switch -glob -- [lindex $file_states($path) 0] { - U? {continue} - ?M - - ?D { - puts -nonewline $fd "[encoding convertto $path]\0" - display_file $path ?_ - } - } - } - - set ui_status_value [format \ - "$msg... %i/%i files (%.2f%%)" \ - $update_index_cp \ - $totalCnt \ - [expr {100.0 * $update_index_cp / $totalCnt}]] -} - -###################################################################### -## -## branch management - -proc is_tracking_branch {name} { - global tracking_branches - - if {![catch {set info $tracking_branches($name)}]} { - return 1 - } - foreach t [array names tracking_branches] { - if {[string match {*/\*} $t] && [string match $t $name]} { - return 1 - } - } - return 0 -} - -proc load_all_heads {} { - global all_heads - - set all_heads [list] - set fd [open "| git for-each-ref --format=%(refname) refs/heads" r] - while {[gets $fd line] > 0} { - if {[is_tracking_branch $line]} continue - if {![regsub ^refs/heads/ $line {} name]} continue - lappend all_heads $name - } - close $fd - - set all_heads [lsort $all_heads] -} - -proc populate_branch_menu {} { - global all_heads disable_on_lock - - set m .mbar.branch - set last [$m index last] - for {set i 0} {$i <= $last} {incr i} { - if {[$m type $i] eq {separator}} { - $m delete $i last - set new_dol [list] - foreach a $disable_on_lock { - if {[lindex $a 0] ne $m || [lindex $a 2] < $i} { - lappend new_dol $a - } - } - set disable_on_lock $new_dol - break - } - } - - if {$all_heads ne {}} { - $m add separator - } - foreach b $all_heads { - $m add radiobutton \ - -label $b \ - -command [list switch_branch $b] \ - -variable current_branch \ - -value $b - lappend disable_on_lock \ - [list $m entryconf [$m index last] -state] - } -} - -proc all_tracking_branches {} { - global tracking_branches - - set all_trackings {} - set cmd {} - foreach name [array names tracking_branches] { - if {[regsub {/\*$} $name {} name]} { - lappend cmd $name - } else { - regsub ^refs/(heads|remotes)/ $name {} name - lappend all_trackings $name - } - } - - if {$cmd ne {}} { - set fd [open "| git for-each-ref --format=%(refname) $cmd" r] - while {[gets $fd name] > 0} { - regsub ^refs/(heads|remotes)/ $name {} name - lappend all_trackings $name - } - close $fd - } - - return [lsort -unique $all_trackings] -} - -proc load_all_tags {} { - set all_tags [list] - set fd [open "| git for-each-ref --format=%(refname) refs/tags" r] - while {[gets $fd line] > 0} { - if {![regsub ^refs/tags/ $line {} name]} continue - lappend all_tags $name - } - close $fd - - return [lsort $all_tags] -} - -proc do_create_branch_action {w} { - global all_heads null_sha1 repo_config - global create_branch_checkout create_branch_revtype - global create_branch_head create_branch_trackinghead - global create_branch_name create_branch_revexp - global create_branch_tag - - set newbranch $create_branch_name - if {$newbranch eq {} - || $newbranch eq $repo_config(gui.newbranchtemplate)} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Please supply a branch name." - focus $w.desc.name_t - return - } - if {![catch {git show-ref --verify -- "refs/heads/$newbranch"}]} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Branch '$newbranch' already exists." - focus $w.desc.name_t - return - } - if {[catch {git check-ref-format "heads/$newbranch"}]} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "We do not like '$newbranch' as a branch name." - focus $w.desc.name_t - return - } - - set rev {} - switch -- $create_branch_revtype { - head {set rev $create_branch_head} - tracking {set rev $create_branch_trackinghead} - tag {set rev $create_branch_tag} - expression {set rev $create_branch_revexp} - } - if {[catch {set cmt [git rev-parse --verify "${rev}^0"]}]} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Invalid starting revision: $rev" - return - } - if {[catch { - git update-ref \ - -m "branch: Created from $rev" \ - "refs/heads/$newbranch" \ - $cmt \ - $null_sha1 - } err]} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Failed to create '$newbranch'.\n\n$err" - return - } - - lappend all_heads $newbranch - set all_heads [lsort $all_heads] - populate_branch_menu - destroy $w - if {$create_branch_checkout} { - switch_branch $newbranch - } -} - -proc radio_selector {varname value args} { - upvar #0 $varname var - set var $value -} - -trace add variable create_branch_head write \ - [list radio_selector create_branch_revtype head] -trace add variable create_branch_trackinghead write \ - [list radio_selector create_branch_revtype tracking] -trace add variable create_branch_tag write \ - [list radio_selector create_branch_revtype tag] - -trace add variable delete_branch_head write \ - [list radio_selector delete_branch_checktype head] -trace add variable delete_branch_trackinghead write \ - [list radio_selector delete_branch_checktype tracking] - -proc do_create_branch {} { - global all_heads current_branch repo_config - global create_branch_checkout create_branch_revtype - global create_branch_head create_branch_trackinghead - global create_branch_name create_branch_revexp - global create_branch_tag - - set w .branch_editor - toplevel $w - wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - - label $w.header -text {Create New Branch} \ - -font font_uibold - pack $w.header -side top -fill x - - frame $w.buttons - button $w.buttons.create -text Create \ - -default active \ - -command [list do_create_branch_action $w] - pack $w.buttons.create -side right - button $w.buttons.cancel -text {Cancel} \ - -command [list destroy $w] - pack $w.buttons.cancel -side right -padx 5 - pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - - labelframe $w.desc -text {Branch Description} - label $w.desc.name_l -text {Name:} - entry $w.desc.name_t \ - -borderwidth 1 \ - -relief sunken \ - -width 40 \ - -textvariable create_branch_name \ - -validate key \ - -validatecommand { - if {%d == 1 && [regexp {[~^:?*\[\0- ]} %S]} {return 0} - return 1 - } - grid $w.desc.name_l $w.desc.name_t -sticky we -padx {0 5} - grid columnconfigure $w.desc 1 -weight 1 - pack $w.desc -anchor nw -fill x -pady 5 -padx 5 - - labelframe $w.from -text {Starting Revision} - radiobutton $w.from.head_r \ - -text {Local Branch:} \ - -value head \ - -variable create_branch_revtype - eval tk_optionMenu $w.from.head_m create_branch_head $all_heads - grid $w.from.head_r $w.from.head_m -sticky w - set all_trackings [all_tracking_branches] - if {$all_trackings ne {}} { - set create_branch_trackinghead [lindex $all_trackings 0] - radiobutton $w.from.tracking_r \ - -text {Tracking Branch:} \ - -value tracking \ - -variable create_branch_revtype - eval tk_optionMenu $w.from.tracking_m \ - create_branch_trackinghead \ - $all_trackings - grid $w.from.tracking_r $w.from.tracking_m -sticky w - } - set all_tags [load_all_tags] - if {$all_tags ne {}} { - set create_branch_tag [lindex $all_tags 0] - radiobutton $w.from.tag_r \ - -text {Tag:} \ - -value tag \ - -variable create_branch_revtype - eval tk_optionMenu $w.from.tag_m create_branch_tag $all_tags - grid $w.from.tag_r $w.from.tag_m -sticky w - } - radiobutton $w.from.exp_r \ - -text {Revision Expression:} \ - -value expression \ - -variable create_branch_revtype - entry $w.from.exp_t \ - -borderwidth 1 \ - -relief sunken \ - -width 50 \ - -textvariable create_branch_revexp \ - -validate key \ - -validatecommand { - if {%d == 1 && [regexp {\s} %S]} {return 0} - if {%d == 1 && [string length %S] > 0} { - set create_branch_revtype expression - } - return 1 - } - grid $w.from.exp_r $w.from.exp_t -sticky we -padx {0 5} - grid columnconfigure $w.from 1 -weight 1 - pack $w.from -anchor nw -fill x -pady 5 -padx 5 - - labelframe $w.postActions -text {Post Creation Actions} - checkbutton $w.postActions.checkout \ - -text {Checkout after creation} \ - -variable create_branch_checkout - pack $w.postActions.checkout -anchor nw - pack $w.postActions -anchor nw -fill x -pady 5 -padx 5 - - set create_branch_checkout 1 - set create_branch_head $current_branch - set create_branch_revtype head - set create_branch_name $repo_config(gui.newbranchtemplate) - set create_branch_revexp {} - - bind $w " - grab $w - $w.desc.name_t icursor end - focus $w.desc.name_t - " - bind $w "destroy $w" - bind $w "do_create_branch_action $w;break" - wm title $w "[appname] ([reponame]): Create Branch" - tkwait window $w -} - -proc do_delete_branch_action {w} { - global all_heads - global delete_branch_checktype delete_branch_head delete_branch_trackinghead - - set check_rev {} - switch -- $delete_branch_checktype { - head {set check_rev $delete_branch_head} - tracking {set check_rev $delete_branch_trackinghead} - always {set check_rev {:none}} - } - if {$check_rev eq {:none}} { - set check_cmt {} - } elseif {[catch {set check_cmt [git rev-parse --verify "${check_rev}^0"]}]} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Invalid check revision: $check_rev" - return - } - - set to_delete [list] - set not_merged [list] - foreach i [$w.list.l curselection] { - set b [$w.list.l get $i] - if {[catch {set o [git rev-parse --verify $b]}]} continue - if {$check_cmt ne {}} { - if {$b eq $check_rev} continue - if {[catch {set m [git merge-base $o $check_cmt]}]} continue - if {$o ne $m} { - lappend not_merged $b - continue - } - } - lappend to_delete [list $b $o] - } - if {$not_merged ne {}} { - set msg "The following branches are not completely merged into $check_rev: - - - [join $not_merged "\n - "]" - tk_messageBox \ - -icon info \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message $msg - } - if {$to_delete eq {}} return - if {$delete_branch_checktype eq {always}} { - set msg {Recovering deleted branches is difficult. - -Delete the selected branches?} - if {[tk_messageBox \ - -icon warning \ - -type yesno \ - -title [wm title $w] \ - -parent $w \ - -message $msg] ne yes} { - return - } - } - - set failed {} - foreach i $to_delete { - set b [lindex $i 0] - set o [lindex $i 1] - if {[catch {git update-ref -d "refs/heads/$b" $o} err]} { - append failed " - $b: $err\n" - } else { - set x [lsearch -sorted -exact $all_heads $b] - if {$x >= 0} { - set all_heads [lreplace $all_heads $x $x] - } - } - } - - if {$failed ne {}} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Failed to delete branches:\n$failed" - } - - set all_heads [lsort $all_heads] - populate_branch_menu - destroy $w -} - -proc do_delete_branch {} { - global all_heads tracking_branches current_branch - global delete_branch_checktype delete_branch_head delete_branch_trackinghead - - set w .branch_editor - toplevel $w - wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - - label $w.header -text {Delete Local Branch} \ - -font font_uibold - pack $w.header -side top -fill x - - frame $w.buttons - button $w.buttons.create -text Delete \ - -command [list do_delete_branch_action $w] - pack $w.buttons.create -side right - button $w.buttons.cancel -text {Cancel} \ - -command [list destroy $w] - pack $w.buttons.cancel -side right -padx 5 - pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - - labelframe $w.list -text {Local Branches} - listbox $w.list.l \ - -height 10 \ - -width 70 \ - -selectmode extended \ - -yscrollcommand [list $w.list.sby set] - foreach h $all_heads { - if {$h ne $current_branch} { - $w.list.l insert end $h - } - } - scrollbar $w.list.sby -command [list $w.list.l yview] - pack $w.list.sby -side right -fill y - pack $w.list.l -side left -fill both -expand 1 - pack $w.list -fill both -expand 1 -pady 5 -padx 5 - - labelframe $w.validate -text {Delete Only If} - radiobutton $w.validate.head_r \ - -text {Merged Into Local Branch:} \ - -value head \ - -variable delete_branch_checktype - eval tk_optionMenu $w.validate.head_m delete_branch_head $all_heads - grid $w.validate.head_r $w.validate.head_m -sticky w - set all_trackings [all_tracking_branches] - if {$all_trackings ne {}} { - set delete_branch_trackinghead [lindex $all_trackings 0] - radiobutton $w.validate.tracking_r \ - -text {Merged Into Tracking Branch:} \ - -value tracking \ - -variable delete_branch_checktype - eval tk_optionMenu $w.validate.tracking_m \ - delete_branch_trackinghead \ - $all_trackings - grid $w.validate.tracking_r $w.validate.tracking_m -sticky w - } - radiobutton $w.validate.always_r \ - -text {Always (Do not perform merge checks)} \ - -value always \ - -variable delete_branch_checktype - grid $w.validate.always_r -columnspan 2 -sticky w - grid columnconfigure $w.validate 1 -weight 1 - pack $w.validate -anchor nw -fill x -pady 5 -padx 5 - - set delete_branch_head $current_branch - set delete_branch_checktype head - - bind $w "grab $w; focus $w" - bind $w "destroy $w" - wm title $w "[appname] ([reponame]): Delete Branch" - tkwait window $w -} - -proc switch_branch {new_branch} { - global HEAD commit_type current_branch repo_config - - if {![lock_index switch]} return - - # -- Our in memory state should match the repository. - # - repository_state curType curHEAD curMERGE_HEAD - if {[string match amend* $commit_type] - && $curType eq {normal} - && $curHEAD eq $HEAD} { - } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} { - info_popup {Last scanned state does not match repository state. - -Another Git program has modified this repository since the last scan. A rescan must be performed before the current branch can be changed. - -The rescan will be automatically started now. -} - unlock_index - rescan {set ui_status_value {Ready.}} - return - } - - # -- Don't do a pointless switch. - # - if {$current_branch eq $new_branch} { - unlock_index - return - } - - if {$repo_config(gui.trustmtime) eq {true}} { - switch_branch_stage2 {} $new_branch - } else { - set ui_status_value {Refreshing file status...} - set cmd [list git update-index] - lappend cmd -q - lappend cmd --unmerged - lappend cmd --ignore-missing - lappend cmd --refresh - set fd_rf [open "| $cmd" r] - fconfigure $fd_rf -blocking 0 -translation binary - fileevent $fd_rf readable \ - [list switch_branch_stage2 $fd_rf $new_branch] - } -} - -proc switch_branch_stage2 {fd_rf new_branch} { - global ui_status_value HEAD - - if {$fd_rf ne {}} { - read $fd_rf - if {![eof $fd_rf]} return - close $fd_rf - } - - set ui_status_value "Updating working directory to '$new_branch'..." - set cmd [list git read-tree] - lappend cmd -m - lappend cmd -u - lappend cmd --exclude-per-directory=.gitignore - lappend cmd $HEAD - lappend cmd $new_branch - set fd_rt [open "| $cmd" r] - fconfigure $fd_rt -blocking 0 -translation binary - fileevent $fd_rt readable \ - [list switch_branch_readtree_wait $fd_rt $new_branch] -} - -proc switch_branch_readtree_wait {fd_rt new_branch} { - global selected_commit_type commit_type HEAD MERGE_HEAD PARENT - global current_branch - global ui_comm ui_status_value - - # -- We never get interesting output on stdout; only stderr. - # - read $fd_rt - fconfigure $fd_rt -blocking 1 - if {![eof $fd_rt]} { - fconfigure $fd_rt -blocking 0 - return - } - - # -- The working directory wasn't in sync with the index and - # we'd have to overwrite something to make the switch. A - # merge is required. - # - if {[catch {close $fd_rt} err]} { - regsub {^fatal: } $err {} err - warn_popup "File level merge required. - -$err - -Staying on branch '$current_branch'." - set ui_status_value "Aborted checkout of '$new_branch' (file level merging is required)." - unlock_index - return - } - - # -- Update the symbolic ref. Core git doesn't even check for failure - # here, it Just Works(tm). If it doesn't we are in some really ugly - # state that is difficult to recover from within git-gui. - # - if {[catch {git symbolic-ref HEAD "refs/heads/$new_branch"} err]} { - error_popup "Failed to set current branch. - -This working directory is only partially switched. We successfully updated your files, but failed to update an internal Git file. - -This should not have occurred. [appname] will now close and give up. - -$err" - do_quit - return - } - - # -- Update our repository state. If we were previously in amend mode - # we need to toss the current buffer and do a full rescan to update - # our file lists. If we weren't in amend mode our file lists are - # accurate and we can avoid the rescan. - # - unlock_index - set selected_commit_type new - if {[string match amend* $commit_type]} { - $ui_comm delete 0.0 end - $ui_comm edit reset - $ui_comm edit modified false - rescan {set ui_status_value "Checked out branch '$current_branch'."} - } else { - repository_state commit_type HEAD MERGE_HEAD - set PARENT $HEAD - set ui_status_value "Checked out branch '$current_branch'." - } -} - -###################################################################### -## -## remote management - -proc load_all_remotes {} { - global repo_config - global all_remotes tracking_branches - - set all_remotes [list] - array unset tracking_branches - - set rm_dir [gitdir remotes] - if {[file isdirectory $rm_dir]} { - set all_remotes [glob \ - -types f \ - -tails \ - -nocomplain \ - -directory $rm_dir *] - - foreach name $all_remotes { - catch { - set fd [open [file join $rm_dir $name] r] - while {[gets $fd line] >= 0} { - if {![regexp {^Pull:[ ]*([^:]+):(.+)$} \ - $line line src dst]} continue - if {![regexp ^refs/ $dst]} { - set dst "refs/heads/$dst" - } - set tracking_branches($dst) [list $name $src] - } - close $fd - } - } - } - - foreach line [array names repo_config remote.*.url] { - if {![regexp ^remote\.(.*)\.url\$ $line line name]} continue - lappend all_remotes $name - - if {[catch {set fl $repo_config(remote.$name.fetch)}]} { - set fl {} - } - foreach line $fl { - if {![regexp {^([^:]+):(.+)$} $line line src dst]} continue - if {![regexp ^refs/ $dst]} { - set dst "refs/heads/$dst" - } - set tracking_branches($dst) [list $name $src] - } - } - - set all_remotes [lsort -unique $all_remotes] -} - -proc populate_fetch_menu {} { - global all_remotes repo_config - - set m .mbar.fetch - foreach r $all_remotes { - set enable 0 - if {![catch {set a $repo_config(remote.$r.url)}]} { - if {![catch {set a $repo_config(remote.$r.fetch)}]} { - set enable 1 - } - } else { - catch { - set fd [open [gitdir remotes $r] r] - while {[gets $fd n] >= 0} { - if {[regexp {^Pull:[ \t]*([^:]+):} $n]} { - set enable 1 - break - } - } - close $fd - } - } - - if {$enable} { - $m add command \ - -label "Fetch from $r..." \ - -command [list fetch_from $r] - } - } -} - -proc populate_push_menu {} { - global all_remotes repo_config - - set m .mbar.push - set fast_count 0 - foreach r $all_remotes { - set enable 0 - if {![catch {set a $repo_config(remote.$r.url)}]} { - if {![catch {set a $repo_config(remote.$r.push)}]} { - set enable 1 - } - } else { - catch { - set fd [open [gitdir remotes $r] r] - while {[gets $fd n] >= 0} { - if {[regexp {^Push:[ \t]*([^:]+):} $n]} { - set enable 1 - break - } - } - close $fd - } - } - - if {$enable} { - if {!$fast_count} { - $m add separator - } - $m add command \ - -label "Push to $r..." \ - -command [list push_to $r] - incr fast_count - } - } -} - -proc start_push_anywhere_action {w} { - global push_urltype push_remote push_url push_thin push_tags - - set r_url {} - switch -- $push_urltype { - remote {set r_url $push_remote} - url {set r_url $push_url} - } - if {$r_url eq {}} return - - set cmd [list git push] - lappend cmd -v - if {$push_thin} { - lappend cmd --thin - } - if {$push_tags} { - lappend cmd --tags - } - lappend cmd $r_url - set cnt 0 - foreach i [$w.source.l curselection] { - set b [$w.source.l get $i] - lappend cmd "refs/heads/$b:refs/heads/$b" - incr cnt - } - if {$cnt == 0} { - return - } elseif {$cnt == 1} { - set unit branch - } else { - set unit branches - } - - set cons [new_console "push $r_url" "Pushing $cnt $unit to $r_url"] - console_exec $cons $cmd console_done - destroy $w -} - -trace add variable push_remote write \ - [list radio_selector push_urltype remote] - -proc do_push_anywhere {} { - global all_heads all_remotes current_branch - global push_urltype push_remote push_url push_thin push_tags - - set w .push_setup - toplevel $w - wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - - label $w.header -text {Push Branches} -font font_uibold - pack $w.header -side top -fill x - - frame $w.buttons - button $w.buttons.create -text Push \ - -default active \ - -command [list start_push_anywhere_action $w] - pack $w.buttons.create -side right - button $w.buttons.cancel -text {Cancel} \ - -default normal \ - -command [list destroy $w] - pack $w.buttons.cancel -side right -padx 5 - pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - - labelframe $w.source -text {Source Branches} - listbox $w.source.l \ - -height 10 \ - -width 70 \ - -selectmode extended \ - -yscrollcommand [list $w.source.sby set] - foreach h $all_heads { - $w.source.l insert end $h - if {$h eq $current_branch} { - $w.source.l select set end - } - } - scrollbar $w.source.sby -command [list $w.source.l yview] - pack $w.source.sby -side right -fill y - pack $w.source.l -side left -fill both -expand 1 - pack $w.source -fill both -expand 1 -pady 5 -padx 5 - - labelframe $w.dest -text {Destination Repository} - if {$all_remotes ne {}} { - radiobutton $w.dest.remote_r \ - -text {Remote:} \ - -value remote \ - -variable push_urltype - eval tk_optionMenu $w.dest.remote_m push_remote $all_remotes - grid $w.dest.remote_r $w.dest.remote_m -sticky w - if {[lsearch -sorted -exact $all_remotes origin] != -1} { - set push_remote origin - } else { - set push_remote [lindex $all_remotes 0] - } - set push_urltype remote - } else { - set push_urltype url - } - radiobutton $w.dest.url_r \ - -text {Arbitrary URL:} \ - -value url \ - -variable push_urltype - entry $w.dest.url_t \ - -borderwidth 1 \ - -relief sunken \ - -width 50 \ - -textvariable push_url \ - -validate key \ - -validatecommand { - if {%d == 1 && [regexp {\s} %S]} {return 0} - if {%d == 1 && [string length %S] > 0} { - set push_urltype url - } - return 1 - } - grid $w.dest.url_r $w.dest.url_t -sticky we -padx {0 5} - grid columnconfigure $w.dest 1 -weight 1 - pack $w.dest -anchor nw -fill x -pady 5 -padx 5 - - labelframe $w.options -text {Transfer Options} - checkbutton $w.options.thin \ - -text {Use thin pack (for slow network connections)} \ - -variable push_thin - grid $w.options.thin -columnspan 2 -sticky w - checkbutton $w.options.tags \ - -text {Include tags} \ - -variable push_tags - grid $w.options.tags -columnspan 2 -sticky w - grid columnconfigure $w.options 1 -weight 1 - pack $w.options -anchor nw -fill x -pady 5 -padx 5 - - set push_url {} - set push_thin 0 - set push_tags 0 - - bind $w "grab $w; focus $w.buttons.create" - bind $w "destroy $w" - bind $w [list start_push_anywhere_action $w] - wm title $w "[appname] ([reponame]): Push" - tkwait window $w -} - -###################################################################### -## -## merge - -proc can_merge {} { - global HEAD commit_type file_states - - if {[string match amend* $commit_type]} { - info_popup {Cannot merge while amending. - -You must finish amending this commit before starting any type of merge. -} - return 0 - } - - if {[committer_ident] eq {}} {return 0} - if {![lock_index merge]} {return 0} - - # -- Our in memory state should match the repository. - # - repository_state curType curHEAD curMERGE_HEAD - if {$commit_type ne $curType || $HEAD ne $curHEAD} { - info_popup {Last scanned state does not match repository state. - -Another Git program has modified this repository since the last scan. A rescan must be performed before a merge can be performed. - -The rescan will be automatically started now. -} - unlock_index - rescan {set ui_status_value {Ready.}} - return 0 - } - - foreach path [array names file_states] { - switch -glob -- [lindex $file_states($path) 0] { - _O { - continue; # and pray it works! - } - U? { - error_popup "You are in the middle of a conflicted merge. - -File [short_path $path] has merge conflicts. - -You must resolve them, add the file, and commit to complete the current merge. Only then can you begin another merge. -" - unlock_index - return 0 - } - ?? { - error_popup "You are in the middle of a change. - -File [short_path $path] is modified. - -You should complete the current commit before starting a merge. Doing so will help you abort a failed merge, should the need arise. -" - unlock_index - return 0 - } - } - } - - return 1 -} - -proc visualize_local_merge {w} { - set revs {} - foreach i [$w.source.l curselection] { - lappend revs [$w.source.l get $i] - } - if {$revs eq {}} return - lappend revs --not HEAD - do_gitk $revs -} - -proc start_local_merge_action {w} { - global HEAD ui_status_value current_branch - - set cmd [list git merge] - set names {} - set revcnt 0 - foreach i [$w.source.l curselection] { - set b [$w.source.l get $i] - lappend cmd $b - lappend names $b - incr revcnt - } - - if {$revcnt == 0} { - return - } elseif {$revcnt == 1} { - set unit branch - } elseif {$revcnt <= 15} { - set unit branches - - if {[tk_dialog \ - $w.confirm_octopus \ - [wm title $w] \ - "Use octopus merge strategy? - -You are merging $revcnt branches at once. This requires using the octopus merge driver, which may not succeed if there are file-level conflicts. -" \ - question \ - 0 \ - {Cancel} \ - {Use octopus} \ - ] != 1} return - } else { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Too many branches selected. - -You have requested to merge $revcnt branches in an octopus merge. This exceeds Git's internal limit of 15 branches per merge. - -Please select fewer branches. To merge more than 15 branches, merge the branches in batches. -" - return - } - - set msg "Merging $current_branch, [join $names {, }]" - set ui_status_value "$msg..." - set cons [new_console "Merge" $msg] - console_exec $cons $cmd [list finish_merge $revcnt] - bind $w {} - destroy $w -} - -proc finish_merge {revcnt w ok} { - console_done $w $ok - if {$ok} { - set msg {Merge completed successfully.} - } else { - if {$revcnt != 1} { - info_popup "Octopus merge failed. - -Your merge of $revcnt branches has failed. - -There are file-level conflicts between the branches which must be resolved manually. - -The working directory will now be reset. - -You can attempt this merge again by merging only one branch at a time." $w - - set fd [open "| git read-tree --reset -u HEAD" r] - fconfigure $fd -blocking 0 -translation binary - fileevent $fd readable [list reset_hard_wait $fd] - set ui_status_value {Aborting... please wait...} - return - } - - set msg {Merge failed. Conflict resolution is required.} - } - unlock_index - rescan [list set ui_status_value $msg] -} - -proc do_local_merge {} { - global current_branch - - if {![can_merge]} return - - set w .merge_setup - toplevel $w - wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - - label $w.header \ - -text "Merge Into $current_branch" \ - -font font_uibold - pack $w.header -side top -fill x - - frame $w.buttons - button $w.buttons.visualize -text Visualize \ - -command [list visualize_local_merge $w] - pack $w.buttons.visualize -side left - button $w.buttons.create -text Merge \ - -command [list start_local_merge_action $w] - pack $w.buttons.create -side right - button $w.buttons.cancel -text {Cancel} \ - -command [list destroy $w] - pack $w.buttons.cancel -side right -padx 5 - pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - - labelframe $w.source -text {Source Branches} - listbox $w.source.l \ - -height 10 \ - -width 70 \ - -selectmode extended \ - -yscrollcommand [list $w.source.sby set] - scrollbar $w.source.sby -command [list $w.source.l yview] - pack $w.source.sby -side right -fill y - pack $w.source.l -side left -fill both -expand 1 - pack $w.source -fill both -expand 1 -pady 5 -padx 5 - - set cmd [list git for-each-ref] - lappend cmd {--format=%(objectname) %(*objectname) %(refname)} - lappend cmd refs/heads - lappend cmd refs/remotes - lappend cmd refs/tags - set fr_fd [open "| $cmd" r] - fconfigure $fr_fd -translation binary - while {[gets $fr_fd line] > 0} { - set line [split $line { }] - set sha1([lindex $line 0]) [lindex $line 2] - set sha1([lindex $line 1]) [lindex $line 2] - } - close $fr_fd - - set to_show {} - set fr_fd [open "| git rev-list --all --not HEAD"] - while {[gets $fr_fd line] > 0} { - if {[catch {set ref $sha1($line)}]} continue - regsub ^refs/(heads|remotes|tags)/ $ref {} ref - lappend to_show $ref - } - close $fr_fd - - foreach ref [lsort -unique $to_show] { - $w.source.l insert end $ref - } - - bind $w "grab $w" - bind $w "unlock_index;destroy $w" - bind $w unlock_index - wm title $w "[appname] ([reponame]): Merge" - tkwait window $w -} - -proc do_reset_hard {} { - global HEAD commit_type file_states - - if {[string match amend* $commit_type]} { - info_popup {Cannot abort while amending. - -You must finish amending this commit. -} - return - } - - if {![lock_index abort]} return - - if {[string match *merge* $commit_type]} { - set op merge - } else { - set op commit - } - - if {[ask_popup "Abort $op? - -Aborting the current $op will cause *ALL* uncommitted changes to be lost. - -Continue with aborting the current $op?"] eq {yes}} { - set fd [open "| git read-tree --reset -u HEAD" r] - fconfigure $fd -blocking 0 -translation binary - fileevent $fd readable [list reset_hard_wait $fd] - set ui_status_value {Aborting... please wait...} - } else { - unlock_index - } -} - -proc reset_hard_wait {fd} { - global ui_comm - - read $fd - if {[eof $fd]} { - close $fd - unlock_index - - $ui_comm delete 0.0 end - $ui_comm edit modified false - - catch {file delete [gitdir MERGE_HEAD]} - catch {file delete [gitdir rr-cache MERGE_RR]} - catch {file delete [gitdir SQUASH_MSG]} - catch {file delete [gitdir MERGE_MSG]} - catch {file delete [gitdir GITGUI_MSG]} - - rescan {set ui_status_value {Abort completed. Ready.}} - } -} - -###################################################################### -## -## browser - -set next_browser_id 0 - -proc new_browser {commit} { - global next_browser_id cursor_ptr M1B - global browser_commit browser_status browser_stack browser_path browser_busy - - if {[winfo ismapped .]} { - set w .browser[incr next_browser_id] - set tl $w - toplevel $w - } else { - set w {} - set tl . - } - set w_list $w.list.l - set browser_commit($w_list) $commit - set browser_status($w_list) {Starting...} - set browser_stack($w_list) {} - set browser_path($w_list) $browser_commit($w_list): - set browser_busy($w_list) 1 - - label $w.path -textvariable browser_path($w_list) \ - -anchor w \ - -justify left \ - -borderwidth 1 \ - -relief sunken \ - -font font_uibold - pack $w.path -anchor w -side top -fill x - - frame $w.list - text $w_list -background white -borderwidth 0 \ - -cursor $cursor_ptr \ - -state disabled \ - -wrap none \ - -height 20 \ - -width 70 \ - -xscrollcommand [list $w.list.sbx set] \ - -yscrollcommand [list $w.list.sby set] - $w_list tag conf in_sel \ - -background [$w_list cget -foreground] \ - -foreground [$w_list cget -background] - scrollbar $w.list.sbx -orient h -command [list $w_list xview] - scrollbar $w.list.sby -orient v -command [list $w_list yview] - pack $w.list.sbx -side bottom -fill x - pack $w.list.sby -side right -fill y - pack $w_list -side left -fill both -expand 1 - pack $w.list -side top -fill both -expand 1 - - label $w.status -textvariable browser_status($w_list) \ - -anchor w \ - -justify left \ - -borderwidth 1 \ - -relief sunken - pack $w.status -anchor w -side bottom -fill x - - bind $w_list "browser_click 0 $w_list @%x,%y;break" - bind $w_list "browser_click 1 $w_list @%x,%y;break" - bind $w_list <$M1B-Up> "browser_parent $w_list;break" - bind $w_list <$M1B-Left> "browser_parent $w_list;break" - bind $w_list "browser_move -1 $w_list;break" - bind $w_list "browser_move 1 $w_list;break" - bind $w_list <$M1B-Right> "browser_enter $w_list;break" - bind $w_list "browser_enter $w_list;break" - bind $w_list "browser_page -1 $w_list;break" - bind $w_list "browser_page 1 $w_list;break" - bind $w_list break - bind $w_list break - - bind $tl "focus $w" - bind $tl " - array unset browser_buffer $w_list - array unset browser_files $w_list - array unset browser_status $w_list - array unset browser_stack $w_list - array unset browser_path $w_list - array unset browser_commit $w_list - array unset browser_busy $w_list - " - wm title $tl "[appname] ([reponame]): File Browser" - ls_tree $w_list $browser_commit($w_list) {} -} - -proc browser_move {dir w} { - global browser_files browser_busy - - if {$browser_busy($w)} return - set lno [lindex [split [$w index in_sel.first] .] 0] - incr lno $dir - if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} { - $w tag remove in_sel 0.0 end - $w tag add in_sel $lno.0 [expr {$lno + 1}].0 - $w see $lno.0 - } -} - -proc browser_page {dir w} { - global browser_files browser_busy - - if {$browser_busy($w)} return - $w yview scroll $dir pages - set lno [expr {int( - [lindex [$w yview] 0] - * [llength $browser_files($w)] - + 1)}] - if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} { - $w tag remove in_sel 0.0 end - $w tag add in_sel $lno.0 [expr {$lno + 1}].0 - $w see $lno.0 - } -} - -proc browser_parent {w} { - global browser_files browser_status browser_path - global browser_stack browser_busy - - if {$browser_busy($w)} return - set info [lindex $browser_files($w) 0] - if {[lindex $info 0] eq {parent}} { - set parent [lindex $browser_stack($w) end-1] - set browser_stack($w) [lrange $browser_stack($w) 0 end-2] - if {$browser_stack($w) eq {}} { - regsub {:.*$} $browser_path($w) {:} browser_path($w) - } else { - regsub {/[^/]+$} $browser_path($w) {} browser_path($w) - } - set browser_status($w) "Loading $browser_path($w)..." - ls_tree $w [lindex $parent 0] [lindex $parent 1] - } -} - -proc browser_enter {w} { - global browser_files browser_status browser_path - global browser_commit browser_stack browser_busy - - if {$browser_busy($w)} return - set lno [lindex [split [$w index in_sel.first] .] 0] - set info [lindex $browser_files($w) [expr {$lno - 1}]] - if {$info ne {}} { - switch -- [lindex $info 0] { - parent { - browser_parent $w - } - tree { - set name [lindex $info 2] - set escn [escape_path $name] - set browser_status($w) "Loading $escn..." - append browser_path($w) $escn - ls_tree $w [lindex $info 1] $name - } - blob { - set name [lindex $info 2] - set p {} - foreach n $browser_stack($w) { - append p [lindex $n 1] - } - append p $name - show_blame $browser_commit($w) $p - } - } - } -} - -proc browser_click {was_double_click w pos} { - global browser_files browser_busy - - if {$browser_busy($w)} return - set lno [lindex [split [$w index $pos] .] 0] - focus $w - - if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} { - $w tag remove in_sel 0.0 end - $w tag add in_sel $lno.0 [expr {$lno + 1}].0 - if {$was_double_click} { - browser_enter $w - } - } -} - -proc ls_tree {w tree_id name} { - global browser_buffer browser_files browser_stack browser_busy - - set browser_buffer($w) {} - set browser_files($w) {} - set browser_busy($w) 1 - - $w conf -state normal - $w tag remove in_sel 0.0 end - $w delete 0.0 end - if {$browser_stack($w) ne {}} { - $w image create end \ - -align center -padx 5 -pady 1 \ - -name icon0 \ - -image file_uplevel - $w insert end {[Up To Parent]} - lappend browser_files($w) parent - } - lappend browser_stack($w) [list $tree_id $name] - $w conf -state disabled - - set cmd [list git ls-tree -z $tree_id] - set fd [open "| $cmd" r] - fconfigure $fd -blocking 0 -translation binary -encoding binary - fileevent $fd readable [list read_ls_tree $fd $w] -} - -proc read_ls_tree {fd w} { - global browser_buffer browser_files browser_status browser_busy - - if {![winfo exists $w]} { - catch {close $fd} - return - } - - append browser_buffer($w) [read $fd] - set pck [split $browser_buffer($w) "\0"] - set browser_buffer($w) [lindex $pck end] - - set n [llength $browser_files($w)] - $w conf -state normal - foreach p [lrange $pck 0 end-1] { - set info [split $p "\t"] - set path [lindex $info 1] - set info [split [lindex $info 0] { }] - set type [lindex $info 1] - set object [lindex $info 2] - - switch -- $type { - blob { - set image file_mod - } - tree { - set image file_dir - append path / - } - default { - set image file_question - } - } - - if {$n > 0} {$w insert end "\n"} - $w image create end \ - -align center -padx 5 -pady 1 \ - -name icon[incr n] \ - -image $image - $w insert end [escape_path $path] - lappend browser_files($w) [list $type $object $path] - } - $w conf -state disabled - - if {[eof $fd]} { - close $fd - set browser_status($w) Ready. - set browser_busy($w) 0 - array unset browser_buffer $w - if {$n > 0} { - $w tag add in_sel 1.0 2.0 - focus -force $w - } - } -} - -proc show_blame {commit path} { - global next_browser_id blame_status blame_data - - if {[winfo ismapped .]} { - set w .browser[incr next_browser_id] - set tl $w - toplevel $w - } else { - set w {} - set tl . - } - set blame_status($w) {Loading current file content...} - - label $w.path -text "$commit:$path" \ - -anchor w \ - -justify left \ - -borderwidth 1 \ - -relief sunken \ - -font font_uibold - pack $w.path -side top -fill x - - frame $w.out - text $w.out.loaded_t \ - -background white -borderwidth 0 \ - -state disabled \ - -wrap none \ - -height 40 \ - -width 1 \ - -font font_diff - $w.out.loaded_t tag conf annotated -background grey - - text $w.out.linenumber_t \ - -background white -borderwidth 0 \ - -state disabled \ - -wrap none \ - -height 40 \ - -width 5 \ - -font font_diff - $w.out.linenumber_t tag conf linenumber -justify right - - text $w.out.file_t \ - -background white -borderwidth 0 \ - -state disabled \ - -wrap none \ - -height 40 \ - -width 80 \ - -xscrollcommand [list $w.out.sbx set] \ - -font font_diff - - scrollbar $w.out.sbx -orient h -command [list $w.out.file_t xview] - scrollbar $w.out.sby -orient v \ - -command [list scrollbar2many [list \ - $w.out.loaded_t \ - $w.out.linenumber_t \ - $w.out.file_t \ - ] yview] - grid \ - $w.out.linenumber_t \ - $w.out.loaded_t \ - $w.out.file_t \ - $w.out.sby \ - -sticky nsew - grid conf $w.out.sbx -column 2 -sticky we - grid columnconfigure $w.out 2 -weight 1 - grid rowconfigure $w.out 0 -weight 1 - pack $w.out -fill both -expand 1 - - label $w.status -textvariable blame_status($w) \ - -anchor w \ - -justify left \ - -borderwidth 1 \ - -relief sunken - pack $w.status -side bottom -fill x - - frame $w.cm - text $w.cm.t \ - -background white -borderwidth 0 \ - -state disabled \ - -wrap none \ - -height 10 \ - -width 80 \ - -xscrollcommand [list $w.cm.sbx set] \ - -yscrollcommand [list $w.cm.sby set] \ - -font font_diff - scrollbar $w.cm.sbx -orient h -command [list $w.cm.t xview] - scrollbar $w.cm.sby -orient v -command [list $w.cm.t yview] - pack $w.cm.sby -side right -fill y - pack $w.cm.sbx -side bottom -fill x - pack $w.cm.t -expand 1 -fill both - pack $w.cm -side bottom -fill x - - menu $w.ctxm -tearoff 0 - $w.ctxm add command -label "Copy Commit" \ - -command "blame_copycommit $w \$cursorW @\$cursorX,\$cursorY" - - foreach i [list \ - $w.out.loaded_t \ - $w.out.linenumber_t \ - $w.out.file_t] { - $i tag conf in_sel \ - -background [$i cget -foreground] \ - -foreground [$i cget -background] - $i conf -yscrollcommand \ - [list many2scrollbar [list \ - $w.out.loaded_t \ - $w.out.linenumber_t \ - $w.out.file_t \ - ] yview $w.out.sby] - bind $i " - blame_click {$w} \\ - $w.cm.t \\ - $w.out.linenumber_t \\ - $w.out.file_t \\ - $i @%x,%y - focus $i - " - bind_button3 $i " - set cursorX %x - set cursorY %y - set cursorW %W - tk_popup $w.ctxm %X %Y - " - } - - bind $w.cm.t "focus $w.cm.t" - bind $tl "focus $tl" - bind $tl " - array unset blame_status {$w} - array unset blame_data $w,* - " - wm title $tl "[appname] ([reponame]): File Viewer" - - set blame_data($w,commit_count) 0 - set blame_data($w,commit_list) {} - set blame_data($w,total_lines) 0 - set blame_data($w,blame_lines) 0 - set blame_data($w,highlight_commit) {} - set blame_data($w,highlight_line) -1 - - set cmd [list git cat-file blob "$commit:$path"] - set fd [open "| $cmd" r] - fconfigure $fd -blocking 0 -translation lf -encoding binary - fileevent $fd readable [list read_blame_catfile \ - $fd $w $commit $path \ - $w.cm.t $w.out.loaded_t $w.out.linenumber_t $w.out.file_t] -} - -proc read_blame_catfile {fd w commit path w_cmit w_load w_line w_file} { - global blame_status blame_data - - if {![winfo exists $w_file]} { - catch {close $fd} - return - } - - set n $blame_data($w,total_lines) - $w_load conf -state normal - $w_line conf -state normal - $w_file conf -state normal - while {[gets $fd line] >= 0} { - regsub "\r\$" $line {} line - incr n - $w_load insert end "\n" - $w_line insert end "$n\n" linenumber - $w_file insert end "$line\n" - } - $w_load conf -state disabled - $w_line conf -state disabled - $w_file conf -state disabled - set blame_data($w,total_lines) $n - - if {[eof $fd]} { - close $fd - blame_incremental_status $w - set cmd [list git blame -M -C --incremental] - lappend cmd $commit -- $path - set fd [open "| $cmd" r] - fconfigure $fd -blocking 0 -translation lf -encoding binary - fileevent $fd readable [list read_blame_incremental $fd $w \ - $w_load $w_cmit $w_line $w_file] - } -} - -proc read_blame_incremental {fd w w_load w_cmit w_line w_file} { - global blame_status blame_data - - if {![winfo exists $w_file]} { - catch {close $fd} - return - } - - while {[gets $fd line] >= 0} { - if {[regexp {^([a-z0-9]{40}) (\d+) (\d+) (\d+)$} $line line \ - cmit original_line final_line line_count]} { - set blame_data($w,commit) $cmit - set blame_data($w,original_line) $original_line - set blame_data($w,final_line) $final_line - set blame_data($w,line_count) $line_count - - if {[catch {set g $blame_data($w,$cmit,order)}]} { - $w_line tag conf g$cmit - $w_file tag conf g$cmit - $w_line tag raise in_sel - $w_file tag raise in_sel - $w_file tag raise sel - set blame_data($w,$cmit,order) $blame_data($w,commit_count) - incr blame_data($w,commit_count) - lappend blame_data($w,commit_list) $cmit - } - } elseif {[string match {filename *} $line]} { - set file [string range $line 9 end] - set n $blame_data($w,line_count) - set lno $blame_data($w,final_line) - set cmit $blame_data($w,commit) - - while {$n > 0} { - if {[catch {set g g$blame_data($w,line$lno,commit)}]} { - $w_load tag add annotated $lno.0 "$lno.0 lineend + 1c" - } else { - $w_line tag remove g$g $lno.0 "$lno.0 lineend + 1c" - $w_file tag remove g$g $lno.0 "$lno.0 lineend + 1c" - } - - set blame_data($w,line$lno,commit) $cmit - set blame_data($w,line$lno,file) $file - $w_line tag add g$cmit $lno.0 "$lno.0 lineend + 1c" - $w_file tag add g$cmit $lno.0 "$lno.0 lineend + 1c" - - if {$blame_data($w,highlight_line) == -1} { - if {[lindex [$w_file yview] 0] == 0} { - $w_file see $lno.0 - blame_showcommit $w $w_cmit $w_line $w_file $lno - } - } elseif {$blame_data($w,highlight_line) == $lno} { - blame_showcommit $w $w_cmit $w_line $w_file $lno - } - - incr n -1 - incr lno - incr blame_data($w,blame_lines) - } - - set hc $blame_data($w,highlight_commit) - if {$hc ne {} - && [expr {$blame_data($w,$hc,order) + 1}] - == $blame_data($w,$cmit,order)} { - blame_showcommit $w $w_cmit $w_line $w_file \ - $blame_data($w,highlight_line) - } - } elseif {[regexp {^([a-z-]+) (.*)$} $line line header data]} { - set blame_data($w,$blame_data($w,commit),$header) $data - } - } - - if {[eof $fd]} { - close $fd - set blame_status($w) {Annotation complete.} - } else { - blame_incremental_status $w - } -} - -proc blame_incremental_status {w} { - global blame_status blame_data - - set have $blame_data($w,blame_lines) - set total $blame_data($w,total_lines) - set pdone 0 - if {$total} {set pdone [expr {100 * $have / $total}]} - - set blame_status($w) [format \ - "Loading annotations... %i of %i lines annotated (%2i%%)" \ - $have $total $pdone] -} - -proc blame_click {w w_cmit w_line w_file cur_w pos} { - set lno [lindex [split [$cur_w index $pos] .] 0] - if {$lno eq {}} return - - $w_line tag remove in_sel 0.0 end - $w_file tag remove in_sel 0.0 end - $w_line tag add in_sel $lno.0 "$lno.0 + 1 line" - $w_file tag add in_sel $lno.0 "$lno.0 + 1 line" - - blame_showcommit $w $w_cmit $w_line $w_file $lno -} - -set blame_colors { - #ff4040 - #ff40ff - #4040ff -} - -proc blame_showcommit {w w_cmit w_line w_file lno} { - global blame_colors blame_data repo_config - - set cmit $blame_data($w,highlight_commit) - if {$cmit ne {}} { - set idx $blame_data($w,$cmit,order) - set i 0 - foreach c $blame_colors { - set h [lindex $blame_data($w,commit_list) [expr {$idx - 1 + $i}]] - $w_line tag conf g$h -background white - $w_file tag conf g$h -background white - incr i - } - } - - $w_cmit conf -state normal - $w_cmit delete 0.0 end - if {[catch {set cmit $blame_data($w,line$lno,commit)}]} { - set cmit {} - $w_cmit insert end "Loading annotation..." - } else { - set idx $blame_data($w,$cmit,order) - set i 0 - foreach c $blame_colors { - set h [lindex $blame_data($w,commit_list) [expr {$idx - 1 + $i}]] - $w_line tag conf g$h -background $c - $w_file tag conf g$h -background $c - incr i - } - - set author_name {} - set author_email {} - set author_time {} - catch {set author_name $blame_data($w,$cmit,author)} - catch {set author_email $blame_data($w,$cmit,author-mail)} - catch {set author_time [clock format $blame_data($w,$cmit,author-time)]} - - set committer_name {} - set committer_email {} - set committer_time {} - catch {set committer_name $blame_data($w,$cmit,committer)} - catch {set committer_email $blame_data($w,$cmit,committer-mail)} - catch {set committer_time [clock format $blame_data($w,$cmit,committer-time)]} - - if {[catch {set msg $blame_data($w,$cmit,message)}]} { - set msg {} - catch { - set fd [open "| git cat-file commit $cmit" r] - fconfigure $fd -encoding binary -translation lf - if {[catch {set enc $repo_config(i18n.commitencoding)}]} { - set enc utf-8 - } - while {[gets $fd line] > 0} { - if {[string match {encoding *} $line]} { - set enc [string tolower [string range $line 9 end]] - } - } - set msg [encoding convertfrom $enc [read $fd]] - set msg [string trim $msg] - close $fd - - set author_name [encoding convertfrom $enc $author_name] - set committer_name [encoding convertfrom $enc $committer_name] - - set blame_data($w,$cmit,author) $author_name - set blame_data($w,$cmit,committer) $committer_name - } - set blame_data($w,$cmit,message) $msg - } - - $w_cmit insert end "commit $cmit\n" - $w_cmit insert end "Author: $author_name $author_email $author_time\n" - $w_cmit insert end "Committer: $committer_name $committer_email $committer_time\n" - $w_cmit insert end "Original File: [escape_path $blame_data($w,line$lno,file)]\n" - $w_cmit insert end "\n" - $w_cmit insert end $msg - } - $w_cmit conf -state disabled - - set blame_data($w,highlight_line) $lno - set blame_data($w,highlight_commit) $cmit -} - -proc blame_copycommit {w i pos} { - global blame_data - set lno [lindex [split [$i index $pos] .] 0] - if {![catch {set commit $blame_data($w,line$lno,commit)}]} { - clipboard clear - clipboard append \ - -format STRING \ - -type STRING \ - -- $commit - } + $ui_index conf -state disabled + $ui_workdir conf -state disabled } ###################################################################### @@ -3845,230 +973,6 @@ proc incr_font_size {font {amt 1}} { font configure ${font}bold -size $sz } -proc hook_failed_popup {hook msg} { - set w .hookfail - toplevel $w - - frame $w.m - label $w.m.l1 -text "$hook hook failed:" \ - -anchor w \ - -justify left \ - -font font_uibold - text $w.m.t \ - -background white -borderwidth 1 \ - -relief sunken \ - -width 80 -height 10 \ - -font font_diff \ - -yscrollcommand [list $w.m.sby set] - label $w.m.l2 \ - -text {You must correct the above errors before committing.} \ - -anchor w \ - -justify left \ - -font font_uibold - scrollbar $w.m.sby -command [list $w.m.t yview] - pack $w.m.l1 -side top -fill x - pack $w.m.l2 -side bottom -fill x - pack $w.m.sby -side right -fill y - pack $w.m.t -side left -fill both -expand 1 - pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10 - - $w.m.t insert 1.0 $msg - $w.m.t conf -state disabled - - button $w.ok -text OK \ - -width 15 \ - -command "destroy $w" - pack $w.ok -side bottom -anchor e -pady 10 -padx 10 - - bind $w "grab $w; focus $w" - bind $w "destroy $w" - wm title $w "[appname] ([reponame]): error" - tkwait window $w -} - -set next_console_id 0 - -proc new_console {short_title long_title} { - global next_console_id console_data - set w .console[incr next_console_id] - set console_data($w) [list $short_title $long_title] - return [console_init $w] -} - -proc console_init {w} { - global console_cr console_data M1B - - set console_cr($w) 1.0 - toplevel $w - frame $w.m - label $w.m.l1 -text "[lindex $console_data($w) 1]:" \ - -anchor w \ - -justify left \ - -font font_uibold - text $w.m.t \ - -background white -borderwidth 1 \ - -relief sunken \ - -width 80 -height 10 \ - -font font_diff \ - -state disabled \ - -yscrollcommand [list $w.m.sby set] - label $w.m.s -text {Working... please wait...} \ - -anchor w \ - -justify left \ - -font font_uibold - scrollbar $w.m.sby -command [list $w.m.t yview] - pack $w.m.l1 -side top -fill x - pack $w.m.s -side bottom -fill x - pack $w.m.sby -side right -fill y - pack $w.m.t -side left -fill both -expand 1 - pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10 - - menu $w.ctxm -tearoff 0 - $w.ctxm add command -label "Copy" \ - -command "tk_textCopy $w.m.t" - $w.ctxm add command -label "Select All" \ - -command "focus $w.m.t;$w.m.t tag add sel 0.0 end" - $w.ctxm add command -label "Copy All" \ - -command " - $w.m.t tag add sel 0.0 end - tk_textCopy $w.m.t - $w.m.t tag remove sel 0.0 end - " - - button $w.ok -text {Close} \ - -state disabled \ - -command "destroy $w" - pack $w.ok -side bottom -anchor e -pady 10 -padx 10 - - bind_button3 $w.m.t "tk_popup $w.ctxm %X %Y" - bind $w.m.t <$M1B-Key-a> "$w.m.t tag add sel 0.0 end;break" - bind $w.m.t <$M1B-Key-A> "$w.m.t tag add sel 0.0 end;break" - bind $w "focus $w" - wm title $w "[appname] ([reponame]): [lindex $console_data($w) 0]" - return $w -} - -proc console_exec {w cmd after} { - # -- Cygwin's Tcl tosses the enviroment when we exec our child. - # But most users need that so we have to relogin. :-( - # - if {[is_Cygwin]} { - set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"] - } - - # -- Tcl won't let us redirect both stdout and stderr to - # the same pipe. So pass it through cat... - # - set cmd [concat | $cmd |& cat] - - set fd_f [open $cmd r] - fconfigure $fd_f -blocking 0 -translation binary - fileevent $fd_f readable [list console_read $w $fd_f $after] -} - -proc console_read {w fd after} { - global console_cr - - set buf [read $fd] - if {$buf ne {}} { - if {![winfo exists $w]} {console_init $w} - $w.m.t conf -state normal - set c 0 - set n [string length $buf] - while {$c < $n} { - set cr [string first "\r" $buf $c] - set lf [string first "\n" $buf $c] - if {$cr < 0} {set cr [expr {$n + 1}]} - if {$lf < 0} {set lf [expr {$n + 1}]} - - if {$lf < $cr} { - $w.m.t insert end [string range $buf $c $lf] - set console_cr($w) [$w.m.t index {end -1c}] - set c $lf - incr c - } else { - $w.m.t delete $console_cr($w) end - $w.m.t insert end "\n" - $w.m.t insert end [string range $buf $c $cr] - set c $cr - incr c - } - } - $w.m.t conf -state disabled - $w.m.t see end - } - - fconfigure $fd -blocking 1 - if {[eof $fd]} { - if {[catch {close $fd}]} { - set ok 0 - } else { - set ok 1 - } - uplevel #0 $after $w $ok - return - } - fconfigure $fd -blocking 0 -} - -proc console_chain {cmdlist w {ok 1}} { - if {$ok} { - if {[llength $cmdlist] == 0} { - console_done $w $ok - return - } - - set cmd [lindex $cmdlist 0] - set cmdlist [lrange $cmdlist 1 end] - - if {[lindex $cmd 0] eq {console_exec}} { - console_exec $w \ - [lindex $cmd 1] \ - [list console_chain $cmdlist] - } else { - uplevel #0 $cmd $cmdlist $w $ok - } - } else { - console_done $w $ok - } -} - -proc console_done {args} { - global console_cr console_data - - switch -- [llength $args] { - 2 { - set w [lindex $args 0] - set ok [lindex $args 1] - } - 3 { - set w [lindex $args 1] - set ok [lindex $args 2] - } - default { - error "wrong number of args: console_done ?ignored? w ok" - } - } - - if {$ok} { - if {[winfo exists $w]} { - $w.m.s conf -background green -text {Success} - $w.ok conf -state normal - focus $w.ok - } - } else { - if {![winfo exists $w]} { - console_init $w - } - $w.m.s conf -background red -text {Error: Command Failed} - $w.ok conf -state normal - focus $w.ok - } - - array unset console_cr $w - array unset console_data $w -} - ###################################################################### ## ## ui commands @@ -4100,93 +1004,6 @@ proc do_gitk {revs} { } } -proc do_stats {} { - set fd [open "| git count-objects -v" r] - while {[gets $fd line] > 0} { - if {[regexp {^([^:]+): (\d+)$} $line _ name value]} { - set stats($name) $value - } - } - close $fd - - set packed_sz 0 - foreach p [glob -directory [gitdir objects pack] \ - -type f \ - -nocomplain -- *] { - incr packed_sz [file size $p] - } - if {$packed_sz > 0} { - set stats(size-pack) [expr {$packed_sz / 1024}] - } - - set w .stats_view - toplevel $w - wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - - label $w.header -text {Database Statistics} - pack $w.header -side top -fill x - - frame $w.buttons -border 1 - button $w.buttons.close -text Close \ - -default active \ - -command [list destroy $w] - button $w.buttons.gc -text {Compress Database} \ - -default normal \ - -command "destroy $w;do_gc" - pack $w.buttons.close -side right - pack $w.buttons.gc -side left - pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - - frame $w.stat -borderwidth 1 -relief solid - foreach s { - {count {Number of loose objects}} - {size {Disk space used by loose objects} { KiB}} - {in-pack {Number of packed objects}} - {packs {Number of packs}} - {size-pack {Disk space used by packed objects} { KiB}} - {prune-packable {Packed objects waiting for pruning}} - {garbage {Garbage files}} - } { - set name [lindex $s 0] - set label [lindex $s 1] - if {[catch {set value $stats($name)}]} continue - if {[llength $s] > 2} { - set value "$value[lindex $s 2]" - } - - label $w.stat.l_$name -text "$label:" -anchor w - label $w.stat.v_$name -text $value -anchor w - grid $w.stat.l_$name $w.stat.v_$name -sticky we -padx {0 5} - } - pack $w.stat -pady 10 -padx 10 - - bind $w "grab $w; focus $w.buttons.close" - bind $w [list destroy $w] - bind $w [list destroy $w] - wm title $w "[appname] ([reponame]): Database Statistics" - tkwait window $w -} - -proc do_gc {} { - set w [new_console {gc} {Compressing the object database}] - console_chain { - {console_exec {git pack-refs --prune}} - {console_exec {git reflog expire --all}} - {console_exec {git repack -a -d -l}} - {console_exec {git rerere gc}} - } $w -} - -proc do_fsck_objects {} { - set w [new_console {fsck-objects} \ - {Verifying the object database with fsck-objects}] - set cmd [list git fsck-objects] - lappend cmd --full - lappend cmd --cache - lappend cmd --strict - console_exec $w $cmd console_done -} - set is_quitting 0 proc do_quit {} { @@ -4234,591 +1051,10 @@ proc do_rescan {} { rescan {set ui_status_value {Ready.}} } -proc unstage_helper {txt paths} { - global file_states current_diff_path - - if {![lock_index begin-update]} return - - set pathList [list] - set after {} - foreach path $paths { - switch -glob -- [lindex $file_states($path) 0] { - A? - - M? - - D? { - lappend pathList $path - if {$path eq $current_diff_path} { - set after {reshow_diff;} - } - } - } - } - if {$pathList eq {}} { - unlock_index - } else { - update_indexinfo \ - $txt \ - $pathList \ - [concat $after {set ui_status_value {Ready.}}] - } -} - -proc do_unstage_selection {} { - global current_diff_path selected_paths - - if {[array size selected_paths] > 0} { - unstage_helper \ - {Unstaging selected files from commit} \ - [array names selected_paths] - } elseif {$current_diff_path ne {}} { - unstage_helper \ - "Unstaging [short_path $current_diff_path] from commit" \ - [list $current_diff_path] - } -} - -proc add_helper {txt paths} { - global file_states current_diff_path - - if {![lock_index begin-update]} return - - set pathList [list] - set after {} - foreach path $paths { - switch -glob -- [lindex $file_states($path) 0] { - _O - - ?M - - ?D - - U? { - lappend pathList $path - if {$path eq $current_diff_path} { - set after {reshow_diff;} - } - } - } - } - if {$pathList eq {}} { - unlock_index - } else { - update_index \ - $txt \ - $pathList \ - [concat $after {set ui_status_value {Ready to commit.}}] - } -} - -proc do_add_selection {} { - global current_diff_path selected_paths - - if {[array size selected_paths] > 0} { - add_helper \ - {Adding selected files} \ - [array names selected_paths] - } elseif {$current_diff_path ne {}} { - add_helper \ - "Adding [short_path $current_diff_path]" \ - [list $current_diff_path] - } -} - -proc do_add_all {} { - global file_states - - set paths [list] - foreach path [array names file_states] { - switch -glob -- [lindex $file_states($path) 0] { - U? {continue} - ?M - - ?D {lappend paths $path} - } - } - add_helper {Adding all changed files} $paths -} - -proc revert_helper {txt paths} { - global file_states current_diff_path - - if {![lock_index begin-update]} return - - set pathList [list] - set after {} - foreach path $paths { - switch -glob -- [lindex $file_states($path) 0] { - U? {continue} - ?M - - ?D { - lappend pathList $path - if {$path eq $current_diff_path} { - set after {reshow_diff;} - } - } - } - } - - set n [llength $pathList] - if {$n == 0} { - unlock_index - return - } elseif {$n == 1} { - set s "[short_path [lindex $pathList]]" - } else { - set s "these $n files" - } - - set reply [tk_dialog \ - .confirm_revert \ - "[appname] ([reponame])" \ - "Revert changes in $s? - -Any unadded changes will be permanently lost by the revert." \ - question \ - 1 \ - {Do Nothing} \ - {Revert Changes} \ - ] - if {$reply == 1} { - checkout_index \ - $txt \ - $pathList \ - [concat $after {set ui_status_value {Ready.}}] - } else { - unlock_index - } -} - -proc do_revert_selection {} { - global current_diff_path selected_paths - - if {[array size selected_paths] > 0} { - revert_helper \ - {Reverting selected files} \ - [array names selected_paths] - } elseif {$current_diff_path ne {}} { - revert_helper \ - "Reverting [short_path $current_diff_path]" \ - [list $current_diff_path] - } -} - -proc do_signoff {} { - global ui_comm - - set me [committer_ident] - if {$me eq {}} return - - set sob "Signed-off-by: $me" - set last [$ui_comm get {end -1c linestart} {end -1c}] - if {$last ne $sob} { - $ui_comm edit separator - if {$last ne {} - && ![regexp {^[A-Z][A-Za-z]*-[A-Za-z-]+: *} $last]} { - $ui_comm insert end "\n" - } - $ui_comm insert end "\n$sob" - $ui_comm edit separator - $ui_comm see end - } -} - -proc do_select_commit_type {} { - global commit_type selected_commit_type - - if {$selected_commit_type eq {new} - && [string match amend* $commit_type]} { - create_new_commit - } elseif {$selected_commit_type eq {amend} - && ![string match amend* $commit_type]} { - load_last_commit - - # The amend request was rejected... - # - if {![string match amend* $commit_type]} { - set selected_commit_type new - } - } -} - proc do_commit {} { commit_tree } -proc do_about {} { - global appvers copyright - global tcl_patchLevel tk_patchLevel - - set w .about_dialog - toplevel $w - wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - - label $w.header -text "About [appname]" \ - -font font_uibold - pack $w.header -side top -fill x - - frame $w.buttons - button $w.buttons.close -text {Close} \ - -default active \ - -command [list destroy $w] - pack $w.buttons.close -side right - pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - - label $w.desc \ - -text "git-gui - a graphical user interface for Git. -$copyright" \ - -padx 5 -pady 5 \ - -justify left \ - -anchor w \ - -borderwidth 1 \ - -relief solid - pack $w.desc -side top -fill x -padx 5 -pady 5 - - set v {} - append v "git-gui version $appvers\n" - append v "[git version]\n" - append v "\n" - if {$tcl_patchLevel eq $tk_patchLevel} { - append v "Tcl/Tk version $tcl_patchLevel" - } else { - append v "Tcl version $tcl_patchLevel" - append v ", Tk version $tk_patchLevel" - } - - label $w.vers \ - -text $v \ - -padx 5 -pady 5 \ - -justify left \ - -anchor w \ - -borderwidth 1 \ - -relief solid - pack $w.vers -side top -fill x -padx 5 -pady 5 - - menu $w.ctxm -tearoff 0 - $w.ctxm add command \ - -label {Copy} \ - -command " - clipboard clear - clipboard append -format STRING -type STRING -- \[$w.vers cget -text\] - " - - bind $w "grab $w; focus $w.buttons.close" - bind $w "destroy $w" - bind $w "destroy $w" - bind_button3 $w.vers "tk_popup $w.ctxm %X %Y; grab $w; focus $w" - wm title $w "About [appname]" - tkwait window $w -} - -proc do_options {} { - global repo_config global_config font_descs - global repo_config_new global_config_new - - array unset repo_config_new - array unset global_config_new - foreach name [array names repo_config] { - set repo_config_new($name) $repo_config($name) - } - load_config 1 - foreach name [array names repo_config] { - switch -- $name { - gui.diffcontext {continue} - } - set repo_config_new($name) $repo_config($name) - } - foreach name [array names global_config] { - set global_config_new($name) $global_config($name) - } - - set w .options_editor - toplevel $w - wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - - label $w.header -text "Options" \ - -font font_uibold - pack $w.header -side top -fill x - - frame $w.buttons - button $w.buttons.restore -text {Restore Defaults} \ - -default normal \ - -command do_restore_defaults - pack $w.buttons.restore -side left - button $w.buttons.save -text Save \ - -default active \ - -command [list do_save_config $w] - pack $w.buttons.save -side right - button $w.buttons.cancel -text {Cancel} \ - -default normal \ - -command [list destroy $w] - pack $w.buttons.cancel -side right -padx 5 - pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - - labelframe $w.repo -text "[reponame] Repository" - labelframe $w.global -text {Global (All Repositories)} - pack $w.repo -side left -fill both -expand 1 -pady 5 -padx 5 - pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5 - - set optid 0 - foreach option { - {t user.name {User Name}} - {t user.email {Email Address}} - - {b merge.summary {Summarize Merge Commits}} - {i-1..5 merge.verbosity {Merge Verbosity}} - - {b gui.trustmtime {Trust File Modification Timestamps}} - {i-1..99 gui.diffcontext {Number of Diff Context Lines}} - {t gui.newbranchtemplate {New Branch Name Template}} - } { - set type [lindex $option 0] - set name [lindex $option 1] - set text [lindex $option 2] - incr optid - foreach f {repo global} { - switch -glob -- $type { - b { - checkbutton $w.$f.$optid -text $text \ - -variable ${f}_config_new($name) \ - -onvalue true \ - -offvalue false - pack $w.$f.$optid -side top -anchor w - } - i-* { - regexp -- {-(\d+)\.\.(\d+)$} $type _junk min max - frame $w.$f.$optid - label $w.$f.$optid.l -text "$text:" - pack $w.$f.$optid.l -side left -anchor w -fill x - spinbox $w.$f.$optid.v \ - -textvariable ${f}_config_new($name) \ - -from $min \ - -to $max \ - -increment 1 \ - -width [expr {1 + [string length $max]}] - bind $w.$f.$optid.v {%W selection range 0 end} - pack $w.$f.$optid.v -side right -anchor e -padx 5 - pack $w.$f.$optid -side top -anchor w -fill x - } - t { - frame $w.$f.$optid - label $w.$f.$optid.l -text "$text:" - entry $w.$f.$optid.v \ - -borderwidth 1 \ - -relief sunken \ - -width 20 \ - -textvariable ${f}_config_new($name) - pack $w.$f.$optid.l -side left -anchor w - pack $w.$f.$optid.v -side left -anchor w \ - -fill x -expand 1 \ - -padx 5 - pack $w.$f.$optid -side top -anchor w -fill x - } - } - } - } - - set all_fonts [lsort [font families]] - foreach option $font_descs { - set name [lindex $option 0] - set font [lindex $option 1] - set text [lindex $option 2] - - set global_config_new(gui.$font^^family) \ - [font configure $font -family] - set global_config_new(gui.$font^^size) \ - [font configure $font -size] - - frame $w.global.$name - label $w.global.$name.l -text "$text:" - pack $w.global.$name.l -side left -anchor w -fill x - eval tk_optionMenu $w.global.$name.family \ - global_config_new(gui.$font^^family) \ - $all_fonts - spinbox $w.global.$name.size \ - -textvariable global_config_new(gui.$font^^size) \ - -from 2 -to 80 -increment 1 \ - -width 3 - bind $w.global.$name.size {%W selection range 0 end} - pack $w.global.$name.size -side right -anchor e - pack $w.global.$name.family -side right -anchor e - pack $w.global.$name -side top -anchor w -fill x - } - - bind $w "grab $w; focus $w.buttons.save" - bind $w "destroy $w" - bind $w [list do_save_config $w] - wm title $w "[appname] ([reponame]): Options" - tkwait window $w -} - -proc do_restore_defaults {} { - global font_descs default_config repo_config - global repo_config_new global_config_new - - foreach name [array names default_config] { - set repo_config_new($name) $default_config($name) - set global_config_new($name) $default_config($name) - } - - foreach option $font_descs { - set name [lindex $option 0] - set repo_config(gui.$name) $default_config(gui.$name) - } - apply_config - - foreach option $font_descs { - set name [lindex $option 0] - set font [lindex $option 1] - set global_config_new(gui.$font^^family) \ - [font configure $font -family] - set global_config_new(gui.$font^^size) \ - [font configure $font -size] - } -} - -proc do_save_config {w} { - if {[catch {save_config} err]} { - error_popup "Failed to completely save options:\n\n$err" - } - reshow_diff - destroy $w -} - -proc do_windows_shortcut {} { - global argv0 - - set fn [tk_getSaveFile \ - -parent . \ - -title "[appname] ([reponame]): Create Desktop Icon" \ - -initialfile "Git [reponame].bat"] - if {$fn != {}} { - if {[catch { - set fd [open $fn w] - puts $fd "@ECHO Entering [reponame]" - puts $fd "@ECHO Starting git-gui... please wait..." - puts $fd "@SET PATH=[file normalize [gitexec]];%PATH%" - puts $fd "@SET GIT_DIR=[file normalize [gitdir]]" - puts -nonewline $fd "@\"[info nameofexecutable]\"" - puts $fd " \"[file normalize $argv0]\"" - close $fd - } err]} { - error_popup "Cannot write script:\n\n$err" - } - } -} - -proc do_cygwin_shortcut {} { - global argv0 - - if {[catch { - set desktop [exec cygpath \ - --windows \ - --absolute \ - --long-name \ - --desktop] - }]} { - set desktop . - } - set fn [tk_getSaveFile \ - -parent . \ - -title "[appname] ([reponame]): Create Desktop Icon" \ - -initialdir $desktop \ - -initialfile "Git [reponame].bat"] - if {$fn != {}} { - if {[catch { - set fd [open $fn w] - set sh [exec cygpath \ - --windows \ - --absolute \ - /bin/sh] - set me [exec cygpath \ - --unix \ - --absolute \ - $argv0] - set gd [exec cygpath \ - --unix \ - --absolute \ - [gitdir]] - set gw [exec cygpath \ - --windows \ - --absolute \ - [file dirname [gitdir]]] - regsub -all ' $me "'\\''" me - regsub -all ' $gd "'\\''" gd - puts $fd "@ECHO Entering $gw" - puts $fd "@ECHO Starting git-gui... please wait..." - puts -nonewline $fd "@\"$sh\" --login -c \"" - puts -nonewline $fd "GIT_DIR='$gd'" - puts -nonewline $fd " '$me'" - puts $fd "&\"" - close $fd - } err]} { - error_popup "Cannot write script:\n\n$err" - } - } -} - -proc do_macosx_app {} { - global argv0 env - - set fn [tk_getSaveFile \ - -parent . \ - -title "[appname] ([reponame]): Create Desktop Icon" \ - -initialdir [file join $env(HOME) Desktop] \ - -initialfile "Git [reponame].app"] - if {$fn != {}} { - if {[catch { - set Contents [file join $fn Contents] - set MacOS [file join $Contents MacOS] - set exe [file join $MacOS git-gui] - - file mkdir $MacOS - - set fd [open [file join $Contents Info.plist] w] - puts $fd { - - - - CFBundleDevelopmentRegion - English - CFBundleExecutable - git-gui - CFBundleIdentifier - org.spearce.git-gui - CFBundleInfoDictionaryVersion - 6.0 - CFBundlePackageType - APPL - CFBundleSignature - ???? - CFBundleVersion - 1.0 - NSPrincipalClass - NSApplication - -} - close $fd - - set fd [open $exe w] - set gd [file normalize [gitdir]] - set ep [file normalize [gitexec]] - regsub -all ' $gd "'\\''" gd - regsub -all ' $ep "'\\''" ep - puts $fd "#!/bin/sh" - foreach name [array names env] { - if {[string match GIT_* $name]} { - regsub -all ' $env($name) "'\\''" v - puts $fd "export $name='$v'" - } - } - puts $fd "export PATH='$ep':\$PATH" - puts $fd "export GIT_DIR='$gd'" - puts $fd "exec [file normalize $argv0]" - close $fd - - file attributes $exe -permissions u+x,g+x,o+x - } err]} { - error_popup "Cannot write icon:\n\n$err" - } - } -} - proc toggle_or_diff {w x y} { global file_states file_lists current_diff_path ui_index ui_workdir global last_clicked selected_paths diff --git a/lib/blame.tcl b/lib/blame.tcl new file mode 100644 index 0000000..c276fa9 --- /dev/null +++ b/lib/blame.tcl @@ -0,0 +1,390 @@ +# git-gui blame viewer +# Copyright (C) 2006, 2007 Shawn Pearce + +proc show_blame {commit path} { + global next_browser_id blame_status blame_data + + if {[winfo ismapped .]} { + set w .browser[incr next_browser_id] + set tl $w + toplevel $w + } else { + set w {} + set tl . + } + set blame_status($w) {Loading current file content...} + + label $w.path -text "$commit:$path" \ + -anchor w \ + -justify left \ + -borderwidth 1 \ + -relief sunken \ + -font font_uibold + pack $w.path -side top -fill x + + frame $w.out + text $w.out.loaded_t \ + -background white -borderwidth 0 \ + -state disabled \ + -wrap none \ + -height 40 \ + -width 1 \ + -font font_diff + $w.out.loaded_t tag conf annotated -background grey + + text $w.out.linenumber_t \ + -background white -borderwidth 0 \ + -state disabled \ + -wrap none \ + -height 40 \ + -width 5 \ + -font font_diff + $w.out.linenumber_t tag conf linenumber -justify right + + text $w.out.file_t \ + -background white -borderwidth 0 \ + -state disabled \ + -wrap none \ + -height 40 \ + -width 80 \ + -xscrollcommand [list $w.out.sbx set] \ + -font font_diff + + scrollbar $w.out.sbx -orient h -command [list $w.out.file_t xview] + scrollbar $w.out.sby -orient v \ + -command [list scrollbar2many [list \ + $w.out.loaded_t \ + $w.out.linenumber_t \ + $w.out.file_t \ + ] yview] + grid \ + $w.out.linenumber_t \ + $w.out.loaded_t \ + $w.out.file_t \ + $w.out.sby \ + -sticky nsew + grid conf $w.out.sbx -column 2 -sticky we + grid columnconfigure $w.out 2 -weight 1 + grid rowconfigure $w.out 0 -weight 1 + pack $w.out -fill both -expand 1 + + label $w.status -textvariable blame_status($w) \ + -anchor w \ + -justify left \ + -borderwidth 1 \ + -relief sunken + pack $w.status -side bottom -fill x + + frame $w.cm + text $w.cm.t \ + -background white -borderwidth 0 \ + -state disabled \ + -wrap none \ + -height 10 \ + -width 80 \ + -xscrollcommand [list $w.cm.sbx set] \ + -yscrollcommand [list $w.cm.sby set] \ + -font font_diff + scrollbar $w.cm.sbx -orient h -command [list $w.cm.t xview] + scrollbar $w.cm.sby -orient v -command [list $w.cm.t yview] + pack $w.cm.sby -side right -fill y + pack $w.cm.sbx -side bottom -fill x + pack $w.cm.t -expand 1 -fill both + pack $w.cm -side bottom -fill x + + menu $w.ctxm -tearoff 0 + $w.ctxm add command -label "Copy Commit" \ + -command "blame_copycommit $w \$cursorW @\$cursorX,\$cursorY" + + foreach i [list \ + $w.out.loaded_t \ + $w.out.linenumber_t \ + $w.out.file_t] { + $i tag conf in_sel \ + -background [$i cget -foreground] \ + -foreground [$i cget -background] + $i conf -yscrollcommand \ + [list many2scrollbar [list \ + $w.out.loaded_t \ + $w.out.linenumber_t \ + $w.out.file_t \ + ] yview $w.out.sby] + bind $i " + blame_click {$w} \\ + $w.cm.t \\ + $w.out.linenumber_t \\ + $w.out.file_t \\ + $i @%x,%y + focus $i + " + bind_button3 $i " + set cursorX %x + set cursorY %y + set cursorW %W + tk_popup $w.ctxm %X %Y + " + } + + bind $w.cm.t "focus $w.cm.t" + bind $tl "focus $tl" + bind $tl " + array unset blame_status {$w} + array unset blame_data $w,* + " + wm title $tl "[appname] ([reponame]): File Viewer" + + set blame_data($w,commit_count) 0 + set blame_data($w,commit_list) {} + set blame_data($w,total_lines) 0 + set blame_data($w,blame_lines) 0 + set blame_data($w,highlight_commit) {} + set blame_data($w,highlight_line) -1 + + set cmd [list git cat-file blob "$commit:$path"] + set fd [open "| $cmd" r] + fconfigure $fd -blocking 0 -translation lf -encoding binary + fileevent $fd readable [list read_blame_catfile \ + $fd $w $commit $path \ + $w.cm.t $w.out.loaded_t $w.out.linenumber_t $w.out.file_t] +} + +proc read_blame_catfile {fd w commit path w_cmit w_load w_line w_file} { + global blame_status blame_data + + if {![winfo exists $w_file]} { + catch {close $fd} + return + } + + set n $blame_data($w,total_lines) + $w_load conf -state normal + $w_line conf -state normal + $w_file conf -state normal + while {[gets $fd line] >= 0} { + regsub "\r\$" $line {} line + incr n + $w_load insert end "\n" + $w_line insert end "$n\n" linenumber + $w_file insert end "$line\n" + } + $w_load conf -state disabled + $w_line conf -state disabled + $w_file conf -state disabled + set blame_data($w,total_lines) $n + + if {[eof $fd]} { + close $fd + blame_incremental_status $w + set cmd [list git blame -M -C --incremental] + lappend cmd $commit -- $path + set fd [open "| $cmd" r] + fconfigure $fd -blocking 0 -translation lf -encoding binary + fileevent $fd readable [list read_blame_incremental $fd $w \ + $w_load $w_cmit $w_line $w_file] + } +} + +proc read_blame_incremental {fd w w_load w_cmit w_line w_file} { + global blame_status blame_data + + if {![winfo exists $w_file]} { + catch {close $fd} + return + } + + while {[gets $fd line] >= 0} { + if {[regexp {^([a-z0-9]{40}) (\d+) (\d+) (\d+)$} $line line \ + cmit original_line final_line line_count]} { + set blame_data($w,commit) $cmit + set blame_data($w,original_line) $original_line + set blame_data($w,final_line) $final_line + set blame_data($w,line_count) $line_count + + if {[catch {set g $blame_data($w,$cmit,order)}]} { + $w_line tag conf g$cmit + $w_file tag conf g$cmit + $w_line tag raise in_sel + $w_file tag raise in_sel + $w_file tag raise sel + set blame_data($w,$cmit,order) $blame_data($w,commit_count) + incr blame_data($w,commit_count) + lappend blame_data($w,commit_list) $cmit + } + } elseif {[string match {filename *} $line]} { + set file [string range $line 9 end] + set n $blame_data($w,line_count) + set lno $blame_data($w,final_line) + set cmit $blame_data($w,commit) + + while {$n > 0} { + if {[catch {set g g$blame_data($w,line$lno,commit)}]} { + $w_load tag add annotated $lno.0 "$lno.0 lineend + 1c" + } else { + $w_line tag remove g$g $lno.0 "$lno.0 lineend + 1c" + $w_file tag remove g$g $lno.0 "$lno.0 lineend + 1c" + } + + set blame_data($w,line$lno,commit) $cmit + set blame_data($w,line$lno,file) $file + $w_line tag add g$cmit $lno.0 "$lno.0 lineend + 1c" + $w_file tag add g$cmit $lno.0 "$lno.0 lineend + 1c" + + if {$blame_data($w,highlight_line) == -1} { + if {[lindex [$w_file yview] 0] == 0} { + $w_file see $lno.0 + blame_showcommit $w $w_cmit $w_line $w_file $lno + } + } elseif {$blame_data($w,highlight_line) == $lno} { + blame_showcommit $w $w_cmit $w_line $w_file $lno + } + + incr n -1 + incr lno + incr blame_data($w,blame_lines) + } + + set hc $blame_data($w,highlight_commit) + if {$hc ne {} + && [expr {$blame_data($w,$hc,order) + 1}] + == $blame_data($w,$cmit,order)} { + blame_showcommit $w $w_cmit $w_line $w_file \ + $blame_data($w,highlight_line) + } + } elseif {[regexp {^([a-z-]+) (.*)$} $line line header data]} { + set blame_data($w,$blame_data($w,commit),$header) $data + } + } + + if {[eof $fd]} { + close $fd + set blame_status($w) {Annotation complete.} + } else { + blame_incremental_status $w + } +} + +proc blame_incremental_status {w} { + global blame_status blame_data + + set have $blame_data($w,blame_lines) + set total $blame_data($w,total_lines) + set pdone 0 + if {$total} {set pdone [expr {100 * $have / $total}]} + + set blame_status($w) [format \ + "Loading annotations... %i of %i lines annotated (%2i%%)" \ + $have $total $pdone] +} + +proc blame_click {w w_cmit w_line w_file cur_w pos} { + set lno [lindex [split [$cur_w index $pos] .] 0] + if {$lno eq {}} return + + $w_line tag remove in_sel 0.0 end + $w_file tag remove in_sel 0.0 end + $w_line tag add in_sel $lno.0 "$lno.0 + 1 line" + $w_file tag add in_sel $lno.0 "$lno.0 + 1 line" + + blame_showcommit $w $w_cmit $w_line $w_file $lno +} + +set blame_colors { + #ff4040 + #ff40ff + #4040ff +} + +proc blame_showcommit {w w_cmit w_line w_file lno} { + global blame_colors blame_data repo_config + + set cmit $blame_data($w,highlight_commit) + if {$cmit ne {}} { + set idx $blame_data($w,$cmit,order) + set i 0 + foreach c $blame_colors { + set h [lindex $blame_data($w,commit_list) [expr {$idx - 1 + $i}]] + $w_line tag conf g$h -background white + $w_file tag conf g$h -background white + incr i + } + } + + $w_cmit conf -state normal + $w_cmit delete 0.0 end + if {[catch {set cmit $blame_data($w,line$lno,commit)}]} { + set cmit {} + $w_cmit insert end "Loading annotation..." + } else { + set idx $blame_data($w,$cmit,order) + set i 0 + foreach c $blame_colors { + set h [lindex $blame_data($w,commit_list) [expr {$idx - 1 + $i}]] + $w_line tag conf g$h -background $c + $w_file tag conf g$h -background $c + incr i + } + + set author_name {} + set author_email {} + set author_time {} + catch {set author_name $blame_data($w,$cmit,author)} + catch {set author_email $blame_data($w,$cmit,author-mail)} + catch {set author_time [clock format $blame_data($w,$cmit,author-time)]} + + set committer_name {} + set committer_email {} + set committer_time {} + catch {set committer_name $blame_data($w,$cmit,committer)} + catch {set committer_email $blame_data($w,$cmit,committer-mail)} + catch {set committer_time [clock format $blame_data($w,$cmit,committer-time)]} + + if {[catch {set msg $blame_data($w,$cmit,message)}]} { + set msg {} + catch { + set fd [open "| git cat-file commit $cmit" r] + fconfigure $fd -encoding binary -translation lf + if {[catch {set enc $repo_config(i18n.commitencoding)}]} { + set enc utf-8 + } + while {[gets $fd line] > 0} { + if {[string match {encoding *} $line]} { + set enc [string tolower [string range $line 9 end]] + } + } + set msg [encoding convertfrom $enc [read $fd]] + set msg [string trim $msg] + close $fd + + set author_name [encoding convertfrom $enc $author_name] + set committer_name [encoding convertfrom $enc $committer_name] + + set blame_data($w,$cmit,author) $author_name + set blame_data($w,$cmit,committer) $committer_name + } + set blame_data($w,$cmit,message) $msg + } + + $w_cmit insert end "commit $cmit\n" + $w_cmit insert end "Author: $author_name $author_email $author_time\n" + $w_cmit insert end "Committer: $committer_name $committer_email $committer_time\n" + $w_cmit insert end "Original File: [escape_path $blame_data($w,line$lno,file)]\n" + $w_cmit insert end "\n" + $w_cmit insert end $msg + } + $w_cmit conf -state disabled + + set blame_data($w,highlight_line) $lno + set blame_data($w,highlight_commit) $cmit +} + +proc blame_copycommit {w i pos} { + global blame_data + set lno [lindex [split [$i index $pos] .] 0] + if {![catch {set commit $blame_data($w,line$lno,commit)}]} { + clipboard clear + clipboard append \ + -format STRING \ + -type STRING \ + -- $commit + } +} diff --git a/lib/branch.tcl b/lib/branch.tcl new file mode 100644 index 0000000..caaee5c --- /dev/null +++ b/lib/branch.tcl @@ -0,0 +1,572 @@ +# git-gui branch (create/delete) support +# Copyright (C) 2006, 2007 Shawn Pearce + +proc load_all_heads {} { + global all_heads + + set all_heads [list] + set fd [open "| git for-each-ref --format=%(refname) refs/heads" r] + while {[gets $fd line] > 0} { + if {[is_tracking_branch $line]} continue + if {![regsub ^refs/heads/ $line {} name]} continue + lappend all_heads $name + } + close $fd + + set all_heads [lsort $all_heads] +} + +proc load_all_tags {} { + set all_tags [list] + set fd [open "| git for-each-ref --format=%(refname) refs/tags" r] + while {[gets $fd line] > 0} { + if {![regsub ^refs/tags/ $line {} name]} continue + lappend all_tags $name + } + close $fd + + return [lsort $all_tags] +} + +proc populate_branch_menu {} { + global all_heads disable_on_lock + + set m .mbar.branch + set last [$m index last] + for {set i 0} {$i <= $last} {incr i} { + if {[$m type $i] eq {separator}} { + $m delete $i last + set new_dol [list] + foreach a $disable_on_lock { + if {[lindex $a 0] ne $m || [lindex $a 2] < $i} { + lappend new_dol $a + } + } + set disable_on_lock $new_dol + break + } + } + + if {$all_heads ne {}} { + $m add separator + } + foreach b $all_heads { + $m add radiobutton \ + -label $b \ + -command [list switch_branch $b] \ + -variable current_branch \ + -value $b + lappend disable_on_lock \ + [list $m entryconf [$m index last] -state] + } +} + +proc do_create_branch_action {w} { + global all_heads null_sha1 repo_config + global create_branch_checkout create_branch_revtype + global create_branch_head create_branch_trackinghead + global create_branch_name create_branch_revexp + global create_branch_tag + + set newbranch $create_branch_name + if {$newbranch eq {} + || $newbranch eq $repo_config(gui.newbranchtemplate)} { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "Please supply a branch name." + focus $w.desc.name_t + return + } + if {![catch {git show-ref --verify -- "refs/heads/$newbranch"}]} { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "Branch '$newbranch' already exists." + focus $w.desc.name_t + return + } + if {[catch {git check-ref-format "heads/$newbranch"}]} { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "We do not like '$newbranch' as a branch name." + focus $w.desc.name_t + return + } + + set rev {} + switch -- $create_branch_revtype { + head {set rev $create_branch_head} + tracking {set rev $create_branch_trackinghead} + tag {set rev $create_branch_tag} + expression {set rev $create_branch_revexp} + } + if {[catch {set cmt [git rev-parse --verify "${rev}^0"]}]} { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "Invalid starting revision: $rev" + return + } + if {[catch { + git update-ref \ + -m "branch: Created from $rev" \ + "refs/heads/$newbranch" \ + $cmt \ + $null_sha1 + } err]} { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "Failed to create '$newbranch'.\n\n$err" + return + } + + lappend all_heads $newbranch + set all_heads [lsort $all_heads] + populate_branch_menu + destroy $w + if {$create_branch_checkout} { + switch_branch $newbranch + } +} + +proc radio_selector {varname value args} { + upvar #0 $varname var + set var $value +} + +trace add variable create_branch_head write \ + [list radio_selector create_branch_revtype head] +trace add variable create_branch_trackinghead write \ + [list radio_selector create_branch_revtype tracking] +trace add variable create_branch_tag write \ + [list radio_selector create_branch_revtype tag] + +trace add variable delete_branch_head write \ + [list radio_selector delete_branch_checktype head] +trace add variable delete_branch_trackinghead write \ + [list radio_selector delete_branch_checktype tracking] + +proc do_create_branch {} { + global all_heads current_branch repo_config + global create_branch_checkout create_branch_revtype + global create_branch_head create_branch_trackinghead + global create_branch_name create_branch_revexp + global create_branch_tag + + set w .branch_editor + toplevel $w + wm geometry $w "+[winfo rootx .]+[winfo rooty .]" + + label $w.header -text {Create New Branch} \ + -font font_uibold + pack $w.header -side top -fill x + + frame $w.buttons + button $w.buttons.create -text Create \ + -default active \ + -command [list do_create_branch_action $w] + pack $w.buttons.create -side right + button $w.buttons.cancel -text {Cancel} \ + -command [list destroy $w] + pack $w.buttons.cancel -side right -padx 5 + pack $w.buttons -side bottom -fill x -pady 10 -padx 10 + + labelframe $w.desc -text {Branch Description} + label $w.desc.name_l -text {Name:} + entry $w.desc.name_t \ + -borderwidth 1 \ + -relief sunken \ + -width 40 \ + -textvariable create_branch_name \ + -validate key \ + -validatecommand { + if {%d == 1 && [regexp {[~^:?*\[\0- ]} %S]} {return 0} + return 1 + } + grid $w.desc.name_l $w.desc.name_t -sticky we -padx {0 5} + grid columnconfigure $w.desc 1 -weight 1 + pack $w.desc -anchor nw -fill x -pady 5 -padx 5 + + labelframe $w.from -text {Starting Revision} + radiobutton $w.from.head_r \ + -text {Local Branch:} \ + -value head \ + -variable create_branch_revtype + eval tk_optionMenu $w.from.head_m create_branch_head $all_heads + grid $w.from.head_r $w.from.head_m -sticky w + set all_trackings [all_tracking_branches] + if {$all_trackings ne {}} { + set create_branch_trackinghead [lindex $all_trackings 0] + radiobutton $w.from.tracking_r \ + -text {Tracking Branch:} \ + -value tracking \ + -variable create_branch_revtype + eval tk_optionMenu $w.from.tracking_m \ + create_branch_trackinghead \ + $all_trackings + grid $w.from.tracking_r $w.from.tracking_m -sticky w + } + set all_tags [load_all_tags] + if {$all_tags ne {}} { + set create_branch_tag [lindex $all_tags 0] + radiobutton $w.from.tag_r \ + -text {Tag:} \ + -value tag \ + -variable create_branch_revtype + eval tk_optionMenu $w.from.tag_m create_branch_tag $all_tags + grid $w.from.tag_r $w.from.tag_m -sticky w + } + radiobutton $w.from.exp_r \ + -text {Revision Expression:} \ + -value expression \ + -variable create_branch_revtype + entry $w.from.exp_t \ + -borderwidth 1 \ + -relief sunken \ + -width 50 \ + -textvariable create_branch_revexp \ + -validate key \ + -validatecommand { + if {%d == 1 && [regexp {\s} %S]} {return 0} + if {%d == 1 && [string length %S] > 0} { + set create_branch_revtype expression + } + return 1 + } + grid $w.from.exp_r $w.from.exp_t -sticky we -padx {0 5} + grid columnconfigure $w.from 1 -weight 1 + pack $w.from -anchor nw -fill x -pady 5 -padx 5 + + labelframe $w.postActions -text {Post Creation Actions} + checkbutton $w.postActions.checkout \ + -text {Checkout after creation} \ + -variable create_branch_checkout + pack $w.postActions.checkout -anchor nw + pack $w.postActions -anchor nw -fill x -pady 5 -padx 5 + + set create_branch_checkout 1 + set create_branch_head $current_branch + set create_branch_revtype head + set create_branch_name $repo_config(gui.newbranchtemplate) + set create_branch_revexp {} + + bind $w " + grab $w + $w.desc.name_t icursor end + focus $w.desc.name_t + " + bind $w "destroy $w" + bind $w "do_create_branch_action $w;break" + wm title $w "[appname] ([reponame]): Create Branch" + tkwait window $w +} + +proc do_delete_branch_action {w} { + global all_heads + global delete_branch_checktype delete_branch_head delete_branch_trackinghead + + set check_rev {} + switch -- $delete_branch_checktype { + head {set check_rev $delete_branch_head} + tracking {set check_rev $delete_branch_trackinghead} + always {set check_rev {:none}} + } + if {$check_rev eq {:none}} { + set check_cmt {} + } elseif {[catch {set check_cmt [git rev-parse --verify "${check_rev}^0"]}]} { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "Invalid check revision: $check_rev" + return + } + + set to_delete [list] + set not_merged [list] + foreach i [$w.list.l curselection] { + set b [$w.list.l get $i] + if {[catch {set o [git rev-parse --verify $b]}]} continue + if {$check_cmt ne {}} { + if {$b eq $check_rev} continue + if {[catch {set m [git merge-base $o $check_cmt]}]} continue + if {$o ne $m} { + lappend not_merged $b + continue + } + } + lappend to_delete [list $b $o] + } + if {$not_merged ne {}} { + set msg "The following branches are not completely merged into $check_rev: + + - [join $not_merged "\n - "]" + tk_messageBox \ + -icon info \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message $msg + } + if {$to_delete eq {}} return + if {$delete_branch_checktype eq {always}} { + set msg {Recovering deleted branches is difficult. + +Delete the selected branches?} + if {[tk_messageBox \ + -icon warning \ + -type yesno \ + -title [wm title $w] \ + -parent $w \ + -message $msg] ne yes} { + return + } + } + + set failed {} + foreach i $to_delete { + set b [lindex $i 0] + set o [lindex $i 1] + if {[catch {git update-ref -d "refs/heads/$b" $o} err]} { + append failed " - $b: $err\n" + } else { + set x [lsearch -sorted -exact $all_heads $b] + if {$x >= 0} { + set all_heads [lreplace $all_heads $x $x] + } + } + } + + if {$failed ne {}} { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "Failed to delete branches:\n$failed" + } + + set all_heads [lsort $all_heads] + populate_branch_menu + destroy $w +} + +proc do_delete_branch {} { + global all_heads tracking_branches current_branch + global delete_branch_checktype delete_branch_head delete_branch_trackinghead + + set w .branch_editor + toplevel $w + wm geometry $w "+[winfo rootx .]+[winfo rooty .]" + + label $w.header -text {Delete Local Branch} \ + -font font_uibold + pack $w.header -side top -fill x + + frame $w.buttons + button $w.buttons.create -text Delete \ + -command [list do_delete_branch_action $w] + pack $w.buttons.create -side right + button $w.buttons.cancel -text {Cancel} \ + -command [list destroy $w] + pack $w.buttons.cancel -side right -padx 5 + pack $w.buttons -side bottom -fill x -pady 10 -padx 10 + + labelframe $w.list -text {Local Branches} + listbox $w.list.l \ + -height 10 \ + -width 70 \ + -selectmode extended \ + -yscrollcommand [list $w.list.sby set] + foreach h $all_heads { + if {$h ne $current_branch} { + $w.list.l insert end $h + } + } + scrollbar $w.list.sby -command [list $w.list.l yview] + pack $w.list.sby -side right -fill y + pack $w.list.l -side left -fill both -expand 1 + pack $w.list -fill both -expand 1 -pady 5 -padx 5 + + labelframe $w.validate -text {Delete Only If} + radiobutton $w.validate.head_r \ + -text {Merged Into Local Branch:} \ + -value head \ + -variable delete_branch_checktype + eval tk_optionMenu $w.validate.head_m delete_branch_head $all_heads + grid $w.validate.head_r $w.validate.head_m -sticky w + set all_trackings [all_tracking_branches] + if {$all_trackings ne {}} { + set delete_branch_trackinghead [lindex $all_trackings 0] + radiobutton $w.validate.tracking_r \ + -text {Merged Into Tracking Branch:} \ + -value tracking \ + -variable delete_branch_checktype + eval tk_optionMenu $w.validate.tracking_m \ + delete_branch_trackinghead \ + $all_trackings + grid $w.validate.tracking_r $w.validate.tracking_m -sticky w + } + radiobutton $w.validate.always_r \ + -text {Always (Do not perform merge checks)} \ + -value always \ + -variable delete_branch_checktype + grid $w.validate.always_r -columnspan 2 -sticky w + grid columnconfigure $w.validate 1 -weight 1 + pack $w.validate -anchor nw -fill x -pady 5 -padx 5 + + set delete_branch_head $current_branch + set delete_branch_checktype head + + bind $w "grab $w; focus $w" + bind $w "destroy $w" + wm title $w "[appname] ([reponame]): Delete Branch" + tkwait window $w +} + +proc switch_branch {new_branch} { + global HEAD commit_type current_branch repo_config + + if {![lock_index switch]} return + + # -- Our in memory state should match the repository. + # + repository_state curType curHEAD curMERGE_HEAD + if {[string match amend* $commit_type] + && $curType eq {normal} + && $curHEAD eq $HEAD} { + } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} { + info_popup {Last scanned state does not match repository state. + +Another Git program has modified this repository since the last scan. A rescan must be performed before the current branch can be changed. + +The rescan will be automatically started now. +} + unlock_index + rescan {set ui_status_value {Ready.}} + return + } + + # -- Don't do a pointless switch. + # + if {$current_branch eq $new_branch} { + unlock_index + return + } + + if {$repo_config(gui.trustmtime) eq {true}} { + switch_branch_stage2 {} $new_branch + } else { + set ui_status_value {Refreshing file status...} + set cmd [list git update-index] + lappend cmd -q + lappend cmd --unmerged + lappend cmd --ignore-missing + lappend cmd --refresh + set fd_rf [open "| $cmd" r] + fconfigure $fd_rf -blocking 0 -translation binary + fileevent $fd_rf readable \ + [list switch_branch_stage2 $fd_rf $new_branch] + } +} + +proc switch_branch_stage2 {fd_rf new_branch} { + global ui_status_value HEAD + + if {$fd_rf ne {}} { + read $fd_rf + if {![eof $fd_rf]} return + close $fd_rf + } + + set ui_status_value "Updating working directory to '$new_branch'..." + set cmd [list git read-tree] + lappend cmd -m + lappend cmd -u + lappend cmd --exclude-per-directory=.gitignore + lappend cmd $HEAD + lappend cmd $new_branch + set fd_rt [open "| $cmd" r] + fconfigure $fd_rt -blocking 0 -translation binary + fileevent $fd_rt readable \ + [list switch_branch_readtree_wait $fd_rt $new_branch] +} + +proc switch_branch_readtree_wait {fd_rt new_branch} { + global selected_commit_type commit_type HEAD MERGE_HEAD PARENT + global current_branch + global ui_comm ui_status_value + + # -- We never get interesting output on stdout; only stderr. + # + read $fd_rt + fconfigure $fd_rt -blocking 1 + if {![eof $fd_rt]} { + fconfigure $fd_rt -blocking 0 + return + } + + # -- The working directory wasn't in sync with the index and + # we'd have to overwrite something to make the switch. A + # merge is required. + # + if {[catch {close $fd_rt} err]} { + regsub {^fatal: } $err {} err + warn_popup "File level merge required. + +$err + +Staying on branch '$current_branch'." + set ui_status_value "Aborted checkout of '$new_branch' (file level merging is required)." + unlock_index + return + } + + # -- Update the symbolic ref. Core git doesn't even check for failure + # here, it Just Works(tm). If it doesn't we are in some really ugly + # state that is difficult to recover from within git-gui. + # + if {[catch {git symbolic-ref HEAD "refs/heads/$new_branch"} err]} { + error_popup "Failed to set current branch. + +This working directory is only partially switched. We successfully updated your files, but failed to update an internal Git file. + +This should not have occurred. [appname] will now close and give up. + +$err" + do_quit + return + } + + # -- Update our repository state. If we were previously in amend mode + # we need to toss the current buffer and do a full rescan to update + # our file lists. If we weren't in amend mode our file lists are + # accurate and we can avoid the rescan. + # + unlock_index + set selected_commit_type new + if {[string match amend* $commit_type]} { + $ui_comm delete 0.0 end + $ui_comm edit reset + $ui_comm edit modified false + rescan {set ui_status_value "Checked out branch '$current_branch'."} + } else { + repository_state commit_type HEAD MERGE_HEAD + set PARENT $HEAD + set ui_status_value "Checked out branch '$current_branch'." + } +} diff --git a/lib/browser.tcl b/lib/browser.tcl new file mode 100644 index 0000000..631859a --- /dev/null +++ b/lib/browser.tcl @@ -0,0 +1,263 @@ +# git-gui tree browser +# Copyright (C) 2006, 2007 Shawn Pearce + +set next_browser_id 0 + +proc new_browser {commit} { + global next_browser_id cursor_ptr M1B + global browser_commit browser_status browser_stack browser_path browser_busy + + if {[winfo ismapped .]} { + set w .browser[incr next_browser_id] + set tl $w + toplevel $w + } else { + set w {} + set tl . + } + set w_list $w.list.l + set browser_commit($w_list) $commit + set browser_status($w_list) {Starting...} + set browser_stack($w_list) {} + set browser_path($w_list) $browser_commit($w_list): + set browser_busy($w_list) 1 + + label $w.path -textvariable browser_path($w_list) \ + -anchor w \ + -justify left \ + -borderwidth 1 \ + -relief sunken \ + -font font_uibold + pack $w.path -anchor w -side top -fill x + + frame $w.list + text $w_list -background white -borderwidth 0 \ + -cursor $cursor_ptr \ + -state disabled \ + -wrap none \ + -height 20 \ + -width 70 \ + -xscrollcommand [list $w.list.sbx set] \ + -yscrollcommand [list $w.list.sby set] + $w_list tag conf in_sel \ + -background [$w_list cget -foreground] \ + -foreground [$w_list cget -background] + scrollbar $w.list.sbx -orient h -command [list $w_list xview] + scrollbar $w.list.sby -orient v -command [list $w_list yview] + pack $w.list.sbx -side bottom -fill x + pack $w.list.sby -side right -fill y + pack $w_list -side left -fill both -expand 1 + pack $w.list -side top -fill both -expand 1 + + label $w.status -textvariable browser_status($w_list) \ + -anchor w \ + -justify left \ + -borderwidth 1 \ + -relief sunken + pack $w.status -anchor w -side bottom -fill x + + bind $w_list "browser_click 0 $w_list @%x,%y;break" + bind $w_list "browser_click 1 $w_list @%x,%y;break" + bind $w_list <$M1B-Up> "browser_parent $w_list;break" + bind $w_list <$M1B-Left> "browser_parent $w_list;break" + bind $w_list "browser_move -1 $w_list;break" + bind $w_list "browser_move 1 $w_list;break" + bind $w_list <$M1B-Right> "browser_enter $w_list;break" + bind $w_list "browser_enter $w_list;break" + bind $w_list "browser_page -1 $w_list;break" + bind $w_list "browser_page 1 $w_list;break" + bind $w_list break + bind $w_list break + + bind $tl "focus $w" + bind $tl " + array unset browser_buffer $w_list + array unset browser_files $w_list + array unset browser_status $w_list + array unset browser_stack $w_list + array unset browser_path $w_list + array unset browser_commit $w_list + array unset browser_busy $w_list + " + wm title $tl "[appname] ([reponame]): File Browser" + ls_tree $w_list $browser_commit($w_list) {} +} + +proc browser_move {dir w} { + global browser_files browser_busy + + if {$browser_busy($w)} return + set lno [lindex [split [$w index in_sel.first] .] 0] + incr lno $dir + if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} { + $w tag remove in_sel 0.0 end + $w tag add in_sel $lno.0 [expr {$lno + 1}].0 + $w see $lno.0 + } +} + +proc browser_page {dir w} { + global browser_files browser_busy + + if {$browser_busy($w)} return + $w yview scroll $dir pages + set lno [expr {int( + [lindex [$w yview] 0] + * [llength $browser_files($w)] + + 1)}] + if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} { + $w tag remove in_sel 0.0 end + $w tag add in_sel $lno.0 [expr {$lno + 1}].0 + $w see $lno.0 + } +} + +proc browser_parent {w} { + global browser_files browser_status browser_path + global browser_stack browser_busy + + if {$browser_busy($w)} return + set info [lindex $browser_files($w) 0] + if {[lindex $info 0] eq {parent}} { + set parent [lindex $browser_stack($w) end-1] + set browser_stack($w) [lrange $browser_stack($w) 0 end-2] + if {$browser_stack($w) eq {}} { + regsub {:.*$} $browser_path($w) {:} browser_path($w) + } else { + regsub {/[^/]+$} $browser_path($w) {} browser_path($w) + } + set browser_status($w) "Loading $browser_path($w)..." + ls_tree $w [lindex $parent 0] [lindex $parent 1] + } +} + +proc browser_enter {w} { + global browser_files browser_status browser_path + global browser_commit browser_stack browser_busy + + if {$browser_busy($w)} return + set lno [lindex [split [$w index in_sel.first] .] 0] + set info [lindex $browser_files($w) [expr {$lno - 1}]] + if {$info ne {}} { + switch -- [lindex $info 0] { + parent { + browser_parent $w + } + tree { + set name [lindex $info 2] + set escn [escape_path $name] + set browser_status($w) "Loading $escn..." + append browser_path($w) $escn + ls_tree $w [lindex $info 1] $name + } + blob { + set name [lindex $info 2] + set p {} + foreach n $browser_stack($w) { + append p [lindex $n 1] + } + append p $name + show_blame $browser_commit($w) $p + } + } + } +} + +proc browser_click {was_double_click w pos} { + global browser_files browser_busy + + if {$browser_busy($w)} return + set lno [lindex [split [$w index $pos] .] 0] + focus $w + + if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} { + $w tag remove in_sel 0.0 end + $w tag add in_sel $lno.0 [expr {$lno + 1}].0 + if {$was_double_click} { + browser_enter $w + } + } +} + +proc ls_tree {w tree_id name} { + global browser_buffer browser_files browser_stack browser_busy + + set browser_buffer($w) {} + set browser_files($w) {} + set browser_busy($w) 1 + + $w conf -state normal + $w tag remove in_sel 0.0 end + $w delete 0.0 end + if {$browser_stack($w) ne {}} { + $w image create end \ + -align center -padx 5 -pady 1 \ + -name icon0 \ + -image file_uplevel + $w insert end {[Up To Parent]} + lappend browser_files($w) parent + } + lappend browser_stack($w) [list $tree_id $name] + $w conf -state disabled + + set cmd [list git ls-tree -z $tree_id] + set fd [open "| $cmd" r] + fconfigure $fd -blocking 0 -translation binary -encoding binary + fileevent $fd readable [list read_ls_tree $fd $w] +} + +proc read_ls_tree {fd w} { + global browser_buffer browser_files browser_status browser_busy + + if {![winfo exists $w]} { + catch {close $fd} + return + } + + append browser_buffer($w) [read $fd] + set pck [split $browser_buffer($w) "\0"] + set browser_buffer($w) [lindex $pck end] + + set n [llength $browser_files($w)] + $w conf -state normal + foreach p [lrange $pck 0 end-1] { + set info [split $p "\t"] + set path [lindex $info 1] + set info [split [lindex $info 0] { }] + set type [lindex $info 1] + set object [lindex $info 2] + + switch -- $type { + blob { + set image file_mod + } + tree { + set image file_dir + append path / + } + default { + set image file_question + } + } + + if {$n > 0} {$w insert end "\n"} + $w image create end \ + -align center -padx 5 -pady 1 \ + -name icon[incr n] \ + -image $image + $w insert end [escape_path $path] + lappend browser_files($w) [list $type $object $path] + } + $w conf -state disabled + + if {[eof $fd]} { + close $fd + set browser_status($w) Ready. + set browser_busy($w) 0 + array unset browser_buffer $w + if {$n > 0} { + $w tag add in_sel 1.0 2.0 + focus -force $w + } + } +} diff --git a/lib/commit.tcl b/lib/commit.tcl new file mode 100644 index 0000000..f9791f6 --- /dev/null +++ b/lib/commit.tcl @@ -0,0 +1,410 @@ +# git-gui misc. commit reading/writing support +# Copyright (C) 2006, 2007 Shawn Pearce + +proc load_last_commit {} { + global HEAD PARENT MERGE_HEAD commit_type ui_comm + global repo_config + + if {[llength $PARENT] == 0} { + error_popup {There is nothing to amend. + +You are about to create the initial commit. There is no commit before this to amend. +} + return + } + + repository_state curType curHEAD curMERGE_HEAD + if {$curType eq {merge}} { + error_popup {Cannot amend while merging. + +You are currently in the middle of a merge that has not been fully completed. You cannot amend the prior commit unless you first abort the current merge activity. +} + return + } + + set msg {} + set parents [list] + if {[catch { + set fd [open "| git cat-file commit $curHEAD" r] + fconfigure $fd -encoding binary -translation lf + if {[catch {set enc $repo_config(i18n.commitencoding)}]} { + set enc utf-8 + } + while {[gets $fd line] > 0} { + if {[string match {parent *} $line]} { + lappend parents [string range $line 7 end] + } elseif {[string match {encoding *} $line]} { + set enc [string tolower [string range $line 9 end]] + } + } + set msg [encoding convertfrom $enc [read $fd]] + set msg [string trim $msg] + close $fd + } err]} { + error_popup "Error loading commit data for amend:\n\n$err" + return + } + + set HEAD $curHEAD + set PARENT $parents + set MERGE_HEAD [list] + switch -- [llength $parents] { + 0 {set commit_type amend-initial} + 1 {set commit_type amend} + default {set commit_type amend-merge} + } + + $ui_comm delete 0.0 end + $ui_comm insert end $msg + $ui_comm edit reset + $ui_comm edit modified false + rescan {set ui_status_value {Ready.}} +} + +set GIT_COMMITTER_IDENT {} + +proc committer_ident {} { + global GIT_COMMITTER_IDENT + + if {$GIT_COMMITTER_IDENT eq {}} { + if {[catch {set me [git var GIT_COMMITTER_IDENT]} err]} { + error_popup "Unable to obtain your identity:\n\n$err" + return {} + } + if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \ + $me me GIT_COMMITTER_IDENT]} { + error_popup "Invalid GIT_COMMITTER_IDENT:\n\n$me" + return {} + } + } + + return $GIT_COMMITTER_IDENT +} + +proc do_signoff {} { + global ui_comm + + set me [committer_ident] + if {$me eq {}} return + + set sob "Signed-off-by: $me" + set last [$ui_comm get {end -1c linestart} {end -1c}] + if {$last ne $sob} { + $ui_comm edit separator + if {$last ne {} + && ![regexp {^[A-Z][A-Za-z]*-[A-Za-z-]+: *} $last]} { + $ui_comm insert end "\n" + } + $ui_comm insert end "\n$sob" + $ui_comm edit separator + $ui_comm see end + } +} + +proc create_new_commit {} { + global commit_type ui_comm + + set commit_type normal + $ui_comm delete 0.0 end + $ui_comm edit reset + $ui_comm edit modified false + rescan {set ui_status_value {Ready.}} +} + +proc commit_tree {} { + global HEAD commit_type file_states ui_comm repo_config + global ui_status_value pch_error + + if {[committer_ident] eq {}} return + if {![lock_index update]} return + + # -- Our in memory state should match the repository. + # + repository_state curType curHEAD curMERGE_HEAD + if {[string match amend* $commit_type] + && $curType eq {normal} + && $curHEAD eq $HEAD} { + } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} { + info_popup {Last scanned state does not match repository state. + +Another Git program has modified this repository since the last scan. A rescan must be performed before another commit can be created. + +The rescan will be automatically started now. +} + unlock_index + rescan {set ui_status_value {Ready.}} + return + } + + # -- At least one file should differ in the index. + # + set files_ready 0 + foreach path [array names file_states] { + switch -glob -- [lindex $file_states($path) 0] { + _? {continue} + A? - + D? - + M? {set files_ready 1} + U? { + error_popup "Unmerged files cannot be committed. + +File [short_path $path] has merge conflicts. You must resolve them and add the file before committing. +" + unlock_index + return + } + default { + error_popup "Unknown file state [lindex $s 0] detected. + +File [short_path $path] cannot be committed by this program. +" + } + } + } + if {!$files_ready && ![string match *merge $curType]} { + info_popup {No changes to commit. + +You must add at least 1 file before you can commit. +} + unlock_index + return + } + + # -- A message is required. + # + set msg [string trim [$ui_comm get 1.0 end]] + regsub -all -line {[ \t\r]+$} $msg {} msg + if {$msg eq {}} { + error_popup {Please supply a commit message. + +A good commit message has the following format: + +- First line: Describe in one sentance what you did. +- Second line: Blank +- Remaining lines: Describe why this change is good. +} + unlock_index + return + } + + # -- Run the pre-commit hook. + # + set pchook [gitdir hooks pre-commit] + + # On Cygwin [file executable] might lie so we need to ask + # the shell if the hook is executable. Yes that's annoying. + # + if {[is_Cygwin] && [file isfile $pchook]} { + set pchook [list sh -c [concat \ + "if test -x \"$pchook\";" \ + "then exec \"$pchook\" 2>&1;" \ + "fi"]] + } elseif {[file executable $pchook]} { + set pchook [list $pchook |& cat] + } else { + commit_writetree $curHEAD $msg + return + } + + set ui_status_value {Calling pre-commit hook...} + set pch_error {} + set fd_ph [open "| $pchook" r] + fconfigure $fd_ph -blocking 0 -translation binary + fileevent $fd_ph readable \ + [list commit_prehook_wait $fd_ph $curHEAD $msg] +} + +proc commit_prehook_wait {fd_ph curHEAD msg} { + global pch_error ui_status_value + + append pch_error [read $fd_ph] + fconfigure $fd_ph -blocking 1 + if {[eof $fd_ph]} { + if {[catch {close $fd_ph}]} { + set ui_status_value {Commit declined by pre-commit hook.} + hook_failed_popup pre-commit $pch_error + unlock_index + } else { + commit_writetree $curHEAD $msg + } + set pch_error {} + return + } + fconfigure $fd_ph -blocking 0 +} + +proc commit_writetree {curHEAD msg} { + global ui_status_value + + set ui_status_value {Committing changes...} + set fd_wt [open "| git write-tree" r] + fileevent $fd_wt readable \ + [list commit_committree $fd_wt $curHEAD $msg] +} + +proc commit_committree {fd_wt curHEAD msg} { + global HEAD PARENT MERGE_HEAD commit_type + global all_heads current_branch + global ui_status_value ui_comm selected_commit_type + global file_states selected_paths rescan_active + global repo_config + + gets $fd_wt tree_id + if {$tree_id eq {} || [catch {close $fd_wt} err]} { + error_popup "write-tree failed:\n\n$err" + set ui_status_value {Commit failed.} + unlock_index + return + } + + # -- Verify this wasn't an empty change. + # + if {$commit_type eq {normal}} { + set old_tree [git rev-parse "$PARENT^{tree}"] + if {$tree_id eq $old_tree} { + info_popup {No changes to commit. + +No files were modified by this commit and it was not a merge commit. + +A rescan will be automatically started now. +} + unlock_index + rescan {set ui_status_value {No changes to commit.}} + return + } + } + + # -- Build the message. + # + set msg_p [gitdir COMMIT_EDITMSG] + set msg_wt [open $msg_p w] + if {[catch {set enc $repo_config(i18n.commitencoding)}]} { + set enc utf-8 + } + fconfigure $msg_wt -encoding binary -translation binary + puts -nonewline $msg_wt [encoding convertto $enc $msg] + close $msg_wt + + # -- Create the commit. + # + set cmd [list commit-tree $tree_id] + foreach p [concat $PARENT $MERGE_HEAD] { + lappend cmd -p $p + } + lappend cmd <$msg_p + if {[catch {set cmt_id [eval git $cmd]} err]} { + error_popup "commit-tree failed:\n\n$err" + set ui_status_value {Commit failed.} + unlock_index + return + } + + # -- Update the HEAD ref. + # + set reflogm commit + if {$commit_type ne {normal}} { + append reflogm " ($commit_type)" + } + set i [string first "\n" $msg] + if {$i >= 0} { + set subject [string range $msg 0 [expr {$i - 1}]] + } else { + set subject $msg + } + append reflogm {: } $subject + if {[catch { + git update-ref -m $reflogm HEAD $cmt_id $curHEAD + } err]} { + error_popup "update-ref failed:\n\n$err" + set ui_status_value {Commit failed.} + unlock_index + return + } + + # -- Cleanup after ourselves. + # + catch {file delete $msg_p} + catch {file delete [gitdir MERGE_HEAD]} + catch {file delete [gitdir MERGE_MSG]} + catch {file delete [gitdir SQUASH_MSG]} + catch {file delete [gitdir GITGUI_MSG]} + + # -- Let rerere do its thing. + # + if {[file isdirectory [gitdir rr-cache]]} { + catch {git rerere} + } + + # -- Run the post-commit hook. + # + set pchook [gitdir hooks post-commit] + if {[is_Cygwin] && [file isfile $pchook]} { + set pchook [list sh -c [concat \ + "if test -x \"$pchook\";" \ + "then exec \"$pchook\";" \ + "fi"]] + } elseif {![file executable $pchook]} { + set pchook {} + } + if {$pchook ne {}} { + catch {exec $pchook &} + } + + $ui_comm delete 0.0 end + $ui_comm edit reset + $ui_comm edit modified false + + if {[is_enabled singlecommit]} do_quit + + # -- Make sure our current branch exists. + # + if {$commit_type eq {initial}} { + lappend all_heads $current_branch + set all_heads [lsort -unique $all_heads] + populate_branch_menu + } + + # -- Update in memory status + # + set selected_commit_type new + set commit_type normal + set HEAD $cmt_id + set PARENT $cmt_id + set MERGE_HEAD [list] + + foreach path [array names file_states] { + set s $file_states($path) + set m [lindex $s 0] + switch -glob -- $m { + _O - + _M - + _D {continue} + __ - + A_ - + M_ - + D_ { + unset file_states($path) + catch {unset selected_paths($path)} + } + DO { + set file_states($path) [list _O [lindex $s 1] {} {}] + } + AM - + AD - + MM - + MD { + set file_states($path) [list \ + _[string index $m 1] \ + [lindex $s 1] \ + [lindex $s 3] \ + {}] + } + } + } + + display_all_files + unlock_index + reshow_diff + set ui_status_value \ + "Created commit [string range $cmt_id 0 7]: $subject" +} diff --git a/lib/console.tcl b/lib/console.tcl new file mode 100644 index 0000000..e40ec96 --- /dev/null +++ b/lib/console.tcl @@ -0,0 +1,185 @@ +# git-gui console support +# Copyright (C) 2006, 2007 Shawn Pearce + +set next_console_id 0 + +proc new_console {short_title long_title} { + global next_console_id console_data + set w .console[incr next_console_id] + set console_data($w) [list $short_title $long_title] + return [console_init $w] +} + +proc console_init {w} { + global console_cr console_data M1B + + set console_cr($w) 1.0 + toplevel $w + frame $w.m + label $w.m.l1 -text "[lindex $console_data($w) 1]:" \ + -anchor w \ + -justify left \ + -font font_uibold + text $w.m.t \ + -background white -borderwidth 1 \ + -relief sunken \ + -width 80 -height 10 \ + -font font_diff \ + -state disabled \ + -yscrollcommand [list $w.m.sby set] + label $w.m.s -text {Working... please wait...} \ + -anchor w \ + -justify left \ + -font font_uibold + scrollbar $w.m.sby -command [list $w.m.t yview] + pack $w.m.l1 -side top -fill x + pack $w.m.s -side bottom -fill x + pack $w.m.sby -side right -fill y + pack $w.m.t -side left -fill both -expand 1 + pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10 + + menu $w.ctxm -tearoff 0 + $w.ctxm add command -label "Copy" \ + -command "tk_textCopy $w.m.t" + $w.ctxm add command -label "Select All" \ + -command "focus $w.m.t;$w.m.t tag add sel 0.0 end" + $w.ctxm add command -label "Copy All" \ + -command " + $w.m.t tag add sel 0.0 end + tk_textCopy $w.m.t + $w.m.t tag remove sel 0.0 end + " + + button $w.ok -text {Close} \ + -state disabled \ + -command "destroy $w" + pack $w.ok -side bottom -anchor e -pady 10 -padx 10 + + bind_button3 $w.m.t "tk_popup $w.ctxm %X %Y" + bind $w.m.t <$M1B-Key-a> "$w.m.t tag add sel 0.0 end;break" + bind $w.m.t <$M1B-Key-A> "$w.m.t tag add sel 0.0 end;break" + bind $w "focus $w" + wm title $w "[appname] ([reponame]): [lindex $console_data($w) 0]" + return $w +} + +proc console_exec {w cmd after} { + # -- Cygwin's Tcl tosses the enviroment when we exec our child. + # But most users need that so we have to relogin. :-( + # + if {[is_Cygwin]} { + set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"] + } + + # -- Tcl won't let us redirect both stdout and stderr to + # the same pipe. So pass it through cat... + # + set cmd [concat | $cmd |& cat] + + set fd_f [open $cmd r] + fconfigure $fd_f -blocking 0 -translation binary + fileevent $fd_f readable [list console_read $w $fd_f $after] +} + +proc console_read {w fd after} { + global console_cr + + set buf [read $fd] + if {$buf ne {}} { + if {![winfo exists $w]} {console_init $w} + $w.m.t conf -state normal + set c 0 + set n [string length $buf] + while {$c < $n} { + set cr [string first "\r" $buf $c] + set lf [string first "\n" $buf $c] + if {$cr < 0} {set cr [expr {$n + 1}]} + if {$lf < 0} {set lf [expr {$n + 1}]} + + if {$lf < $cr} { + $w.m.t insert end [string range $buf $c $lf] + set console_cr($w) [$w.m.t index {end -1c}] + set c $lf + incr c + } else { + $w.m.t delete $console_cr($w) end + $w.m.t insert end "\n" + $w.m.t insert end [string range $buf $c $cr] + set c $cr + incr c + } + } + $w.m.t conf -state disabled + $w.m.t see end + } + + fconfigure $fd -blocking 1 + if {[eof $fd]} { + if {[catch {close $fd}]} { + set ok 0 + } else { + set ok 1 + } + uplevel #0 $after $w $ok + return + } + fconfigure $fd -blocking 0 +} + +proc console_chain {cmdlist w {ok 1}} { + if {$ok} { + if {[llength $cmdlist] == 0} { + console_done $w $ok + return + } + + set cmd [lindex $cmdlist 0] + set cmdlist [lrange $cmdlist 1 end] + + if {[lindex $cmd 0] eq {console_exec}} { + console_exec $w \ + [lindex $cmd 1] \ + [list console_chain $cmdlist] + } else { + uplevel #0 $cmd $cmdlist $w $ok + } + } else { + console_done $w $ok + } +} + +proc console_done {args} { + global console_cr console_data + + switch -- [llength $args] { + 2 { + set w [lindex $args 0] + set ok [lindex $args 1] + } + 3 { + set w [lindex $args 1] + set ok [lindex $args 2] + } + default { + error "wrong number of args: console_done ?ignored? w ok" + } + } + + if {$ok} { + if {[winfo exists $w]} { + $w.m.s conf -background green -text {Success} + $w.ok conf -state normal + focus $w.ok + } + } else { + if {![winfo exists $w]} { + console_init $w + } + $w.m.s conf -background red -text {Error: Command Failed} + $w.ok conf -state normal + focus $w.ok + } + + array unset console_cr $w + array unset console_data $w +} diff --git a/lib/database.tcl b/lib/database.tcl new file mode 100644 index 0000000..e31466f --- /dev/null +++ b/lib/database.tcl @@ -0,0 +1,89 @@ +# git-gui object database management support +# Copyright (C) 2006, 2007 Shawn Pearce + +proc do_stats {} { + set fd [open "| git count-objects -v" r] + while {[gets $fd line] > 0} { + if {[regexp {^([^:]+): (\d+)$} $line _ name value]} { + set stats($name) $value + } + } + close $fd + + set packed_sz 0 + foreach p [glob -directory [gitdir objects pack] \ + -type f \ + -nocomplain -- *] { + incr packed_sz [file size $p] + } + if {$packed_sz > 0} { + set stats(size-pack) [expr {$packed_sz / 1024}] + } + + set w .stats_view + toplevel $w + wm geometry $w "+[winfo rootx .]+[winfo rooty .]" + + label $w.header -text {Database Statistics} + pack $w.header -side top -fill x + + frame $w.buttons -border 1 + button $w.buttons.close -text Close \ + -default active \ + -command [list destroy $w] + button $w.buttons.gc -text {Compress Database} \ + -default normal \ + -command "destroy $w;do_gc" + pack $w.buttons.close -side right + pack $w.buttons.gc -side left + pack $w.buttons -side bottom -fill x -pady 10 -padx 10 + + frame $w.stat -borderwidth 1 -relief solid + foreach s { + {count {Number of loose objects}} + {size {Disk space used by loose objects} { KiB}} + {in-pack {Number of packed objects}} + {packs {Number of packs}} + {size-pack {Disk space used by packed objects} { KiB}} + {prune-packable {Packed objects waiting for pruning}} + {garbage {Garbage files}} + } { + set name [lindex $s 0] + set label [lindex $s 1] + if {[catch {set value $stats($name)}]} continue + if {[llength $s] > 2} { + set value "$value[lindex $s 2]" + } + + label $w.stat.l_$name -text "$label:" -anchor w + label $w.stat.v_$name -text $value -anchor w + grid $w.stat.l_$name $w.stat.v_$name -sticky we -padx {0 5} + } + pack $w.stat -pady 10 -padx 10 + + bind $w "grab $w; focus $w.buttons.close" + bind $w [list destroy $w] + bind $w [list destroy $w] + wm title $w "[appname] ([reponame]): Database Statistics" + tkwait window $w +} + +proc do_gc {} { + set w [new_console {gc} {Compressing the object database}] + console_chain { + {console_exec {git pack-refs --prune}} + {console_exec {git reflog expire --all}} + {console_exec {git repack -a -d -l}} + {console_exec {git rerere gc}} + } $w +} + +proc do_fsck_objects {} { + set w [new_console {fsck-objects} \ + {Verifying the object database with fsck-objects}] + set cmd [list git fsck-objects] + lappend cmd --full + lappend cmd --cache + lappend cmd --strict + console_exec $w $cmd console_done +} diff --git a/lib/diff.tcl b/lib/diff.tcl new file mode 100644 index 0000000..7e715a6 --- /dev/null +++ b/lib/diff.tcl @@ -0,0 +1,336 @@ +# git-gui diff viewer +# Copyright (C) 2006, 2007 Shawn Pearce + +proc clear_diff {} { + global ui_diff current_diff_path current_diff_header + global ui_index ui_workdir + + $ui_diff conf -state normal + $ui_diff delete 0.0 end + $ui_diff conf -state disabled + + set current_diff_path {} + set current_diff_header {} + + $ui_index tag remove in_diff 0.0 end + $ui_workdir tag remove in_diff 0.0 end +} + +proc reshow_diff {} { + global ui_status_value file_states file_lists + global current_diff_path current_diff_side + + set p $current_diff_path + if {$p eq {}} { + # No diff is being shown. + } elseif {$current_diff_side eq {} + || [catch {set s $file_states($p)}] + || [lsearch -sorted -exact $file_lists($current_diff_side) $p] == -1} { + clear_diff + } else { + show_diff $p $current_diff_side + } +} + +proc handle_empty_diff {} { + global current_diff_path file_states file_lists + + set path $current_diff_path + set s $file_states($path) + if {[lindex $s 0] ne {_M}} return + + info_popup "No differences detected. + +[short_path $path] has no changes. + +The modification date of this file was updated by another application, but the content within the file was not changed. + +A rescan will be automatically started to find other files which may have the same state." + + clear_diff + display_file $path __ + rescan {set ui_status_value {Ready.}} 0 +} + +proc show_diff {path w {lno {}}} { + global file_states file_lists + global is_3way_diff diff_active repo_config + global ui_diff ui_status_value ui_index ui_workdir + global current_diff_path current_diff_side current_diff_header + + if {$diff_active || ![lock_index read]} return + + clear_diff + if {$lno == {}} { + set lno [lsearch -sorted -exact $file_lists($w) $path] + if {$lno >= 0} { + incr lno + } + } + if {$lno >= 1} { + $w tag add in_diff $lno.0 [expr {$lno + 1}].0 + } + + set s $file_states($path) + set m [lindex $s 0] + set is_3way_diff 0 + set diff_active 1 + set current_diff_path $path + set current_diff_side $w + set current_diff_header {} + set ui_status_value "Loading diff of [escape_path $path]..." + + # - Git won't give us the diff, there's nothing to compare to! + # + if {$m eq {_O}} { + set max_sz [expr {128 * 1024}] + if {[catch { + set fd [open $path r] + set content [read $fd $max_sz] + close $fd + set sz [file size $path] + } err ]} { + set diff_active 0 + unlock_index + set ui_status_value "Unable to display [escape_path $path]" + error_popup "Error loading file:\n\n$err" + return + } + $ui_diff conf -state normal + if {![catch {set type [exec file $path]}]} { + set n [string length $path] + if {[string equal -length $n $path $type]} { + set type [string range $type $n end] + regsub {^:?\s*} $type {} type + } + $ui_diff insert end "* $type\n" d_@ + } + if {[string first "\0" $content] != -1} { + $ui_diff insert end \ + "* Binary file (not showing content)." \ + d_@ + } else { + if {$sz > $max_sz} { + $ui_diff insert end \ +"* Untracked file is $sz bytes. +* Showing only first $max_sz bytes. +" d_@ + } + $ui_diff insert end $content + if {$sz > $max_sz} { + $ui_diff insert end " +* Untracked file clipped here by [appname]. +* To see the entire file, use an external editor. +" d_@ + } + } + $ui_diff conf -state disabled + set diff_active 0 + unlock_index + set ui_status_value {Ready.} + return + } + + set cmd [list | git] + if {$w eq $ui_index} { + lappend cmd diff-index + lappend cmd --cached + } elseif {$w eq $ui_workdir} { + if {[string index $m 0] eq {U}} { + lappend cmd diff + } else { + lappend cmd diff-files + } + } + + lappend cmd -p + lappend cmd --no-color + if {$repo_config(gui.diffcontext) > 0} { + lappend cmd "-U$repo_config(gui.diffcontext)" + } + if {$w eq $ui_index} { + lappend cmd [PARENT] + } + lappend cmd -- + lappend cmd $path + + if {[catch {set fd [open $cmd r]} err]} { + set diff_active 0 + unlock_index + set ui_status_value "Unable to display [escape_path $path]" + error_popup "Error loading diff:\n\n$err" + return + } + + fconfigure $fd \ + -blocking 0 \ + -encoding binary \ + -translation binary + fileevent $fd readable [list read_diff $fd] +} + +proc read_diff {fd} { + global ui_diff ui_status_value diff_active + global is_3way_diff current_diff_header + + $ui_diff conf -state normal + while {[gets $fd line] >= 0} { + # -- Cleanup uninteresting diff header lines. + # + if { [string match {diff --git *} $line] + || [string match {diff --cc *} $line] + || [string match {diff --combined *} $line] + || [string match {--- *} $line] + || [string match {+++ *} $line]} { + append current_diff_header $line "\n" + continue + } + if {[string match {index *} $line]} continue + if {$line eq {deleted file mode 120000}} { + set line "deleted symlink" + } + + # -- Automatically detect if this is a 3 way diff. + # + if {[string match {@@@ *} $line]} {set is_3way_diff 1} + + if {[string match {mode *} $line] + || [string match {new file *} $line] + || [string match {deleted file *} $line] + || [string match {Binary files * and * differ} $line] + || $line eq {\ No newline at end of file} + || [regexp {^\* Unmerged path } $line]} { + set tags {} + } elseif {$is_3way_diff} { + set op [string range $line 0 1] + switch -- $op { + { } {set tags {}} + {@@} {set tags d_@} + { +} {set tags d_s+} + { -} {set tags d_s-} + {+ } {set tags d_+s} + {- } {set tags d_-s} + {--} {set tags d_--} + {++} { + if {[regexp {^\+\+([<>]{7} |={7})} $line _g op]} { + set line [string replace $line 0 1 { }] + set tags d$op + } else { + set tags d_++ + } + } + default { + puts "error: Unhandled 3 way diff marker: {$op}" + set tags {} + } + } + } else { + set op [string index $line 0] + switch -- $op { + { } {set tags {}} + {@} {set tags d_@} + {-} {set tags d_-} + {+} { + if {[regexp {^\+([<>]{7} |={7})} $line _g op]} { + set line [string replace $line 0 0 { }] + set tags d$op + } else { + set tags d_+ + } + } + default { + puts "error: Unhandled 2 way diff marker: {$op}" + set tags {} + } + } + } + $ui_diff insert end $line $tags + if {[string index $line end] eq "\r"} { + $ui_diff tag add d_cr {end - 2c} + } + $ui_diff insert end "\n" $tags + } + $ui_diff conf -state disabled + + if {[eof $fd]} { + close $fd + set diff_active 0 + unlock_index + set ui_status_value {Ready.} + + if {[$ui_diff index end] eq {2.0}} { + handle_empty_diff + } + } +} + +proc apply_hunk {x y} { + global current_diff_path current_diff_header current_diff_side + global ui_diff ui_index file_states + + if {$current_diff_path eq {} || $current_diff_header eq {}} return + if {![lock_index apply_hunk]} return + + set apply_cmd {git apply --cached --whitespace=nowarn} + set mi [lindex $file_states($current_diff_path) 0] + if {$current_diff_side eq $ui_index} { + set mode unstage + lappend apply_cmd --reverse + if {[string index $mi 0] ne {M}} { + unlock_index + return + } + } else { + set mode stage + if {[string index $mi 1] ne {M}} { + unlock_index + return + } + } + + set s_lno [lindex [split [$ui_diff index @$x,$y] .] 0] + set s_lno [$ui_diff search -backwards -regexp ^@@ $s_lno.0 0.0] + if {$s_lno eq {}} { + unlock_index + return + } + + set e_lno [$ui_diff search -forwards -regexp ^@@ "$s_lno + 1 lines" end] + if {$e_lno eq {}} { + set e_lno end + } + + if {[catch { + set p [open "| $apply_cmd" w] + fconfigure $p -translation binary -encoding binary + puts -nonewline $p $current_diff_header + puts -nonewline $p [$ui_diff get $s_lno $e_lno] + close $p} err]} { + error_popup "Failed to $mode selected hunk.\n\n$err" + unlock_index + return + } + + $ui_diff conf -state normal + $ui_diff delete $s_lno $e_lno + $ui_diff conf -state disabled + + if {[$ui_diff get 1.0 end] eq "\n"} { + set o _ + } else { + set o ? + } + + if {$current_diff_side eq $ui_index} { + set mi ${o}M + } elseif {[string index $mi 0] eq {_}} { + set mi M$o + } else { + set mi ?$o + } + unlock_index + display_file $current_diff_path $mi + if {$o eq {_}} { + clear_diff + } +} diff --git a/lib/error.tcl b/lib/error.tcl new file mode 100644 index 0000000..d0253ae --- /dev/null +++ b/lib/error.tcl @@ -0,0 +1,101 @@ +# git-gui branch (create/delete) support +# Copyright (C) 2006, 2007 Shawn Pearce + +proc error_popup {msg} { + set title [appname] + if {[reponame] ne {}} { + append title " ([reponame])" + } + set cmd [list tk_messageBox \ + -icon error \ + -type ok \ + -title "$title: error" \ + -message $msg] + if {[winfo ismapped .]} { + lappend cmd -parent . + } + eval $cmd +} + +proc warn_popup {msg} { + set title [appname] + if {[reponame] ne {}} { + append title " ([reponame])" + } + set cmd [list tk_messageBox \ + -icon warning \ + -type ok \ + -title "$title: warning" \ + -message $msg] + if {[winfo ismapped .]} { + lappend cmd -parent . + } + eval $cmd +} + +proc info_popup {msg {parent .}} { + set title [appname] + if {[reponame] ne {}} { + append title " ([reponame])" + } + tk_messageBox \ + -parent $parent \ + -icon info \ + -type ok \ + -title $title \ + -message $msg +} + +proc ask_popup {msg} { + set title [appname] + if {[reponame] ne {}} { + append title " ([reponame])" + } + return [tk_messageBox \ + -parent . \ + -icon question \ + -type yesno \ + -title $title \ + -message $msg] +} + +proc hook_failed_popup {hook msg} { + set w .hookfail + toplevel $w + + frame $w.m + label $w.m.l1 -text "$hook hook failed:" \ + -anchor w \ + -justify left \ + -font font_uibold + text $w.m.t \ + -background white -borderwidth 1 \ + -relief sunken \ + -width 80 -height 10 \ + -font font_diff \ + -yscrollcommand [list $w.m.sby set] + label $w.m.l2 \ + -text {You must correct the above errors before committing.} \ + -anchor w \ + -justify left \ + -font font_uibold + scrollbar $w.m.sby -command [list $w.m.t yview] + pack $w.m.l1 -side top -fill x + pack $w.m.l2 -side bottom -fill x + pack $w.m.sby -side right -fill y + pack $w.m.t -side left -fill both -expand 1 + pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10 + + $w.m.t insert 1.0 $msg + $w.m.t conf -state disabled + + button $w.ok -text OK \ + -width 15 \ + -command "destroy $w" + pack $w.ok -side bottom -anchor e -pady 10 -padx 10 + + bind $w "grab $w; focus $w" + bind $w "destroy $w" + wm title $w "[appname] ([reponame]): error" + tkwait window $w +} diff --git a/lib/index.tcl b/lib/index.tcl new file mode 100644 index 0000000..4274285 --- /dev/null +++ b/lib/index.tcl @@ -0,0 +1,409 @@ +# git-gui index (add/remove) support +# Copyright (C) 2006, 2007 Shawn Pearce + +proc update_indexinfo {msg pathList after} { + global update_index_cp ui_status_value + + if {![lock_index update]} return + + set update_index_cp 0 + set pathList [lsort $pathList] + set totalCnt [llength $pathList] + set batch [expr {int($totalCnt * .01) + 1}] + if {$batch > 25} {set batch 25} + + set ui_status_value [format \ + "$msg... %i/%i files (%.2f%%)" \ + $update_index_cp \ + $totalCnt \ + 0.0] + set fd [open "| git update-index -z --index-info" w] + fconfigure $fd \ + -blocking 0 \ + -buffering full \ + -buffersize 512 \ + -encoding binary \ + -translation binary + fileevent $fd writable [list \ + write_update_indexinfo \ + $fd \ + $pathList \ + $totalCnt \ + $batch \ + $msg \ + $after \ + ] +} + +proc write_update_indexinfo {fd pathList totalCnt batch msg after} { + global update_index_cp ui_status_value + global file_states current_diff_path + + if {$update_index_cp >= $totalCnt} { + close $fd + unlock_index + uplevel #0 $after + return + } + + for {set i $batch} \ + {$update_index_cp < $totalCnt && $i > 0} \ + {incr i -1} { + set path [lindex $pathList $update_index_cp] + incr update_index_cp + + set s $file_states($path) + switch -glob -- [lindex $s 0] { + A? {set new _O} + M? {set new _M} + D_ {set new _D} + D? {set new _?} + ?? {continue} + } + set info [lindex $s 2] + if {$info eq {}} continue + + puts -nonewline $fd "$info\t[encoding convertto $path]\0" + display_file $path $new + } + + set ui_status_value [format \ + "$msg... %i/%i files (%.2f%%)" \ + $update_index_cp \ + $totalCnt \ + [expr {100.0 * $update_index_cp / $totalCnt}]] +} + +proc update_index {msg pathList after} { + global update_index_cp ui_status_value + + if {![lock_index update]} return + + set update_index_cp 0 + set pathList [lsort $pathList] + set totalCnt [llength $pathList] + set batch [expr {int($totalCnt * .01) + 1}] + if {$batch > 25} {set batch 25} + + set ui_status_value [format \ + "$msg... %i/%i files (%.2f%%)" \ + $update_index_cp \ + $totalCnt \ + 0.0] + set fd [open "| git update-index --add --remove -z --stdin" w] + fconfigure $fd \ + -blocking 0 \ + -buffering full \ + -buffersize 512 \ + -encoding binary \ + -translation binary + fileevent $fd writable [list \ + write_update_index \ + $fd \ + $pathList \ + $totalCnt \ + $batch \ + $msg \ + $after \ + ] +} + +proc write_update_index {fd pathList totalCnt batch msg after} { + global update_index_cp ui_status_value + global file_states current_diff_path + + if {$update_index_cp >= $totalCnt} { + close $fd + unlock_index + uplevel #0 $after + return + } + + for {set i $batch} \ + {$update_index_cp < $totalCnt && $i > 0} \ + {incr i -1} { + set path [lindex $pathList $update_index_cp] + incr update_index_cp + + switch -glob -- [lindex $file_states($path) 0] { + AD {set new __} + ?D {set new D_} + _O - + AM {set new A_} + U? { + if {[file exists $path]} { + set new M_ + } else { + set new D_ + } + } + ?M {set new M_} + ?? {continue} + } + puts -nonewline $fd "[encoding convertto $path]\0" + display_file $path $new + } + + set ui_status_value [format \ + "$msg... %i/%i files (%.2f%%)" \ + $update_index_cp \ + $totalCnt \ + [expr {100.0 * $update_index_cp / $totalCnt}]] +} + +proc checkout_index {msg pathList after} { + global update_index_cp ui_status_value + + if {![lock_index update]} return + + set update_index_cp 0 + set pathList [lsort $pathList] + set totalCnt [llength $pathList] + set batch [expr {int($totalCnt * .01) + 1}] + if {$batch > 25} {set batch 25} + + set ui_status_value [format \ + "$msg... %i/%i files (%.2f%%)" \ + $update_index_cp \ + $totalCnt \ + 0.0] + set cmd [list git checkout-index] + lappend cmd --index + lappend cmd --quiet + lappend cmd --force + lappend cmd -z + lappend cmd --stdin + set fd [open "| $cmd " w] + fconfigure $fd \ + -blocking 0 \ + -buffering full \ + -buffersize 512 \ + -encoding binary \ + -translation binary + fileevent $fd writable [list \ + write_checkout_index \ + $fd \ + $pathList \ + $totalCnt \ + $batch \ + $msg \ + $after \ + ] +} + +proc write_checkout_index {fd pathList totalCnt batch msg after} { + global update_index_cp ui_status_value + global file_states current_diff_path + + if {$update_index_cp >= $totalCnt} { + close $fd + unlock_index + uplevel #0 $after + return + } + + for {set i $batch} \ + {$update_index_cp < $totalCnt && $i > 0} \ + {incr i -1} { + set path [lindex $pathList $update_index_cp] + incr update_index_cp + switch -glob -- [lindex $file_states($path) 0] { + U? {continue} + ?M - + ?D { + puts -nonewline $fd "[encoding convertto $path]\0" + display_file $path ?_ + } + } + } + + set ui_status_value [format \ + "$msg... %i/%i files (%.2f%%)" \ + $update_index_cp \ + $totalCnt \ + [expr {100.0 * $update_index_cp / $totalCnt}]] +} + +proc unstage_helper {txt paths} { + global file_states current_diff_path + + if {![lock_index begin-update]} return + + set pathList [list] + set after {} + foreach path $paths { + switch -glob -- [lindex $file_states($path) 0] { + A? - + M? - + D? { + lappend pathList $path + if {$path eq $current_diff_path} { + set after {reshow_diff;} + } + } + } + } + if {$pathList eq {}} { + unlock_index + } else { + update_indexinfo \ + $txt \ + $pathList \ + [concat $after {set ui_status_value {Ready.}}] + } +} + +proc do_unstage_selection {} { + global current_diff_path selected_paths + + if {[array size selected_paths] > 0} { + unstage_helper \ + {Unstaging selected files from commit} \ + [array names selected_paths] + } elseif {$current_diff_path ne {}} { + unstage_helper \ + "Unstaging [short_path $current_diff_path] from commit" \ + [list $current_diff_path] + } +} + +proc add_helper {txt paths} { + global file_states current_diff_path + + if {![lock_index begin-update]} return + + set pathList [list] + set after {} + foreach path $paths { + switch -glob -- [lindex $file_states($path) 0] { + _O - + ?M - + ?D - + U? { + lappend pathList $path + if {$path eq $current_diff_path} { + set after {reshow_diff;} + } + } + } + } + if {$pathList eq {}} { + unlock_index + } else { + update_index \ + $txt \ + $pathList \ + [concat $after {set ui_status_value {Ready to commit.}}] + } +} + +proc do_add_selection {} { + global current_diff_path selected_paths + + if {[array size selected_paths] > 0} { + add_helper \ + {Adding selected files} \ + [array names selected_paths] + } elseif {$current_diff_path ne {}} { + add_helper \ + "Adding [short_path $current_diff_path]" \ + [list $current_diff_path] + } +} + +proc do_add_all {} { + global file_states + + set paths [list] + foreach path [array names file_states] { + switch -glob -- [lindex $file_states($path) 0] { + U? {continue} + ?M - + ?D {lappend paths $path} + } + } + add_helper {Adding all changed files} $paths +} + +proc revert_helper {txt paths} { + global file_states current_diff_path + + if {![lock_index begin-update]} return + + set pathList [list] + set after {} + foreach path $paths { + switch -glob -- [lindex $file_states($path) 0] { + U? {continue} + ?M - + ?D { + lappend pathList $path + if {$path eq $current_diff_path} { + set after {reshow_diff;} + } + } + } + } + + set n [llength $pathList] + if {$n == 0} { + unlock_index + return + } elseif {$n == 1} { + set s "[short_path [lindex $pathList]]" + } else { + set s "these $n files" + } + + set reply [tk_dialog \ + .confirm_revert \ + "[appname] ([reponame])" \ + "Revert changes in $s? + +Any unadded changes will be permanently lost by the revert." \ + question \ + 1 \ + {Do Nothing} \ + {Revert Changes} \ + ] + if {$reply == 1} { + checkout_index \ + $txt \ + $pathList \ + [concat $after {set ui_status_value {Ready.}}] + } else { + unlock_index + } +} + +proc do_revert_selection {} { + global current_diff_path selected_paths + + if {[array size selected_paths] > 0} { + revert_helper \ + {Reverting selected files} \ + [array names selected_paths] + } elseif {$current_diff_path ne {}} { + revert_helper \ + "Reverting [short_path $current_diff_path]" \ + [list $current_diff_path] + } +} + +proc do_select_commit_type {} { + global commit_type selected_commit_type + + if {$selected_commit_type eq {new} + && [string match amend* $commit_type]} { + create_new_commit + } elseif {$selected_commit_type eq {amend} + && ![string match amend* $commit_type]} { + load_last_commit + + # The amend request was rejected... + # + if {![string match amend* $commit_type]} { + set selected_commit_type new + } + } +} diff --git a/lib/merge.tcl b/lib/merge.tcl new file mode 100644 index 0000000..75724a9 --- /dev/null +++ b/lib/merge.tcl @@ -0,0 +1,281 @@ +# git-gui branch merge support +# Copyright (C) 2006, 2007 Shawn Pearce + +proc can_merge {} { + global HEAD commit_type file_states + + if {[string match amend* $commit_type]} { + info_popup {Cannot merge while amending. + +You must finish amending this commit before starting any type of merge. +} + return 0 + } + + if {[committer_ident] eq {}} {return 0} + if {![lock_index merge]} {return 0} + + # -- Our in memory state should match the repository. + # + repository_state curType curHEAD curMERGE_HEAD + if {$commit_type ne $curType || $HEAD ne $curHEAD} { + info_popup {Last scanned state does not match repository state. + +Another Git program has modified this repository since the last scan. A rescan must be performed before a merge can be performed. + +The rescan will be automatically started now. +} + unlock_index + rescan {set ui_status_value {Ready.}} + return 0 + } + + foreach path [array names file_states] { + switch -glob -- [lindex $file_states($path) 0] { + _O { + continue; # and pray it works! + } + U? { + error_popup "You are in the middle of a conflicted merge. + +File [short_path $path] has merge conflicts. + +You must resolve them, add the file, and commit to complete the current merge. Only then can you begin another merge. +" + unlock_index + return 0 + } + ?? { + error_popup "You are in the middle of a change. + +File [short_path $path] is modified. + +You should complete the current commit before starting a merge. Doing so will help you abort a failed merge, should the need arise. +" + unlock_index + return 0 + } + } + } + + return 1 +} + +proc visualize_local_merge {w} { + set revs {} + foreach i [$w.source.l curselection] { + lappend revs [$w.source.l get $i] + } + if {$revs eq {}} return + lappend revs --not HEAD + do_gitk $revs +} + +proc start_local_merge_action {w} { + global HEAD ui_status_value current_branch + + set cmd [list git merge] + set names {} + set revcnt 0 + foreach i [$w.source.l curselection] { + set b [$w.source.l get $i] + lappend cmd $b + lappend names $b + incr revcnt + } + + if {$revcnt == 0} { + return + } elseif {$revcnt == 1} { + set unit branch + } elseif {$revcnt <= 15} { + set unit branches + + if {[tk_dialog \ + $w.confirm_octopus \ + [wm title $w] \ + "Use octopus merge strategy? + +You are merging $revcnt branches at once. This requires using the octopus merge driver, which may not succeed if there are file-level conflicts. +" \ + question \ + 0 \ + {Cancel} \ + {Use octopus} \ + ] != 1} return + } else { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "Too many branches selected. + +You have requested to merge $revcnt branches in an octopus merge. This exceeds Git's internal limit of 15 branches per merge. + +Please select fewer branches. To merge more than 15 branches, merge the branches in batches. +" + return + } + + set msg "Merging $current_branch, [join $names {, }]" + set ui_status_value "$msg..." + set cons [new_console "Merge" $msg] + console_exec $cons $cmd [list finish_merge $revcnt] + bind $w {} + destroy $w +} + +proc finish_merge {revcnt w ok} { + console_done $w $ok + if {$ok} { + set msg {Merge completed successfully.} + } else { + if {$revcnt != 1} { + info_popup "Octopus merge failed. + +Your merge of $revcnt branches has failed. + +There are file-level conflicts between the branches which must be resolved manually. + +The working directory will now be reset. + +You can attempt this merge again by merging only one branch at a time." $w + + set fd [open "| git read-tree --reset -u HEAD" r] + fconfigure $fd -blocking 0 -translation binary + fileevent $fd readable [list reset_hard_wait $fd] + set ui_status_value {Aborting... please wait...} + return + } + + set msg {Merge failed. Conflict resolution is required.} + } + unlock_index + rescan [list set ui_status_value $msg] +} + +proc do_local_merge {} { + global current_branch + + if {![can_merge]} return + + set w .merge_setup + toplevel $w + wm geometry $w "+[winfo rootx .]+[winfo rooty .]" + + label $w.header \ + -text "Merge Into $current_branch" \ + -font font_uibold + pack $w.header -side top -fill x + + frame $w.buttons + button $w.buttons.visualize -text Visualize \ + -command [list visualize_local_merge $w] + pack $w.buttons.visualize -side left + button $w.buttons.create -text Merge \ + -command [list start_local_merge_action $w] + pack $w.buttons.create -side right + button $w.buttons.cancel -text {Cancel} \ + -command [list destroy $w] + pack $w.buttons.cancel -side right -padx 5 + pack $w.buttons -side bottom -fill x -pady 10 -padx 10 + + labelframe $w.source -text {Source Branches} + listbox $w.source.l \ + -height 10 \ + -width 70 \ + -selectmode extended \ + -yscrollcommand [list $w.source.sby set] + scrollbar $w.source.sby -command [list $w.source.l yview] + pack $w.source.sby -side right -fill y + pack $w.source.l -side left -fill both -expand 1 + pack $w.source -fill both -expand 1 -pady 5 -padx 5 + + set cmd [list git for-each-ref] + lappend cmd {--format=%(objectname) %(*objectname) %(refname)} + lappend cmd refs/heads + lappend cmd refs/remotes + lappend cmd refs/tags + set fr_fd [open "| $cmd" r] + fconfigure $fr_fd -translation binary + while {[gets $fr_fd line] > 0} { + set line [split $line { }] + set sha1([lindex $line 0]) [lindex $line 2] + set sha1([lindex $line 1]) [lindex $line 2] + } + close $fr_fd + + set to_show {} + set fr_fd [open "| git rev-list --all --not HEAD"] + while {[gets $fr_fd line] > 0} { + if {[catch {set ref $sha1($line)}]} continue + regsub ^refs/(heads|remotes|tags)/ $ref {} ref + lappend to_show $ref + } + close $fr_fd + + foreach ref [lsort -unique $to_show] { + $w.source.l insert end $ref + } + + bind $w "grab $w" + bind $w "unlock_index;destroy $w" + bind $w unlock_index + wm title $w "[appname] ([reponame]): Merge" + tkwait window $w +} + +proc do_reset_hard {} { + global HEAD commit_type file_states + + if {[string match amend* $commit_type]} { + info_popup {Cannot abort while amending. + +You must finish amending this commit. +} + return + } + + if {![lock_index abort]} return + + if {[string match *merge* $commit_type]} { + set op merge + } else { + set op commit + } + + if {[ask_popup "Abort $op? + +Aborting the current $op will cause *ALL* uncommitted changes to be lost. + +Continue with aborting the current $op?"] eq {yes}} { + set fd [open "| git read-tree --reset -u HEAD" r] + fconfigure $fd -blocking 0 -translation binary + fileevent $fd readable [list reset_hard_wait $fd] + set ui_status_value {Aborting... please wait...} + } else { + unlock_index + } +} + +proc reset_hard_wait {fd} { + global ui_comm + + read $fd + if {[eof $fd]} { + close $fd + unlock_index + + $ui_comm delete 0.0 end + $ui_comm edit modified false + + catch {file delete [gitdir MERGE_HEAD]} + catch {file delete [gitdir rr-cache MERGE_RR]} + catch {file delete [gitdir SQUASH_MSG]} + catch {file delete [gitdir MERGE_MSG]} + catch {file delete [gitdir GITGUI_MSG]} + + rescan {set ui_status_value {Abort completed. Ready.}} + } +} diff --git a/lib/option.tcl b/lib/option.tcl new file mode 100644 index 0000000..17fcc65 --- /dev/null +++ b/lib/option.tcl @@ -0,0 +1,290 @@ +# git-gui options editor +# Copyright (C) 2006, 2007 Shawn Pearce + +proc save_config {} { + global default_config font_descs + global repo_config global_config + global repo_config_new global_config_new + + foreach option $font_descs { + set name [lindex $option 0] + set font [lindex $option 1] + font configure $font \ + -family $global_config_new(gui.$font^^family) \ + -size $global_config_new(gui.$font^^size) + font configure ${font}bold \ + -family $global_config_new(gui.$font^^family) \ + -size $global_config_new(gui.$font^^size) + set global_config_new(gui.$name) [font configure $font] + unset global_config_new(gui.$font^^family) + unset global_config_new(gui.$font^^size) + } + + foreach name [array names default_config] { + set value $global_config_new($name) + if {$value ne $global_config($name)} { + if {$value eq $default_config($name)} { + catch {git config --global --unset $name} + } else { + regsub -all "\[{}\]" $value {"} value + git config --global $name $value + } + set global_config($name) $value + if {$value eq $repo_config($name)} { + catch {git config --unset $name} + set repo_config($name) $value + } + } + } + + foreach name [array names default_config] { + set value $repo_config_new($name) + if {$value ne $repo_config($name)} { + if {$value eq $global_config($name)} { + catch {git config --unset $name} + } else { + regsub -all "\[{}\]" $value {"} value + git config $name $value + } + set repo_config($name) $value + } + } +} + +proc do_about {} { + global appvers copyright + global tcl_patchLevel tk_patchLevel + + set w .about_dialog + toplevel $w + wm geometry $w "+[winfo rootx .]+[winfo rooty .]" + + label $w.header -text "About [appname]" \ + -font font_uibold + pack $w.header -side top -fill x + + frame $w.buttons + button $w.buttons.close -text {Close} \ + -default active \ + -command [list destroy $w] + pack $w.buttons.close -side right + pack $w.buttons -side bottom -fill x -pady 10 -padx 10 + + label $w.desc \ + -text "git-gui - a graphical user interface for Git. +$copyright" \ + -padx 5 -pady 5 \ + -justify left \ + -anchor w \ + -borderwidth 1 \ + -relief solid + pack $w.desc -side top -fill x -padx 5 -pady 5 + + set v {} + append v "git-gui version $appvers\n" + append v "[git version]\n" + append v "\n" + if {$tcl_patchLevel eq $tk_patchLevel} { + append v "Tcl/Tk version $tcl_patchLevel" + } else { + append v "Tcl version $tcl_patchLevel" + append v ", Tk version $tk_patchLevel" + } + + label $w.vers \ + -text $v \ + -padx 5 -pady 5 \ + -justify left \ + -anchor w \ + -borderwidth 1 \ + -relief solid + pack $w.vers -side top -fill x -padx 5 -pady 5 + + menu $w.ctxm -tearoff 0 + $w.ctxm add command \ + -label {Copy} \ + -command " + clipboard clear + clipboard append -format STRING -type STRING -- \[$w.vers cget -text\] + " + + bind $w "grab $w; focus $w.buttons.close" + bind $w "destroy $w" + bind $w "destroy $w" + bind_button3 $w.vers "tk_popup $w.ctxm %X %Y; grab $w; focus $w" + wm title $w "About [appname]" + tkwait window $w +} + +proc do_options {} { + global repo_config global_config font_descs + global repo_config_new global_config_new + + array unset repo_config_new + array unset global_config_new + foreach name [array names repo_config] { + set repo_config_new($name) $repo_config($name) + } + load_config 1 + foreach name [array names repo_config] { + switch -- $name { + gui.diffcontext {continue} + } + set repo_config_new($name) $repo_config($name) + } + foreach name [array names global_config] { + set global_config_new($name) $global_config($name) + } + + set w .options_editor + toplevel $w + wm geometry $w "+[winfo rootx .]+[winfo rooty .]" + + label $w.header -text "Options" \ + -font font_uibold + pack $w.header -side top -fill x + + frame $w.buttons + button $w.buttons.restore -text {Restore Defaults} \ + -default normal \ + -command do_restore_defaults + pack $w.buttons.restore -side left + button $w.buttons.save -text Save \ + -default active \ + -command [list do_save_config $w] + pack $w.buttons.save -side right + button $w.buttons.cancel -text {Cancel} \ + -default normal \ + -command [list destroy $w] + pack $w.buttons.cancel -side right -padx 5 + pack $w.buttons -side bottom -fill x -pady 10 -padx 10 + + labelframe $w.repo -text "[reponame] Repository" + labelframe $w.global -text {Global (All Repositories)} + pack $w.repo -side left -fill both -expand 1 -pady 5 -padx 5 + pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5 + + set optid 0 + foreach option { + {t user.name {User Name}} + {t user.email {Email Address}} + + {b merge.summary {Summarize Merge Commits}} + {i-1..5 merge.verbosity {Merge Verbosity}} + + {b gui.trustmtime {Trust File Modification Timestamps}} + {i-1..99 gui.diffcontext {Number of Diff Context Lines}} + {t gui.newbranchtemplate {New Branch Name Template}} + } { + set type [lindex $option 0] + set name [lindex $option 1] + set text [lindex $option 2] + incr optid + foreach f {repo global} { + switch -glob -- $type { + b { + checkbutton $w.$f.$optid -text $text \ + -variable ${f}_config_new($name) \ + -onvalue true \ + -offvalue false + pack $w.$f.$optid -side top -anchor w + } + i-* { + regexp -- {-(\d+)\.\.(\d+)$} $type _junk min max + frame $w.$f.$optid + label $w.$f.$optid.l -text "$text:" + pack $w.$f.$optid.l -side left -anchor w -fill x + spinbox $w.$f.$optid.v \ + -textvariable ${f}_config_new($name) \ + -from $min \ + -to $max \ + -increment 1 \ + -width [expr {1 + [string length $max]}] + bind $w.$f.$optid.v {%W selection range 0 end} + pack $w.$f.$optid.v -side right -anchor e -padx 5 + pack $w.$f.$optid -side top -anchor w -fill x + } + t { + frame $w.$f.$optid + label $w.$f.$optid.l -text "$text:" + entry $w.$f.$optid.v \ + -borderwidth 1 \ + -relief sunken \ + -width 20 \ + -textvariable ${f}_config_new($name) + pack $w.$f.$optid.l -side left -anchor w + pack $w.$f.$optid.v -side left -anchor w \ + -fill x -expand 1 \ + -padx 5 + pack $w.$f.$optid -side top -anchor w -fill x + } + } + } + } + + set all_fonts [lsort [font families]] + foreach option $font_descs { + set name [lindex $option 0] + set font [lindex $option 1] + set text [lindex $option 2] + + set global_config_new(gui.$font^^family) \ + [font configure $font -family] + set global_config_new(gui.$font^^size) \ + [font configure $font -size] + + frame $w.global.$name + label $w.global.$name.l -text "$text:" + pack $w.global.$name.l -side left -anchor w -fill x + eval tk_optionMenu $w.global.$name.family \ + global_config_new(gui.$font^^family) \ + $all_fonts + spinbox $w.global.$name.size \ + -textvariable global_config_new(gui.$font^^size) \ + -from 2 -to 80 -increment 1 \ + -width 3 + bind $w.global.$name.size {%W selection range 0 end} + pack $w.global.$name.size -side right -anchor e + pack $w.global.$name.family -side right -anchor e + pack $w.global.$name -side top -anchor w -fill x + } + + bind $w "grab $w; focus $w.buttons.save" + bind $w "destroy $w" + bind $w [list do_save_config $w] + wm title $w "[appname] ([reponame]): Options" + tkwait window $w +} + +proc do_restore_defaults {} { + global font_descs default_config repo_config + global repo_config_new global_config_new + + foreach name [array names default_config] { + set repo_config_new($name) $default_config($name) + set global_config_new($name) $default_config($name) + } + + foreach option $font_descs { + set name [lindex $option 0] + set repo_config(gui.$name) $default_config(gui.$name) + } + apply_config + + foreach option $font_descs { + set name [lindex $option 0] + set font [lindex $option 1] + set global_config_new(gui.$font^^family) \ + [font configure $font -family] + set global_config_new(gui.$font^^size) \ + [font configure $font -size] + } +} + +proc do_save_config {w} { + if {[catch {save_config} err]} { + error_popup "Failed to completely save options:\n\n$err" + } + reshow_diff + destroy $w +} diff --git a/lib/remote.tcl b/lib/remote.tcl new file mode 100644 index 0000000..99f353e --- /dev/null +++ b/lib/remote.tcl @@ -0,0 +1,159 @@ +# git-gui remote management +# Copyright (C) 2006, 2007 Shawn Pearce + +proc is_tracking_branch {name} { + global tracking_branches + + if {![catch {set info $tracking_branches($name)}]} { + return 1 + } + foreach t [array names tracking_branches] { + if {[string match {*/\*} $t] && [string match $t $name]} { + return 1 + } + } + return 0 +} + +proc all_tracking_branches {} { + global tracking_branches + + set all_trackings {} + set cmd {} + foreach name [array names tracking_branches] { + if {[regsub {/\*$} $name {} name]} { + lappend cmd $name + } else { + regsub ^refs/(heads|remotes)/ $name {} name + lappend all_trackings $name + } + } + + if {$cmd ne {}} { + set fd [open "| git for-each-ref --format=%(refname) $cmd" r] + while {[gets $fd name] > 0} { + regsub ^refs/(heads|remotes)/ $name {} name + lappend all_trackings $name + } + close $fd + } + + return [lsort -unique $all_trackings] +} + +proc load_all_remotes {} { + global repo_config + global all_remotes tracking_branches + + set all_remotes [list] + array unset tracking_branches + + set rm_dir [gitdir remotes] + if {[file isdirectory $rm_dir]} { + set all_remotes [glob \ + -types f \ + -tails \ + -nocomplain \ + -directory $rm_dir *] + + foreach name $all_remotes { + catch { + set fd [open [file join $rm_dir $name] r] + while {[gets $fd line] >= 0} { + if {![regexp {^Pull:[ ]*([^:]+):(.+)$} \ + $line line src dst]} continue + if {![regexp ^refs/ $dst]} { + set dst "refs/heads/$dst" + } + set tracking_branches($dst) [list $name $src] + } + close $fd + } + } + } + + foreach line [array names repo_config remote.*.url] { + if {![regexp ^remote\.(.*)\.url\$ $line line name]} continue + lappend all_remotes $name + + if {[catch {set fl $repo_config(remote.$name.fetch)}]} { + set fl {} + } + foreach line $fl { + if {![regexp {^([^:]+):(.+)$} $line line src dst]} continue + if {![regexp ^refs/ $dst]} { + set dst "refs/heads/$dst" + } + set tracking_branches($dst) [list $name $src] + } + } + + set all_remotes [lsort -unique $all_remotes] +} + +proc populate_fetch_menu {} { + global all_remotes repo_config + + set m .mbar.fetch + foreach r $all_remotes { + set enable 0 + if {![catch {set a $repo_config(remote.$r.url)}]} { + if {![catch {set a $repo_config(remote.$r.fetch)}]} { + set enable 1 + } + } else { + catch { + set fd [open [gitdir remotes $r] r] + while {[gets $fd n] >= 0} { + if {[regexp {^Pull:[ \t]*([^:]+):} $n]} { + set enable 1 + break + } + } + close $fd + } + } + + if {$enable} { + $m add command \ + -label "Fetch from $r..." \ + -command [list fetch_from $r] + } + } +} + +proc populate_push_menu {} { + global all_remotes repo_config + + set m .mbar.push + set fast_count 0 + foreach r $all_remotes { + set enable 0 + if {![catch {set a $repo_config(remote.$r.url)}]} { + if {![catch {set a $repo_config(remote.$r.push)}]} { + set enable 1 + } + } else { + catch { + set fd [open [gitdir remotes $r] r] + while {[gets $fd n] >= 0} { + if {[regexp {^Push:[ \t]*([^:]+):} $n]} { + set enable 1 + break + } + } + close $fd + } + } + + if {$enable} { + if {!$fast_count} { + $m add separator + } + $m add command \ + -label "Push to $r..." \ + -command [list push_to $r] + incr fast_count + } + } +} diff --git a/lib/shortcut.tcl b/lib/shortcut.tcl new file mode 100644 index 0000000..ebf72e4 --- /dev/null +++ b/lib/shortcut.tcl @@ -0,0 +1,141 @@ +# git-gui desktop icon creators +# Copyright (C) 2006, 2007 Shawn Pearce + +proc do_windows_shortcut {} { + global argv0 + + set fn [tk_getSaveFile \ + -parent . \ + -title "[appname] ([reponame]): Create Desktop Icon" \ + -initialfile "Git [reponame].bat"] + if {$fn != {}} { + if {[catch { + set fd [open $fn w] + puts $fd "@ECHO Entering [reponame]" + puts $fd "@ECHO Starting git-gui... please wait..." + puts $fd "@SET PATH=[file normalize [gitexec]];%PATH%" + puts $fd "@SET GIT_DIR=[file normalize [gitdir]]" + puts -nonewline $fd "@\"[info nameofexecutable]\"" + puts $fd " \"[file normalize $argv0]\"" + close $fd + } err]} { + error_popup "Cannot write script:\n\n$err" + } + } +} + +proc do_cygwin_shortcut {} { + global argv0 + + if {[catch { + set desktop [exec cygpath \ + --windows \ + --absolute \ + --long-name \ + --desktop] + }]} { + set desktop . + } + set fn [tk_getSaveFile \ + -parent . \ + -title "[appname] ([reponame]): Create Desktop Icon" \ + -initialdir $desktop \ + -initialfile "Git [reponame].bat"] + if {$fn != {}} { + if {[catch { + set fd [open $fn w] + set sh [exec cygpath \ + --windows \ + --absolute \ + /bin/sh] + set me [exec cygpath \ + --unix \ + --absolute \ + $argv0] + set gd [exec cygpath \ + --unix \ + --absolute \ + [gitdir]] + set gw [exec cygpath \ + --windows \ + --absolute \ + [file dirname [gitdir]]] + regsub -all ' $me "'\\''" me + regsub -all ' $gd "'\\''" gd + puts $fd "@ECHO Entering $gw" + puts $fd "@ECHO Starting git-gui... please wait..." + puts -nonewline $fd "@\"$sh\" --login -c \"" + puts -nonewline $fd "GIT_DIR='$gd'" + puts -nonewline $fd " '$me'" + puts $fd "&\"" + close $fd + } err]} { + error_popup "Cannot write script:\n\n$err" + } + } +} + +proc do_macosx_app {} { + global argv0 env + + set fn [tk_getSaveFile \ + -parent . \ + -title "[appname] ([reponame]): Create Desktop Icon" \ + -initialdir [file join $env(HOME) Desktop] \ + -initialfile "Git [reponame].app"] + if {$fn != {}} { + if {[catch { + set Contents [file join $fn Contents] + set MacOS [file join $Contents MacOS] + set exe [file join $MacOS git-gui] + + file mkdir $MacOS + + set fd [open [file join $Contents Info.plist] w] + puts $fd { + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + git-gui + CFBundleIdentifier + org.spearce.git-gui + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleVersion + 1.0 + NSPrincipalClass + NSApplication + +} + close $fd + + set fd [open $exe w] + set gd [file normalize [gitdir]] + set ep [file normalize [gitexec]] + regsub -all ' $gd "'\\''" gd + regsub -all ' $ep "'\\''" ep + puts $fd "#!/bin/sh" + foreach name [array names env] { + if {[string match GIT_* $name]} { + regsub -all ' $env($name) "'\\''" v + puts $fd "export $name='$v'" + } + } + puts $fd "export PATH='$ep':\$PATH" + puts $fd "export GIT_DIR='$gd'" + puts $fd "exec [file normalize $argv0]" + close $fd + + file attributes $exe -permissions u+x,g+x,o+x + } err]} { + error_popup "Cannot write icon:\n\n$err" + } + } +} diff --git a/lib/transport.tcl b/lib/transport.tcl new file mode 100644 index 0000000..ce6fc45 --- /dev/null +++ b/lib/transport.tcl @@ -0,0 +1,162 @@ +# git-gui transport (fetch/push) support +# Copyright (C) 2006, 2007 Shawn Pearce + +proc fetch_from {remote} { + set w [new_console \ + "fetch $remote" \ + "Fetching new changes from $remote"] + set cmd [list git fetch] + lappend cmd $remote + console_exec $w $cmd console_done +} + +proc push_to {remote} { + set w [new_console \ + "push $remote" \ + "Pushing changes to $remote"] + set cmd [list git push] + lappend cmd -v + lappend cmd $remote + console_exec $w $cmd console_done +} + +proc start_push_anywhere_action {w} { + global push_urltype push_remote push_url push_thin push_tags + + set r_url {} + switch -- $push_urltype { + remote {set r_url $push_remote} + url {set r_url $push_url} + } + if {$r_url eq {}} return + + set cmd [list git push] + lappend cmd -v + if {$push_thin} { + lappend cmd --thin + } + if {$push_tags} { + lappend cmd --tags + } + lappend cmd $r_url + set cnt 0 + foreach i [$w.source.l curselection] { + set b [$w.source.l get $i] + lappend cmd "refs/heads/$b:refs/heads/$b" + incr cnt + } + if {$cnt == 0} { + return + } elseif {$cnt == 1} { + set unit branch + } else { + set unit branches + } + + set cons [new_console "push $r_url" "Pushing $cnt $unit to $r_url"] + console_exec $cons $cmd console_done + destroy $w +} + +trace add variable push_remote write \ + [list radio_selector push_urltype remote] + +proc do_push_anywhere {} { + global all_heads all_remotes current_branch + global push_urltype push_remote push_url push_thin push_tags + + set w .push_setup + toplevel $w + wm geometry $w "+[winfo rootx .]+[winfo rooty .]" + + label $w.header -text {Push Branches} -font font_uibold + pack $w.header -side top -fill x + + frame $w.buttons + button $w.buttons.create -text Push \ + -default active \ + -command [list start_push_anywhere_action $w] + pack $w.buttons.create -side right + button $w.buttons.cancel -text {Cancel} \ + -default normal \ + -command [list destroy $w] + pack $w.buttons.cancel -side right -padx 5 + pack $w.buttons -side bottom -fill x -pady 10 -padx 10 + + labelframe $w.source -text {Source Branches} + listbox $w.source.l \ + -height 10 \ + -width 70 \ + -selectmode extended \ + -yscrollcommand [list $w.source.sby set] + foreach h $all_heads { + $w.source.l insert end $h + if {$h eq $current_branch} { + $w.source.l select set end + } + } + scrollbar $w.source.sby -command [list $w.source.l yview] + pack $w.source.sby -side right -fill y + pack $w.source.l -side left -fill both -expand 1 + pack $w.source -fill both -expand 1 -pady 5 -padx 5 + + labelframe $w.dest -text {Destination Repository} + if {$all_remotes ne {}} { + radiobutton $w.dest.remote_r \ + -text {Remote:} \ + -value remote \ + -variable push_urltype + eval tk_optionMenu $w.dest.remote_m push_remote $all_remotes + grid $w.dest.remote_r $w.dest.remote_m -sticky w + if {[lsearch -sorted -exact $all_remotes origin] != -1} { + set push_remote origin + } else { + set push_remote [lindex $all_remotes 0] + } + set push_urltype remote + } else { + set push_urltype url + } + radiobutton $w.dest.url_r \ + -text {Arbitrary URL:} \ + -value url \ + -variable push_urltype + entry $w.dest.url_t \ + -borderwidth 1 \ + -relief sunken \ + -width 50 \ + -textvariable push_url \ + -validate key \ + -validatecommand { + if {%d == 1 && [regexp {\s} %S]} {return 0} + if {%d == 1 && [string length %S] > 0} { + set push_urltype url + } + return 1 + } + grid $w.dest.url_r $w.dest.url_t -sticky we -padx {0 5} + grid columnconfigure $w.dest 1 -weight 1 + pack $w.dest -anchor nw -fill x -pady 5 -padx 5 + + labelframe $w.options -text {Transfer Options} + checkbutton $w.options.thin \ + -text {Use thin pack (for slow network connections)} \ + -variable push_thin + grid $w.options.thin -columnspan 2 -sticky w + checkbutton $w.options.tags \ + -text {Include tags} \ + -variable push_tags + grid $w.options.tags -columnspan 2 -sticky w + grid columnconfigure $w.options 1 -weight 1 + pack $w.options -anchor nw -fill x -pady 5 -padx 5 + + set push_url {} + set push_thin 0 + set push_tags 0 + + bind $w "grab $w; focus $w.buttons.create" + bind $w "destroy $w" + bind $w [list start_push_anywhere_action $w] + wm title $w "[appname] ([reponame]): Push" + tkwait window $w +} -- cgit v0.10.2-6-g49f6 From a35d65d9c8a2a6a10d369c00e22aded6fbdff2a9 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 1 May 2007 02:59:53 -0400 Subject: git-gui: Move console procs into their own namespace To help modularize git-gui better I'm isolating the code and variables required to handle our little console windows into their own namespace. This way we can say console::new rather than new_console, and the hidden internal procs to create the window and read data from our filehandle are off in their own private little land, where most users don't see them. Signed-off-by: Shawn O. Pearce diff --git a/lib/console.tcl b/lib/console.tcl index e40ec96..75f3e04 100644 --- a/lib/console.tcl +++ b/lib/console.tcl @@ -1,17 +1,25 @@ # git-gui console support # Copyright (C) 2006, 2007 Shawn Pearce -set next_console_id 0 +namespace eval console { + +variable next_console_id 0 +variable console_data +variable console_cr + +proc new {short_title long_title} { + variable next_console_id + variable console_data -proc new_console {short_title long_title} { - global next_console_id console_data set w .console[incr next_console_id] set console_data($w) [list $short_title $long_title] - return [console_init $w] + return [_init $w] } -proc console_init {w} { - global console_cr console_data M1B +proc _init {w} { + global M1B + variable console_cr + variable console_data set console_cr($w) 1.0 toplevel $w @@ -63,7 +71,7 @@ proc console_init {w} { return $w } -proc console_exec {w cmd after} { +proc exec {w cmd {after {}}} { # -- Cygwin's Tcl tosses the enviroment when we exec our child. # But most users need that so we have to relogin. :-( # @@ -78,15 +86,16 @@ proc console_exec {w cmd after} { set fd_f [open $cmd r] fconfigure $fd_f -blocking 0 -translation binary - fileevent $fd_f readable [list console_read $w $fd_f $after] + fileevent $fd_f readable \ + [namespace code [list _read $w $fd_f $after]] } -proc console_read {w fd after} { - global console_cr +proc _read {w fd after} { + variable console_cr set buf [read $fd] if {$buf ne {}} { - if {![winfo exists $w]} {console_init $w} + if {![winfo exists $w]} {_init $w} $w.m.t conf -state normal set c 0 set n [string length $buf] @@ -120,36 +129,41 @@ proc console_read {w fd after} { } else { set ok 1 } - uplevel #0 $after $w $ok + if {$after ne {}} { + uplevel #0 $after $w $ok + } else { + done $w $ok + } return } fconfigure $fd -blocking 0 } -proc console_chain {cmdlist w {ok 1}} { +proc chain {cmdlist w {ok 1}} { if {$ok} { if {[llength $cmdlist] == 0} { - console_done $w $ok + done $w $ok return } set cmd [lindex $cmdlist 0] set cmdlist [lrange $cmdlist 1 end] - if {[lindex $cmd 0] eq {console_exec}} { - console_exec $w \ + if {[lindex $cmd 0] eq {exec}} { + exec $w \ [lindex $cmd 1] \ - [list console_chain $cmdlist] + [namespace code [list chain $cmdlist]] } else { uplevel #0 $cmd $cmdlist $w $ok } } else { - console_done $w $ok + done $w $ok } } -proc console_done {args} { - global console_cr console_data +proc done {args} { + variable console_cr + variable console_data switch -- [llength $args] { 2 { @@ -161,7 +175,7 @@ proc console_done {args} { set ok [lindex $args 2] } default { - error "wrong number of args: console_done ?ignored? w ok" + error "wrong number of args: done ?ignored? w ok" } } @@ -173,7 +187,7 @@ proc console_done {args} { } } else { if {![winfo exists $w]} { - console_init $w + _init $w } $w.m.s conf -background red -text {Error: Command Failed} $w.ok conf -state normal @@ -183,3 +197,5 @@ proc console_done {args} { array unset console_cr $w array unset console_data $w } + +} diff --git a/lib/database.tcl b/lib/database.tcl index e31466f..73058a8 100644 --- a/lib/database.tcl +++ b/lib/database.tcl @@ -69,21 +69,21 @@ proc do_stats {} { } proc do_gc {} { - set w [new_console {gc} {Compressing the object database}] - console_chain { - {console_exec {git pack-refs --prune}} - {console_exec {git reflog expire --all}} - {console_exec {git repack -a -d -l}} - {console_exec {git rerere gc}} + set w [console::new {gc} {Compressing the object database}] + console::chain { + {exec {git pack-refs --prune}} + {exec {git reflog expire --all}} + {exec {git repack -a -d -l}} + {exec {git rerere gc}} } $w } proc do_fsck_objects {} { - set w [new_console {fsck-objects} \ + set w [console::new {fsck-objects} \ {Verifying the object database with fsck-objects}] set cmd [list git fsck-objects] lappend cmd --full lappend cmd --cache lappend cmd --strict - console_exec $w $cmd console_done + console::exec $w $cmd } diff --git a/lib/merge.tcl b/lib/merge.tcl index 75724a9..e0e84ae 100644 --- a/lib/merge.tcl +++ b/lib/merge.tcl @@ -120,14 +120,14 @@ Please select fewer branches. To merge more than 15 branches, merge the branche set msg "Merging $current_branch, [join $names {, }]" set ui_status_value "$msg..." - set cons [new_console "Merge" $msg] - console_exec $cons $cmd [list finish_merge $revcnt] + set cons [console::new "Merge" $msg] + console::exec $cons $cmd [list finish_merge $revcnt] bind $w {} destroy $w } proc finish_merge {revcnt w ok} { - console_done $w $ok + console::done $w $ok if {$ok} { set msg {Merge completed successfully.} } else { diff --git a/lib/transport.tcl b/lib/transport.tcl index ce6fc45..c0e7d20 100644 --- a/lib/transport.tcl +++ b/lib/transport.tcl @@ -2,22 +2,22 @@ # Copyright (C) 2006, 2007 Shawn Pearce proc fetch_from {remote} { - set w [new_console \ + set w [console::new \ "fetch $remote" \ "Fetching new changes from $remote"] set cmd [list git fetch] lappend cmd $remote - console_exec $w $cmd console_done + console::exec $w $cmd } proc push_to {remote} { - set w [new_console \ + set w [console::new \ "push $remote" \ "Pushing changes to $remote"] set cmd [list git push] lappend cmd -v lappend cmd $remote - console_exec $w $cmd console_done + console::exec $w $cmd } proc start_push_anywhere_action {w} { @@ -53,8 +53,10 @@ proc start_push_anywhere_action {w} { set unit branches } - set cons [new_console "push $r_url" "Pushing $cnt $unit to $r_url"] - console_exec $cons $cmd console_done + set cons [console::new \ + "push $r_url" \ + "Pushing $cnt $unit to $r_url"] + console::exec $cons $cmd destroy $w } -- cgit v0.10.2-6-g49f6 From 60aa065f6958a888d14178ca94f17a60100d3709 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 1 May 2007 15:51:09 -0400 Subject: git-gui: Allow vi keys to scroll the diff/blame regions Users who are used to vi and recent versions of gitk may want to scroll the diff region using vi style keybindings. Since these aren't bound to anything else and that widget does not accept focus for data input, we can easily support that too. Signed-off-by: Shawn O. Pearce diff --git a/git-gui.sh b/git-gui.sh index 72673c6..dc6664c 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2038,6 +2038,12 @@ bind $ui_diff {catch {%W yview scroll -1 units};break} bind $ui_diff {catch {%W yview scroll 1 units};break} bind $ui_diff {catch {%W xview scroll -1 units};break} bind $ui_diff {catch {%W xview scroll 1 units};break} +bind $ui_diff {catch {%W yview scroll -1 units};break} +bind $ui_diff {catch {%W yview scroll 1 units};break} +bind $ui_diff {catch {%W xview scroll -1 units};break} +bind $ui_diff {catch {%W xview scroll 1 units};break} +bind $ui_diff {catch {%W yview scroll -1 pages};break} +bind $ui_diff {catch {%W yview scroll 1 pages};break} bind $ui_diff {focus %W} if {[is_enabled branch]} { diff --git a/lib/blame.tcl b/lib/blame.tcl index c276fa9..6d894e5 100644 --- a/lib/blame.tcl +++ b/lib/blame.tcl @@ -125,6 +125,23 @@ proc show_blame {commit path} { " } + foreach i [list \ + $w.out.loaded_t \ + $w.out.linenumber_t \ + $w.out.file_t \ + $w.cm.t] { + bind $i {catch {%W yview scroll -1 units};break} + bind $i {catch {%W yview scroll 1 units};break} + bind $i {catch {%W xview scroll -1 units};break} + bind $i {catch {%W xview scroll 1 units};break} + bind $i {catch {%W yview scroll -1 units};break} + bind $i {catch {%W yview scroll 1 units};break} + bind $i {catch {%W xview scroll -1 units};break} + bind $i {catch {%W xview scroll 1 units};break} + bind $i {catch {%W yview scroll -1 pages};break} + bind $i {catch {%W yview scroll 1 pages};break} + } + bind $w.cm.t "focus $w.cm.t" bind $tl "focus $tl" bind $tl " -- cgit v0.10.2-6-g49f6 From a6c9b081b6860816615e84b75bbc7916aab184e9 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 2 May 2007 13:56:27 -0400 Subject: git-gui: Move merge support into a namespace Like the console procs I have moved the code related to merge support into their own namespace, so that they are isolated from the rest of the world. Signed-off-by: Shawn O. Pearce diff --git a/git-gui.sh b/git-gui.sh index dc6664c..4635825 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1376,7 +1376,7 @@ if {[is_enabled branch]} { [.mbar.branch index last] -state] .mbar.branch add command -label {Reset...} \ - -command do_reset_hard + -command merge::reset_hard lappend disable_on_lock [list .mbar.branch entryconf \ [.mbar.branch index last] -state] } @@ -1449,11 +1449,11 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} { if {[is_enabled branch]} { menu .mbar.merge .mbar.merge add command -label {Local Merge...} \ - -command do_local_merge + -command merge::dialog lappend disable_on_lock \ [list .mbar.merge entryconf [.mbar.merge index last] -state] .mbar.merge add command -label {Abort Merge...} \ - -command do_reset_hard + -command merge::reset_hard lappend disable_on_lock \ [list .mbar.merge entryconf [.mbar.merge index last] -state] diff --git a/lib/merge.tcl b/lib/merge.tcl index e0e84ae..21cd83d 100644 --- a/lib/merge.tcl +++ b/lib/merge.tcl @@ -1,7 +1,9 @@ # git-gui branch merge support # Copyright (C) 2006, 2007 Shawn Pearce -proc can_merge {} { +namespace eval merge { + +proc _can_merge {} { global HEAD commit_type file_states if {[string match amend* $commit_type]} { @@ -61,7 +63,7 @@ You should complete the current commit before starting a merge. Doing so will h return 1 } -proc visualize_local_merge {w} { +proc _visualize {w} { set revs {} foreach i [$w.source.l curselection] { lappend revs [$w.source.l get $i] @@ -71,7 +73,7 @@ proc visualize_local_merge {w} { do_gitk $revs } -proc start_local_merge_action {w} { +proc _start {w} { global HEAD ui_status_value current_branch set cmd [list git merge] @@ -121,12 +123,12 @@ Please select fewer branches. To merge more than 15 branches, merge the branche set msg "Merging $current_branch, [join $names {, }]" set ui_status_value "$msg..." set cons [console::new "Merge" $msg] - console::exec $cons $cmd [list finish_merge $revcnt] + console::exec $cons $cmd [namespace code [list _finish $revcnt]] bind $w {} destroy $w } -proc finish_merge {revcnt w ok} { +proc _finish {revcnt w ok} { console::done $w $ok if {$ok} { set msg {Merge completed successfully.} @@ -144,7 +146,8 @@ You can attempt this merge again by merging only one branch at a time." $w set fd [open "| git read-tree --reset -u HEAD" r] fconfigure $fd -blocking 0 -translation binary - fileevent $fd readable [list reset_hard_wait $fd] + fileevent $fd readable \ + [namespace code [list _reset_wait $fd]] set ui_status_value {Aborting... please wait...} return } @@ -155,10 +158,10 @@ You can attempt this merge again by merging only one branch at a time." $w rescan [list set ui_status_value $msg] } -proc do_local_merge {} { +proc dialog {} { global current_branch - if {![can_merge]} return + if {![_can_merge]} return set w .merge_setup toplevel $w @@ -171,10 +174,10 @@ proc do_local_merge {} { frame $w.buttons button $w.buttons.visualize -text Visualize \ - -command [list visualize_local_merge $w] + -command [namespace code [list _visualize $w]] pack $w.buttons.visualize -side left button $w.buttons.create -text Merge \ - -command [list start_local_merge_action $w] + -command [namespace code [list _start $w]] pack $w.buttons.create -side right button $w.buttons.cancel -text {Cancel} \ -command [list destroy $w] @@ -226,7 +229,7 @@ proc do_local_merge {} { tkwait window $w } -proc do_reset_hard {} { +proc reset_hard {} { global HEAD commit_type file_states if {[string match amend* $commit_type]} { @@ -252,14 +255,14 @@ Aborting the current $op will cause *ALL* uncommitted changes to be lost. Continue with aborting the current $op?"] eq {yes}} { set fd [open "| git read-tree --reset -u HEAD" r] fconfigure $fd -blocking 0 -translation binary - fileevent $fd readable [list reset_hard_wait $fd] + fileevent $fd readable [namespace code [list _reset_wait $fd]] set ui_status_value {Aborting... please wait...} } else { unlock_index } } -proc reset_hard_wait {fd} { +proc _reset_wait {fd} { global ui_comm read $fd @@ -279,3 +282,5 @@ proc reset_hard_wait {fd} { rescan {set ui_status_value {Abort completed. Ready.}} } } + +} -- cgit v0.10.2-6-g49f6 From 349f92e3a25216b6f79c58044d74b61c89de20b8 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 2 May 2007 14:25:22 -0400 Subject: git-gui: Show all possible branches for merge Johannes Sixt pointed out that git-gui was randomly selecting which branch (or tag!) it will show in the merge dialog when more than one ref points at the same commit. This can be a problem for the user if they want to merge a branch, but the ref that git-gui selected to display was actually a tag that points at the commit at the tip of that branch. Since the user is looking for the branch, and not the tag, its confusing to not find it, and worse, merging the tag causes git-merge to generate a different message than if the branch was selected. While I am in here and am messing around I have changed the for-each-ref usage to take advantage of its --tcl formatting, and to fetch the subject line of the commit (or tag) we are looking at. This way we could present the subject line in the UI to the user, given them an even better chance to select the correct branch. Signed-off-by: Shawn O. Pearce diff --git a/lib/merge.tcl b/lib/merge.tcl index 21cd83d..8a07660 100644 --- a/lib/merge.tcl +++ b/lib/merge.tcl @@ -195,17 +195,22 @@ proc dialog {} { pack $w.source.l -side left -fill both -expand 1 pack $w.source -fill both -expand 1 -pady 5 -padx 5 - set cmd [list git for-each-ref] - lappend cmd {--format=%(objectname) %(*objectname) %(refname)} + set fmt {list %(objectname) %(*objectname) %(refname) %(subject)} + set cmd [list git for-each-ref --tcl --format=$fmt] lappend cmd refs/heads lappend cmd refs/remotes lappend cmd refs/tags set fr_fd [open "| $cmd" r] fconfigure $fr_fd -translation binary while {[gets $fr_fd line] > 0} { - set line [split $line { }] - set sha1([lindex $line 0]) [lindex $line 2] - set sha1([lindex $line 1]) [lindex $line 2] + set line [eval $line] + set ref [lindex $line 2] + regsub ^refs/(heads|remotes|tags)/ $ref {} ref + set subj($ref) [lindex $line 3] + lappend sha1([lindex $line 0]) $ref + if {[lindex $line 1] ne {}} { + lappend sha1([lindex $line 1]) $ref + } } close $fr_fd @@ -213,8 +218,9 @@ proc dialog {} { set fr_fd [open "| git rev-list --all --not HEAD"] while {[gets $fr_fd line] > 0} { if {[catch {set ref $sha1($line)}]} continue - regsub ^refs/(heads|remotes|tags)/ $ref {} ref - lappend to_show $ref + foreach n $ref { + lappend to_show $n + } } close $fr_fd -- cgit v0.10.2-6-g49f6 From 1fc4ba86f80c1365943abac54679dd1924a6737b Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 3 May 2007 18:21:39 -0400 Subject: git-gui: Include commit id/subject in merge choices When merging branches using our local merge feature it can be handy to know the first few digits of the commit the ref points at as well as the short description of the branch name. Unfortunately I'm unable to use three listboxes in a row, as Tcl freaks out and refuses to let me have a selection in more than one of them at any given point in time. So instead we use a fixed width font in the existing listbox and organize the data into three columns. Not nearly as nice looking, but users can continue to use the listbox's features. Signed-off-by: Shawn O. Pearce diff --git a/lib/merge.tcl b/lib/merge.tcl index 8a07660..304490a 100644 --- a/lib/merge.tcl +++ b/lib/merge.tcl @@ -63,28 +63,28 @@ You should complete the current commit before starting a merge. Doing so will h return 1 } -proc _visualize {w} { - set revs {} +proc _refs {w list} { + set r {} foreach i [$w.source.l curselection] { - lappend revs [$w.source.l get $i] + lappend r [lindex [lindex $list $i] 0] } + return $r +} + +proc _visualize {w list} { + set revs [_refs $w $list] if {$revs eq {}} return lappend revs --not HEAD do_gitk $revs } -proc _start {w} { +proc _start {w list} { global HEAD ui_status_value current_branch set cmd [list git merge] - set names {} - set revcnt 0 - foreach i [$w.source.l curselection] { - set b [$w.source.l get $i] - lappend cmd $b - lappend names $b - incr revcnt - } + set names [_refs $w $list] + set revcnt [llength $names] + append cmd { } $names if {$revcnt == 0} { return @@ -163,6 +163,36 @@ proc dialog {} { if {![_can_merge]} return + set fmt {list %(objectname) %(*objectname) %(refname) %(subject)} + set cmd [list git for-each-ref --tcl --format=$fmt] + lappend cmd refs/heads + lappend cmd refs/remotes + lappend cmd refs/tags + set fr_fd [open "| $cmd" r] + fconfigure $fr_fd -translation binary + while {[gets $fr_fd line] > 0} { + set line [eval $line] + set ref [lindex $line 2] + regsub ^refs/(heads|remotes|tags)/ $ref {} ref + set subj($ref) [lindex $line 3] + lappend sha1([lindex $line 0]) $ref + if {[lindex $line 1] ne {}} { + lappend sha1([lindex $line 1]) $ref + } + } + close $fr_fd + + set to_show {} + set fr_fd [open "| git rev-list --all --not HEAD"] + while {[gets $fr_fd line] > 0} { + if {[catch {set ref $sha1($line)}]} continue + foreach n $ref { + lappend to_show [list $n $line] + } + } + close $fr_fd + set to_show [lsort -unique $to_show] + set w .merge_setup toplevel $w wm geometry $w "+[winfo rootx .]+[winfo rooty .]" @@ -174,10 +204,10 @@ proc dialog {} { frame $w.buttons button $w.buttons.visualize -text Visualize \ - -command [namespace code [list _visualize $w]] + -command [namespace code [list _visualize $w $to_show]] pack $w.buttons.visualize -side left button $w.buttons.create -text Merge \ - -command [namespace code [list _start $w]] + -command [namespace code [list _start $w $to_show]] pack $w.buttons.create -side right button $w.buttons.cancel -text {Cancel} \ -command [list destroy $w] @@ -188,6 +218,7 @@ proc dialog {} { listbox $w.source.l \ -height 10 \ -width 70 \ + -font font_diff \ -selectmode extended \ -yscrollcommand [list $w.source.sby set] scrollbar $w.source.sby -command [list $w.source.l yview] @@ -195,40 +226,18 @@ proc dialog {} { pack $w.source.l -side left -fill both -expand 1 pack $w.source -fill both -expand 1 -pady 5 -padx 5 - set fmt {list %(objectname) %(*objectname) %(refname) %(subject)} - set cmd [list git for-each-ref --tcl --format=$fmt] - lappend cmd refs/heads - lappend cmd refs/remotes - lappend cmd refs/tags - set fr_fd [open "| $cmd" r] - fconfigure $fr_fd -translation binary - while {[gets $fr_fd line] > 0} { - set line [eval $line] - set ref [lindex $line 2] - regsub ^refs/(heads|remotes|tags)/ $ref {} ref - set subj($ref) [lindex $line 3] - lappend sha1([lindex $line 0]) $ref - if {[lindex $line 1] ne {}} { - lappend sha1([lindex $line 1]) $ref - } - } - close $fr_fd - - set to_show {} - set fr_fd [open "| git rev-list --all --not HEAD"] - while {[gets $fr_fd line] > 0} { - if {[catch {set ref $sha1($line)}]} continue - foreach n $ref { - lappend to_show $n + foreach ref $to_show { + set n [lindex $ref 0] + if {[string length $n] > 20} { + set n "[string range $n 0 16]..." } - } - close $fr_fd - - foreach ref [lsort -unique $to_show] { - $w.source.l insert end $ref + $w.source.l insert end [format {%s %-20s %s} \ + [string range [lindex $ref 1] 0 5] \ + $n \ + $subj([lindex $ref 0])] } - bind $w "grab $w" + bind $w "grab $w; focus $w.source.l" bind $w "unlock_index;destroy $w" bind $w unlock_index wm title $w "[appname] ([reponame]): Merge" -- cgit v0.10.2-6-g49f6 From ebcaadabcb90eaf35a3bd69a50cccce4be4dcc2c Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 5 May 2007 02:28:41 -0400 Subject: git-gui: Use vi-like keys in merge dialog Since we support vi-like keys for scrolling in other UI contexts we can easily do so here too. Tk's handy little `event generate' makes this a lot easier than I thought it would be. We may want to go back and fix some of the other vi-like bindings to redirect to the arrow and pageup/pagedown keys, rather than running the view changes directly. I've bound 'v' to visualize, as this is a somewhat common thing to want to do in the merge dialog. Control (or Command) Return is also bound to start the merge, much as it is bound in the main window to activate the commit. Signed-off-by: Shawn O. Pearce diff --git a/lib/merge.tcl b/lib/merge.tcl index 304490a..3dce856 100644 --- a/lib/merge.tcl +++ b/lib/merge.tcl @@ -160,6 +160,7 @@ You can attempt this merge again by merging only one branch at a time." $w proc dialog {} { global current_branch + global M1B if {![_can_merge]} return @@ -197,20 +198,20 @@ proc dialog {} { toplevel $w wm geometry $w "+[winfo rootx .]+[winfo rooty .]" + set _visualize [namespace code [list _visualize $w $to_show]] + set _start [namespace code [list _start $w $to_show]] + label $w.header \ -text "Merge Into $current_branch" \ -font font_uibold pack $w.header -side top -fill x frame $w.buttons - button $w.buttons.visualize -text Visualize \ - -command [namespace code [list _visualize $w $to_show]] + button $w.buttons.visualize -text Visualize -command $_visualize pack $w.buttons.visualize -side left - button $w.buttons.create -text Merge \ - -command [namespace code [list _start $w $to_show]] + button $w.buttons.create -text Merge -command $_start pack $w.buttons.create -side right - button $w.buttons.cancel -text {Cancel} \ - -command [list destroy $w] + button $w.buttons.cancel -text {Cancel} -command [list destroy $w] pack $w.buttons.cancel -side right -padx 5 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 @@ -237,6 +238,13 @@ proc dialog {} { $subj([lindex $ref 0])] } + bind $w.source.l [list event generate %W ] + bind $w.source.l [list event generate %W ] + bind $w.source.l [list event generate %W ] + bind $w.source.l [list event generate %W ] + bind $w.source.l $_visualize + + bind $w <$M1B-Key-Return> $_start bind $w "grab $w; focus $w.source.l" bind $w "unlock_index;destroy $w" bind $w unlock_index -- cgit v0.10.2-6-g49f6