X-Git-Url: https://asedeno.scripts.mit.edu/gitweb/?a=blobdiff_plain;f=lib%2Fblame.tcl;h=33596cd892ab8065fa1282bf0e6c723ac3ae5a01;hb=37ebc93f6dddb6d73034f027873266eef1e3e774;hp=c276fa9852aeb3b383a62da31a805e79537c78c6;hpb=f522c9b5ed367172f969397589ae3d686b867ac0;p=git.git diff --git a/lib/blame.tcl b/lib/blame.tcl index c276fa985..33596cd89 100644 --- a/lib/blame.tcl +++ b/lib/blame.tcl @@ -1,18 +1,51 @@ # git-gui blame viewer # Copyright (C) 2006, 2007 Shawn Pearce -proc show_blame {commit path} { - global next_browser_id blame_status blame_data +class blame { + +field commit ; # input commit to blame +field path ; # input filename to view in $commit + +field w +field w_line +field w_cgrp +field w_load +field w_file +field w_cmit +field status + +field highlight_line -1 ; # current line selected +field highlight_commit {} ; # sha1 of commit selected + +field total_lines 0 ; # total length of file +field blame_lines 0 ; # number of lines computed +field commit_count 0 ; # number of commits in $commit_list +field commit_list {} ; # list of commit sha1 in receipt order +field order ; # array commit -> receipt order +field header ; # array commit,key -> header field +field line_commit ; # array line -> sha1 commit +field line_file ; # array line -> file name + +field r_commit ; # commit currently being parsed +field r_orig_line ; # original line number +field r_final_line ; # final line number +field r_line_count ; # lines in this region + +variable active_color #98e1a0 +variable group_colors { + #cbcbcb + #e1e1e1 +} - if {[winfo ismapped .]} { - set w .browser[incr next_browser_id] - set tl $w - toplevel $w - } else { - set w {} - set tl . - } - set blame_status($w) {Loading current file content...} +constructor new {i_commit i_path} { + global cursor_ptr + + set commit $i_commit + set path $i_path + + make_toplevel top w + wm title $top "[appname] ([reponame]): File Viewer" + set status "Loading $commit:$path..." label $w.path -text "$commit:$path" \ -anchor w \ @@ -23,25 +56,37 @@ proc show_blame {commit path} { pack $w.path -side top -fill x frame $w.out - text $w.out.loaded_t \ + set w_load $w.out.loaded_t + text $w_load \ -background white -borderwidth 0 \ -state disabled \ -wrap none \ -height 40 \ -width 1 \ -font font_diff - $w.out.loaded_t tag conf annotated -background grey + $w_load tag conf annotated -background grey - text $w.out.linenumber_t \ + set w_line $w.out.linenumber_t + text $w_line \ -background white -borderwidth 0 \ -state disabled \ -wrap none \ -height 40 \ -width 5 \ -font font_diff - $w.out.linenumber_t tag conf linenumber -justify right + $w_line tag conf linenumber -justify right - text $w.out.file_t \ + set w_cgrp $w.out.commit_t + text $w_cgrp \ + -background white -borderwidth 0 \ + -state disabled \ + -wrap none \ + -height 40 \ + -width 4 \ + -font font_diff + + set w_file $w.out.file_t + text $w_file \ -background white -borderwidth 0 \ -state disabled \ -wrap none \ @@ -50,25 +95,28 @@ proc show_blame {commit path} { -xscrollcommand [list $w.out.sbx set] \ -font font_diff - scrollbar $w.out.sbx -orient h -command [list $w.out.file_t xview] + scrollbar $w.out.sbx -orient h -command [list $w_file xview] scrollbar $w.out.sby -orient v \ -command [list scrollbar2many [list \ - $w.out.loaded_t \ - $w.out.linenumber_t \ - $w.out.file_t \ + $w_load \ + $w_line \ + $w_cgrp \ + $w_file \ ] yview] grid \ - $w.out.linenumber_t \ - $w.out.loaded_t \ - $w.out.file_t \ + $w_cgrp \ + $w_line \ + $w_load \ + $w_file \ $w.out.sby \ -sticky nsew - grid conf $w.out.sbx -column 2 -sticky we - grid columnconfigure $w.out 2 -weight 1 + grid conf $w.out.sbx -column 3 -sticky we + grid columnconfigure $w.out 3 -weight 1 grid rowconfigure $w.out 0 -weight 1 pack $w.out -fill both -expand 1 - label $w.status -textvariable blame_status($w) \ + label $w.status \ + -textvariable @status \ -anchor w \ -justify left \ -borderwidth 1 \ @@ -76,7 +124,8 @@ proc show_blame {commit path} { pack $w.status -side bottom -fill x frame $w.cm - text $w.cm.t \ + set w_cmit $w.cm.t + text $w_cmit \ -background white -borderwidth 0 \ -state disabled \ -wrap none \ @@ -85,38 +134,32 @@ proc show_blame {commit path} { -xscrollcommand [list $w.cm.sbx set] \ -yscrollcommand [list $w.cm.sby set] \ -font font_diff - scrollbar $w.cm.sbx -orient h -command [list $w.cm.t xview] - scrollbar $w.cm.sby -orient v -command [list $w.cm.t yview] + scrollbar $w.cm.sbx -orient h -command [list $w_cmit xview] + scrollbar $w.cm.sby -orient v -command [list $w_cmit yview] pack $w.cm.sby -side right -fill y pack $w.cm.sbx -side bottom -fill x - pack $w.cm.t -expand 1 -fill both + pack $w_cmit -expand 1 -fill both pack $w.cm -side bottom -fill x menu $w.ctxm -tearoff 0 - $w.ctxm add command -label "Copy Commit" \ - -command "blame_copycommit $w \$cursorW @\$cursorX,\$cursorY" + $w.ctxm add command \ + -label "Copy Commit" \ + -command [cb _copycommit] foreach i [list \ - $w.out.loaded_t \ - $w.out.linenumber_t \ - $w.out.file_t] { - $i tag conf in_sel \ - -background [$i cget -foreground] \ - -foreground [$i cget -background] + $w_cgrp \ + $w_load \ + $w_line \ + $w_file] { + $i conf -cursor $cursor_ptr $i conf -yscrollcommand \ [list many2scrollbar [list \ - $w.out.loaded_t \ - $w.out.linenumber_t \ - $w.out.file_t \ + $w_cgrp \ + $w_load \ + $w_line \ + $w_file \ ] yview $w.out.sby] - bind $i " - blame_click {$w} \\ - $w.cm.t \\ - $w.out.linenumber_t \\ - $w.out.file_t \\ - $i @%x,%y - focus $i - " + bind $i "[cb _click $i @%x,%y]; focus $i" bind_button3 $i " set cursorX %x set cursorY %y @@ -125,220 +168,249 @@ proc show_blame {commit path} { " } - bind $w.cm.t "focus $w.cm.t" - bind $tl "focus $tl" - bind $tl " - array unset blame_status {$w} - array unset blame_data $w,* - " - wm title $tl "[appname] ([reponame]): File Viewer" - - set blame_data($w,commit_count) 0 - set blame_data($w,commit_list) {} - set blame_data($w,total_lines) 0 - set blame_data($w,blame_lines) 0 - set blame_data($w,highlight_commit) {} - set blame_data($w,highlight_line) -1 - - set cmd [list git cat-file blob "$commit:$path"] - set fd [open "| $cmd" r] - fconfigure $fd -blocking 0 -translation lf -encoding binary - fileevent $fd readable [list read_blame_catfile \ - $fd $w $commit $path \ - $w.cm.t $w.out.loaded_t $w.out.linenumber_t $w.out.file_t] -} + foreach i [list \ + $w_cgrp \ + $w_load \ + $w_line \ + $w_file \ + $w_cmit] { + bind $i {catch {%W yview scroll -1 units};break} + bind $i {catch {%W yview scroll 1 units};break} + bind $i {catch {%W xview scroll -1 units};break} + bind $i {catch {%W xview scroll 1 units};break} + bind $i {catch {%W yview scroll -1 units};break} + bind $i {catch {%W yview scroll 1 units};break} + bind $i {catch {%W xview scroll -1 units};break} + bind $i {catch {%W xview scroll 1 units};break} + bind $i {catch {%W yview scroll -1 pages};break} + bind $i {catch {%W yview scroll 1 pages};break} + } -proc read_blame_catfile {fd w commit path w_cmit w_load w_line w_file} { - global blame_status blame_data + bind $w_cmit [list focus $w_cmit] + bind $top [list focus $top] + bind $top [list delete_this $this] - if {![winfo exists $w_file]} { - catch {close $fd} - return + if {$commit eq {}} { + set fd [open $path r] + } else { + set cmd [list git cat-file blob "$commit:$path"] + set fd [open "| $cmd" r] } + fconfigure $fd -blocking 0 -translation lf -encoding binary + fileevent $fd readable [cb _read_file $fd] +} - set n $blame_data($w,total_lines) +method _read_file {fd} { $w_load conf -state normal + $w_cgrp conf -state normal $w_line conf -state normal $w_file conf -state normal while {[gets $fd line] >= 0} { regsub "\r\$" $line {} line - incr n - $w_load insert end "\n" - $w_line insert end "$n\n" linenumber - $w_file insert end "$line\n" + incr total_lines + + if {$total_lines > 1} { + $w_load insert end "\n" + $w_cgrp insert end "\n" + $w_line insert end "\n" + $w_file insert end "\n" + } + + $w_line insert end "$total_lines" linenumber + $w_file insert end "$line" } $w_load conf -state disabled + $w_cgrp conf -state disabled $w_line conf -state disabled $w_file conf -state disabled - set blame_data($w,total_lines) $n if {[eof $fd]} { close $fd - blame_incremental_status $w + _status $this set cmd [list git blame -M -C --incremental] - lappend cmd $commit -- $path + 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 [list read_blame_incremental $fd $w \ - $w_load $w_cmit $w_line $w_file] + fileevent $fd readable [cb _read_blame $fd] } -} +} ifdeleted { catch {close $fd} } -proc read_blame_incremental {fd w w_load w_cmit w_line w_file} { - global blame_status blame_data - - if {![winfo exists $w_file]} { - catch {close $fd} - return - } +method _read_blame {fd} { + variable group_colors + $w_cgrp 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 blame_data($w,commit) $cmit - set blame_data($w,original_line) $original_line - set blame_data($w,final_line) $final_line - set blame_data($w,line_count) $line_count - - if {[catch {set g $blame_data($w,$cmit,order)}]} { - $w_line tag conf g$cmit - $w_file tag conf g$cmit - $w_line tag raise in_sel - $w_file tag raise in_sel - $w_file tag raise sel - set blame_data($w,$cmit,order) $blame_data($w,commit_count) - incr blame_data($w,commit_count) - lappend blame_data($w,commit_list) $cmit + set r_commit $cmit + set r_orig_line $original_line + set r_final_line $final_line + set r_line_count $line_count + + if {[catch {set g $order($cmit)}]} { + set bg [lindex $group_colors 0] + set group_colors [lrange $group_colors 1 end] + lappend group_colors $bg + + $w_cgrp tag conf g$cmit -background $bg + $w_line tag conf g$cmit -background $bg + $w_file tag conf g$cmit -background $bg + + set order($cmit) $commit_count + incr commit_count + lappend commit_list $cmit } } elseif {[string match {filename *} $line]} { set file [string range $line 9 end] - set n $blame_data($w,line_count) - set lno $blame_data($w,final_line) - set cmit $blame_data($w,commit) + set n $r_line_count + set lno $r_final_line + set cmit $r_commit + + if {[regexp {^0{40}$} $cmit]} { + set abbr work + } else { + set abbr [string range $cmit 0 4] + } + + if {![catch {set ncmit $line_commit([expr {$lno - 1}])}]} { + if {$ncmit eq $cmit} { + set abbr | + } + } while {$n > 0} { - if {[catch {set g g$blame_data($w,line$lno,commit)}]} { - $w_load tag add annotated $lno.0 "$lno.0 lineend + 1c" + set lno_e "$lno.0 lineend + 1c" + if {[catch {set g g$line_commit($lno)}]} { + $w_load tag add annotated $lno.0 $lno_e } else { - $w_line tag remove g$g $lno.0 "$lno.0 lineend + 1c" - $w_file tag remove g$g $lno.0 "$lno.0 lineend + 1c" + $w_cgrp tag remove g$g $lno.0 $lno_e + $w_line tag remove g$g $lno.0 $lno_e + $w_file tag remove g$g $lno.0 $lno_e + + $w_cgrp tag remove a$g $lno.0 $lno_e + $w_line tag remove a$g $lno.0 $lno_e + $w_file tag remove a$g $lno.0 $lno_e } - set blame_data($w,line$lno,commit) $cmit - set blame_data($w,line$lno,file) $file - $w_line tag add g$cmit $lno.0 "$lno.0 lineend + 1c" - $w_file tag add g$cmit $lno.0 "$lno.0 lineend + 1c" + set line_commit($lno) $cmit + set line_file($lno) $file + + $w_cgrp delete $lno.0 "$lno.0 lineend" + $w_cgrp insert $lno.0 $abbr + set abbr | + + $w_cgrp tag add g$cmit $lno.0 $lno_e + $w_line tag add g$cmit $lno.0 $lno_e + $w_file tag add g$cmit $lno.0 $lno_e - if {$blame_data($w,highlight_line) == -1} { + $w_cgrp tag add a$cmit $lno.0 $lno_e + $w_line tag add a$cmit $lno.0 $lno_e + $w_file tag add a$cmit $lno.0 $lno_e + + if {$highlight_line == -1} { if {[lindex [$w_file yview] 0] == 0} { $w_file see $lno.0 - blame_showcommit $w $w_cmit $w_line $w_file $lno + _showcommit $this $lno } - } elseif {$blame_data($w,highlight_line) == $lno} { - blame_showcommit $w $w_cmit $w_line $w_file $lno + } elseif {$highlight_line == $lno} { + _showcommit $this $lno } incr n -1 incr lno - incr blame_data($w,blame_lines) + incr blame_lines + } + + if {![catch {set ncmit $line_commit($lno)}]} { + if {$ncmit eq $cmit} { + $w_cgrp delete $lno.0 "$lno.0 lineend + 1c" + $w_cgrp insert $lno.0 "|\n" + } } - set hc $blame_data($w,highlight_commit) + set hc $highlight_commit if {$hc ne {} - && [expr {$blame_data($w,$hc,order) + 1}] - == $blame_data($w,$cmit,order)} { - blame_showcommit $w $w_cmit $w_line $w_file \ - $blame_data($w,highlight_line) + && [expr {$order($hc) + 1}] == $order($cmit)} { + _showcommit $this $highlight_line } - } elseif {[regexp {^([a-z-]+) (.*)$} $line line header data]} { - set blame_data($w,$blame_data($w,commit),$header) $data + } elseif {[regexp {^([a-z-]+) (.*)$} $line line key data]} { + set header($r_commit,$key) $data } } + $w_cgrp conf -state disabled if {[eof $fd]} { close $fd - set blame_status($w) {Annotation complete.} + set status {Annotation complete.} } else { - blame_incremental_status $w + _status $this } -} - -proc blame_incremental_status {w} { - global blame_status blame_data +} ifdeleted { catch {close $fd} } - set have $blame_data($w,blame_lines) - set total $blame_data($w,total_lines) +method _status {} { + set have $blame_lines + set total $total_lines set pdone 0 if {$total} {set pdone [expr {100 * $have / $total}]} - set blame_status($w) [format \ + set status [format \ "Loading annotations... %i of %i lines annotated (%2i%%)" \ $have $total $pdone] } -proc blame_click {w w_cmit w_line w_file cur_w pos} { +method _click {cur_w pos} { set lno [lindex [split [$cur_w index $pos] .] 0] if {$lno eq {}} return - - $w_line tag remove in_sel 0.0 end - $w_file tag remove in_sel 0.0 end - $w_line tag add in_sel $lno.0 "$lno.0 + 1 line" - $w_file tag add in_sel $lno.0 "$lno.0 + 1 line" - - blame_showcommit $w $w_cmit $w_line $w_file $lno + _showcommit $this $lno } -set blame_colors { - #ff4040 - #ff40ff - #4040ff -} +method _showcommit {lno} { + global repo_config + variable active_color -proc blame_showcommit {w w_cmit w_line w_file lno} { - global blame_colors blame_data repo_config - - set cmit $blame_data($w,highlight_commit) - if {$cmit ne {}} { - set idx $blame_data($w,$cmit,order) - set i 0 - foreach c $blame_colors { - set h [lindex $blame_data($w,commit_list) [expr {$idx - 1 + $i}]] - $w_line tag conf g$h -background white - $w_file tag conf g$h -background white - incr i - } + if {$highlight_commit ne {}} { + set cmit $highlight_commit + $w_cgrp tag conf a$cmit -background {} + $w_line tag conf a$cmit -background {} + $w_file tag conf a$cmit -background {} } $w_cmit conf -state normal $w_cmit delete 0.0 end - if {[catch {set cmit $blame_data($w,line$lno,commit)}]} { + if {[catch {set cmit $line_commit($lno)}]} { set cmit {} $w_cmit insert end "Loading annotation..." } else { - set idx $blame_data($w,$cmit,order) - set i 0 - foreach c $blame_colors { - set h [lindex $blame_data($w,commit_list) [expr {$idx - 1 + $i}]] - $w_line tag conf g$h -background $c - $w_file tag conf g$h -background $c - incr i - } + $w_cgrp tag conf a$cmit -background $active_color + $w_line tag conf a$cmit -background $active_color + $w_file tag conf a$cmit -background $active_color set author_name {} set author_email {} set author_time {} - catch {set author_name $blame_data($w,$cmit,author)} - catch {set author_email $blame_data($w,$cmit,author-mail)} - catch {set author_time [clock format $blame_data($w,$cmit,author-time)]} + catch {set author_name $header($cmit,author)} + catch {set author_email $header($cmit,author-mail)} + catch {set author_time [clock format \ + $header($cmit,author-time) \ + -format {%Y-%m-%d %H:%M:%S} + ]} set committer_name {} set committer_email {} set committer_time {} - catch {set committer_name $blame_data($w,$cmit,committer)} - catch {set committer_email $blame_data($w,$cmit,committer-mail)} - catch {set committer_time [clock format $blame_data($w,$cmit,committer-time)]} - - if {[catch {set msg $blame_data($w,$cmit,message)}]} { + catch {set committer_name $header($cmit,committer)} + catch {set committer_email $header($cmit,committer-mail)} + catch {set committer_time [clock format \ + $header($cmit,committer-time) \ + -format {%Y-%m-%d %H:%M:%S} + ]} + + if {[catch {set msg $header($cmit,message)}]} { set msg {} catch { set fd [open "| git cat-file commit $cmit" r] @@ -358,29 +430,29 @@ proc blame_showcommit {w w_cmit w_line w_file lno} { set author_name [encoding convertfrom $enc $author_name] set committer_name [encoding convertfrom $enc $committer_name] - set blame_data($w,$cmit,author) $author_name - set blame_data($w,$cmit,committer) $committer_name + set header($cmit,author) $author_name + set header($cmit,committer) $committer_name } - set blame_data($w,$cmit,message) $msg + set header($cmit,message) $msg } - $w_cmit insert end "commit $cmit\n" - $w_cmit insert end "Author: $author_name $author_email $author_time\n" - $w_cmit insert end "Committer: $committer_name $committer_email $committer_time\n" - $w_cmit insert end "Original File: [escape_path $blame_data($w,line$lno,file)]\n" - $w_cmit insert end "\n" - $w_cmit insert end $msg + $w_cmit insert end "commit $cmit +Author: $author_name $author_email $author_time +Committer: $committer_name $committer_email $committer_time +Original File: [escape_path $line_file($lno)] + +$msg" } $w_cmit conf -state disabled - set blame_data($w,highlight_line) $lno - set blame_data($w,highlight_commit) $cmit + set highlight_line $lno + set highlight_commit $cmit } -proc blame_copycommit {w i pos} { - global blame_data - set lno [lindex [split [$i index $pos] .] 0] - if {![catch {set commit $blame_data($w,line$lno,commit)}]} { +method _copycommit {} { + set pos @$::cursorX,$::cursorY + set lno [lindex [split [$::cursorW index $pos] .] 0] + if {![catch {set commit $line_commit($lno)}]} { clipboard clear clipboard append \ -format STRING \ @@ -388,3 +460,5 @@ proc blame_copycommit {w i pos} { -- $commit } } + +}