threads / patch / 9230

patchTeach git-gui to split hunks

Subject: [PATCH] Teach git-gui to split hunks

## tl;dr

6 messages between Jul 26, 2007 and Jul 26, 2007. Diffs are folded; open one to read it.

replies: 5people: 4as markdown or json

Johannes Schindelin· Jul 26, 2007, 05:32 UTC · lore

When you select the context menu item "Split Hunk" in the diff area, git-gui will now split the current hunk so that a new hunk starts at the current position.

For this to work, apply has to be called with --unidiff-zero, since the new hunks can start or stop with a "-" or "+" line.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
	No more complaining from me about the lack of that feature.
	And I did not even need a C compiler to do that.
 git-gui.sh   |    4 +++
 lib/diff.tcl |   75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 78 insertions(+), 1 deletions(-)
Show changes to 2 files +78 −1

git-gui.sh, lib/diff.tcl

diff --git a/git-gui.sh b/git-gui.sh
index a38293a..a0f7617 100755
--- a/git-gui.sh
+++ b/git-gui.sh
@@ -2400,6 +2400,10 @@ $ctxm add command \
 	-command {apply_hunk $cursorX $cursorY}
 set ui_diff_applyhunk [$ctxm index last]
 lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
+$ctxm add command \
+	-label [mc "Split Hunk"] \
+	-command {split_hunk $cursorX $cursorY}
+lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
 $ctxm add separator
 $ctxm add command \
 	-label {Decrease Font Size} \
diff --git a/lib/diff.tcl b/lib/diff.tcl
index e09e125..88ec8f3 100644
--- a/lib/diff.tcl
+++ b/lib/diff.tcl
@@ -265,6 +265,79 @@ proc read_diff {fd} {
 	}
 }
 
+proc split_hunk {x y} {
+	global current_diff_path current_diff_header current_diff_side
+	global ui_diff ui_index file_states
+
+	if {$current_diff_path eq {} || $current_diff_header eq {}} return
+	if {![lock_index apply_hunk]} return
+
+	set c_lno [lindex [split [$ui_diff index @$x,$y] .] 0]
+	set s_idx [$ui_diff search -backwards -regexp ^@@ $c_lno.0 0.0]
+	if {$s_idx eq {} || $s_idx >= [expr $c_lno - 1]} {
+		unlock_index
+		return
+	}
+	set s_lno [lindex [split $s_idx .] 0]
+
+	# the first hunk will look like this: @@ -$m1,$m2 +$p1,$p2 @@
+	# the second hunk will look like this: @@ -$m3,$m4 +$p3,$p4 @@
+
+	# get original hunk numbers
+	set hunk_line [$ui_diff get $s_idx "$s_idx lineend"]
+	set re "@@ +-(\[0-9\]+)(,(\[0-9\]+))? +\\+(\[0-9\]+)(,(\[0-9\]+))? *@@"
+	if {![regexp $re $hunk_line dummy m1 dummy m4 p1 dummy p4] ||
+			$m1 == 0 || $p1 == 0} { # create/delete file
+		unlock_index
+		return
+	}
+	if {$m4 == ""} {
+		set m4 1
+	}
+	if {$p4 == ""} {
+		set p4 1
+	}
+
+	# count changes
+	set m2 0
+	set p2 0
+	for {set l [expr $s_lno + 1]} {$l < $c_lno} {incr l} {
+		switch -exact -- [$ui_diff get $l.0] {
+			" " {
+				incr m2
+				incr p2
+			}
+			"+" {
+				incr p2
+			}
+			"-" {
+				incr m2
+			}
+		}
+	}
+
+	# We could check if {$m2 == $p2 && $m2 == [expr $c_lno - $s_lno]}
+	# and just remove the hunk.  But let's not be too clever here.
+
+	set m3 [expr $m1 + $m2]
+	set m4 [expr $m4 - $m2]
+	set p3 [expr $p1 + $p2]
+	set p4 [expr $p4 - $p2]
+
+	if {$m4 == 0 && $p4 == 0} {
+		index_unlock
+		return
+	}
+
+	$ui_diff configure -state normal
+	$ui_diff delete $s_idx "$s_idx lineend"
+	$ui_diff insert $s_idx "@@ -$m1,$m2 +$p1,$p2 @@" d_@
+	$ui_diff insert $c_lno.0 "@@ -$m3,$m4 +$p3,$p4 @@\n" d_@
+	$ui_diff configure -state disabled
+
+	unlock_index
+}
+
 proc apply_hunk {x y} {
 	global current_diff_path current_diff_header current_diff_side
 	global ui_diff ui_index file_states
@@ -272,7 +345,7 @@ proc apply_hunk {x y} {
 	if {$current_diff_path eq {} || $current_diff_header eq {}} return
 	if {![lock_index apply_hunk]} return
 
-	set apply_cmd {apply --cached --whitespace=nowarn}
+	set apply_cmd {apply --cached --whitespace=nowarn --unidiff-zero}
 	set mi [lindex $file_states($current_diff_path) 0]
 	if {$current_diff_side eq $ui_index} {
 		set mode unstage
-- 
1.5.3.rc2.42.gda8d-dirty
David Kastrup· Jul 26, 2007, 05:48 UTC · re: Johannes Schindelin · lore

Re: [PATCH] Teach git-gui to split hunks

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
Show 6 quoted lines
> When you select the context menu item "Split Hunk" in the diff area,
> git-gui will now split the current hunk so that a new hunk starts at
> the current position.
>
> For this to work, apply has to be called with --unidiff-zero, since
> the new hunks can start or stop with a "-" or "+" line.

Unless one splits right at the beginning or end of an existing hunk, wouldn't there be context which one could use? Or does it confuse patch when adjacent hunks have overlapping contexts? At least if the first hunk patches what is to be used as context in the second hunk, I could imagine this. And there is really no danger of losing synch in this situation, anyhow. So it would be more of a convenience thing than anything else to be able to omit --unidiff-zero.

-- 
David Kastrup, Kriemhildstr. 15, 44793 Bochum
Shawn O. Pearce· Jul 26, 2007, 07:07 UTC · re: David Kastrup · lore

Re: [PATCH] Teach git-gui to split hunks

David Kastrup <dak@gnu.org> wrote:
Show 16 quoted lines
> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> > When you select the context menu item "Split Hunk" in the diff area,
> > git-gui will now split the current hunk so that a new hunk starts at
> > the current position.
> >
> > For this to work, apply has to be called with --unidiff-zero, since
> > the new hunks can start or stop with a "-" or "+" line.
> 
> Unless one splits right at the beginning or end of an existing hunk,
> wouldn't there be context which one could use?  Or does it confuse
> patch when adjacent hunks have overlapping contexts?  At least if the
> first hunk patches what is to be used as context in the second hunk, I
> could imagine this.  And there is really no danger of losing synch in
> this situation, anyhow.  So it would be more of a convenience thing
> than anything else to be able to omit --unidiff-zero.

Yea, there's context there. Junio and I talked about this patch on #git a few minutes ago. I really appreciate that Dscho wrote it, especially given that he hasn't really been into Tcl hacking for Git much before. But I'd really like to save/create context, like `git add -i` does, so that we don't have to use --unidiff-zero here.

It won't matter if git-apply rejected overlapping context in this case. git-gui will only ever feed one hunk at a time to git-apply. And if things get really f'd in the diff buffer the user can easily regenerate it (right click, Refresh).

Right now git-gui's apply doesn't correctly update the other hunk headers when you apply a hunk. I've seen git-apply fail on some hunks just for this reason. Refreshing the diff (so git recomputes the headers) works around the issue. So I'm a little worried about using --unidiff-zero here.

-- 
Shawn.
Johannes Schindelin· Jul 26, 2007, 14:34 UTC · re: Shawn O. Pearce · lore

Re: [PATCH] Teach git-gui to split hunks

Hi,
On Thu, 26 Jul 2007, Shawn O. Pearce wrote:
Show 5 quoted lines
> [talk about the --unidiff-zero stuff]
> 
> Yea, there's context there.  Junio and I talked about this patch on #git 
> a few minutes ago.  I really appreciate that Dscho wrote it, especially 
> given that he hasn't really been into Tcl hacking for Git much before.

Heh. I did my share of Tcl hacking. In one (closed source) project, I was in constant awe why the devs chose Tcl as a scripting language, but Qt as windowing library (back when it was pretty expensive for a startup to use Qt).

> But I'd really like to save/create context, like `git add -i` does, so 
> that we don't have to use --unidiff-zero here.
See below.
> It won't matter if git-apply rejected overlapping context in this case.  
> git-gui will only ever feed one hunk at a time to git-apply. And if 
> things get really f'd in the diff buffer the user can easily regenerate 
> it (right click, Refresh).

But you're right, the other hunk headers should be updated. I'll have a look at it this weekend.

Show 5 quoted lines
> Right now git-gui's apply doesn't correctly update the other hunk 
> headers when you apply a hunk.  I've seen git-apply fail on some hunks 
> just for this reason.  Refreshing the diff (so git recomputes the 
> headers) works around the issue.  So I'm a little worried about using 
> --unidiff-zero here.

Okay, but just using more context means that you also have to _update_ the context of another hunk. Imagine this:

Show changes to diff +1 −1
--- a/x
+++ a/x
 one line
-a removed one
+an added one
 another line

Now you split between the "-" and "+" line.  If you stage the first hunk, 
not only do you have to update the hunk header of the second hunk (now the 
only one shown), which you already hinted should be done; you _also_ have 
to update the context in the second hunk, since it changed.

This is just tricky enough that I am tempted to have a go at it.  Not 
today, though, since I have other work to do, and the issues are easier to 
work around for the moment than to work around the lack of "Split Hunk" 
for me.

BTW did you think about any kind of integrity checking, i.e. see if the 
files involved still have the same hashes as when the diff was generated?  
It _is_ possible to use git-gui _and_ the command line...

Ciao,
Dscho
Johannes Sixt· Jul 26, 2007, 07:32 UTC · re: Johannes Schindelin · lore

Re: [PATCH] Teach git-gui to split hunks

Johannes Schindelin wrote:
Show 7 quoted lines
> 
> When you select the context menu item "Split Hunk" in the diff area,
> git-gui will now split the current hunk so that a new hunk starts at
> the current position.
> 
> For this to work, apply has to be called with --unidiff-zero, since
> the new hunks can start or stop with a "-" or "+" line.
For chrissake, NO!
I tried this already, and it immediately corrupted my data.

The problem case is when the hunk you want to apply is not the first one and the first one does not add and remove the same number of lines. In this case, all that git-apply can do is to rely on line numbers. But they are WRONG and apply the patch at the WRONG spot.

First, I didn't believe Linus when he preached that --unidiff-zero is bad; it took only a day to become a follower. ;)

-- Hannes
Johannes Schindelin· Jul 26, 2007, 14:26 UTC · re: Johannes Sixt · lore

Re: [PATCH] Teach git-gui to split hunks

Hi,
On Thu, 26 Jul 2007, Johannes Sixt wrote:
Show 20 quoted lines
> Johannes Schindelin wrote:
> > 
> > When you select the context menu item "Split Hunk" in the diff area,
> > git-gui will now split the current hunk so that a new hunk starts at
> > the current position.
> > 
> > For this to work, apply has to be called with --unidiff-zero, since
> > the new hunks can start or stop with a "-" or "+" line.
> 
> For chrissake, NO!
> 
> I tried this already, and it immediately corrupted my data.
> 
> The problem case is when the hunk you want to apply is not the first one
> and the first one does not add and remove the same number of lines. In
> this case, all that git-apply can do is to rely on line numbers. But
> they are WRONG and apply the patch at the WRONG spot.
> 
> First, I didn't believe Linus when he preached that --unidiff-zero is
> bad; it took only a day to become a follower. ;)

Okay, convinced. But I have some issues there, which I will outline in the reply to Shawn.

Ciao, Dscho

← back to recent threads