gitkon commit source ~/.gitk for user-specific option settings (b5721c7)
   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.4 $
  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 gettreediffs {id} {
 119    global treediffs parents
 120    set p [lindex $parents($id) 0]
 121    set diff {}
 122    foreach line [split [exec git-diff-tree -r $p $id] "\n"] {
 123        set type [lindex $line 1]
 124        set file [lindex $line 3]
 125        if {$type == "blob"} {
 126            lappend diff $file
 127        }
 128    }
 129    set treediffs($id) $diff
 130}
 131
 132proc makewindow {} {
 133    global canv canv2 canv3 linespc charspc ctext cflist
 134    panedwindow .ctop -orient vertical
 135    panedwindow .ctop.clist -orient horizontal -sashpad 0 -handlesize 4
 136    .ctop add .ctop.clist
 137    set canv .ctop.clist.canv
 138    set cscroll .ctop.clist.dates.csb
 139    canvas $canv -height [expr 30 * $linespc + 4] -width [expr 45 * $charspc] \
 140        -bg white -bd 0 \
 141        -yscrollincr $linespc -yscrollcommand "$cscroll set"
 142    .ctop.clist add $canv
 143    set canv2 .ctop.clist.canv2
 144    canvas $canv2 -height [expr 30 * $linespc +4] -width [expr 30 * $charspc] \
 145        -bg white -bd 0 -yscrollincr $linespc
 146    .ctop.clist add $canv2
 147    frame .ctop.clist.dates
 148    .ctop.clist add .ctop.clist.dates
 149    set canv3 .ctop.clist.dates.canv3
 150    canvas $canv3 -height [expr 30 * $linespc +4] -width [expr 15 * $charspc] \
 151        -bg white -bd 0 -yscrollincr $linespc
 152    scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
 153    pack .ctop.clist.dates.csb -side right -fill y
 154    pack $canv3 -side left -fill both -expand 1
 155
 156    panedwindow .ctop.cdet -orient horizontal
 157    .ctop add .ctop.cdet
 158    set ctext .ctop.cdet.ctext
 159    text $ctext -bg white -state disabled
 160    .ctop.cdet add $ctext
 161    #pack $ctext -side top -fill x -expand 1
 162    set cflist .ctop.cdet.cfiles
 163    listbox $cflist -width 30 -bg white
 164    .ctop.cdet add $cflist
 165    pack .ctop -side top -fill both -expand 1
 166
 167    bindall <1> {selcanvline %x %y}
 168    bindall <B1-Motion> {selcanvline %x %y}
 169    bindall <ButtonRelease-4> "allcanvs yview scroll -5 u"
 170    bindall <ButtonRelease-5> "allcanvs yview scroll 5 u"
 171    bindall <2> "allcanvs scan mark 0 %y"
 172    bindall <B2-Motion> "allcanvs scan dragto 0 %y"
 173    bind . <Key-Prior> "allcanvs yview scroll -1 p"
 174    bind . <Key-Next> "allcanvs yview scroll 1 p"
 175    bind . <Key-Delete> "allcanvs yview scroll -1 p"
 176    bind . <Key-BackSpace> "allcanvs yview scroll -1 p"
 177    bind . <Key-space> "allcanvs yview scroll 1 p"
 178    bind . <Key-Up> "selnextline -1"
 179    bind . <Key-Down> "selnextline 1"
 180    bind . Q "set stopped 1; destroy ."
 181}
 182
 183proc allcanvs args {
 184    global canv canv2 canv3
 185    eval $canv $args
 186    eval $canv2 $args
 187    eval $canv3 $args
 188}
 189
 190proc bindall {event action} {
 191    global canv canv2 canv3
 192    bind $canv $event $action
 193    bind $canv2 $event $action
 194    bind $canv3 $event $action
 195}
 196
 197proc truncatetofit {str width font} {
 198    if {[font measure $font $str] <= $width} {
 199        return $str
 200    }
 201    set best 0
 202    set bad [string length $str]
 203    set tmp $str
 204    while {$best < $bad - 1} {
 205        set try [expr {int(($best + $bad) / 2)}]
 206        set tmp "[string range $str 0 [expr $try-1]]..."
 207        if {[font measure $font $tmp] <= $width} {
 208            set best $try
 209        } else {
 210            set bad $try
 211        }
 212    }
 213    return $tmp
 214}
 215
 216proc drawgraph {start} {
 217    global parents children nparents nchildren commits
 218    global canv canv2 canv3 mainfont namefont canvx0 canvy0 canvy linespc
 219    global datemode cdate
 220    global lineid linehtag linentag linedtag commitsummary
 221
 222    set colors {green red blue magenta darkgrey brown orange}
 223    set ncolors [llength $colors]
 224    set nextcolor 0
 225    set colormap($start) [lindex $colors 0]
 226    foreach id $commits {
 227        set ncleft($id) $nchildren($id)
 228    }
 229    set todo [list $start]
 230    set level 0
 231    set y2 $canvy0
 232    set linestarty(0) $canvy0
 233    set nullentry -1
 234    set lineno -1
 235    while 1 {
 236        set canvy $y2
 237        allcanvs conf -scrollregion [list 0 0 0 $canvy]
 238        update
 239        incr lineno
 240        set nlines [llength $todo]
 241        set id [lindex $todo $level]
 242        set lineid($lineno) $id
 243        set actualparents {}
 244        foreach p $parents($id) {
 245            if {[info exists ncleft($p)]} {
 246                incr ncleft($p) -1
 247                lappend actualparents $p
 248            }
 249        }
 250        if {![info exists commitsummary($id)]} {
 251            readcommit $id
 252        }
 253        set x [expr $canvx0 + $level * $linespc]
 254        set y2 [expr $canvy + $linespc]
 255        if {$linestarty($level) < $canvy} {
 256            set t [$canv create line $x $linestarty($level) $x $canvy \
 257                       -width 2 -fill $colormap($id)]
 258            $canv lower $t
 259            set linestarty($level) $canvy
 260        }
 261        set t [$canv create oval [expr $x - 4] [expr $canvy - 4] \
 262                   [expr $x + 3] [expr $canvy + 3] \
 263                   -fill blue -outline black -width 1]
 264        $canv raise $t
 265        set xt [expr $canvx0 + $nlines * $linespc]
 266        set headline [lindex $commitsummary($id) 0]
 267        set name [lindex $commitsummary($id) 1]
 268        set date [lindex $commitsummary($id) 2]
 269        set linehtag($lineno) [$canv create text $xt $canvy -anchor w \
 270                                   -text $headline -font $mainfont ]
 271        set linentag($lineno) [$canv2 create text 3 $canvy -anchor w \
 272                                   -text $name -font $namefont]
 273        set linedtag($lineno) [$canv3 create text 3 $canvy -anchor w \
 274                                 -text $date -font $mainfont]
 275        if {!$datemode && [llength $actualparents] == 1} {
 276            set p [lindex $actualparents 0]
 277            if {$ncleft($p) == 0 && [lsearch -exact $todo $p] < 0} {
 278                set todo [lreplace $todo $level $level $p]
 279                set colormap($p) $colormap($id)
 280                continue
 281            }
 282        }
 283
 284        set oldtodo $todo
 285        set oldlevel $level
 286        set lines {}
 287        for {set i 0} {$i < $nlines} {incr i} {
 288            if {[lindex $todo $i] == {}} continue
 289            set oldstarty($i) $linestarty($i)
 290            if {$i != $level} {
 291                lappend lines [list $i [lindex $todo $i]]
 292            }
 293        }
 294        unset linestarty
 295        if {$nullentry >= 0} {
 296            set todo [lreplace $todo $nullentry $nullentry]
 297            if {$nullentry < $level} {
 298                incr level -1
 299            }
 300        }
 301
 302        set badcolors [list $colormap($id)]
 303        foreach p $actualparents {
 304            if {[info exists colormap($p)]} {
 305                lappend badcolors $colormap($p)
 306            }
 307        }
 308        set todo [lreplace $todo $level $level]
 309        if {$nullentry > $level} {
 310            incr nullentry -1
 311        }
 312        set i $level
 313        foreach p $actualparents {
 314            set k [lsearch -exact $todo $p]
 315            if {$k < 0} {
 316                set todo [linsert $todo $i $p]
 317                if {$nullentry >= $i} {
 318                    incr nullentry
 319                }
 320                if {$nparents($id) == 1 && $nparents($p) == 1
 321                    && $nchildren($p) == 1} {
 322                    set colormap($p) $colormap($id)
 323                } else {
 324                    for {set j 0} {$j <= $ncolors} {incr j} {
 325                        if {[incr nextcolor] >= $ncolors} {
 326                            set nextcolor 0
 327                        }
 328                        set c [lindex $colors $nextcolor]
 329                        # make sure the incoming and outgoing colors differ
 330                        if {[lsearch -exact $badcolors $c] < 0} break
 331                    }
 332                    set colormap($p) $c
 333                    lappend badcolors $c
 334                }
 335            }
 336            lappend lines [list $oldlevel $p]
 337        }
 338
 339        # choose which one to do next time around
 340        set todol [llength $todo]
 341        set level -1
 342        set latest {}
 343        for {set k $todol} {[incr k -1] >= 0} {} {
 344            set p [lindex $todo $k]
 345            if {$p == {}} continue
 346            if {$ncleft($p) == 0} {
 347                if {$datemode} {
 348                    if {$latest == {} || $cdate($p) > $latest} {
 349                        set level $k
 350                        set latest $cdate($p)
 351                    }
 352                } else {
 353                    set level $k
 354                    break
 355                }
 356            }
 357        }
 358        if {$level < 0} {
 359            if {$todo != {}} {
 360                puts "ERROR: none of the pending commits can be done yet:"
 361                foreach p $todo {
 362                    puts "  $p"
 363                }
 364            }
 365            break
 366        }
 367
 368        # If we are reducing, put in a null entry
 369        if {$todol < $nlines} {
 370            if {$nullentry >= 0} {
 371                set i $nullentry
 372                while {$i < $todol
 373                       && [lindex $oldtodo $i] == [lindex $todo $i]} {
 374                    incr i
 375                }
 376            } else {
 377                set i $oldlevel
 378                if {$level >= $i} {
 379                    incr i
 380                }
 381            }
 382            if {$i >= $todol} {
 383                set nullentry -1
 384            } else {
 385                set nullentry $i
 386                set todo [linsert $todo $nullentry {}]
 387                if {$level >= $i} {
 388                    incr level
 389                }
 390            }
 391        } else {
 392            set nullentry -1
 393        }
 394
 395        foreach l $lines {
 396            set i [lindex $l 0]
 397            set dst [lindex $l 1]
 398            set j [lsearch -exact $todo $dst]
 399            if {$i == $j} {
 400                set linestarty($i) $oldstarty($i)
 401                continue
 402            }
 403            set xi [expr {$canvx0 + $i * $linespc}]
 404            set xj [expr {$canvx0 + $j * $linespc}]
 405            set coords {}
 406            if {$oldstarty($i) < $canvy} {
 407                lappend coords $xi $oldstarty($i)
 408            }
 409            lappend coords $xi $canvy
 410            if {$j < $i - 1} {
 411                lappend coords [expr $xj + $linespc] $canvy
 412            } elseif {$j > $i + 1} {
 413                lappend coords [expr $xj - $linespc] $canvy
 414            }
 415            lappend coords $xj $y2
 416            set t [$canv create line $coords -width 2 -fill $colormap($dst)]
 417            $canv lower $t
 418            if {![info exists linestarty($j)]} {
 419                set linestarty($j) $y2
 420            }
 421        }
 422    }
 423}
 424
 425proc selcanvline {x y} {
 426    global canv canvy0 ctext linespc selectedline
 427    global lineid linehtag linentag linedtag commitinfo
 428    set ymax [lindex [$canv cget -scrollregion] 3]
 429    set yfrac [lindex [$canv yview] 0]
 430    set y [expr {$y + $yfrac * $ymax}]
 431    set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
 432    if {$l < 0} {
 433        set l 0
 434    }
 435    if {[info exists selectedline] && $selectedline == $l} return
 436    selectline $l
 437}
 438
 439proc selectline {l} {
 440    global canv ctext commitinfo selectedline lineid linehtag
 441    global canvy canvy0 linespc nparents
 442    global cflist treediffs
 443    if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
 444    $canv select clear
 445    $canv select from $linehtag($l) 0
 446    $canv select to $linehtag($l) end
 447    set y [expr {$canvy0 + $l * $linespc}]
 448    set ytop [expr {($y - $linespc / 2.0) / $canvy}]
 449    set ybot [expr {($y + $linespc / 2.0) / $canvy}]
 450    set wnow [$canv yview]
 451    if {$ytop < [lindex $wnow 0]} {
 452        allcanvs yview moveto $ytop
 453    } elseif {$ybot > [lindex $wnow 1]} {
 454        set wh [expr {[lindex $wnow 1] - [lindex $wnow 0]}]
 455        allcanvs yview moveto [expr {$ybot - $wh}]
 456    }
 457    set selectedline $l
 458
 459    set id $lineid($l)
 460    $ctext conf -state normal
 461    $ctext delete 0.0 end
 462    set info $commitinfo($id)
 463    $ctext insert end "Author: [lindex $info 1]  \t[lindex $info 2]\n"
 464    $ctext insert end "Committer: [lindex $info 3]  \t[lindex $info 4]\n"
 465    $ctext insert end "\n"
 466    $ctext insert end [lindex $info 0]
 467    $ctext conf -state disabled
 468
 469    $cflist delete 0 end
 470    if {$nparents($id) == 1} {
 471        if {![info exists treediffs($id)]} {
 472            gettreediffs $id
 473        }
 474        foreach f $treediffs($id) {
 475            $cflist insert end $f
 476        }
 477    }
 478
 479}
 480
 481proc selnextline {dir} {
 482    global selectedline
 483    if {![info exists selectedline]} return
 484    set l [expr $selectedline + $dir]
 485    selectline $l
 486}
 487
 488getcommits $revtreeargs
 489
 490set linespc [font metrics $mainfont -linespace]
 491set charspc [font measure $mainfont "m"]
 492
 493set canvy0 [expr 3 + 0.5 * $linespc]
 494set canvx0 [expr 3 + 0.5 * $linespc]
 495set namex [expr 45 * $charspc]
 496set datex [expr 75 * $charspc]
 497
 498makewindow
 499
 500set start {}
 501foreach id $commits {
 502    if {$nchildren($id) == 0} {
 503        set start $id
 504        break
 505    }
 506}
 507if {$start != {}} {
 508    drawgraph $start
 509}