{"thread":{"id":"21284","subject":"[RFC PATCH] git-gui: Allow staging multiple lines at once","startedAt":"2009-10-19T19:54:57Z","lastAt":"2009-10-23T01:53:15Z","messageCount":6,"participants":["Jeff Epler","Geert Bosch","Johannes Sixt","Dirk Süsserott","Heiko Voigt"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"125413","messageId":"20091019195456.GA11121@unpythonic.net","threadId":"21284","inReplyTo":null,"subject":"[RFC PATCH] git-gui: Allow staging multiple lines at once","fromName":"Jeff Epler","fromEmail":"jepler@unpythonic.net","sentAt":"2009-10-19T19:54:57Z","receivedAt":"2009-10-19T19:54:57Z","isPatch":true,"sender":{"key":"jepler@unpythonic.net","avatar":"https://avatars.githubusercontent.com/u/1517291?v=4"},"body":"When applying less than a full hunk, it's still often desirable to apply\na number of consecutive lines.\n\nThis change makes it possible to sweep out a range of lines in the diff view\nwith the left mouse button, then right click and \"Stage Lines For Commit\".\n\nThe selected lines may span multiple hunks.\n\nSigned-off-by: Jeff Epler <jepler@unpythonic.net>\n---\nThe diff looks bigger than it is because it changed the indentation level\nof about 80 lines, and that made it necessary to reflow a lengthy comment\nblock as well.\n\nThis introduces new user interface strings.  I felt this was probably a\nbetter decision than using the inaccurate \"Stage Line For Commit\" when\na block of text was swept out.\n\nI wonder a bit about the message [mc \"Apply/Reverse Line\"] -- as far as\nI can tell, it is never shown to a user, so why is it translated?\n\n git-gui/git-gui.sh   |   15 +++-\n git-gui/lib/diff.tcl |  222 +++++++++++++++++++++++++++----------------------\n 2 files changed, 134 insertions(+), 103 deletions(-)\n\ndiff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh\nindex 14b92ba..a80ed0d 100755\n--- a/git-gui/git-gui.sh\n+++ b/git-gui/git-gui.sh\n@@ -3165,7 +3165,7 @@ set ui_diff_applyhunk [$ctxm index last]\n lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]\n $ctxm add command \\\n \t-label [mc \"Apply/Reverse Line\"] \\\n-\t-command {apply_line $cursorX $cursorY; do_rescan}\n+\t-command {apply_range_or_line $cursorX $cursorY; do_rescan}\n set ui_diff_applyline [$ctxm index last]\n lappend diff_actions [list $ctxm entryconf $ui_diff_applyline -state]\n $ctxm add separator\n@@ -3205,12 +3205,21 @@ proc popup_diff_menu {ctxm ctxmmg x y X Y} {\n \tif {[string first {U} $state] >= 0} {\n \t\ttk_popup $ctxmmg $X $Y\n \t} else {\n+\t\tset has_range [expr {[$::ui_diff tag nextrange sel 0.0] != {}}]\n \t\tif {$::ui_index eq $::current_diff_side} {\n \t\t\tset l [mc \"Unstage Hunk From Commit\"]\n-\t\t\tset t [mc \"Unstage Line From Commit\"]\n+\t\t\tif {$has_range} {\n+\t\t\t\tset t [mc \"Unstage Lines From Commit\"]\n+\t\t\t} else {\n+\t\t\t\tset t [mc \"Unstage Line From Commit\"]\n+\t\t\t}\n \t\t} else {\n \t\t\tset l [mc \"Stage Hunk For Commit\"]\n-\t\t\tset t [mc \"Stage Line For Commit\"]\n+\t\t\tif {$has_range} {\n+\t\t\t\tset t [mc \"Stage Lines For Commit\"]\n+\t\t\t} else {\n+\t\t\t\tset t [mc \"Stage Line For Commit\"]\n+\t\t\t}\n \t\t}\n \t\tif {$::is_3way_diff\n \t\t\t|| $current_diff_path eq {}\ndiff --git a/git-gui/lib/diff.tcl b/git-gui/lib/diff.tcl\nindex 925b3f5..30ac659 100644\n--- a/git-gui/lib/diff.tcl\n+++ b/git-gui/lib/diff.tcl\n@@ -505,10 +505,23 @@ proc apply_hunk {x y} {\n \t}\n }\n \n-proc apply_line {x y} {\n+proc apply_range_or_line {x y} {\n \tglobal current_diff_path current_diff_header current_diff_side\n \tglobal ui_diff ui_index file_states\n \n+\tset selected [$ui_diff tag nextrange sel 0.0]\n+\n+\tif {$selected == {}} {\n+\t\tset first [$ui_diff index \"@$x,$y\"]\n+\t\tset last $first\n+\t} else {\n+\t\tset first [lindex $selected 0]\n+\t\tset last [lindex $selected 1]\n+\t}\n+\n+\tset first_l [$ui_diff index \"$first linestart\"]\n+\tset last_l [$ui_diff index \"$last lineend\"]\n+\n \tif {$current_diff_path eq {} || $current_diff_header eq {}} return\n \tif {![lock_index apply_hunk]} return\n \n@@ -531,119 +544,128 @@ proc apply_line {x y} {\n \t\t}\n \t}\n \n-\tset the_l [$ui_diff index @$x,$y]\n+\tset wholepatch {}\n \n-\t# operate only on change lines\n-\tset c1 [$ui_diff get \"$the_l linestart\"]\n-\tif {$c1 ne {+} && $c1 ne {-}} {\n-\t\tunlock_index\n-\t\treturn\n-\t}\n-\tset sign $c1\n-\n-\tset i_l [$ui_diff search -backwards -regexp ^@@ $the_l 0.0]\n-\tif {$i_l eq {}} {\n-\t\tunlock_index\n-\t\treturn\n-\t}\n-\t# $i_l is now at the beginning of a line\n+\twhile {$first_l < $last_l} {\n+\t\tset i_l [$ui_diff search -backwards -regexp ^@@ $first_l 0.0]\n+\t\tif {$i_l eq {}} {\n+\t\t\t# If there's not a @@ above, then the selected range\n+\t\t\t# must have come before the first_l @@\n+\t\t\tset i_l [$ui_diff search -regexp ^@@ $first_l $last_l]\n+\t\t}\n+\t\tif {$i_l eq {}} {\n+\t\t\tunlock_index\n+\t\t\treturn\n+\t\t}\n+\t\t# $i_l is now at the beginning of a line\n \n-\t# pick start line number from hunk header\n-\tset hh [$ui_diff get $i_l \"$i_l + 1 lines\"]\n-\tset hh [lindex [split $hh ,] 0]\n-\tset hln [lindex [split $hh -] 1]\n+\t\t# pick start line number from hunk header\n+\t\tset hh [$ui_diff get $i_l \"$i_l + 1 lines\"]\n+\t\tset hh [lindex [split $hh ,] 0]\n+\t\tset hln [lindex [split $hh -] 1]\n \n-\t# There is a special situation to take care of. Consider this hunk:\n-\t#\n-\t#    @@ -10,4 +10,4 @@\n-\t#     context before\n-\t#    -old 1\n-\t#    -old 2\n-\t#    +new 1\n-\t#    +new 2\n-\t#     context after\n-\t#\n-\t# We used to keep the context lines in the order they appear in the\n-\t# hunk. But then it is not possible to correctly stage only\n-\t# \"-old 1\" and \"+new 1\" - it would result in this staged text:\n-\t#\n-\t#    context before\n-\t#    old 2\n-\t#    new 1\n-\t#    context after\n-\t#\n-\t# (By symmetry it is not possible to *un*stage \"old 2\" and \"new 2\".)\n-\t#\n-\t# We resolve the problem by introducing an asymmetry, namely, when\n-\t# a \"+\" line is *staged*, it is moved in front of the context lines\n-\t# that are generated from the \"-\" lines that are immediately before\n-\t# the \"+\" block. That is, we construct this patch:\n-\t#\n-\t#    @@ -10,4 +10,5 @@\n-\t#     context before\n-\t#    +new 1\n-\t#     old 1\n-\t#     old 2\n-\t#     context after\n-\t#\n-\t# But we do *not* treat \"-\" lines that are *un*staged in a special\n-\t# way.\n-\t#\n-\t# With this asymmetry it is possible to stage the change\n-\t# \"old 1\" -> \"new 1\" directly, and to stage the change\n-\t# \"old 2\" -> \"new 2\" by first staging the entire hunk and\n-\t# then unstaging the change \"old 1\" -> \"new 1\".\n-\n-\t# This is non-empty if and only if we are _staging_ changes;\n-\t# then it accumulates the consecutive \"-\" lines (after converting\n-\t# them to context lines) in order to be moved after the \"+\" change\n-\t# line.\n-\tset pre_context {}\n-\n-\tset n 0\n-\tset i_l [$ui_diff index \"$i_l + 1 lines\"]\n-\tset patch {}\n-\twhile {[$ui_diff compare $i_l < \"end - 1 chars\"] &&\n-\t       [$ui_diff get $i_l \"$i_l + 2 chars\"] ne {@@}} {\n-\t\tset next_l [$ui_diff index \"$i_l + 1 lines\"]\n-\t\tset c1 [$ui_diff get $i_l]\n-\t\tif {[$ui_diff compare $i_l <= $the_l] &&\n-\t\t    [$ui_diff compare $the_l < $next_l]} {\n-\t\t\t# the line to stage/unstage\n-\t\t\tset ln [$ui_diff get $i_l $next_l]\n-\t\t\tif {$c1 eq {-}} {\n-\t\t\t\tset n [expr $n+1]\n+\t\t# There is a special situation to take care of. Consider this\n+\t\t# hunk:\n+\t\t#\n+\t\t#    @@ -10,4 +10,4 @@\n+\t\t#     context before\n+\t\t#    -old 1\n+\t\t#    -old 2\n+\t\t#    +new 1\n+\t\t#    +new 2\n+\t\t#     context after\n+\t\t#\n+\t\t# We used to keep the context lines in the order they appear in\n+\t\t# the hunk. But then it is not possible to correctly stage only\n+\t\t# \"-old 1\" and \"+new 1\" - it would result in this staged text:\n+\t\t#\n+\t\t#    context before\n+\t\t#    old 2\n+\t\t#    new 1\n+\t\t#    context after\n+\t\t#\n+\t\t# (By symmetry it is not possible to *un*stage \"old 2\" and \"new\n+\t\t# 2\".)\n+\t\t#\n+\t\t# We resolve the problem by introducing an asymmetry, namely,\n+\t\t# when a \"+\" line is *staged*, it is moved in front of the\n+\t\t# context lines that are generated from the \"-\" lines that are\n+\t\t# immediately before the \"+\" block. That is, we construct this\n+\t\t# patch:\n+\t\t#\n+\t\t#    @@ -10,4 +10,5 @@\n+\t\t#     context before\n+\t\t#    +new 1\n+\t\t#     old 1\n+\t\t#     old 2\n+\t\t#     context after\n+\t\t#\n+\t\t# But we do *not* treat \"-\" lines that are *un*staged in a\n+\t\t# special way.\n+\t\t#\n+\t\t# With this asymmetry it is possible to stage the change \"old\n+\t\t# 1\" -> \"new 1\" directly, and to stage the change \"old 2\" ->\n+\t\t# \"new 2\" by first staging the entire hunk and then unstaging\n+\t\t# the change \"old 1\" -> \"new 1\".\n+\n+\t\t# This is non-empty if and only if we are _staging_ changes;\n+\t\t# then it accumulates the consecutive \"-\" lines (after\n+\t\t# converting them to context lines) in order to be moved after\n+\t\t# the \"+\" change line.\n+\t\tset pre_context {}\n+\n+\t\tset n 0\n+\t\tset m 0\n+\t\tset i_l [$ui_diff index \"$i_l + 1 lines\"]\n+\t\tset patch {}\n+\t\twhile {[$ui_diff compare $i_l < \"end - 1 chars\"] &&\n+\t\t       [$ui_diff get $i_l \"$i_l + 2 chars\"] ne {@@}} {\n+\t\t\tset next_l [$ui_diff index \"$i_l + 1 lines\"]\n+\t\t\tset c1 [$ui_diff get $i_l]\n+\t\t\tif {[$ui_diff compare $first_l <= $i_l] &&\n+\t\t\t    [$ui_diff compare $i_l < $last_l] &&\n+\t\t\t    ($c1 eq {-} || $c1 eq {+})} {\n+\t\t\t\t# the line to stage/unstage\n+\t\t\t\tset ln [$ui_diff get $i_l $next_l]\n+\t\t\t\tif {$c1 eq {-}} {\n+\t\t\t\t\tset n [expr $n+1]\n+\t\t\t\t\tset patch \"$patch$pre_context$ln\"\n+\t\t\t\t} else {\n+\t\t\t\t\tset m [expr $m+1]\n+\t\t\t\t\tset patch \"$patch$ln$pre_context\"\n+\t\t\t\t}\n+\t\t\t\tset pre_context {}\n+\t\t\t} elseif {$c1 ne {-} && $c1 ne {+}} {\n+\t\t\t\t# context line\n+\t\t\t\tset ln [$ui_diff get $i_l $next_l]\n \t\t\t\tset patch \"$patch$pre_context$ln\"\n+\t\t\t\tset n [expr $n+1]\n+\t\t\t\tset m [expr $m+1]\n+\t\t\t\tset pre_context {}\n+\t\t\t} elseif {$c1 eq $to_context} {\n+\t\t\t\t# turn change line into context line\n+\t\t\t\tset ln [$ui_diff get \"$i_l + 1 chars\" $next_l]\n+\t\t\t\tif {$c1 eq {-}} {\n+\t\t\t\t\tset pre_context \"$pre_context $ln\"\n+\t\t\t\t} else {\n+\t\t\t\t\tset patch \"$patch $ln\"\n+\t\t\t\t}\n+\t\t\t\tset n [expr $n+1]\n+\t\t\t\tset m [expr $m+1]\n \t\t\t} else {\n-\t\t\t\tset patch \"$patch$ln$pre_context\"\n-\t\t\t}\n-\t\t\tset pre_context {}\n-\t\t} elseif {$c1 ne {-} && $c1 ne {+}} {\n-\t\t\t# context line\n-\t\t\tset ln [$ui_diff get $i_l $next_l]\n-\t\t\tset patch \"$patch$pre_context$ln\"\n-\t\t\tset n [expr $n+1]\n-\t\t\tset pre_context {}\n-\t\t} elseif {$c1 eq $to_context} {\n-\t\t\t# turn change line into context line\n-\t\t\tset ln [$ui_diff get \"$i_l + 1 chars\" $next_l]\n-\t\t\tif {$c1 eq {-}} {\n-\t\t\t\tset pre_context \"$pre_context $ln\"\n-\t\t\t} else {\n-\t\t\t\tset patch \"$patch $ln\"\n \t\t\t}\n-\t\t\tset n [expr $n+1]\n+\t\t\tset i_l $next_l\n \t\t}\n-\t\tset i_l $next_l\n+\t\tset wholepatch \"$wholepatch@@ -$hln,$n +$hln,$m @@\\n$patch\"\n+\t\tset first_l [$ui_diff index \"$next_l + 1 lines\"]\n \t}\n-\tset patch \"@@ -$hln,$n +$hln,[eval expr $n $sign 1] @@\\n$patch\"\n \n \tif {[catch {\n \t\tset enc [get_path_encoding $current_diff_path]\n \t\tset p [eval git_write $apply_cmd]\n \t\tfconfigure $p -translation binary -encoding $enc\n \t\tputs -nonewline $p $current_diff_header\n-\t\tputs -nonewline $p $patch\n+\t\tputs -nonewline $p $wholepatch\n \t\tclose $p} err]} {\n \t\terror_popup [append $failed_msg \"\\n\\n$err\"]\n \t}\n-- \n1.6.5.rc1.49.ge970\n"},{"id":"125415","messageId":"3A647965-01B7-4025-A708-C05D25A63A7D@adacore.com","threadId":"21284","inReplyTo":"20091019195456.GA11121@unpythonic.net","subject":"Re: [RFC PATCH] git-gui: Allow staging multiple lines at once","fromName":"Geert Bosch","fromEmail":"bosch@adacore.com","sentAt":"2009-10-19T20:08:39Z","receivedAt":"2009-10-19T20:08:39Z","isPatch":true,"sender":{"key":"bosch@adacore.com","avatar":null},"body":"\nOn Oct 19, 2009, at 15:54, Jeff Epler wrote:\n\n> When applying less than a full hunk, it's still often desirable to  \n> apply\n> a number of consecutive lines.\n>\n> This change makes it possible to sweep out a range of lines in the  \n> diff view\n> with the left mouse button, then right click and \"Stage Lines For  \n> Commit\".\n>\n> The selected lines may span multiple hunks.\n\nGreat! I've wished for this feature...\n\nThanks,\n    -Geert\n"},{"id":"125440","messageId":"4ADD5488.5020808@viscovery.net","threadId":"21284","inReplyTo":"20091019195456.GA11121@unpythonic.net","subject":"Re: [RFC PATCH] git-gui: Allow staging multiple lines at once","fromName":"Johannes Sixt","fromEmail":"j.sixt@viscovery.net","sentAt":"2009-10-20T06:11:20Z","receivedAt":"2009-10-20T06:11:20Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Jeff Epler schrieb:\n> When applying less than a full hunk, it's still often desirable to apply\n> a number of consecutive lines.\n> \n> This change makes it possible to sweep out a range of lines in the diff view\n> with the left mouse button, then right click and \"Stage Lines For Commit\".\n> \n> The selected lines may span multiple hunks.\n\nSuperb! Will test.\n\n-- Hannes\n"},{"id":"125497","messageId":"4ADDEEB1.80107@dirk.my1.cc","threadId":"21284","inReplyTo":"20091019195456.GA11121@unpythonic.net","subject":"Re: [RFC PATCH] git-gui: Allow staging multiple lines at once","fromName":"Dirk Süsserott","fromEmail":"newsletter@dirk.my1.cc","sentAt":"2009-10-20T17:09:05Z","receivedAt":"2009-10-20T17:09:05Z","isPatch":true,"sender":{"key":"newsletter@dirk.my1.cc","avatar":null},"body":"Am 19.10.2009 21:54 schrieb Jeff Epler:\n> When applying less than a full hunk, it's still often desirable to apply\n> a number of consecutive lines.\n> \n> This change makes it possible to sweep out a range of lines in the diff view\n> with the left mouse button, then right click and \"Stage Lines For Commit\".\n> \n> The selected lines may span multiple hunks.\n> \n\nAwesome! I've been waiting for that very feature. Thanks in advance.\n-- Dirk\n"},{"id":"125730","messageId":"20091022195116.GB3944@book.hvoigt.net","threadId":"21284","inReplyTo":"20091019195456.GA11121@unpythonic.net","subject":"Re: [RFC PATCH] git-gui: Allow staging multiple lines at once","fromName":"Heiko Voigt","fromEmail":"hvoigt@hvoigt.net","sentAt":"2009-10-22T19:51:16Z","receivedAt":"2009-10-22T19:51:16Z","isPatch":true,"sender":{"key":"hvoigt@hvoigt.net","avatar":"https://avatars.githubusercontent.com/u/184958?v=4"},"body":"On Mon, Oct 19, 2009 at 02:54:57PM -0500, Jeff Epler wrote:\n> When applying less than a full hunk, it's still often desirable to apply\n> a number of consecutive lines.\n> \n> This change makes it possible to sweep out a range of lines in the diff view\n> with the left mouse button, then right click and \"Stage Lines For Commit\".\n> \n> The selected lines may span multiple hunks.\n\nThanks a lot! Very nice. I've been planning to implement this sometime\nmyself but never got around it.\n\nTested it and it works like a charm. I will include Shawn in the CC: so\nhe can comment.\n\ncheers Heiko\n"},{"id":"125762","messageId":"20091023015315.GB10347@unpythonic.net","threadId":"21284","inReplyTo":"20091022195116.GB3944@book.hvoigt.net","subject":"Re: [RFC PATCH] git-gui: Allow staging multiple lines at once","fromName":"Jeff Epler","fromEmail":"jepler@unpythonic.net","sentAt":"2009-10-23T01:53:15Z","receivedAt":"2009-10-23T01:53:15Z","isPatch":true,"sender":{"key":"jepler@unpythonic.net","avatar":"https://avatars.githubusercontent.com/u/1517291?v=4"},"body":"On Thu, Oct 22, 2009 at 09:51:16PM +0200, Heiko Voigt wrote:\n> Tested it and it works like a charm. I will include Shawn in the CC: so\n> he can comment.\n\nThanks, but don't miss the v2 patch:\nhttp://thread.gmane.org/gmane.comp.version-control.git/130968\nthis fixes a bug staging line(s) followed only by deletions.\n\nJeff\n"}]}