lib / blame.tclon commit git-gui: Don't attempt to inline array reads in methods (28bf928)
   1# git-gui blame viewer
   2# Copyright (C) 2006, 2007 Shawn Pearce
   3
   4set next_browser_id 0
   5
   6proc show_blame {commit path} {
   7        global next_browser_id blame_status blame_data
   8
   9        if {[winfo ismapped .]} {
  10                set w .browser[incr next_browser_id]
  11                set tl $w
  12                toplevel $w
  13        } else {
  14                set w {}
  15                set tl .
  16        }
  17        set blame_status($w) {Loading current file content...}
  18
  19        label $w.path -text "$commit:$path" \
  20                -anchor w \
  21                -justify left \
  22                -borderwidth 1 \
  23                -relief sunken \
  24                -font font_uibold
  25        pack $w.path -side top -fill x
  26
  27        frame $w.out
  28        text $w.out.loaded_t \
  29                -background white -borderwidth 0 \
  30                -state disabled \
  31                -wrap none \
  32                -height 40 \
  33                -width 1 \
  34                -font font_diff
  35        $w.out.loaded_t tag conf annotated -background grey
  36
  37        text $w.out.linenumber_t \
  38                -background white -borderwidth 0 \
  39                -state disabled \
  40                -wrap none \
  41                -height 40 \
  42                -width 5 \
  43                -font font_diff
  44        $w.out.linenumber_t tag conf linenumber -justify right
  45
  46        text $w.out.file_t \
  47                -background white -borderwidth 0 \
  48                -state disabled \
  49                -wrap none \
  50                -height 40 \
  51                -width 80 \
  52                -xscrollcommand [list $w.out.sbx set] \
  53                -font font_diff
  54
  55        scrollbar $w.out.sbx -orient h -command [list $w.out.file_t xview]
  56        scrollbar $w.out.sby -orient v \
  57                -command [list scrollbar2many [list \
  58                $w.out.loaded_t \
  59                $w.out.linenumber_t \
  60                $w.out.file_t \
  61                ] yview]
  62        grid \
  63                $w.out.linenumber_t \
  64                $w.out.loaded_t \
  65                $w.out.file_t \
  66                $w.out.sby \
  67                -sticky nsew
  68        grid conf $w.out.sbx -column 2 -sticky we
  69        grid columnconfigure $w.out 2 -weight 1
  70        grid rowconfigure $w.out 0 -weight 1
  71        pack $w.out -fill both -expand 1
  72
  73        label $w.status -textvariable blame_status($w) \
  74                -anchor w \
  75                -justify left \
  76                -borderwidth 1 \
  77                -relief sunken
  78        pack $w.status -side bottom -fill x
  79
  80        frame $w.cm
  81        text $w.cm.t \
  82                -background white -borderwidth 0 \
  83                -state disabled \
  84                -wrap none \
  85                -height 10 \
  86                -width 80 \
  87                -xscrollcommand [list $w.cm.sbx set] \
  88                -yscrollcommand [list $w.cm.sby set] \
  89                -font font_diff
  90        scrollbar $w.cm.sbx -orient h -command [list $w.cm.t xview]
  91        scrollbar $w.cm.sby -orient v -command [list $w.cm.t yview]
  92        pack $w.cm.sby -side right -fill y
  93        pack $w.cm.sbx -side bottom -fill x
  94        pack $w.cm.t -expand 1 -fill both
  95        pack $w.cm -side bottom -fill x
  96
  97        menu $w.ctxm -tearoff 0
  98        $w.ctxm add command -label "Copy Commit" \
  99                -command "blame_copycommit $w \$cursorW @\$cursorX,\$cursorY"
 100
 101        foreach i [list \
 102                $w.out.loaded_t \
 103                $w.out.linenumber_t \
 104                $w.out.file_t] {
 105                $i tag conf in_sel \
 106                        -background [$i cget -foreground] \
 107                        -foreground [$i cget -background]
 108                $i conf -yscrollcommand \
 109                        [list many2scrollbar [list \
 110                        $w.out.loaded_t \
 111                        $w.out.linenumber_t \
 112                        $w.out.file_t \
 113                        ] yview $w.out.sby]
 114                bind $i <Button-1> "
 115                        blame_click {$w} \\
 116                                $w.cm.t \\
 117                                $w.out.linenumber_t \\
 118                                $w.out.file_t \\
 119                                $i @%x,%y
 120                        focus $i
 121                "
 122                bind_button3 $i "
 123                        set cursorX %x
 124                        set cursorY %y
 125                        set cursorW %W
 126                        tk_popup $w.ctxm %X %Y
 127                "
 128        }
 129
 130        foreach i [list \
 131                $w.out.loaded_t \
 132                $w.out.linenumber_t \
 133                $w.out.file_t \
 134                $w.cm.t] {
 135                bind $i <Key-Up>        {catch {%W yview scroll -1 units};break}
 136                bind $i <Key-Down>      {catch {%W yview scroll  1 units};break}
 137                bind $i <Key-Left>      {catch {%W xview scroll -1 units};break}
 138                bind $i <Key-Right>     {catch {%W xview scroll  1 units};break}
 139                bind $i <Key-k>         {catch {%W yview scroll -1 units};break}
 140                bind $i <Key-j>         {catch {%W yview scroll  1 units};break}
 141                bind $i <Key-h>         {catch {%W xview scroll -1 units};break}
 142                bind $i <Key-l>         {catch {%W xview scroll  1 units};break}
 143                bind $i <Control-Key-b> {catch {%W yview scroll -1 pages};break}
 144                bind $i <Control-Key-f> {catch {%W yview scroll  1 pages};break}
 145        }
 146
 147        bind $w.cm.t <Button-1> "focus $w.cm.t"
 148        bind $tl <Visibility> "focus $tl"
 149        bind $tl <Destroy> "
 150                array unset blame_status {$w}
 151                array unset blame_data $w,*
 152        "
 153        wm title $tl "[appname] ([reponame]): File Viewer"
 154
 155        set blame_data($w,commit_count) 0
 156        set blame_data($w,commit_list) {}
 157        set blame_data($w,total_lines) 0
 158        set blame_data($w,blame_lines) 0
 159        set blame_data($w,highlight_commit) {}
 160        set blame_data($w,highlight_line) -1
 161
 162        set cmd [list git cat-file blob "$commit:$path"]
 163        set fd [open "| $cmd" r]
 164        fconfigure $fd -blocking 0 -translation lf -encoding binary
 165        fileevent $fd readable [list read_blame_catfile \
 166                $fd $w $commit $path \
 167                $w.cm.t $w.out.loaded_t $w.out.linenumber_t $w.out.file_t]
 168}
 169
 170proc read_blame_catfile {fd w commit path w_cmit w_load w_line w_file} {
 171        global blame_status blame_data
 172
 173        if {![winfo exists $w_file]} {
 174                catch {close $fd}
 175                return
 176        }
 177
 178        set n $blame_data($w,total_lines)
 179        $w_load conf -state normal
 180        $w_line conf -state normal
 181        $w_file conf -state normal
 182        while {[gets $fd line] >= 0} {
 183                regsub "\r\$" $line {} line
 184                incr n
 185                $w_load insert end "\n"
 186                $w_line insert end "$n\n" linenumber
 187                $w_file insert end "$line\n"
 188        }
 189        $w_load conf -state disabled
 190        $w_line conf -state disabled
 191        $w_file conf -state disabled
 192        set blame_data($w,total_lines) $n
 193
 194        if {[eof $fd]} {
 195                close $fd
 196                blame_incremental_status $w
 197                set cmd [list git blame -M -C --incremental]
 198                lappend cmd $commit -- $path
 199                set fd [open "| $cmd" r]
 200                fconfigure $fd -blocking 0 -translation lf -encoding binary
 201                fileevent $fd readable [list read_blame_incremental $fd $w \
 202                        $w_load $w_cmit $w_line $w_file]
 203        }
 204}
 205
 206proc read_blame_incremental {fd w w_load w_cmit w_line w_file} {
 207        global blame_status blame_data
 208
 209        if {![winfo exists $w_file]} {
 210                catch {close $fd}
 211                return
 212        }
 213
 214        while {[gets $fd line] >= 0} {
 215                if {[regexp {^([a-z0-9]{40}) (\d+) (\d+) (\d+)$} $line line \
 216                        cmit original_line final_line line_count]} {
 217                        set blame_data($w,commit) $cmit
 218                        set blame_data($w,original_line) $original_line
 219                        set blame_data($w,final_line) $final_line
 220                        set blame_data($w,line_count) $line_count
 221
 222                        if {[catch {set g $blame_data($w,$cmit,order)}]} {
 223                                $w_line tag conf g$cmit
 224                                $w_file tag conf g$cmit
 225                                $w_line tag raise in_sel
 226                                $w_file tag raise in_sel
 227                                $w_file tag raise sel
 228                                set blame_data($w,$cmit,order) $blame_data($w,commit_count)
 229                                incr blame_data($w,commit_count)
 230                                lappend blame_data($w,commit_list) $cmit
 231                        }
 232                } elseif {[string match {filename *} $line]} {
 233                        set file [string range $line 9 end]
 234                        set n $blame_data($w,line_count)
 235                        set lno $blame_data($w,final_line)
 236                        set cmit $blame_data($w,commit)
 237
 238                        while {$n > 0} {
 239                                if {[catch {set g g$blame_data($w,line$lno,commit)}]} {
 240                                        $w_load tag add annotated $lno.0 "$lno.0 lineend + 1c"
 241                                } else {
 242                                        $w_line tag remove g$g $lno.0 "$lno.0 lineend + 1c"
 243                                        $w_file tag remove g$g $lno.0 "$lno.0 lineend + 1c"
 244                                }
 245
 246                                set blame_data($w,line$lno,commit) $cmit
 247                                set blame_data($w,line$lno,file) $file
 248                                $w_line tag add g$cmit $lno.0 "$lno.0 lineend + 1c"
 249                                $w_file tag add g$cmit $lno.0 "$lno.0 lineend + 1c"
 250
 251                                if {$blame_data($w,highlight_line) == -1} {
 252                                        if {[lindex [$w_file yview] 0] == 0} {
 253                                                $w_file see $lno.0
 254                                                blame_showcommit $w $w_cmit $w_line $w_file $lno
 255                                        }
 256                                } elseif {$blame_data($w,highlight_line) == $lno} {
 257                                        blame_showcommit $w $w_cmit $w_line $w_file $lno
 258                                }
 259
 260                                incr n -1
 261                                incr lno
 262                                incr blame_data($w,blame_lines)
 263                        }
 264
 265                        set hc $blame_data($w,highlight_commit)
 266                        if {$hc ne {}
 267                                && [expr {$blame_data($w,$hc,order) + 1}]
 268                                        == $blame_data($w,$cmit,order)} {
 269                                blame_showcommit $w $w_cmit $w_line $w_file \
 270                                        $blame_data($w,highlight_line)
 271                        }
 272                } elseif {[regexp {^([a-z-]+) (.*)$} $line line header data]} {
 273                        set blame_data($w,$blame_data($w,commit),$header) $data
 274                }
 275        }
 276
 277        if {[eof $fd]} {
 278                close $fd
 279                set blame_status($w) {Annotation complete.}
 280        } else {
 281                blame_incremental_status $w
 282        }
 283}
 284
 285proc blame_incremental_status {w} {
 286        global blame_status blame_data
 287
 288        set have  $blame_data($w,blame_lines)
 289        set total $blame_data($w,total_lines)
 290        set pdone 0
 291        if {$total} {set pdone [expr {100 * $have / $total}]}
 292
 293        set blame_status($w) [format \
 294                "Loading annotations... %i of %i lines annotated (%2i%%)" \
 295                $have $total $pdone]
 296}
 297
 298proc blame_click {w w_cmit w_line w_file cur_w pos} {
 299        set lno [lindex [split [$cur_w index $pos] .] 0]
 300        if {$lno eq {}} return
 301
 302        $w_line tag remove in_sel 0.0 end
 303        $w_file tag remove in_sel 0.0 end
 304        $w_line tag add in_sel $lno.0 "$lno.0 + 1 line"
 305        $w_file tag add in_sel $lno.0 "$lno.0 + 1 line"
 306
 307        blame_showcommit $w $w_cmit $w_line $w_file $lno
 308}
 309
 310set blame_colors {
 311        #ff4040
 312        #ff40ff
 313        #4040ff
 314}
 315
 316proc blame_showcommit {w w_cmit w_line w_file lno} {
 317        global blame_colors blame_data repo_config
 318
 319        set cmit $blame_data($w,highlight_commit)
 320        if {$cmit ne {}} {
 321                set idx $blame_data($w,$cmit,order)
 322                set i 0
 323                foreach c $blame_colors {
 324                        set h [lindex $blame_data($w,commit_list) [expr {$idx - 1 + $i}]]
 325                        $w_line tag conf g$h -background white
 326                        $w_file tag conf g$h -background white
 327                        incr i
 328                }
 329        }
 330
 331        $w_cmit conf -state normal
 332        $w_cmit delete 0.0 end
 333        if {[catch {set cmit $blame_data($w,line$lno,commit)}]} {
 334                set cmit {}
 335                $w_cmit insert end "Loading annotation..."
 336        } else {
 337                set idx $blame_data($w,$cmit,order)
 338                set i 0
 339                foreach c $blame_colors {
 340                        set h [lindex $blame_data($w,commit_list) [expr {$idx - 1 + $i}]]
 341                        $w_line tag conf g$h -background $c
 342                        $w_file tag conf g$h -background $c
 343                        incr i
 344                }
 345
 346                set author_name {}
 347                set author_email {}
 348                set author_time {}
 349                catch {set author_name $blame_data($w,$cmit,author)}
 350                catch {set author_email $blame_data($w,$cmit,author-mail)}
 351                catch {set author_time [clock format $blame_data($w,$cmit,author-time)]}
 352
 353                set committer_name {}
 354                set committer_email {}
 355                set committer_time {}
 356                catch {set committer_name $blame_data($w,$cmit,committer)}
 357                catch {set committer_email $blame_data($w,$cmit,committer-mail)}
 358                catch {set committer_time [clock format $blame_data($w,$cmit,committer-time)]}
 359
 360                if {[catch {set msg $blame_data($w,$cmit,message)}]} {
 361                        set msg {}
 362                        catch {
 363                                set fd [open "| git cat-file commit $cmit" r]
 364                                fconfigure $fd -encoding binary -translation lf
 365                                if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
 366                                        set enc utf-8
 367                                }
 368                                while {[gets $fd line] > 0} {
 369                                        if {[string match {encoding *} $line]} {
 370                                                set enc [string tolower [string range $line 9 end]]
 371                                        }
 372                                }
 373                                set msg [encoding convertfrom $enc [read $fd]]
 374                                set msg [string trim $msg]
 375                                close $fd
 376
 377                                set author_name [encoding convertfrom $enc $author_name]
 378                                set committer_name [encoding convertfrom $enc $committer_name]
 379
 380                                set blame_data($w,$cmit,author) $author_name
 381                                set blame_data($w,$cmit,committer) $committer_name
 382                        }
 383                        set blame_data($w,$cmit,message) $msg
 384                }
 385
 386                $w_cmit insert end "commit $cmit\n"
 387                $w_cmit insert end "Author: $author_name $author_email $author_time\n"
 388                $w_cmit insert end "Committer: $committer_name $committer_email $committer_time\n"
 389                $w_cmit insert end "Original File: [escape_path $blame_data($w,line$lno,file)]\n"
 390                $w_cmit insert end "\n"
 391                $w_cmit insert end $msg
 392        }
 393        $w_cmit conf -state disabled
 394
 395        set blame_data($w,highlight_line) $lno
 396        set blame_data($w,highlight_commit) $cmit
 397}
 398
 399proc blame_copycommit {w i pos} {
 400        global blame_data
 401        set lno [lindex [split [$i index $pos] .] 0]
 402        if {![catch {set commit $blame_data($w,line$lno,commit)}]} {
 403                clipboard clear
 404                clipboard append \
 405                        -format STRING \
 406                        -type STRING \
 407                        -- $commit
 408        }
 409}