Merge branch 'py/revert-hunks-lines'
[gitweb.git] / git-gui.sh
index 2381c3e54807ecd0b326d8c11b3f0c6008c5636a..ac258d0dcfb64e262cfca2c67e132b9684a96d34 100755 (executable)
@@ -534,28 +534,10 @@ proc _lappend_nice {cmd_var} {
 }
 
 proc git {args} {
-       set opt [list]
-
-       while {1} {
-               switch -- [lindex $args 0] {
-               --nice {
-                       _lappend_nice opt
-               }
-
-               default {
-                       break
-               }
-
-               }
-
-               set args [lrange $args 1 end]
-       }
-
-       set cmdp [_git_cmd [lindex $args 0]]
-       set args [lrange $args 1 end]
-
-       _trace_exec [concat $opt $cmdp $args]
-       set result [eval exec $opt $cmdp $args]
+       set fd [eval [list git_read] $args]
+       fconfigure $fd -translation binary -encoding utf-8
+       set result [string trimright [read $fd] "\n"]
+       close $fd
        if {$::_trace} {
                puts stderr "< $result"
        }
@@ -1111,7 +1093,7 @@ git-version proc _parse_config {arr_name args} {
                                [list git_read config] \
                                $args \
                                [list --null --list]]
-                       fconfigure $fd_rc -translation binary
+                       fconfigure $fd_rc -translation binary -encoding utf-8
                        set buf [read $fd_rc]
                        close $fd_rc
                }
@@ -1368,6 +1350,8 @@ set is_submodule_diff 0
 set is_conflict_diff 0
 set selected_commit_type new
 set diff_empty_count 0
+set last_revert {}
+set last_revert_enc {}
 
 set nullid "0000000000000000000000000000000000000000"
 set nullid2 "0000000000000000000000000000000000000001"
@@ -1620,11 +1604,13 @@ proc run_prepare_commit_msg_hook {} {
        if {[file isfile [gitdir MERGE_MSG]]} {
                set pcm_source "merge"
                set fd_mm [open [gitdir MERGE_MSG] r]
+               fconfigure $fd_mm -encoding utf-8
                puts -nonewline $fd_pcm [read $fd_mm]
                close $fd_mm
        } elseif {[file isfile [gitdir SQUASH_MSG]]} {
                set pcm_source "squash"
                set fd_sm [open [gitdir SQUASH_MSG] r]
+               fconfigure $fd_sm -encoding utf-8
                puts -nonewline $fd_pcm [read $fd_sm]
                close $fd_sm
        } else {
@@ -1689,7 +1675,7 @@ proc read_diff_index {fd after} {
                set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }]
                set p [string range $buf_rdi $z1 [expr {$z2 - 1}]]
                merge_state \
-                       [encoding convertfrom $p] \
+                       [encoding convertfrom utf-8 $p] \
                        [lindex $i 4]? \
                        [list [lindex $i 0] [lindex $i 2]] \
                        [list]
@@ -1722,7 +1708,7 @@ proc read_diff_files {fd after} {
                set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }]
                set p [string range $buf_rdf $z1 [expr {$z2 - 1}]]
                merge_state \
-                       [encoding convertfrom $p] \
+                       [encoding convertfrom utf-8 $p] \
                        ?[lindex $i 4] \
                        [list] \
                        [list [lindex $i 0] [lindex $i 2]]
@@ -1745,7 +1731,7 @@ proc read_ls_others {fd after} {
        set pck [split $buf_rlo "\0"]
        set buf_rlo [lindex $pck end]
        foreach p [lrange $pck 0 end-1] {
-               set p [encoding convertfrom $p]
+               set p [encoding convertfrom utf-8 $p]
                if {[string index $p end] eq {/}} {
                        set p [string range $p 0 end-1]
                }
@@ -2509,13 +2495,28 @@ proc force_first_diff {after} {
        }
 }
 
-proc toggle_or_diff {w x y} {
+proc toggle_or_diff {mode w args} {
        global file_states file_lists current_diff_path ui_index ui_workdir
-       global last_clicked selected_paths
+       global last_clicked selected_paths file_lists_last_clicked
+
+       if {$mode eq "click"} {
+               foreach {x y} $args break
+               set pos [split [$w index @$x,$y] .]
+               foreach {lno col} $pos break
+       } else {
+               if {$last_clicked ne {}} {
+                       set lno [lindex $last_clicked 1]
+               } else {
+                       set lno [expr {int([lindex [$w tag ranges in_diff] 0])}]
+               }
+               if {$mode eq "toggle"} {
+                       set col 0; set y 2
+               } else {
+                       incr lno [expr {$mode eq "up" ? -1 : 1}]
+                       set col 1
+               }
+       }
 
-       set pos [split [$w index @$x,$y] .]
-       set lno [lindex $pos 0]
-       set col [lindex $pos 1]
        set path [lindex $file_lists($w) [expr {$lno - 1}]]
        if {$path eq {}} {
                set last_clicked {}
@@ -2523,10 +2524,13 @@ proc toggle_or_diff {w x y} {
        }
 
        set last_clicked [list $w $lno]
+       focus $w
        array unset selected_paths
        $ui_index tag remove in_sel 0.0 end
        $ui_workdir tag remove in_sel 0.0 end
 
+       set file_lists_last_clicked($w) $path
+
        # Determine the state of the file
        if {[info exists file_states($path)]} {
                set state [lindex $file_states($path) 0]
@@ -2602,7 +2606,7 @@ proc add_range_to_selection {w x y} {
        global file_lists last_clicked selected_paths
 
        if {[lindex $last_clicked 0] ne $w} {
-               toggle_or_diff $w $x $y
+               toggle_or_diff click $w $x $y
                return
        }
 
@@ -2640,6 +2644,26 @@ proc show_less_context {} {
        }
 }
 
+proc focus_widget {widget} {
+       global file_lists last_clicked selected_paths
+       global file_lists_last_clicked
+
+       if {[llength $file_lists($widget)] > 0} {
+               set path $file_lists_last_clicked($widget)
+               set index [lsearch -sorted -exact $file_lists($widget) $path]
+               if {$index < 0} {
+                       set index 0
+                       set path [lindex $file_lists($widget) $index]
+               }
+
+               focus $widget
+               set last_clicked [list $widget [expr $index + 1]]
+               array unset selected_paths
+               set selected_paths($path) 1
+               show_diff $path $widget
+       }
+}
+
 ######################################################################
 ##
 ## ui construction
@@ -3011,7 +3035,7 @@ bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
 
 set subcommand_args {}
 proc usage {} {
-       set s "usage: $::argv0 $::subcommand $::subcommand_args"
+       set s "[mc usage:] $::argv0 $::subcommand $::subcommand_args"
        if {[tk windowingsystem] eq "win32"} {
                wm withdraw .
                tk_messageBox -icon info -message $s \
@@ -3143,7 +3167,7 @@ gui {
        # fall through to setup UI for commits
 }
 default {
-       set err "usage: $argv0 \[{blame|browser|citool}\]"
+       set err "[mc usage:] $argv0 \[{blame|browser|citool}\]"
        if {[tk windowingsystem] eq "win32"} {
                wm withdraw .
                tk_messageBox -icon error -message $err \
@@ -3182,16 +3206,38 @@ if {$use_ttk} {
 }
 pack .vpane -anchor n -side top -fill both -expand 1
 
+# -- Working Directory File List
+
+textframe .vpane.files.workdir -height 100 -width 200
+tlabel .vpane.files.workdir.title -text [mc "Unstaged Changes"] \
+       -background lightsalmon -foreground black
+ttext $ui_workdir -background white -foreground black \
+       -borderwidth 0 \
+       -width 20 -height 10 \
+       -wrap none \
+       -takefocus 1 -highlightthickness 1\
+       -cursor $cursor_ptr \
+       -xscrollcommand {.vpane.files.workdir.sx set} \
+       -yscrollcommand {.vpane.files.workdir.sy set} \
+       -state disabled
+${NS}::scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview]
+${NS}::scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview]
+pack .vpane.files.workdir.title -side top -fill x
+pack .vpane.files.workdir.sx -side bottom -fill x
+pack .vpane.files.workdir.sy -side right -fill y
+pack $ui_workdir -side left -fill both -expand 1
+
 # -- Index File List
 #
-${NS}::frame .vpane.files.index -height 100 -width 200
+textframe .vpane.files.index -height 100 -width 200
 tlabel .vpane.files.index.title \
        -text [mc "Staged Changes (Will Commit)"] \
        -background lightgreen -foreground black
-text $ui_index -background white -foreground black \
+ttext $ui_index -background white -foreground black \
        -borderwidth 0 \
        -width 20 -height 10 \
        -wrap none \
+       -takefocus 1 -highlightthickness 1\
        -cursor $cursor_ptr \
        -xscrollcommand {.vpane.files.index.sx set} \
        -yscrollcommand {.vpane.files.index.sy set} \
@@ -3203,26 +3249,8 @@ pack .vpane.files.index.sx -side bottom -fill x
 pack .vpane.files.index.sy -side right -fill y
 pack $ui_index -side left -fill both -expand 1
 
-# -- Working Directory File List
+# -- Insert the workdir and index into the panes
 #
-${NS}::frame .vpane.files.workdir -height 100 -width 200
-tlabel .vpane.files.workdir.title -text [mc "Unstaged Changes"] \
-       -background lightsalmon -foreground black
-text $ui_workdir -background white -foreground black \
-       -borderwidth 0 \
-       -width 20 -height 10 \
-       -wrap none \
-       -cursor $cursor_ptr \
-       -xscrollcommand {.vpane.files.workdir.sx set} \
-       -yscrollcommand {.vpane.files.workdir.sy set} \
-       -state disabled
-${NS}::scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview]
-${NS}::scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview]
-pack .vpane.files.workdir.title -side top -fill x
-pack .vpane.files.workdir.sx -side bottom -fill x
-pack .vpane.files.workdir.sy -side right -fill y
-pack $ui_workdir -side left -fill both -expand 1
-
 .vpane.files add .vpane.files.workdir
 .vpane.files add .vpane.files.index
 if {!$use_ttk} {
@@ -3305,7 +3333,7 @@ if {![is_enabled nocommit]} {
 #
 ${NS}::frame .vpane.lower.commarea.buffer
 ${NS}::frame .vpane.lower.commarea.buffer.header
-set ui_comm .vpane.lower.commarea.buffer.t
+set ui_comm .vpane.lower.commarea.buffer.frame.t
 set ui_coml .vpane.lower.commarea.buffer.header.l
 
 if {![is_enabled nocommit]} {
@@ -3348,20 +3376,25 @@ if {![is_enabled nocommit]} {
        pack .vpane.lower.commarea.buffer.header.new -side right
 }
 
-text $ui_comm -background white -foreground black \
+textframe .vpane.lower.commarea.buffer.frame
+ttext $ui_comm -background white -foreground black \
        -borderwidth 1 \
        -undo true \
        -maxundo 20 \
        -autoseparators true \
+       -takefocus 1 \
+       -highlightthickness 1 \
        -relief sunken \
        -width $repo_config(gui.commitmsgwidth) -height 9 -wrap none \
        -font font_diff \
-       -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
-${NS}::scrollbar .vpane.lower.commarea.buffer.sby \
+       -yscrollcommand {.vpane.lower.commarea.buffer.frame.sby set}
+${NS}::scrollbar .vpane.lower.commarea.buffer.frame.sby \
        -command [list $ui_comm yview]
-pack .vpane.lower.commarea.buffer.header -side top -fill x
-pack .vpane.lower.commarea.buffer.sby -side right -fill y
+
+pack .vpane.lower.commarea.buffer.frame.sby -side right -fill y
 pack $ui_comm -side left -fill y
+pack .vpane.lower.commarea.buffer.header -side top -fill x
+pack .vpane.lower.commarea.buffer.frame -side left -fill y
 pack .vpane.lower.commarea.buffer -side left -fill y
 
 # -- Commit Message Buffer Context Menu
@@ -3459,12 +3492,13 @@ bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
 
 # -- Diff Body
 #
-${NS}::frame .vpane.lower.diff.body
+textframe .vpane.lower.diff.body
 set ui_diff .vpane.lower.diff.body.t
-text $ui_diff -background white -foreground black \
+ttext $ui_diff -background white -foreground black \
        -borderwidth 0 \
        -width 80 -height 5 -wrap none \
        -font font_diff \
+       -takefocus 1 -highlightthickness 1 \
        -xscrollcommand {.vpane.lower.diff.body.sbx set} \
        -yscrollcommand {.vpane.lower.diff.body.sby set} \
        -state disabled
@@ -3572,15 +3606,31 @@ set ctxm .vpane.lower.diff.body.ctxm
 menu $ctxm -tearoff 0
 $ctxm add command \
        -label [mc "Apply/Reverse Hunk"] \
-       -command {apply_hunk $cursorX $cursorY}
+       -command {apply_or_revert_hunk $cursorX $cursorY 0}
 set ui_diff_applyhunk [$ctxm index last]
 lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
 $ctxm add command \
        -label [mc "Apply/Reverse Line"] \
-       -command {apply_range_or_line $cursorX $cursorY; do_rescan}
+       -command {apply_or_revert_range_or_line $cursorX $cursorY 0; do_rescan}
 set ui_diff_applyline [$ctxm index last]
 lappend diff_actions [list $ctxm entryconf $ui_diff_applyline -state]
 $ctxm add separator
+$ctxm add command \
+       -label [mc "Revert Hunk"] \
+       -command {apply_or_revert_hunk $cursorX $cursorY 1}
+set ui_diff_reverthunk [$ctxm index last]
+lappend diff_actions [list $ctxm entryconf $ui_diff_reverthunk -state]
+$ctxm add command \
+       -label [mc "Revert Line"] \
+       -command {apply_or_revert_range_or_line $cursorX $cursorY 1; do_rescan}
+set ui_diff_revertline [$ctxm index last]
+lappend diff_actions [list $ctxm entryconf $ui_diff_revertline -state]
+$ctxm add command \
+       -label [mc "Undo Last Revert"] \
+       -command {undo_last_revert; do_rescan}
+set ui_diff_undorevert [$ctxm index last]
+lappend diff_actions [list $ctxm entryconf $ui_diff_undorevert -state]
+$ctxm add separator
 $ctxm add command \
        -label [mc "Show Less Context"] \
        -command show_less_context
@@ -3659,7 +3709,7 @@ proc has_textconv {path} {
 }
 
 proc popup_diff_menu {ctxm ctxmmg ctxmsm x y X Y} {
-       global current_diff_path file_states
+       global current_diff_path file_states last_revert
        set ::cursorX $x
        set ::cursorY $y
        if {[info exists file_states($current_diff_path)]} {
@@ -3673,19 +3723,28 @@ proc popup_diff_menu {ctxm ctxmmg ctxmsm x y X Y} {
                tk_popup $ctxmsm $X $Y
        } else {
                set has_range [expr {[$::ui_diff tag nextrange sel 0.0] != {}}]
+               set u [mc "Undo Last Revert"]
                if {$::ui_index eq $::current_diff_side} {
                        set l [mc "Unstage Hunk From Commit"]
+                       set h [mc "Revert Hunk"]
+
                        if {$has_range} {
                                set t [mc "Unstage Lines From Commit"]
+                               set r [mc "Revert Lines"]
                        } else {
                                set t [mc "Unstage Line From Commit"]
+                               set r [mc "Revert Line"]
                        }
                } else {
                        set l [mc "Stage Hunk For Commit"]
+                       set h [mc "Revert Hunk"]
+
                        if {$has_range} {
                                set t [mc "Stage Lines For Commit"]
+                               set r [mc "Revert Lines"]
                        } else {
                                set t [mc "Stage Line For Commit"]
+                               set r [mc "Revert Line"]
                        }
                }
                if {$::is_3way_diff
@@ -3696,11 +3755,35 @@ proc popup_diff_menu {ctxm ctxmmg ctxmsm x y X Y} {
                        || [string match {T?} $state]
                        || [has_textconv $current_diff_path]} {
                        set s disabled
+                       set revert_state disabled
                } else {
                        set s normal
+
+                       # Only allow reverting changes in the working tree. If
+                       # the user wants to revert changes in the index, they
+                       # need to unstage those first.
+                       if {$::ui_workdir eq $::current_diff_side} {
+                               set revert_state normal
+                       } else {
+                               set revert_state disabled
+                       }
                }
+
+               if {$last_revert eq {}} {
+                       set undo_state disabled
+               } else {
+                       set undo_state normal
+               }
+
                $ctxm entryconf $::ui_diff_applyhunk -state $s -label $l
                $ctxm entryconf $::ui_diff_applyline -state $s -label $t
+               $ctxm entryconf $::ui_diff_revertline -state $revert_state \
+                       -label $r
+               $ctxm entryconf $::ui_diff_reverthunk -state $revert_state \
+                       -label $h
+               $ctxm entryconf $::ui_diff_undorevert -state $undo_state \
+                       -label $u
+
                tk_popup $ctxm $X $Y
        }
 }
@@ -3819,10 +3902,10 @@ bind .   <$M1B-Key-r> ui_do_rescan
 bind .   <$M1B-Key-R> ui_do_rescan
 bind .   <$M1B-Key-s> do_signoff
 bind .   <$M1B-Key-S> do_signoff
-bind .   <$M1B-Key-t> do_add_selection
-bind .   <$M1B-Key-T> do_add_selection
-bind .   <$M1B-Key-u> do_unstage_selection
-bind .   <$M1B-Key-U> do_unstage_selection
+bind .   <$M1B-Key-t> { toggle_or_diff toggle %W }
+bind .   <$M1B-Key-T> { toggle_or_diff toggle %W }
+bind .   <$M1B-Key-u> { toggle_or_diff toggle %W }
+bind .   <$M1B-Key-U> { toggle_or_diff toggle %W }
 bind .   <$M1B-Key-j> do_revert_selection
 bind .   <$M1B-Key-J> do_revert_selection
 bind .   <$M1B-Key-i> do_add_all
@@ -3834,12 +3917,22 @@ bind .   <$M1B-Key-plus> {show_more_context;break}
 bind .   <$M1B-Key-KP_Add> {show_more_context;break}
 bind .   <$M1B-Key-Return> do_commit
 foreach i [list $ui_index $ui_workdir] {
-       bind $i <Button-1>       "toggle_or_diff         $i %x %y; break"
-       bind $i <$M1B-Button-1>  "add_one_to_selection   $i %x %y; break"
-       bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break"
+       bind $i <Button-1>       { toggle_or_diff click %W %x %y; break }
+       bind $i <$M1B-Button-1>  { add_one_to_selection %W %x %y; break }
+       bind $i <Shift-Button-1> { add_range_to_selection %W %x %y; break }
+       bind $i <Key-Up>         { toggle_or_diff up %W; break }
+       bind $i <Key-Down>       { toggle_or_diff down %W; break }
 }
 unset i
 
+bind .   <Alt-Key-1> {focus_widget $::ui_workdir}
+bind .   <Alt-Key-2> {focus_widget $::ui_index}
+bind .   <Alt-Key-3> {focus $::ui_diff}
+bind .   <Alt-Key-4> {focus $::ui_comm}
+
+set file_lists_last_clicked($ui_index) {}
+set file_lists_last_clicked($ui_workdir) {}
+
 set file_lists($ui_index) [list]
 set file_lists($ui_workdir) [list]