git-gui: Support applying a range of changes at once

Multiple lines can be selected in the diff viewer and applied all
at once, rather than selecting "Stage Line For Commit" on each
individual line.

Signed-off-by: Jeff Epler <jepler@unpythonic.net>
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
maint
Jeff Epler 2009-12-07 18:22:43 -06:00 committed by Shawn O. Pearce
parent 25476c63e7
commit ff07c3b621
2 changed files with 158 additions and 109 deletions

View File

@ -3266,7 +3266,7 @@ set ui_diff_applyhunk [$ctxm index last]
lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state] lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
$ctxm add command \ $ctxm add command \
-label [mc "Apply/Reverse Line"] \ -label [mc "Apply/Reverse Line"] \
-command {apply_line $cursorX $cursorY; do_rescan} -command {apply_range_or_line $cursorX $cursorY; do_rescan}
set ui_diff_applyline [$ctxm index last] set ui_diff_applyline [$ctxm index last]
lappend diff_actions [list $ctxm entryconf $ui_diff_applyline -state] lappend diff_actions [list $ctxm entryconf $ui_diff_applyline -state]
$ctxm add separator $ctxm add separator
@ -3348,13 +3348,22 @@ proc popup_diff_menu {ctxm ctxmmg ctxmsm x y X Y} {
} elseif {$::is_submodule_diff} { } elseif {$::is_submodule_diff} {
tk_popup $ctxmsm $X $Y tk_popup $ctxmsm $X $Y
} else { } else {
set has_range [expr {[$::ui_diff tag nextrange sel 0.0] != {}}]
if {$::ui_index eq $::current_diff_side} { if {$::ui_index eq $::current_diff_side} {
set l [mc "Unstage Hunk From Commit"] set l [mc "Unstage Hunk From Commit"]
if {$has_range} {
set t [mc "Unstage Lines From Commit"]
} else {
set t [mc "Unstage Line From Commit"] set t [mc "Unstage Line From Commit"]
}
} else { } else {
set l [mc "Stage Hunk For Commit"] set l [mc "Stage Hunk For Commit"]
if {$has_range} {
set t [mc "Stage Lines For Commit"]
} else {
set t [mc "Stage Line For Commit"] set t [mc "Stage Line For Commit"]
} }
}
if {$::is_3way_diff if {$::is_3way_diff
|| $current_diff_path eq {} || $current_diff_path eq {}
|| {__} eq $state || {__} eq $state

View File

@ -542,10 +542,23 @@ proc apply_hunk {x y} {
} }
} }


proc apply_line {x y} { proc apply_range_or_line {x y} {
global current_diff_path current_diff_header current_diff_side global current_diff_path current_diff_header current_diff_side
global ui_diff ui_index file_states global ui_diff ui_index file_states


set selected [$ui_diff tag nextrange sel 0.0]

if {$selected == {}} {
set first [$ui_diff index "@$x,$y"]
set last $first
} else {
set first [lindex $selected 0]
set last [lindex $selected 1]
}

set first_l [$ui_diff index "$first linestart"]
set last_l [$ui_diff index "$last lineend"]

if {$current_diff_path eq {} || $current_diff_header eq {}} return if {$current_diff_path eq {} || $current_diff_header eq {}} return
if {![lock_index apply_hunk]} return if {![lock_index apply_hunk]} return


@ -568,17 +581,15 @@ proc apply_line {x y} {
} }
} }


set the_l [$ui_diff index @$x,$y] set wholepatch {}


# operate only on change lines while {$first_l < $last_l} {
set c1 [$ui_diff get "$the_l linestart"] set i_l [$ui_diff search -backwards -regexp ^@@ $first_l 0.0]
if {$c1 ne {+} && $c1 ne {-}} { if {$i_l eq {}} {
unlock_index # If there's not a @@ above, then the selected range
return # must have come before the first_l @@
set i_l [$ui_diff search -regexp ^@@ $first_l $last_l]
} }
set sign $c1

set i_l [$ui_diff search -backwards -regexp ^@@ $the_l 0.0]
if {$i_l eq {}} { if {$i_l eq {}} {
unlock_index unlock_index
return return
@ -590,7 +601,8 @@ proc apply_line {x y} {
set hh [lindex [split $hh ,] 0] set hh [lindex [split $hh ,] 0]
set hln [lindex [split $hh -] 1] set hln [lindex [split $hh -] 1]


# There is a special situation to take care of. Consider this hunk: # There is a special situation to take care of. Consider this
# hunk:
# #
# @@ -10,4 +10,4 @@ # @@ -10,4 +10,4 @@
# context before # context before
@ -600,8 +612,8 @@ proc apply_line {x y} {
# +new 2 # +new 2
# context after # context after
# #
# We used to keep the context lines in the order they appear in the # We used to keep the context lines in the order they appear in
# hunk. But then it is not possible to correctly stage only # the hunk. But then it is not possible to correctly stage only
# "-old 1" and "+new 1" - it would result in this staged text: # "-old 1" and "+new 1" - it would result in this staged text:
# #
# context before # context before
@ -609,12 +621,14 @@ proc apply_line {x y} {
# new 1 # new 1
# context after # context after
# #
# (By symmetry it is not possible to *un*stage "old 2" and "new 2".) # (By symmetry it is not possible to *un*stage "old 2" and "new
# 2".)
# #
# We resolve the problem by introducing an asymmetry, namely, when # We resolve the problem by introducing an asymmetry, namely,
# a "+" line is *staged*, it is moved in front of the context lines # when a "+" line is *staged*, it is moved in front of the
# that are generated from the "-" lines that are immediately before # context lines that are generated from the "-" lines that are
# the "+" block. That is, we construct this patch: # immediately before the "+" block. That is, we construct this
# patch:
# #
# @@ -10,4 +10,5 @@ # @@ -10,4 +10,5 @@
# context before # context before
@ -623,43 +637,60 @@ proc apply_line {x y} {
# old 2 # old 2
# context after # context after
# #
# But we do *not* treat "-" lines that are *un*staged in a special # But we do *not* treat "-" lines that are *un*staged in a
# way. # special way.
# #
# With this asymmetry it is possible to stage the change # With this asymmetry it is possible to stage the change "old
# "old 1" -> "new 1" directly, and to stage the change # 1" -> "new 1" directly, and to stage the change "old 2" ->
# "old 2" -> "new 2" by first staging the entire hunk and # "new 2" by first staging the entire hunk and then unstaging
# then unstaging the change "old 1" -> "new 1". # the change "old 1" -> "new 1".
#
# Applying multiple lines adds complexity to the special
# situation. The pre_context must be moved after the entire
# first block of consecutive staged "+" lines, so that
# staging both additions gives the following patch:
#
# @@ -10,4 +10,6 @@
# context before
# +new 1
# +new 2
# old 1
# old 2
# context after


# This is non-empty if and only if we are _staging_ changes; # This is non-empty if and only if we are _staging_ changes;
# then it accumulates the consecutive "-" lines (after converting # then it accumulates the consecutive "-" lines (after
# them to context lines) in order to be moved after the "+" change # converting them to context lines) in order to be moved after
# line. # "+" change lines.
set pre_context {} set pre_context {}


set n 0 set n 0
set m 0
set i_l [$ui_diff index "$i_l + 1 lines"] set i_l [$ui_diff index "$i_l + 1 lines"]
set patch {} set patch {}
while {[$ui_diff compare $i_l < "end - 1 chars"] && while {[$ui_diff compare $i_l < "end - 1 chars"] &&
[$ui_diff get $i_l "$i_l + 2 chars"] ne {@@}} { [$ui_diff get $i_l "$i_l + 2 chars"] ne {@@}} {
set next_l [$ui_diff index "$i_l + 1 lines"] set next_l [$ui_diff index "$i_l + 1 lines"]
set c1 [$ui_diff get $i_l] set c1 [$ui_diff get $i_l]
if {[$ui_diff compare $i_l <= $the_l] && if {[$ui_diff compare $first_l <= $i_l] &&
[$ui_diff compare $the_l < $next_l]} { [$ui_diff compare $i_l < $last_l] &&
# the line to stage/unstage ($c1 eq {-} || $c1 eq {+})} {
# a line to stage/unstage
set ln [$ui_diff get $i_l $next_l] set ln [$ui_diff get $i_l $next_l]
if {$c1 eq {-}} { if {$c1 eq {-}} {
set n [expr $n+1] set n [expr $n+1]
set patch "$patch$pre_context$ln" set patch "$patch$pre_context$ln"
} else {
set patch "$patch$ln$pre_context"
}
set pre_context {} set pre_context {}
} else {
set m [expr $m+1]
set patch "$patch$ln"
}
} elseif {$c1 ne {-} && $c1 ne {+}} { } elseif {$c1 ne {-} && $c1 ne {+}} {
# context line # context line
set ln [$ui_diff get $i_l $next_l] set ln [$ui_diff get $i_l $next_l]
set patch "$patch$pre_context$ln" set patch "$patch$pre_context$ln"
set n [expr $n+1] set n [expr $n+1]
set m [expr $m+1]
set pre_context {} set pre_context {}
} elseif {$c1 eq $to_context} { } elseif {$c1 eq $to_context} {
# turn change line into context line # turn change line into context line
@ -670,18 +701,27 @@ proc apply_line {x y} {
set patch "$patch $ln" set patch "$patch $ln"
} }
set n [expr $n+1] set n [expr $n+1]
set m [expr $m+1]
} else {
# a change in the opposite direction of
# to_context which is outside the range of
# lines to apply.
set patch "$patch$pre_context"
set pre_context {}
} }
set i_l $next_l set i_l $next_l
} }
set patch "$patch$pre_context" set patch "$patch$pre_context"
set patch "@@ -$hln,$n +$hln,[eval expr $n $sign 1] @@\n$patch" set wholepatch "$wholepatch@@ -$hln,$n +$hln,$m @@\n$patch"
set first_l [$ui_diff index "$next_l + 1 lines"]
}


if {[catch { if {[catch {
set enc [get_path_encoding $current_diff_path] set enc [get_path_encoding $current_diff_path]
set p [eval git_write $apply_cmd] set p [eval git_write $apply_cmd]
fconfigure $p -translation binary -encoding $enc fconfigure $p -translation binary -encoding $enc
puts -nonewline $p $current_diff_header puts -nonewline $p $current_diff_header
puts -nonewline $p $patch puts -nonewline $p $wholepatch
close $p} err]} { close $p} err]} {
error_popup [append $failed_msg "\n\n$err"] error_popup [append $failed_msg "\n\n$err"]
} }