lib / blame.tclon commit git-gui: Remove empty blank line at end of blame (bea39c2)
   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                if {$total_lines > 1} {
 207                        $w_load insert end "\n"
 208                        $w_cgrp insert end "\n"
 209                        $w_line insert end "\n"
 210                        $w_file insert end "\n"
 211                }
 212
 213                $w_line insert end "$total_lines" linenumber
 214                $w_file insert end "$line"
 215        }
 216        $w_load conf -state disabled
 217        $w_cgrp conf -state disabled
 218        $w_line conf -state disabled
 219        $w_file conf -state disabled
 220
 221        if {[eof $fd]} {
 222                close $fd
 223                _status $this
 224                set cmd [list git blame -M -C --incremental]
 225                if {$commit eq {}} {
 226                        lappend cmd --contents $path
 227                } else {
 228                        lappend cmd $commit
 229                }
 230                lappend cmd -- $path
 231                set fd [open "| $cmd" r]
 232                fconfigure $fd -blocking 0 -translation lf -encoding binary
 233                fileevent $fd readable [cb _read_blame $fd]
 234        }
 235} ifdeleted { catch {close $fd} }
 236
 237method _read_blame {fd} {
 238        $w_cgrp conf -state normal
 239        while {[gets $fd line] >= 0} {
 240                if {[regexp {^([a-z0-9]{40}) (\d+) (\d+) (\d+)$} $line line \
 241                        cmit original_line final_line line_count]} {
 242                        set r_commit     $cmit
 243                        set r_orig_line  $original_line
 244                        set r_final_line $final_line
 245                        set r_line_count $line_count
 246
 247                        if {[catch {set g $order($cmit)}]} {
 248                                $w_cgrp tag conf g$cmit
 249                                $w_line tag conf g$cmit
 250                                $w_file tag conf g$cmit
 251
 252                                $w_cgrp tag raise in_sel
 253                                $w_line tag raise in_sel
 254                                $w_file tag raise in_sel
 255
 256                                $w_file tag raise sel
 257                                set order($cmit) $commit_count
 258                                incr commit_count
 259                                lappend commit_list $cmit
 260                        }
 261                } elseif {[string match {filename *} $line]} {
 262                        set file [string range $line 9 end]
 263                        set n    $r_line_count
 264                        set lno  $r_final_line
 265                        set cmit $r_commit
 266                        set abbr [string range $cmit 0 4]
 267
 268                        while {$n > 0} {
 269                                set lno_e "$lno.0 lineend + 1c"
 270                                if {[catch {set g g$line_commit($lno)}]} {
 271                                        $w_load tag add annotated $lno.0 $lno_e
 272                                } else {
 273                                        $w_cgrp tag remove g$g $lno.0 $lno_e
 274                                        $w_line tag remove g$g $lno.0 $lno_e
 275                                        $w_file tag remove g$g $lno.0 $lno_e
 276                                }
 277
 278                                set line_commit($lno) $cmit
 279                                set line_file($lno)   $file
 280
 281                                $w_cgrp delete $lno.0 $lno_e
 282                                $w_cgrp insert $lno.0 "$abbr\n"
 283
 284                                $w_cgrp tag add g$cmit $lno.0 $lno_e
 285                                $w_line tag add g$cmit $lno.0 $lno_e
 286                                $w_file tag add g$cmit $lno.0 $lno_e
 287
 288                                if {$highlight_line == -1} {
 289                                        if {[lindex [$w_file yview] 0] == 0} {
 290                                                $w_file see $lno.0
 291                                                _showcommit $this $lno
 292                                        }
 293                                } elseif {$highlight_line == $lno} {
 294                                        _showcommit $this $lno
 295                                }
 296
 297                                incr n -1
 298                                incr lno
 299                                incr blame_lines
 300                        }
 301
 302                        set hc $highlight_commit
 303                        if {$hc ne {}
 304                                && [expr {$order($hc) + 1}] == $order($cmit)} {
 305                                _showcommit $this $highlight_line
 306                        }
 307                } elseif {[regexp {^([a-z-]+) (.*)$} $line line key data]} {
 308                        set header($r_commit,$key) $data
 309                }
 310        }
 311        $w_cgrp conf -state disabled
 312
 313        if {[eof $fd]} {
 314                close $fd
 315                set status {Annotation complete.}
 316        } else {
 317                _status $this
 318        }
 319} ifdeleted { catch {close $fd} }
 320
 321method _status {} {
 322        set have  $blame_lines
 323        set total $total_lines
 324        set pdone 0
 325        if {$total} {set pdone [expr {100 * $have / $total}]}
 326
 327        set status [format \
 328                "Loading annotations... %i of %i lines annotated (%2i%%)" \
 329                $have $total $pdone]
 330}
 331
 332method _click {cur_w pos} {
 333        set lno [lindex [split [$cur_w index $pos] .] 0]
 334        if {$lno eq {}} return
 335
 336        set lno_e "$lno.0 + 1 line"
 337
 338        $w_cgrp tag remove in_sel 0.0 end
 339        $w_line tag remove in_sel 0.0 end
 340        $w_file tag remove in_sel 0.0 end
 341
 342        $w_cgrp tag add in_sel $lno.0 $lno_e
 343        $w_line tag add in_sel $lno.0 $lno_e
 344        $w_file tag add in_sel $lno.0 $lno_e
 345
 346        _showcommit $this $lno
 347}
 348
 349variable blame_colors {
 350        #ff4040
 351        #ff40ff
 352        #4040ff
 353}
 354
 355method _showcommit {lno} {
 356        global repo_config
 357        variable blame_colors
 358
 359        if {$highlight_commit ne {}} {
 360                set idx $order($highlight_commit)
 361                set i 0
 362                foreach c $blame_colors {
 363                        set h [lindex $commit_list [expr {$idx - 1 + $i}]]
 364                        $w_cgrp tag conf g$h -background white
 365                        $w_line tag conf g$h -background white
 366                        $w_file tag conf g$h -background white
 367                        incr i
 368                }
 369        }
 370
 371        $w_cmit conf -state normal
 372        $w_cmit delete 0.0 end
 373        if {[catch {set cmit $line_commit($lno)}]} {
 374                set cmit {}
 375                $w_cmit insert end "Loading annotation..."
 376        } else {
 377                set idx $order($cmit)
 378                set i 0
 379                foreach c $blame_colors {
 380                        set h [lindex $commit_list [expr {$idx - 1 + $i}]]
 381                        $w_cgrp tag conf g$h -background $c
 382                        $w_line tag conf g$h -background $c
 383                        $w_file tag conf g$h -background $c
 384                        incr i
 385                }
 386
 387                set author_name {}
 388                set author_email {}
 389                set author_time {}
 390                catch {set author_name $header($cmit,author)}
 391                catch {set author_email $header($cmit,author-mail)}
 392                catch {set author_time [clock format \
 393                        $header($cmit,author-time) \
 394                        -format {%Y-%m-%d %H:%M:%S}
 395                ]}
 396
 397                set committer_name {}
 398                set committer_email {}
 399                set committer_time {}
 400                catch {set committer_name $header($cmit,committer)}
 401                catch {set committer_email $header($cmit,committer-mail)}
 402                catch {set committer_time [clock format \
 403                        $header($cmit,committer-time) \
 404                        -format {%Y-%m-%d %H:%M:%S}
 405                ]}
 406
 407                if {[catch {set msg $header($cmit,message)}]} {
 408                        set msg {}
 409                        catch {
 410                                set fd [open "| git cat-file commit $cmit" r]
 411                                fconfigure $fd -encoding binary -translation lf
 412                                if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
 413                                        set enc utf-8
 414                                }
 415                                while {[gets $fd line] > 0} {
 416                                        if {[string match {encoding *} $line]} {
 417                                                set enc [string tolower [string range $line 9 end]]
 418                                        }
 419                                }
 420                                set msg [encoding convertfrom $enc [read $fd]]
 421                                set msg [string trim $msg]
 422                                close $fd
 423
 424                                set author_name [encoding convertfrom $enc $author_name]
 425                                set committer_name [encoding convertfrom $enc $committer_name]
 426
 427                                set header($cmit,author) $author_name
 428                                set header($cmit,committer) $committer_name
 429                        }
 430                        set header($cmit,message) $msg
 431                }
 432
 433                $w_cmit insert end "commit $cmit
 434Author: $author_name $author_email  $author_time
 435Committer: $committer_name $committer_email  $committer_time
 436Original File: [escape_path $line_file($lno)]
 437
 438$msg"
 439        }
 440        $w_cmit conf -state disabled
 441
 442        set highlight_line $lno
 443        set highlight_commit $cmit
 444}
 445
 446method _copycommit {} {
 447        set pos @$::cursorX,$::cursorY
 448        set lno [lindex [split [$::cursorW index $pos] .] 0]
 449        if {![catch {set commit $line_commit($lno)}]} {
 450                clipboard clear
 451                clipboard append \
 452                        -format STRING \
 453                        -type STRING \
 454                        -- $commit
 455        }
 456}
 457
 458}