Anonymous avatar Anonymous committed 172c92b

git-gui: Run blame twice on the same file and display both outputs

We now perform two passes over any input file given to the blame
viewer. Our first pass is a quick "git-blame" with no options,
getting the details of how each line arrived into this file. We
are specifically ignoring/omitting the rename detection logic as
this first pass is to determine why things got into the state they
are in.

Once the first pass is complete and is displayed in the UI we run
a second pass, using the much more CPU intensive "-M -C -C" options
to perform extensive rename/movement detection. The output of this
second pass is shown in a different column, allowing the user to see
for any given line how it got to be, and if it came from somewhere
else, where that is.

This is actually very instructive when run on our own lib/branch.tcl
script. That file grew recently out of a very large block of code
in git-gui.sh. The first pass shows when I created that file, while
the second pass shows the original commit information.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>;

Comments (0)

Files changed (1)

 field w_columns  ; # list of all column widgets in the viewer
 field w_line     ; # text column: all line numbers
 field w_amov     ; # text column: annotations + move tracking
+field w_asim     ; # text column: annotations (simple computation)
 field w_file     ; # text column: actual file data
 field w_cviewer  ; # pane showing commit message
 field status     ; # text variable bound to status bar
 
 field current_fd        {} ; # background process running
 field highlight_line    -1 ; # current line selected
+field highlight_column  {} ; # current commit column selected
 field highlight_commit  {} ; # sha1 of commit selected
 field old_bgcolor       {} ; # background of current selection
 
 field blame_lines       0  ; # number of lines computed
 field have_commit          ; # array commit -> 1
 field amov_data            ; # list of {commit origfile origline}
+field asim_data            ; # list of {commit origfile origline}
 
 field r_commit             ; # commit currently being parsed
 field r_orig_line          ; # original line number
 		-state disabled \
 		-wrap none \
 		-height 40 \
-		-width 4 \
+		-width 5 \
 		-font font_diff
+	$w_amov tag conf author_abbr -justify right -rmargin 5
 	$w_amov tag conf curr_commit
-	$w_amov tag conf prior_commit \
-		-foreground blue \
-		-underline 1
+	$w_amov tag conf prior_commit -foreground blue -underline 1
 	$w_amov tag bind prior_commit \
 		<Button-1> \
-		"[cb _load_commit @%x,%y];break"
+		"[cb _load_commit $w_amov @amov_data @%x,%y];break"
+
+	set w_asim $w.file_pane.out.asimple_t
+	text $w_asim \
+		-takefocus 0 \
+		-highlightthickness 0 \
+		-padx 0 -pady 0 \
+		-background white -borderwidth 0 \
+		-state disabled \
+		-wrap none \
+		-height 40 \
+		-width 4 \
+		-font font_diff
+	$w_asim tag conf author_abbr -justify right
+	$w_asim tag conf curr_commit
+	$w_asim tag conf prior_commit -foreground blue -underline 1
+	$w_asim tag bind prior_commit \
+		<Button-1> \
+		"[cb _load_commit $w_asim @asim_data @%x,%y];break"
 
 	set w_file $w.file_pane.out.file_t
 	text $w_file \
 		-xscrollcommand [list $w.file_pane.out.sbx set] \
 		-font font_diff
 
-	set w_columns [list $w_amov $w_line $w_file]
+	set w_columns [list $w_amov $w_asim $w_line $w_file]
 
 	scrollbar $w.file_pane.out.sbx \
 		-orient h \
 		}
 
 		set highlight_line -1
+		set highlight_column {}
 		set highlight_commit {}
 		set total_lines 0
-		set blame_lines 0
 		array unset have_commit
 	}
 
 	# git-blame output and with Tk's text widget.
 	#
 	set amov_data [list [list]]
+	set asim_data [list [list]]
 
 	set status "Loading $commit:[escape_path $path]..."
 	$w_path conf -text [escape_path $path]
 		regsub "\r\$" $line {} line
 		incr total_lines
 		lappend amov_data {}
+		lappend asim_data {}
 
 		if {$total_lines > 1} {
 			foreach i $w_columns {$i insert end "\n"}
 
 	if {[eof $fd]} {
 		close $fd
-
-		_status $this
-		set cmd {nice git blame -M -C --incremental}
-		if {$commit eq {}} {
-			lappend cmd --contents $path
-		} else {
-			lappend cmd $commit
-		}
-		lappend cmd -- $path
-		set fd [open "| $cmd" r]
-		fconfigure $fd -blocking 0 -translation lf -encoding binary
-		fileevent $fd readable [cb _read_blame $fd]
-		set current_fd $fd
+		_exec_blame $this $w_asim @asim_data [list] {}
 	}
 } ifdeleted { catch {close $fd} }
 
-method _read_blame {fd} {
+method _exec_blame {cur_w cur_d options cur_s} {
+	set cmd [list nice git blame]
+	set cmd [concat $cmd $options]
+	lappend cmd --incremental
+	if {$commit eq {}} {
+		lappend cmd --contents $path
+	} else {
+		lappend cmd $commit
+	}
+	lappend cmd -- $path
+	set fd [open "| $cmd" r]
+	fconfigure $fd -blocking 0 -translation lf -encoding binary
+	fileevent $fd readable [cb _read_blame $fd $cur_w $cur_d $cur_s]
+	set current_fd $fd
+	set blame_lines 0
+	_status $this $cur_s
+}
+
+method _read_blame {fd cur_w cur_d cur_s} {
+	upvar #0 $cur_d line_data
+
 	if {$fd ne $current_fd} {
 		catch {close $fd}
 		return
 	}
 
-	$w_amov conf -state normal
+	$cur_w conf -state normal
 	while {[gets $fd line] >= 0} {
 		if {[regexp {^([a-z0-9]{40}) (\d+) (\d+) (\d+)$} $line line \
 			cmit original_line final_line line_count]} {
 				set commit_type curr_commit
 			} else {
 				set commit_type prior_commit
-				set commit_abbr [string range $cmit 0 4]
+				set commit_abbr [string range $cmit 0 3]
 			}
 
 			set author_abbr {}
 				set author_abbr { |}
 			} else {
 				set author_abbr [string range $author_abbr 0 3]
-				while {[string length $author_abbr] < 4} {
-					set author_abbr " $author_abbr"
-				}
 			}
 			unset a_name
 
 			set first_lno $lno
 			while {
 			   $first_lno > 1
-			&& $cmit eq [lindex $amov_data [expr {$first_lno - 1}] 0]
-			&& $file eq [lindex $amov_data [expr {$first_lno - 1}] 1]
+			&& $cmit eq [lindex $line_data [expr {$first_lno - 1}] 0]
+			&& $file eq [lindex $line_data [expr {$first_lno - 1}] 1]
 			} {
 				incr first_lno -1
 			}
 
 			while {$n > 0} {
 				set lno_e "$lno.0 lineend + 1c"
-				if {[lindex $amov_data $lno] ne {}} {
-					set g [lindex $amov_data $lno 0]
+				if {[lindex $line_data $lno] ne {}} {
+					set g [lindex $line_data $lno 0]
 					foreach i $w_columns {
 						$i tag remove g$g $lno.0 $lno_e
 					}
 				}
-				lset amov_data $lno [list $cmit $file]
+				lset line_data $lno [list $cmit $file]
 
-				$w_amov delete $lno.0 "$lno.0 lineend"
+				$cur_w delete $lno.0 "$lno.0 lineend"
 				if {$lno == $first_lno} {
-					$w_amov insert $lno.0 $commit_abbr $commit_type
+					$cur_w insert $lno.0 $commit_abbr $commit_type
 				} elseif {$lno == [expr {$first_lno + 1}]} {
-					$w_amov insert $lno.0 $author_abbr
+					$cur_w insert $lno.0 $author_abbr author_abbr
 				} else {
-					$w_amov insert $lno.0 { |}
+					$cur_w insert $lno.0 { |}
 				}
 
 				foreach i $w_columns {
 					$i tag add g$cmit $lno.0 $lno_e
 				}
 
-				if {$highlight_line == -1} {
-					if {[lindex [$w_file yview] 0] == 0} {
+				if {$highlight_column eq $cur_w} {
+					if {$highlight_line == -1
+					 && [lindex [$w_file yview] 0] == 0} {
 						$w_file see $lno.0
-						_showcommit $this $lno
+						set highlight_line $lno
+					}
+					if {$highlight_line == $lno} {
+						_showcommit $this $cur_w $lno
 					}
-				} elseif {$highlight_line == $lno} {
-					_showcommit $this $lno
 				}
 
 				incr n -1
 			}
 
 			while {
-			   $cmit eq [lindex $amov_data $lno 0]
-			&& $file eq [lindex $amov_data $lno 1]
+			   $cmit eq [lindex $line_data $lno 0]
+			&& $file eq [lindex $line_data $lno 1]
 			} {
-				$w_amov delete $lno.0 "$lno.0 lineend"
+				$cur_w delete $lno.0 "$lno.0 lineend"
 
 				if {$lno == $first_lno} {
-					$w_amov insert $lno.0 $commit_abbr $commit_type
+					$cur_w insert $lno.0 $commit_abbr $commit_type
 				} elseif {$lno == [expr {$first_lno + 1}]} {
-					$w_amov insert $lno.0 $author_abbr
+					$cur_w insert $lno.0 $author_abbr author_abbr
 				} else {
-					$w_amov insert $lno.0 { |}
+					$cur_w insert $lno.0 { |}
 				}
 				incr lno
 			}
 			set header($r_commit,$key) $data
 		}
 	}
-	$w_amov conf -state disabled
+	$cur_w conf -state disabled
 
 	if {[eof $fd]} {
 		close $fd
-		set current_fd {}
-		set status {Annotation complete.}
-		destroy $w.status.c
+		if {$cur_w eq $w_asim} {
+			_exec_blame $this $w_amov @amov_data \
+				[list -M -C -C] \
+				{ move/copy tracking}
+		} else {
+			set current_fd {}
+			set status {Annotation complete.}
+			destroy $w.status.c
+		}
 	} else {
-		_status $this
+		_status $this $cur_s
 	}
 } ifdeleted { catch {close $fd} }
 
-method _status {} {
+method _status {cur_s} {
 	set have  $blame_lines
 	set total $total_lines
 	set pdone 0
 	if {$total} {set pdone [expr {100 * $have / $total}]}
 
 	set status [format \
-		"Loading annotations... %i of %i lines annotated (%2i%%)" \
-		$have $total $pdone]
+		"Loading%s annotations... %i of %i lines annotated (%2i%%)" \
+		$cur_s $have $total $pdone]
 	$w.status.c coords bar 0 0 $pdone 20
 }
 
 method _click {cur_w pos} {
 	set lno [lindex [split [$cur_w index $pos] .] 0]
-	if {$lno eq {}} return
-	_showcommit $this $lno
+	_showcommit $this $cur_w $lno
 }
 
-method _load_commit {pos} {
-	set lno [lindex [split [$w_amov index $pos] .] 0]
-	set dat [lindex $amov_data $lno]
+method _load_commit {cur_w cur_d pos} {
+	upvar #0 $cur_d line_data
+	set lno [lindex [split [$cur_w index $pos] .] 0]
+	set dat [lindex $line_data $lno]
 	if {$dat ne {}} {
 		set commit [lindex $dat 0]
 		set path   [lindex $dat 1]
 	}
 }
 
-method _showcommit {lno} {
+method _showcommit {cur_w lno} {
 	global repo_config
 
 	if {$highlight_commit ne {}} {
 		}
 	}
 
+	if {$cur_w eq $w_amov} {
+		set dat [lindex $amov_data $lno]
+		set highlight_column $w_amov
+	} else {
+		set dat [lindex $asim_data $lno]
+		set highlight_column $w_asim
+	}
+
 	$w_cviewer conf -state normal
 	$w_cviewer delete 0.0 end
 
-	set dat [lindex $amov_data $lno]
 	if {$dat eq {}} {
 		set cmit {}
 		$w_cviewer insert end "Loading annotation..." still_loading
 
 method _show_tooltip {cur_w pos} {
 	set lno [lindex [split [$cur_w index $pos] .] 0]
-	set dat [lindex $amov_data $lno]
+	if {$cur_w eq $w_amov} {
+		set dat [lindex $amov_data $lno]
+	} else {
+		set dat [lindex $asim_data $lno]
+	}
 	if {$dat eq {}} {
 		_hide_tooltip $this
 		return
 		[expr {$pos_x - [winfo rootx $cur_w]}] \
 		[expr {$pos_y - [winfo rooty $cur_w]}]] ,]
 	set lno [lindex [split [$cur_w index $pos] .] 0]
-	set dat [lindex $amov_data $lno]
+	if {$cur_w eq $w_amov} {
+		set dat [lindex $amov_data $lno]
+	} else {
+		set dat [lindex $asim_data $lno]
+	}
 	set cmit [lindex $dat 0]
 	set file [lindex $dat 1]
 
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.