@matheusbsbs / matheus-bagliano / commits / 5821988f97
Fork 0

git-gui: Implement "Stage/Unstage Line"

This adds a context menu entry below "Stage/Unstage Hunk" that stages or unstages just the line under the mouse pointer. This is by itself useful, for example, if there are unrelated changes in the same hunk and the hunk cannot be split by reducing the context. The feature can also be used to split a hunk by staging a number of additions (or unstaging a number of removals) until there are enough context lines that the hunk gets split. The implementation reads the complete hunk that the line lives in, and constructs a new hunk by picking existing context lines, removing unneeded change lines and transforming other change lines to context lines. The resulting hunk is fed through 'git apply' just like in the "Stage/Unstage Hunk" case. Signed-off-by: Johannes Sixt <johannes.sixt@telecom.at> Signed-off-by: Shawn O. Pearce <spearce@spearce.org>

Johannes Sixt committed Jun 27, 2008 at 09:22 UTC 5821988f97b827f6ba81dfeebff932067c88ba6c
2 files changed +95
git-gui.sh
+8
@@ -2666,6 +2666,11 @@ $ctxm add command \
2666 -command {apply_hunk $cursorX $cursorY}
2667 set ui_diff_applyhunk [$ctxm index last]
2668 lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
2669 +$ctxm add command \
2670 + -label [mc "Apply/Reverse Line"] \
2671 + -command {apply_line $cursorX $cursorY; do_rescan}
2672 +set ui_diff_applyline [$ctxm index last]
2673 +lappend diff_actions [list $ctxm entryconf $ui_diff_applyline -state]
2674 $ctxm add separator
2675 $ctxm add command \
2676 -label [mc "Show Less Context"] \
@@ -2714,8 +2719,10 @@ proc popup_diff_menu {ctxm x y X Y} {
2719 set ::cursorY $y
2720 if {$::ui_index eq $::current_diff_side} {
2721 set l [mc "Unstage Hunk From Commit"]
2722 + set t [mc "Unstage Line From Commit"]
2723 } else {
2724 set l [mc "Stage Hunk For Commit"]
2725 + set t [mc "Stage Line For Commit"]
2726 }
2727 if {$::is_3way_diff
2728 || $current_diff_path eq {}
@@ -2726,6 +2733,7 @@ proc popup_diff_menu {ctxm x y X Y} {
2733 set s normal
2734 }
2735 $ctxm entryconf $::ui_diff_applyhunk -state $s -label $l
2736 + $ctxm entryconf $::ui_diff_applyline -state $s -label $t
2737 tk_popup $ctxm $X $Y
2738 }
2739 bind_button3 $ui_diff [list popup_diff_menu $ctxm %x %y %X %Y]
lib/diff.tcl
+87
@@ -362,3 +362,90 @@ proc apply_hunk {x y} {
362 set current_diff_path $current_diff_path
363 }
364 }
365 +
366 +proc apply_line {x y} {
367 + global current_diff_path current_diff_header current_diff_side
368 + global ui_diff ui_index file_states
369 +
370 + if {$current_diff_path eq {} || $current_diff_header eq {}} return
371 + if {![lock_index apply_hunk]} return
372 +
373 + set apply_cmd {apply --cached --whitespace=nowarn}
374 + set mi [lindex $file_states($current_diff_path) 0]
375 + if {$current_diff_side eq $ui_index} {
376 + set failed_msg [mc "Failed to unstage selected line."]
377 + set to_context {+}
378 + lappend apply_cmd --reverse
379 + if {[string index $mi 0] ne {M}} {
380 + unlock_index
381 + return
382 + }
383 + } else {
384 + set failed_msg [mc "Failed to stage selected line."]
385 + set to_context {-}
386 + if {[string index $mi 1] ne {M}} {
387 + unlock_index
388 + return
389 + }
390 + }
391 +
392 + set the_l [$ui_diff index @$x,$y]
393 +
394 + # operate only on change lines
395 + set c1 [$ui_diff get "$the_l linestart"]
396 + if {$c1 ne {+} && $c1 ne {-}} {
397 + unlock_index
398 + return
399 + }
400 + set sign $c1
401 +
402 + set i_l [$ui_diff search -backwards -regexp ^@@ $the_l 0.0]
403 + if {$i_l eq {}} {
404 + unlock_index
405 + return
406 + }
407 + # $i_l is now at the beginning of a line
408 +
409 + # pick start line number from hunk header
410 + set hh [$ui_diff get $i_l "$i_l + 1 lines"]
411 + set hh [lindex [split $hh ,] 0]
412 + set hln [lindex [split $hh -] 1]
413 +
414 + set n 0
415 + set i_l [$ui_diff index "$i_l + 1 lines"]
416 + set patch {}
417 + while {[$ui_diff compare $i_l < "end - 1 chars"] &&
418 + [$ui_diff get $i_l "$i_l + 2 chars"] ne {@@}} {
419 + set next_l [$ui_diff index "$i_l + 1 lines"]
420 + set c1 [$ui_diff get $i_l]
421 + if {[$ui_diff compare $i_l <= $the_l] &&
422 + [$ui_diff compare $the_l < $next_l]} {
423 + # the line to stage/unstage
424 + set ln [$ui_diff get $i_l $next_l]
425 + set patch "$patch$ln"
426 + } elseif {$c1 ne {-} && $c1 ne {+}} {
427 + # context line
428 + set ln [$ui_diff get $i_l $next_l]
429 + set patch "$patch$ln"
430 + set n [expr $n+1]
431 + } elseif {$c1 eq $to_context} {
432 + # turn change line into context line
433 + set ln [$ui_diff get "$i_l + 1 chars" $next_l]
434 + set patch "$patch $ln"
435 + set n [expr $n+1]
436 + }
437 + set i_l $next_l
438 + }
439 + set patch "@@ -$hln,$n +$hln,[eval expr $n $sign 1] @@\n$patch"
440 +
441 + if {[catch {
442 + set p [eval git_write $apply_cmd]
443 + fconfigure $p -translation binary -encoding binary
444 + puts -nonewline $p $current_diff_header
445 + puts -nonewline $p $patch
446 + close $p} err]} {
447 + error_popup [append $failed_msg "\n\n$err"]
448 + }
449 +
450 + unlock_index
451 +}