gitkon commit Make getting file lists asynchronous (d2610d1)
   1#!/bin/sh
   2# Tcl ignores the next line -*- tcl -*- \
   3exec wish "$0" -- "${1+$@}"
   4
   5# Copyright (C) 2005 Paul Mackerras.  All rights reserved.
   6# This program is free software; it may be used, copied, modified
   7# and distributed under the terms of the GNU General Public Licence,
   8# either version 2, or (at your option) any later version.
   9
  10# CVS $Revision: 1.5 $
  11
  12set datemode 0
  13set boldnames 0
  14set revtreeargs {}
  15
  16set mainfont {Helvetica 9}
  17set namefont $mainfont
  18if {$boldnames} {
  19    lappend namefont bold
  20}
  21catch {source ~/.gitk}
  22
  23foreach arg $argv {
  24    switch -regexp -- $arg {
  25        "^$" { }
  26        "^-d" { set datemode 1 }
  27        "^-b" { set boldnames 1 }
  28        "^-.*" {
  29            puts stderr "unrecognized option $arg"
  30            exit 1
  31        }
  32        default {
  33            lappend revtreeargs $arg
  34        }
  35    }
  36}
  37
  38proc getcommits {rargs} {
  39    global commits parents cdate nparents children nchildren
  40    if {$rargs == {}} {
  41        set rargs HEAD
  42    }
  43    set commits {}
  44    foreach c [split [eval exec git-rev-tree $rargs] "\n"] {
  45        set i 0
  46        set cid {}
  47        foreach f $c {
  48            if {$i == 0} {
  49                set d $f
  50            } else {
  51                set id [lindex [split $f :] 0]
  52                if {![info exists nchildren($id)]} {
  53                    set children($id) {}
  54                    set nchildren($id) 0
  55                }
  56                if {$i == 1} {
  57                    set cid $id
  58                    lappend commits $id
  59                    set parents($id) {}
  60                    set cdate($id) $d
  61                    set nparents($id) 0
  62                } else {
  63                    lappend parents($cid) $id
  64                    incr nparents($cid)
  65                    incr nchildren($id)
  66                    lappend children($id) $cid
  67                }
  68            }
  69            incr i
  70        }
  71    }
  72}
  73
  74proc readcommit {id} {
  75    global commitinfo commitsummary
  76    set inhdr 1
  77    set comment {}
  78    set headline {}
  79    set auname {}
  80    set audate {}
  81    set comname {}
  82    set comdate {}
  83    foreach line [split [exec git-cat-file commit $id] "\n"] {
  84        if {$inhdr} {
  85            if {$line == {}} {
  86                set inhdr 0
  87            } else {
  88                set tag [lindex $line 0]
  89                if {$tag == "author"} {
  90                    set x [expr {[llength $line] - 2}]
  91                    set audate [lindex $line $x]
  92                    set auname [lrange $line 1 [expr {$x - 1}]]
  93                } elseif {$tag == "committer"} {
  94                    set x [expr {[llength $line] - 2}]
  95                    set comdate [lindex $line $x]
  96                    set comname [lrange $line 1 [expr {$x - 1}]]
  97                }
  98            }
  99        } else {
 100            if {$comment == {}} {
 101                set headline $line
 102            } else {
 103                append comment "\n"
 104            }
 105            append comment $line
 106        }
 107    }
 108    if {$audate != {}} {
 109        set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"]
 110    }
 111    if {$comdate != {}} {
 112        set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
 113    }
 114    set commitinfo($id) [list $comment $auname $audate $comname $comdate]
 115    set commitsummary($id) [list $headline $auname $audate]
 116}
 117
 118proc makewindow {} {
 119    global canv canv2 canv3 linespc charspc ctext cflist
 120    panedwindow .ctop -orient vertical
 121    panedwindow .ctop.clist -orient horizontal -sashpad 0 -handlesize 4
 122    .ctop add .ctop.clist
 123    set canv .ctop.clist.canv
 124    set cscroll .ctop.clist.dates.csb
 125    canvas $canv -height [expr 30 * $linespc + 4] -width [expr 45 * $charspc] \
 126        -bg white -bd 0 \
 127        -yscrollincr $linespc -yscrollcommand "$cscroll set"
 128    .ctop.clist add $canv
 129    set canv2 .ctop.clist.canv2
 130    canvas $canv2 -height [expr 30 * $linespc +4] -width [expr 30 * $charspc] \
 131        -bg white -bd 0 -yscrollincr $linespc
 132    .ctop.clist add $canv2
 133    frame .ctop.clist.dates
 134    .ctop.clist add .ctop.clist.dates
 135    set canv3 .ctop.clist.dates.canv3
 136    canvas $canv3 -height [expr 30 * $linespc +4] -width [expr 15 * $charspc] \
 137        -bg white -bd 0 -yscrollincr $linespc
 138    scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
 139    pack .ctop.clist.dates.csb -side right -fill y
 140    pack $canv3 -side left -fill both -expand 1
 141
 142    panedwindow .ctop.cdet -orient horizontal
 143    .ctop add .ctop.cdet
 144    frame .ctop.cdet.left
 145    set ctext .ctop.cdet.left.ctext
 146    text $ctext -bg white -state disabled \
 147        -yscrollcommand ".ctop.cdet.left.sb set"
 148    scrollbar .ctop.cdet.left.sb -command "$ctext yview"
 149    pack .ctop.cdet.left.sb -side right -fill y
 150    pack $ctext -side left -fill both -expand 1
 151    .ctop.cdet add .ctop.cdet.left
 152
 153    frame .ctop.cdet.right
 154    set cflist .ctop.cdet.right.cfiles
 155    listbox $cflist -width 30 -bg white \
 156        -yscrollcommand ".ctop.cdet.right.sb set"
 157    scrollbar .ctop.cdet.right.sb -command "$cflist yview"
 158    pack .ctop.cdet.right.sb -side right -fill y
 159    pack $cflist -side left -fill both -expand 1
 160    .ctop.cdet add .ctop.cdet.right
 161
 162    pack .ctop -side top -fill both -expand 1
 163
 164    bindall <1> {selcanvline %x %y}
 165    bindall <B1-Motion> {selcanvline %x %y}
 166    bindall <ButtonRelease-4> "allcanvs yview scroll -5 u"
 167    bindall <ButtonRelease-5> "allcanvs yview scroll 5 u"
 168    bindall <2> "allcanvs scan mark 0 %y"
 169    bindall <B2-Motion> "allcanvs scan dragto 0 %y"
 170    bind . <Key-Prior> "allcanvs yview scroll -1 p"
 171    bind . <Key-Next> "allcanvs yview scroll 1 p"
 172    bind . <Key-Delete> "allcanvs yview scroll -1 p"
 173    bind . <Key-BackSpace> "allcanvs yview scroll -1 p"
 174    bind . <Key-space> "allcanvs yview scroll 1 p"
 175    bind . <Key-Up> "selnextline -1"
 176    bind . <Key-Down> "selnextline 1"
 177    bind . Q "set stopped 1; destroy ."
 178}
 179
 180proc allcanvs args {
 181    global canv canv2 canv3
 182    eval $canv $args
 183    eval $canv2 $args
 184    eval $canv3 $args
 185}
 186
 187proc bindall {event action} {
 188    global canv canv2 canv3
 189    bind $canv $event $action
 190    bind $canv2 $event $action
 191    bind $canv3 $event $action
 192}
 193
 194proc truncatetofit {str width font} {
 195    if {[font measure $font $str] <= $width} {
 196        return $str
 197    }
 198    set best 0
 199    set bad [string length $str]
 200    set tmp $str
 201    while {$best < $bad - 1} {
 202        set try [expr {int(($best + $bad) / 2)}]
 203        set tmp "[string range $str 0 [expr $try-1]]..."
 204        if {[font measure $font $tmp] <= $width} {
 205            set best $try
 206        } else {
 207            set bad $try
 208        }
 209    }
 210    return $tmp
 211}
 212
 213proc drawgraph {start} {
 214    global parents children nparents nchildren commits
 215    global canv canv2 canv3 mainfont namefont canvx0 canvy0 canvy linespc
 216    global datemode cdate
 217    global lineid linehtag linentag linedtag commitsummary
 218
 219    set colors {green red blue magenta darkgrey brown orange}
 220    set ncolors [llength $colors]
 221    set nextcolor 0
 222    set colormap($start) [lindex $colors 0]
 223    foreach id $commits {
 224        set ncleft($id) $nchildren($id)
 225    }
 226    set todo [list $start]
 227    set level 0
 228    set y2 $canvy0
 229    set linestarty(0) $canvy0
 230    set nullentry -1
 231    set lineno -1
 232    while 1 {
 233        set canvy $y2
 234        allcanvs conf -scrollregion [list 0 0 0 $canvy]
 235        update
 236        incr lineno
 237        set nlines [llength $todo]
 238        set id [lindex $todo $level]
 239        set lineid($lineno) $id
 240        set actualparents {}
 241        foreach p $parents($id) {
 242            if {[info exists ncleft($p)]} {
 243                incr ncleft($p) -1
 244                lappend actualparents $p
 245            }
 246        }
 247        if {![info exists commitsummary($id)]} {
 248            readcommit $id
 249        }
 250        set x [expr $canvx0 + $level * $linespc]
 251        set y2 [expr $canvy + $linespc]
 252        if {$linestarty($level) < $canvy} {
 253            set t [$canv create line $x $linestarty($level) $x $canvy \
 254                       -width 2 -fill $colormap($id)]
 255            $canv lower $t
 256            set linestarty($level) $canvy
 257        }
 258        set t [$canv create oval [expr $x - 4] [expr $canvy - 4] \
 259                   [expr $x + 3] [expr $canvy + 3] \
 260                   -fill blue -outline black -width 1]
 261        $canv raise $t
 262        set xt [expr $canvx0 + $nlines * $linespc]
 263        set headline [lindex $commitsummary($id) 0]
 264        set name [lindex $commitsummary($id) 1]
 265        set date [lindex $commitsummary($id) 2]
 266        set linehtag($lineno) [$canv create text $xt $canvy -anchor w \
 267                                   -text $headline -font $mainfont ]
 268        set linentag($lineno) [$canv2 create text 3 $canvy -anchor w \
 269                                   -text $name -font $namefont]
 270        set linedtag($lineno) [$canv3 create text 3 $canvy -anchor w \
 271                                 -text $date -font $mainfont]
 272        if {!$datemode && [llength $actualparents] == 1} {
 273            set p [lindex $actualparents 0]
 274            if {$ncleft($p) == 0 && [lsearch -exact $todo $p] < 0} {
 275                set todo [lreplace $todo $level $level $p]
 276                set colormap($p) $colormap($id)
 277                continue
 278            }
 279        }
 280
 281        set oldtodo $todo
 282        set oldlevel $level
 283        set lines {}
 284        for {set i 0} {$i < $nlines} {incr i} {
 285            if {[lindex $todo $i] == {}} continue
 286            set oldstarty($i) $linestarty($i)
 287            if {$i != $level} {
 288                lappend lines [list $i [lindex $todo $i]]
 289            }
 290        }
 291        unset linestarty
 292        if {$nullentry >= 0} {
 293            set todo [lreplace $todo $nullentry $nullentry]
 294            if {$nullentry < $level} {
 295                incr level -1
 296            }
 297        }
 298
 299        set badcolors [list $colormap($id)]
 300        foreach p $actualparents {
 301            if {[info exists colormap($p)]} {
 302                lappend badcolors $colormap($p)
 303            }
 304        }
 305        set todo [lreplace $todo $level $level]
 306        if {$nullentry > $level} {
 307            incr nullentry -1
 308        }
 309        set i $level
 310        foreach p $actualparents {
 311            set k [lsearch -exact $todo $p]
 312            if {$k < 0} {
 313                set todo [linsert $todo $i $p]
 314                if {$nullentry >= $i} {
 315                    incr nullentry
 316                }
 317                if {$nparents($id) == 1 && $nparents($p) == 1
 318                    && $nchildren($p) == 1} {
 319                    set colormap($p) $colormap($id)
 320                } else {
 321                    for {set j 0} {$j <= $ncolors} {incr j} {
 322                        if {[incr nextcolor] >= $ncolors} {
 323                            set nextcolor 0
 324                        }
 325                        set c [lindex $colors $nextcolor]
 326                        # make sure the incoming and outgoing colors differ
 327                        if {[lsearch -exact $badcolors $c] < 0} break
 328                    }
 329                    set colormap($p) $c
 330                    lappend badcolors $c
 331                }
 332            }
 333            lappend lines [list $oldlevel $p]
 334        }
 335
 336        # choose which one to do next time around
 337        set todol [llength $todo]
 338        set level -1
 339        set latest {}
 340        for {set k $todol} {[incr k -1] >= 0} {} {
 341            set p [lindex $todo $k]
 342            if {$p == {}} continue
 343            if {$ncleft($p) == 0} {
 344                if {$datemode} {
 345                    if {$latest == {} || $cdate($p) > $latest} {
 346                        set level $k
 347                        set latest $cdate($p)
 348                    }
 349                } else {
 350                    set level $k
 351                    break
 352                }
 353            }
 354        }
 355        if {$level < 0} {
 356            if {$todo != {}} {
 357                puts "ERROR: none of the pending commits can be done yet:"
 358                foreach p $todo {
 359                    puts "  $p"
 360                }
 361            }
 362            break
 363        }
 364
 365        # If we are reducing, put in a null entry
 366        if {$todol < $nlines} {
 367            if {$nullentry >= 0} {
 368                set i $nullentry
 369                while {$i < $todol
 370                       && [lindex $oldtodo $i] == [lindex $todo $i]} {
 371                    incr i
 372                }
 373            } else {
 374                set i $oldlevel
 375                if {$level >= $i} {
 376                    incr i
 377                }
 378            }
 379            if {$i >= $todol} {
 380                set nullentry -1
 381            } else {
 382                set nullentry $i
 383                set todo [linsert $todo $nullentry {}]
 384                if {$level >= $i} {
 385                    incr level
 386                }
 387            }
 388        } else {
 389            set nullentry -1
 390        }
 391
 392        foreach l $lines {
 393            set i [lindex $l 0]
 394            set dst [lindex $l 1]
 395            set j [lsearch -exact $todo $dst]
 396            if {$i == $j} {
 397                set linestarty($i) $oldstarty($i)
 398                continue
 399            }
 400            set xi [expr {$canvx0 + $i * $linespc}]
 401            set xj [expr {$canvx0 + $j * $linespc}]
 402            set coords {}
 403            if {$oldstarty($i) < $canvy} {
 404                lappend coords $xi $oldstarty($i)
 405            }
 406            lappend coords $xi $canvy
 407            if {$j < $i - 1} {
 408                lappend coords [expr $xj + $linespc] $canvy
 409            } elseif {$j > $i + 1} {
 410                lappend coords [expr $xj - $linespc] $canvy
 411            }
 412            lappend coords $xj $y2
 413            set t [$canv create line $coords -width 2 -fill $colormap($dst)]
 414            $canv lower $t
 415            if {![info exists linestarty($j)]} {
 416                set linestarty($j) $y2
 417            }
 418        }
 419    }
 420}
 421
 422proc selcanvline {x y} {
 423    global canv canvy0 ctext linespc selectedline
 424    global lineid linehtag linentag linedtag commitinfo
 425    set ymax [lindex [$canv cget -scrollregion] 3]
 426    set yfrac [lindex [$canv yview] 0]
 427    set y [expr {$y + $yfrac * $ymax}]
 428    set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
 429    if {$l < 0} {
 430        set l 0
 431    }
 432    if {[info exists selectedline] && $selectedline == $l} return
 433    selectline $l
 434}
 435
 436proc selectline {l} {
 437    global canv canv2 canv3 ctext commitinfo selectedline
 438    global lineid linehtag linentag linedtag
 439    global canvy canvy0 linespc nparents treepending
 440    global cflist treediffs currentid
 441    if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
 442    $canv delete secsel
 443    set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
 444               -tags secsel -fill [$canv cget -selectbackground]]
 445    $canv lower $t
 446    $canv2 delete secsel
 447    set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
 448               -tags secsel -fill [$canv2 cget -selectbackground]]
 449    $canv2 lower $t
 450    $canv3 delete secsel
 451    set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
 452               -tags secsel -fill [$canv3 cget -selectbackground]]
 453    $canv3 lower $t
 454    set y [expr {$canvy0 + $l * $linespc}]
 455    set ytop [expr {($y - $linespc / 2.0) / $canvy}]
 456    set ybot [expr {($y + $linespc / 2.0) / $canvy}]
 457    set wnow [$canv yview]
 458    if {$ytop < [lindex $wnow 0]} {
 459        allcanvs yview moveto $ytop
 460    } elseif {$ybot > [lindex $wnow 1]} {
 461        set wh [expr {[lindex $wnow 1] - [lindex $wnow 0]}]
 462        allcanvs yview moveto [expr {$ybot - $wh}]
 463    }
 464    set selectedline $l
 465
 466    set id $lineid($l)
 467    $ctext conf -state normal
 468    $ctext delete 0.0 end
 469    set info $commitinfo($id)
 470    $ctext insert end "Author: [lindex $info 1]  [lindex $info 2]\n"
 471    $ctext insert end "Committer: [lindex $info 3]  [lindex $info 4]\n"
 472    $ctext insert end "\n"
 473    $ctext insert end [lindex $info 0]
 474    $ctext conf -state disabled
 475
 476    $cflist delete 0 end
 477    set currentid $id
 478    if {$nparents($id) == 1} {
 479        if {![info exists treediffs($id)]} {
 480            if {![info exists treepending]} {
 481                gettreediffs $id
 482            }
 483        } else {
 484            addtocflist $id
 485        }
 486    }
 487
 488}
 489
 490proc addtocflist {id} {
 491    global currentid treediffs cflist treepending
 492    if {$id != $currentid} {
 493        gettreediffs $currentid
 494        return
 495    }
 496    foreach f $treediffs($currentid) {
 497        $cflist insert end $f
 498    }
 499}
 500
 501proc gettreediffs {id} {
 502    global treediffs parents treepending
 503    set treepending $id
 504    set treediffs($id) {}
 505    set p [lindex $parents($id) 0]
 506    if [catch {set gdtf [open "|git-diff-tree -r $p $id" r]}] return
 507    fconfigure $gdtf -blocking 0
 508    fileevent $gdtf readable "gettreediffline $gdtf $id"
 509}
 510
 511proc gettreediffline {gdtf id} {
 512    global treediffs treepending
 513    set n [gets $gdtf line]
 514    if {$n < 0} {
 515        if {![eof $gdtf]} return
 516        close $gdtf
 517        unset treepending
 518        addtocflist $id
 519        return
 520    }
 521    set type [lindex $line 1]
 522    set file [lindex $line 3]
 523    if {$type == "blob"} {
 524        lappend treediffs($id) $file
 525    }
 526}
 527
 528proc selnextline {dir} {
 529    global selectedline
 530    if {![info exists selectedline]} return
 531    set l [expr $selectedline + $dir]
 532    selectline $l
 533}
 534
 535getcommits $revtreeargs
 536
 537set linespc [font metrics $mainfont -linespace]
 538set charspc [font measure $mainfont "m"]
 539
 540set canvy0 [expr 3 + 0.5 * $linespc]
 541set canvx0 [expr 3 + 0.5 * $linespc]
 542set namex [expr 45 * $charspc]
 543set datex [expr 75 * $charspc]
 544
 545makewindow
 546
 547set start {}
 548foreach id $commits {
 549    if {$nchildren($id) == 0} {
 550        set start $id
 551        break
 552    }
 553}
 554if {$start != {}} {
 555    drawgraph $start
 556}