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