From: Paul Mackerras <paulus@samba.org>
Date: Mon, 27 Oct 2008 10:36:25 +0000 (+1100)
Subject: gitk: Add a menu item to show where a given line comes from
X-Git-Url: http://test.brassandglass.co.uk/gitweb?a=commitdiff_plain;h=8a8977425e2697029414c3bcf4b627b074934bbc;p=gitk

gitk: Add a menu item to show where a given line comes from

This adds a menu item to the pop-up menu for the diff display window
which makes gitk find which commit added the line (via git blame)
and show that commit, with the line highlighted with a light-blue
background.

Signed-off-by: Paul Mackerras <paulus@samba.org>
---

diff --git a/gitk b/gitk
index 477590e..7b02efb 100755
--- a/gitk
+++ b/gitk
@@ -2296,6 +2296,7 @@ proc makewindow {} {
     global diff_menu
     set diff_menu .diffctxmenu
     makemenu $diff_menu {
+	{mc "Show origin of this line" command show_line_source}
 	{mc "Run git gui blame on this line" command {external_blame_diff}}
     }
     $diff_menu configure -tearoff 0
@@ -2830,9 +2831,15 @@ proc treeclick {w x y} {
 }
 
 proc setfilelist {id} {
-    global treefilelist cflist
+    global treefilelist cflist jump_to_here
 
     treeview $cflist $treefilelist($id) 0
+    if {$jump_to_here ne {}} {
+	set f [lindex $jump_to_here 0]
+	if {[lsearch -exact $treefilelist($id) $f] >= 0} {
+	    showfile $f
+	}
+    }
 }
 
 image create bitmap tri-rt -background black -foreground blue -data {
@@ -3256,6 +3263,91 @@ proc external_blame {parent_idx {line {}}} {
     }
 }
 
+proc show_line_source {} {
+    global cmitmode currentid parents curview blamestuff blameinst
+    global diff_menu_line diff_menu_filebase flist_menu_file
+
+    if {$cmitmode eq "tree"} {
+	set id $currentid
+	set line [expr {$diff_menu_line - $diff_menu_filebase}]
+    } else {
+	set h [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
+	if {$h eq {}} return
+	set pi [lindex $h 0]
+	if {$pi == 0} {
+	    mark_ctext_line $diff_menu_line
+	    return
+	}
+	set id [lindex $parents($curview,$currentid) [expr {$pi - 1}]]
+	set line [lindex $h 1]
+    }
+    if {[catch {
+	set f [open [list | git blame -p -L$line,+1 $id -- $flist_menu_file] r]
+    } err]} {
+	error_popup [mc "Couldn't start git blame: %s" $err]
+	return
+    }
+    fconfigure $f -blocking 0
+    set i [reg_instance $f]
+    set blamestuff($i) {}
+    set blameinst $i
+    filerun $f [list read_line_source $f $i]
+}
+
+proc stopblaming {} {
+    global blameinst
+
+    if {[info exists blameinst]} {
+	stop_instance $blameinst
+	unset blameinst
+    }
+}
+
+proc read_line_source {fd inst} {
+    global blamestuff curview commfd blameinst
+
+    while {[gets $fd line] >= 0} {
+	lappend blamestuff($inst) $line
+    }
+    if {![eof $fd]} {
+	return 1
+    }
+    unset commfd($inst)
+    unset blameinst
+    fconfigure $fd -blocking 1
+    if {[catch {close $fd} err]} {
+	error_popup [mc "Error running git blame: %s" $err]
+	return 0
+    }
+
+    set fname {}
+    set line [split [lindex $blamestuff($inst) 0] " "]
+    set id [lindex $line 0]
+    set lnum [lindex $line 1]
+    if {[string length $id] == 40 && [string is xdigit $id] &&
+	[string is digit -strict $lnum]} {
+	# look for "filename" line
+	foreach l $blamestuff($inst) {
+	    if {[string match "filename *" $l]} {
+		set fname [string range $l 9 end]
+		break
+	    }
+	}
+    }
+    if {$fname ne {}} {
+	# all looks good, select it
+	if {[commitinview $id $curview]} {
+	    selectline [rowofcommit $id] 1 [list $fname $lnum]
+	} else {
+	    error_popup [mc "That line comes from commit %s, \
+			     which is not in this view" [shortids $id]]
+	}
+    } else {
+	puts "oops couldn't parse git blame output"
+    }
+    return 0
+}
+
 # delete $dir when we see eof on $f (presumably because the child has exited)
 proc delete_at_eof {f dir} {
     while {[gets $f line] >= 0} {}
@@ -5748,6 +5840,7 @@ proc stopfinding {} {
 	set fprogcoord 0
 	adjustprogress
     }
+    stopblaming
 }
 
 proc findmore {} {
@@ -6152,7 +6245,7 @@ proc make_secsel {l} {
     $canv3 lower $t
 }
 
-proc selectline {l isnew} {
+proc selectline {l isnew {desired_loc {}}} {
     global canv ctext commitinfo selectedline
     global canvy0 linespc parents children curview
     global currentid sha1entry
@@ -6160,7 +6253,7 @@ proc selectline {l isnew} {
     global mergemax numcommits pending_select
     global cmitmode showneartags allcommits
     global targetrow targetid lastscrollrows
-    global autoselect
+    global autoselect jump_to_here
 
     catch {unset pending_select}
     $canv delete hover
@@ -6299,6 +6392,7 @@ proc selectline {l isnew} {
     $ctext conf -state disabled
     set commentend [$ctext index "end - 1c"]
 
+    set jump_to_here $desired_loc
     init_flist [mc "Comments"]
     if {$cmitmode eq "tree"} {
 	gettree $id
@@ -6546,15 +6640,32 @@ proc getblobline {bf id} {
 	$ctext insert end "$line\n"
     }
     if {[eof $bf]} {
+	global jump_to_here ctext_file_names commentend
+
 	# delete last newline
 	$ctext delete "end - 2c" "end - 1c"
 	close $bf
+	if {$jump_to_here ne {} &&
+	    [lindex $jump_to_here 0] eq [lindex $ctext_file_names 0]} {
+	    set lnum [expr {[lindex $jump_to_here 1] +
+			    [lindex [split $commentend .] 0]}]
+	    mark_ctext_line $lnum
+	}
 	return 0
     }
     $ctext config -state disabled
     return [expr {$nl >= 1000? 2: 1}]
 }
 
+proc mark_ctext_line {lnum} {
+    global ctext
+
+    $ctext tag delete omark
+    $ctext tag add omark $lnum.0 "$lnum.0 + 1 line"
+    $ctext tag conf omark -background "#e0e0ff"
+    $ctext see $lnum.0
+}
+
 proc mergediff {id} {
     global diffmergeid mdifffd
     global diffids treediffs
@@ -6562,10 +6673,12 @@ proc mergediff {id} {
     global diffcontext
     global diffencoding
     global limitdiffs vfilelimit curview
+    global targetline
 
     set diffmergeid $id
     set diffids $id
     set treediffs($id) {}
+    set targetline {}
     # this doesn't seem to actually affect anything...
     set cmd [concat | git diff-tree --no-commit-id --cc -U$diffcontext $id]
     if {$limitdiffs && $vfilelimit($curview) ne {}} {
@@ -6587,7 +6700,7 @@ proc getmergediffline {mdf id np} {
     global diffmergeid ctext cflist mergemax
     global difffilestart mdifffd treediffs
     global ctext_file_names ctext_file_lines
-    global diffencoding
+    global diffencoding jump_to_here targetline diffline
 
     $ctext conf -state normal
     set nr 0
@@ -6611,9 +6724,17 @@ proc getmergediffline {mdf id np} {
 	    set l [expr {(78 - [string length $fname]) / 2}]
 	    set pad [string range "----------------------------------------" 1 $l]
 	    $ctext insert end "$pad $fname $pad\n" filesep
+	    set targetline {}
+	    if {$jump_to_here ne {} && [lindex $jump_to_here 0] eq $fname} {
+		set targetline [lindex $jump_to_here 1]
+	    }
+	    set diffline 0
 	} elseif {[regexp {^@@} $line]} {
 	    set line [encoding convertfrom $diffencoding $line]
 	    $ctext insert end "$line\n" hunksep
+	    if {[regexp { \+(\d+),\d+ @@} $line m nl]} {
+		set diffline $nl
+	    }
 	} elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
 	    # do nothing
 	} else {
@@ -6653,6 +6774,15 @@ proc getmergediffline {mdf id np} {
 		lappend tags m$num
 	    }
 	    $ctext insert end "$line\n" $tags
+	    if {$targetline ne {} && $minuses eq {}} {
+		if {$diffline == $targetline} {
+		    set here [$ctext index "end - 1 line"]
+		    mark_ctext_line [lindex [split $here .] 0]
+		    set targetline {}
+		} else {
+		    incr diffline
+		}
+	    }
 	}
     }
     $ctext conf -state disabled
@@ -6840,7 +6970,7 @@ proc getblobdiffs {ids} {
     global diffcontext
     global ignorespace
     global limitdiffs vfilelimit curview
-    global diffencoding
+    global diffencoding targetline
 
     set cmd [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"]
     if {$ignorespace} {
@@ -6853,6 +6983,7 @@ proc getblobdiffs {ids} {
 	puts "error getting diffs: $err"
 	return
     }
+    set targetline {}
     set diffinhdr 0
     set diffencoding [get_path_encoding {}]
     fconfigure $bdf -blocking 0 -encoding binary
@@ -6875,7 +7006,7 @@ proc setinlist {var i val} {
 
 proc makediffhdr {fname ids} {
     global ctext curdiffstart treediffs
-    global ctext_file_names
+    global ctext_file_names jump_to_here targetline diffline
 
     set i [lsearch -exact $treediffs($ids) $fname]
     if {$i >= 0} {
@@ -6885,6 +7016,11 @@ proc makediffhdr {fname ids} {
     set l [expr {(78 - [string length $fname]) / 2}]
     set pad [string range "----------------------------------------" 1 $l]
     $ctext insert $curdiffstart "$pad $fname $pad" filesep
+    set targetline {}
+    if {$jump_to_here ne {} && [lindex $jump_to_here 0] eq $fname} {
+	set targetline [lindex $jump_to_here 1]
+    }
+    set diffline 0
 }
 
 proc getblobdiffline {bdf ids} {
@@ -6892,7 +7028,7 @@ proc getblobdiffline {bdf ids} {
     global diffnexthead diffnextnote difffilestart
     global ctext_file_names ctext_file_lines
     global diffinhdr treediffs
-    global diffencoding
+    global diffencoding jump_to_here targetline diffline
 
     set nr 0
     $ctext conf -state normal
@@ -6941,6 +7077,7 @@ proc getblobdiffline {bdf ids} {
 	    set line [encoding convertfrom $diffencoding $line]
 	    $ctext insert end "$line\n" hunksep
 	    set diffinhdr 0
+	    set diffline $f2l
 
 	} elseif {$diffinhdr} {
 	    if {![string compare -length 12 "rename from " $line]} {
@@ -6974,6 +7111,7 @@ proc getblobdiffline {bdf ids} {
 	} else {
 	    set line [encoding convertfrom $diffencoding $line]
 	    set x [string range $line 0 0]
+	    set here [$ctext index "end - 1 chars"]
 	    if {$x == "-" || $x == "+"} {
 		set tag [expr {$x == "+"}]
 		$ctext insert end "$line\n" d$tag
@@ -6984,6 +7122,14 @@ proc getblobdiffline {bdf ids} {
 		# or something else we don't recognize
 		$ctext insert end "$line\n" hunksep
 	    }
+	    if {$targetline ne {} && ($x eq " " || $x eq "+")} {
+		if {$diffline == $targetline} {
+		    mark_ctext_line [lindex [split $here .] 0]
+		    set targetline {}
+		} else {
+		    incr diffline
+		}
+	    }
 	}
     }
     $ctext conf -state disabled