diff --git a/git-gui.sh b/git-gui.sh index 1bbae15a50..d89f156fd5 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2666,6 +2666,11 @@ $ctxm add command \ -command {apply_hunk $cursorX $cursorY} 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_line $cursorX $cursorY; 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 "Show Less Context"] \ @@ -2714,8 +2719,10 @@ proc popup_diff_menu {ctxm x y X Y} { set ::cursorY $y if {$::ui_index eq $::current_diff_side} { set l [mc "Unstage Hunk From Commit"] + set t [mc "Unstage Line From Commit"] } else { set l [mc "Stage Hunk For Commit"] + set t [mc "Stage Line For Commit"] } if {$::is_3way_diff || $current_diff_path eq {} @@ -2726,6 +2733,7 @@ proc popup_diff_menu {ctxm x y X Y} { set s normal } $ctxm entryconf $::ui_diff_applyhunk -state $s -label $l + $ctxm entryconf $::ui_diff_applyline -state $s -label $t tk_popup $ctxm $X $Y } bind_button3 $ui_diff [list popup_diff_menu $ctxm %x %y %X %Y] diff --git a/lib/diff.tcl b/lib/diff.tcl index d04f6dbde2..96ba94906c 100644 --- a/lib/diff.tcl +++ b/lib/diff.tcl @@ -362,3 +362,90 @@ proc apply_hunk {x y} { set current_diff_path $current_diff_path } } + +proc apply_line {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 {apply --cached --whitespace=nowarn} + set mi [lindex $file_states($current_diff_path) 0] + if {$current_diff_side eq $ui_index} { + set failed_msg [mc "Failed to unstage selected line."] + set to_context {+} + lappend apply_cmd --reverse + if {[string index $mi 0] ne {M}} { + unlock_index + return + } + } else { + set failed_msg [mc "Failed to stage selected line."] + set to_context {-} + if {[string index $mi 1] ne {M}} { + unlock_index + return + } + } + + set the_l [$ui_diff index @$x,$y] + + # operate only on change lines + set c1 [$ui_diff get "$the_l linestart"] + if {$c1 ne {+} && $c1 ne {-}} { + unlock_index + return + } + set sign $c1 + + set i_l [$ui_diff search -backwards -regexp ^@@ $the_l 0.0] + if {$i_l eq {}} { + unlock_index + return + } + # $i_l is now at the beginning of a line + + # pick start line number from hunk header + set hh [$ui_diff get $i_l "$i_l + 1 lines"] + set hh [lindex [split $hh ,] 0] + set hln [lindex [split $hh -] 1] + + set n 0 + set i_l [$ui_diff index "$i_l + 1 lines"] + set patch {} + while {[$ui_diff compare $i_l < "end - 1 chars"] && + [$ui_diff get $i_l "$i_l + 2 chars"] ne {@@}} { + set next_l [$ui_diff index "$i_l + 1 lines"] + set c1 [$ui_diff get $i_l] + if {[$ui_diff compare $i_l <= $the_l] && + [$ui_diff compare $the_l < $next_l]} { + # the line to stage/unstage + set ln [$ui_diff get $i_l $next_l] + set patch "$patch$ln" + } elseif {$c1 ne {-} && $c1 ne {+}} { + # context line + set ln [$ui_diff get $i_l $next_l] + set patch "$patch$ln" + set n [expr $n+1] + } elseif {$c1 eq $to_context} { + # turn change line into context line + set ln [$ui_diff get "$i_l + 1 chars" $next_l] + set patch "$patch $ln" + set n [expr $n+1] + } + set i_l $next_l + } + set patch "@@ -$hln,$n +$hln,[eval expr $n $sign 1] @@\n$patch" + + if {[catch { + set p [eval git_write $apply_cmd] + fconfigure $p -translation binary -encoding binary + puts -nonewline $p $current_diff_header + puts -nonewline $p $patch + close $p} err]} { + error_popup [append $failed_msg "\n\n$err"] + } + + unlock_index +}