Skip to content

Commit ff07c3b

Browse files
jeplerspearce
authored andcommitted
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 <[email protected]> Signed-off-by: Shawn O. Pearce <[email protected]>
1 parent 25476c6 commit ff07c3b

File tree

2 files changed

+153
-104
lines changed

2 files changed

+153
-104
lines changed

git-gui.sh

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3266,7 +3266,7 @@ set ui_diff_applyhunk [$ctxm index last]
32663266
lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
32673267
$ctxm add command \
32683268
-label [mc "Apply/Reverse Line"] \
3269-
-command {apply_line $cursorX $cursorY; do_rescan}
3269+
-command {apply_range_or_line $cursorX $cursorY; do_rescan}
32703270
set ui_diff_applyline [$ctxm index last]
32713271
lappend diff_actions [list $ctxm entryconf $ui_diff_applyline -state]
32723272
$ctxm add separator
@@ -3348,12 +3348,21 @@ proc popup_diff_menu {ctxm ctxmmg ctxmsm x y X Y} {
33483348
} elseif {$::is_submodule_diff} {
33493349
tk_popup $ctxmsm $X $Y
33503350
} else {
3351+
set has_range [expr {[$::ui_diff tag nextrange sel 0.0] != {}}]
33513352
if {$::ui_index eq $::current_diff_side} {
33523353
set l [mc "Unstage Hunk From Commit"]
3353-
set t [mc "Unstage Line From Commit"]
3354+
if {$has_range} {
3355+
set t [mc "Unstage Lines From Commit"]
3356+
} else {
3357+
set t [mc "Unstage Line From Commit"]
3358+
}
33543359
} else {
33553360
set l [mc "Stage Hunk For Commit"]
3356-
set t [mc "Stage Line For Commit"]
3361+
if {$has_range} {
3362+
set t [mc "Stage Lines For Commit"]
3363+
} else {
3364+
set t [mc "Stage Line For Commit"]
3365+
}
33573366
}
33583367
if {$::is_3way_diff
33593368
|| $current_diff_path eq {}

lib/diff.tcl

Lines changed: 141 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -542,10 +542,23 @@ proc apply_hunk {x y} {
542542
}
543543
}
544544

545-
proc apply_line {x y} {
545+
proc apply_range_or_line {x y} {
546546
global current_diff_path current_diff_header current_diff_side
547547
global ui_diff ui_index file_states
548548

549+
set selected [$ui_diff tag nextrange sel 0.0]
550+
551+
if {$selected == {}} {
552+
set first [$ui_diff index "@$x,$y"]
553+
set last $first
554+
} else {
555+
set first [lindex $selected 0]
556+
set last [lindex $selected 1]
557+
}
558+
559+
set first_l [$ui_diff index "$first linestart"]
560+
set last_l [$ui_diff index "$last lineend"]
561+
549562
if {$current_diff_path eq {} || $current_diff_header eq {}} return
550563
if {![lock_index apply_hunk]} return
551564

@@ -568,120 +581,147 @@ proc apply_line {x y} {
568581
}
569582
}
570583

571-
set the_l [$ui_diff index @$x,$y]
584+
set wholepatch {}
572585

573-
# operate only on change lines
574-
set c1 [$ui_diff get "$the_l linestart"]
575-
if {$c1 ne {+} && $c1 ne {-}} {
576-
unlock_index
577-
return
578-
}
579-
set sign $c1
580-
581-
set i_l [$ui_diff search -backwards -regexp ^@@ $the_l 0.0]
582-
if {$i_l eq {}} {
583-
unlock_index
584-
return
585-
}
586-
# $i_l is now at the beginning of a line
586+
while {$first_l < $last_l} {
587+
set i_l [$ui_diff search -backwards -regexp ^@@ $first_l 0.0]
588+
if {$i_l eq {}} {
589+
# If there's not a @@ above, then the selected range
590+
# must have come before the first_l @@
591+
set i_l [$ui_diff search -regexp ^@@ $first_l $last_l]
592+
}
593+
if {$i_l eq {}} {
594+
unlock_index
595+
return
596+
}
597+
# $i_l is now at the beginning of a line
587598

588-
# pick start line number from hunk header
589-
set hh [$ui_diff get $i_l "$i_l + 1 lines"]
590-
set hh [lindex [split $hh ,] 0]
591-
set hln [lindex [split $hh -] 1]
599+
# pick start line number from hunk header
600+
set hh [$ui_diff get $i_l "$i_l + 1 lines"]
601+
set hh [lindex [split $hh ,] 0]
602+
set hln [lindex [split $hh -] 1]
592603

593-
# There is a special situation to take care of. Consider this hunk:
594-
#
595-
# @@ -10,4 +10,4 @@
596-
# context before
597-
# -old 1
598-
# -old 2
599-
# +new 1
600-
# +new 2
601-
# context after
602-
#
603-
# We used to keep the context lines in the order they appear in the
604-
# hunk. But then it is not possible to correctly stage only
605-
# "-old 1" and "+new 1" - it would result in this staged text:
606-
#
607-
# context before
608-
# old 2
609-
# new 1
610-
# context after
611-
#
612-
# (By symmetry it is not possible to *un*stage "old 2" and "new 2".)
613-
#
614-
# We resolve the problem by introducing an asymmetry, namely, when
615-
# a "+" line is *staged*, it is moved in front of the context lines
616-
# that are generated from the "-" lines that are immediately before
617-
# the "+" block. That is, we construct this patch:
618-
#
619-
# @@ -10,4 +10,5 @@
620-
# context before
621-
# +new 1
622-
# old 1
623-
# old 2
624-
# context after
625-
#
626-
# But we do *not* treat "-" lines that are *un*staged in a special
627-
# way.
628-
#
629-
# With this asymmetry it is possible to stage the change
630-
# "old 1" -> "new 1" directly, and to stage the change
631-
# "old 2" -> "new 2" by first staging the entire hunk and
632-
# then unstaging the change "old 1" -> "new 1".
633-
634-
# This is non-empty if and only if we are _staging_ changes;
635-
# then it accumulates the consecutive "-" lines (after converting
636-
# them to context lines) in order to be moved after the "+" change
637-
# line.
638-
set pre_context {}
639-
640-
set n 0
641-
set i_l [$ui_diff index "$i_l + 1 lines"]
642-
set patch {}
643-
while {[$ui_diff compare $i_l < "end - 1 chars"] &&
644-
[$ui_diff get $i_l "$i_l + 2 chars"] ne {@@}} {
645-
set next_l [$ui_diff index "$i_l + 1 lines"]
646-
set c1 [$ui_diff get $i_l]
647-
if {[$ui_diff compare $i_l <= $the_l] &&
648-
[$ui_diff compare $the_l < $next_l]} {
649-
# the line to stage/unstage
650-
set ln [$ui_diff get $i_l $next_l]
651-
if {$c1 eq {-}} {
652-
set n [expr $n+1]
604+
# There is a special situation to take care of. Consider this
605+
# hunk:
606+
#
607+
# @@ -10,4 +10,4 @@
608+
# context before
609+
# -old 1
610+
# -old 2
611+
# +new 1
612+
# +new 2
613+
# context after
614+
#
615+
# We used to keep the context lines in the order they appear in
616+
# the hunk. But then it is not possible to correctly stage only
617+
# "-old 1" and "+new 1" - it would result in this staged text:
618+
#
619+
# context before
620+
# old 2
621+
# new 1
622+
# context after
623+
#
624+
# (By symmetry it is not possible to *un*stage "old 2" and "new
625+
# 2".)
626+
#
627+
# We resolve the problem by introducing an asymmetry, namely,
628+
# when a "+" line is *staged*, it is moved in front of the
629+
# context lines that are generated from the "-" lines that are
630+
# immediately before the "+" block. That is, we construct this
631+
# patch:
632+
#
633+
# @@ -10,4 +10,5 @@
634+
# context before
635+
# +new 1
636+
# old 1
637+
# old 2
638+
# context after
639+
#
640+
# But we do *not* treat "-" lines that are *un*staged in a
641+
# special way.
642+
#
643+
# With this asymmetry it is possible to stage the change "old
644+
# 1" -> "new 1" directly, and to stage the change "old 2" ->
645+
# "new 2" by first staging the entire hunk and then unstaging
646+
# the change "old 1" -> "new 1".
647+
#
648+
# Applying multiple lines adds complexity to the special
649+
# situation. The pre_context must be moved after the entire
650+
# first block of consecutive staged "+" lines, so that
651+
# staging both additions gives the following patch:
652+
#
653+
# @@ -10,4 +10,6 @@
654+
# context before
655+
# +new 1
656+
# +new 2
657+
# old 1
658+
# old 2
659+
# context after
660+
661+
# This is non-empty if and only if we are _staging_ changes;
662+
# then it accumulates the consecutive "-" lines (after
663+
# converting them to context lines) in order to be moved after
664+
# "+" change lines.
665+
set pre_context {}
666+
667+
set n 0
668+
set m 0
669+
set i_l [$ui_diff index "$i_l + 1 lines"]
670+
set patch {}
671+
while {[$ui_diff compare $i_l < "end - 1 chars"] &&
672+
[$ui_diff get $i_l "$i_l + 2 chars"] ne {@@}} {
673+
set next_l [$ui_diff index "$i_l + 1 lines"]
674+
set c1 [$ui_diff get $i_l]
675+
if {[$ui_diff compare $first_l <= $i_l] &&
676+
[$ui_diff compare $i_l < $last_l] &&
677+
($c1 eq {-} || $c1 eq {+})} {
678+
# a line to stage/unstage
679+
set ln [$ui_diff get $i_l $next_l]
680+
if {$c1 eq {-}} {
681+
set n [expr $n+1]
682+
set patch "$patch$pre_context$ln"
683+
set pre_context {}
684+
} else {
685+
set m [expr $m+1]
686+
set patch "$patch$ln"
687+
}
688+
} elseif {$c1 ne {-} && $c1 ne {+}} {
689+
# context line
690+
set ln [$ui_diff get $i_l $next_l]
653691
set patch "$patch$pre_context$ln"
692+
set n [expr $n+1]
693+
set m [expr $m+1]
694+
set pre_context {}
695+
} elseif {$c1 eq $to_context} {
696+
# turn change line into context line
697+
set ln [$ui_diff get "$i_l + 1 chars" $next_l]
698+
if {$c1 eq {-}} {
699+
set pre_context "$pre_context $ln"
700+
} else {
701+
set patch "$patch $ln"
702+
}
703+
set n [expr $n+1]
704+
set m [expr $m+1]
654705
} else {
655-
set patch "$patch$ln$pre_context"
656-
}
657-
set pre_context {}
658-
} elseif {$c1 ne {-} && $c1 ne {+}} {
659-
# context line
660-
set ln [$ui_diff get $i_l $next_l]
661-
set patch "$patch$pre_context$ln"
662-
set n [expr $n+1]
663-
set pre_context {}
664-
} elseif {$c1 eq $to_context} {
665-
# turn change line into context line
666-
set ln [$ui_diff get "$i_l + 1 chars" $next_l]
667-
if {$c1 eq {-}} {
668-
set pre_context "$pre_context $ln"
669-
} else {
670-
set patch "$patch $ln"
706+
# a change in the opposite direction of
707+
# to_context which is outside the range of
708+
# lines to apply.
709+
set patch "$patch$pre_context"
710+
set pre_context {}
671711
}
672-
set n [expr $n+1]
712+
set i_l $next_l
673713
}
674-
set i_l $next_l
714+
set patch "$patch$pre_context"
715+
set wholepatch "$wholepatch@@ -$hln,$n +$hln,$m @@\n$patch"
716+
set first_l [$ui_diff index "$next_l + 1 lines"]
675717
}
676-
set patch "$patch$pre_context"
677-
set patch "@@ -$hln,$n +$hln,[eval expr $n $sign 1] @@\n$patch"
678718

679719
if {[catch {
680720
set enc [get_path_encoding $current_diff_path]
681721
set p [eval git_write $apply_cmd]
682722
fconfigure $p -translation binary -encoding $enc
683723
puts -nonewline $p $current_diff_header
684-
puts -nonewline $p $patch
724+
puts -nonewline $p $wholepatch
685725
close $p} err]} {
686726
error_popup [append $failed_msg "\n\n$err"]
687727
}

0 commit comments

Comments
 (0)