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>
Jeff Epler committed
Dec 7, 2009 at 18:22 UTC
ff07c3b6210fbebf07ce2820b5a9a95f0583ad8d
2 files changed
+153
-104
git-gui.sh
+12
-3
@@ -3266,7 +3266,7 @@ set ui_diff_applyhunk [$ctxm index last]
3266
lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
3267
$ctxm add command \
3268
-label [mc "Apply/Reverse Line"] \
3269
- -command {apply_line $cursorX $cursorY; do_rescan}
3269
+ -command {apply_range_or_line $cursorX $cursorY; do_rescan}
3270
set ui_diff_applyline [$ctxm index last]
3271
lappend diff_actions [list $ctxm entryconf $ui_diff_applyline -state]
3272
$ctxm add separator
@@ -3348,12 +3348,21 @@ proc popup_diff_menu {ctxm ctxmmg ctxmsm x y X Y} {
3348
} elseif {$::is_submodule_diff} {
3349
tk_popup $ctxmsm $X $Y
3350
} else {
3351
+ set has_range [expr {[$::ui_diff tag nextrange sel 0.0] != {}}]
3352
if {$::ui_index eq $::current_diff_side} {
3353
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
+ }
3359
} else {
3360
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
+ }
3366
}
3367
if {$::is_3way_diff
3368
|| $current_diff_path eq {}
lib/diff.tcl
+141
-101
@@ -542,10 +542,23 @@ proc apply_hunk {x y} {
542
}
543
}
544
545
-proc apply_line {x y} {
545
+proc apply_range_or_line {x y} {
546
global current_diff_path current_diff_header current_diff_side
547
global ui_diff ui_index file_states
548
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
+
562
if {$current_diff_path eq {} || $current_diff_header eq {}} return
563
if {![lock_index apply_hunk]} return
564
@@ -568,120 +581,147 @@ proc apply_line {x y} {
581
}
582
}
583
571
- set the_l [$ui_diff index @$x,$y]
584
+ set wholepatch {}
585
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
598
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]
603
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]
691
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]
705
} 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 {}
711
}
672
- set n [expr $n+1]
712
+ set i_l $next_l
713
}
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"]
717
}
676
- set patch "$patch$pre_context"
677
- set patch "@@ -$hln,$n +$hln,[eval expr $n $sign 1] @@\n$patch"
718
719
if {[catch {
720
set enc [get_path_encoding $current_diff_path]
721
set p [eval git_write $apply_cmd]
722
fconfigure $p -translation binary -encoding $enc
723
puts -nonewline $p $current_diff_header
684
- puts -nonewline $p $patch
724
+ puts -nonewline $p $wholepatch
725
close $p} err]} {
726
error_popup [append $failed_msg "\n\n$err"]
727
}