lib / blame.tclon commit git-gui: Simplify consecutive lines that come from the same commit (c9e6bfd)
   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
 271                        if {[regexp {^0{40}$} $cmit]} {
 272                                set abbr work
 273                        } else {
 274                                set abbr [string range $cmit 0 4]
 275                        }
 276
 277                        if {![catch {set ncmit $line_commit([expr {$lno - 1}])}]} {
 278                                if {$ncmit eq $cmit} {
 279                                        set abbr |
 280                                }
 281                        }
 282
 283                        while {$n > 0} {
 284                                set lno_e "$lno.0 lineend + 1c"
 285                                if {[catch {set g g$line_commit($lno)}]} {
 286                                        $w_load tag add annotated $lno.0 $lno_e
 287                                } else {
 288                                        $w_cgrp tag remove g$g $lno.0 $lno_e
 289                                        $w_line tag remove g$g $lno.0 $lno_e
 290                                        $w_file tag remove g$g $lno.0 $lno_e
 291
 292                                        $w_cgrp tag remove a$g $lno.0 $lno_e
 293                                        $w_line tag remove a$g $lno.0 $lno_e
 294                                        $w_file tag remove a$g $lno.0 $lno_e
 295                                }
 296
 297                                set line_commit($lno) $cmit
 298                                set line_file($lno)   $file
 299
 300                                $w_cgrp delete $lno.0 "$lno.0 lineend"
 301                                $w_cgrp insert $lno.0 $abbr
 302                                set abbr |
 303
 304                                $w_cgrp tag add g$cmit $lno.0 $lno_e
 305                                $w_line tag add g$cmit $lno.0 $lno_e
 306                                $w_file tag add g$cmit $lno.0 $lno_e
 307
 308                                $w_cgrp tag add a$cmit $lno.0 $lno_e
 309                                $w_line tag add a$cmit $lno.0 $lno_e
 310                                $w_file tag add a$cmit $lno.0 $lno_e
 311
 312                                if {$highlight_line == -1} {
 313                                        if {[lindex [$w_file yview] 0] == 0} {
 314                                                $w_file see $lno.0
 315                                                _showcommit $this $lno
 316                                        }
 317                                } elseif {$highlight_line == $lno} {
 318                                        _showcommit $this $lno
 319                                }
 320
 321                                incr n -1
 322                                incr lno
 323                                incr blame_lines
 324                        }
 325
 326                        if {![catch {set ncmit $line_commit($lno)}]} {
 327                                if {$ncmit eq $cmit} {
 328                                        $w_cgrp delete $lno.0 "$lno.0 lineend + 1c"
 329                                        $w_cgrp insert $lno.0 "|\n"
 330                                }
 331                        }
 332
 333                        set hc $highlight_commit
 334                        if {$hc ne {}
 335                                && [expr {$order($hc) + 1}] == $order($cmit)} {
 336                                _showcommit $this $highlight_line
 337                        }
 338                } elseif {[regexp {^([a-z-]+) (.*)$} $line line key data]} {
 339                        set header($r_commit,$key) $data
 340                }
 341        }
 342        $w_cgrp conf -state disabled
 343
 344        if {[eof $fd]} {
 345                close $fd
 346                set status {Annotation complete.}
 347        } else {
 348                _status $this
 349        }
 350} ifdeleted { catch {close $fd} }
 351
 352method _status {} {
 353        set have  $blame_lines
 354        set total $total_lines
 355        set pdone 0
 356        if {$total} {set pdone [expr {100 * $have / $total}]}
 357
 358        set status [format \
 359                "Loading annotations... %i of %i lines annotated (%2i%%)" \
 360                $have $total $pdone]
 361}
 362
 363method _click {cur_w pos} {
 364        set lno [lindex [split [$cur_w index $pos] .] 0]
 365        if {$lno eq {}} return
 366        _showcommit $this $lno
 367}
 368
 369method _showcommit {lno} {
 370        global repo_config
 371        variable active_color
 372
 373        if {$highlight_commit ne {}} {
 374                set cmit $highlight_commit
 375                $w_cgrp tag conf a$cmit -background {}
 376                $w_line tag conf a$cmit -background {}
 377                $w_file tag conf a$cmit -background {}
 378        }
 379
 380        $w_cmit conf -state normal
 381        $w_cmit delete 0.0 end
 382        if {[catch {set cmit $line_commit($lno)}]} {
 383                set cmit {}
 384                $w_cmit insert end "Loading annotation..."
 385        } else {
 386                $w_cgrp tag conf a$cmit -background $active_color
 387                $w_line tag conf a$cmit -background $active_color
 388                $w_file tag conf a$cmit -background $active_color
 389
 390                set author_name {}
 391                set author_email {}
 392                set author_time {}
 393                catch {set author_name $header($cmit,author)}
 394                catch {set author_email $header($cmit,author-mail)}
 395                catch {set author_time [clock format \
 396                        $header($cmit,author-time) \
 397                        -format {%Y-%m-%d %H:%M:%S}
 398                ]}
 399
 400                set committer_name {}
 401                set committer_email {}
 402                set committer_time {}
 403                catch {set committer_name $header($cmit,committer)}
 404                catch {set committer_email $header($cmit,committer-mail)}
 405                catch {set committer_time [clock format \
 406                        $header($cmit,committer-time) \
 407                        -format {%Y-%m-%d %H:%M:%S}
 408                ]}
 409
 410                if {[catch {set msg $header($cmit,message)}]} {
 411                        set msg {}
 412                        catch {
 413                                set fd [open "| git cat-file commit $cmit" r]
 414                                fconfigure $fd -encoding binary -translation lf
 415                                if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
 416                                        set enc utf-8
 417                                }
 418                                while {[gets $fd line] > 0} {
 419                                        if {[string match {encoding *} $line]} {
 420                                                set enc [string tolower [string range $line 9 end]]
 421                                        }
 422                                }
 423                                set msg [encoding convertfrom $enc [read $fd]]
 424                                set msg [string trim $msg]
 425                                close $fd
 426
 427                                set author_name [encoding convertfrom $enc $author_name]
 428                                set committer_name [encoding convertfrom $enc $committer_name]
 429
 430                                set header($cmit,author) $author_name
 431                                set header($cmit,committer) $committer_name
 432                        }
 433                        set header($cmit,message) $msg
 434                }
 435
 436                $w_cmit insert end "commit $cmit
 437Author: $author_name $author_email  $author_time
 438Committer: $committer_name $committer_email  $committer_time
 439Original File: [escape_path $line_file($lno)]
 440
 441$msg"
 442        }
 443        $w_cmit conf -state disabled
 444
 445        set highlight_line $lno
 446        set highlight_commit $cmit
 447}
 448
 449method _copycommit {} {
 450        set pos @$::cursorX,$::cursorY
 451        set lno [lindex [split [$::cursorW index $pos] .] 0]
 452        if {![catch {set commit $line_commit($lno)}]} {
 453                clipboard clear
 454                clipboard append \
 455                        -format STRING \
 456                        -type STRING \
 457                        -- $commit
 458        }
 459}
 460
 461}