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