gitkon commit Add initial version of gitk to the CVS repository (1db95b0)
   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
  10set datemode 0
  11set boldnames 0
  12set revtreeargs {}
  13
  14foreach arg $argv {
  15    switch -regexp -- $arg {
  16        "^$" { }
  17        "^-d" { set datemode 1 }
  18        "^-b" { set boldnames 1 }
  19        "^-.*" {
  20            puts stderr "unrecognized option $arg"
  21            exit 1
  22        }
  23        default {
  24            lappend revtreeargs $arg
  25        }
  26    }
  27}
  28
  29proc getcommits {rargs} {
  30    global commits parents cdate nparents children nchildren
  31    if {$rargs == {}} {
  32        set rargs HEAD
  33    }
  34    set commits {}
  35    foreach c [split [eval exec git-rev-tree $rargs] "\n"] {
  36        set i 0
  37        set cid {}
  38        foreach f $c {
  39            if {$i == 0} {
  40                set d $f
  41            } else {
  42                set id [lindex [split $f :] 0]
  43                if {![info exists nchildren($id)]} {
  44                    set children($id) {}
  45                    set nchildren($id) 0
  46                }
  47                if {$i == 1} {
  48                    set cid $id
  49                    lappend commits $id
  50                    set parents($id) {}
  51                    set cdate($id) $d
  52                    set nparents($id) 0
  53                } else {
  54                    lappend parents($cid) $id
  55                    incr nparents($cid)
  56                    incr nchildren($id)
  57                    lappend children($id) $cid
  58                }
  59            }
  60            incr i
  61        }
  62    }
  63}
  64
  65proc readcommit {id} {
  66    global commitinfo
  67    set inhdr 1
  68    set comment {}
  69    set headline {}
  70    set auname {}
  71    set audate {}
  72    set comname {}
  73    set comdate {}
  74    foreach line [split [exec git-cat-file commit $id] "\n"] {
  75        if {$inhdr} {
  76            if {$line == {}} {
  77                set inhdr 0
  78            } else {
  79                set tag [lindex $line 0]
  80                if {$tag == "author"} {
  81                    set x [expr {[llength $line] - 2}]
  82                    set audate [lindex $line $x]
  83                    set auname [lrange $line 1 [expr {$x - 1}]]
  84                } elseif {$tag == "committer"} {
  85                    set x [expr {[llength $line] - 2}]
  86                    set comdate [lindex $line $x]
  87                    set comname [lrange $line 1 [expr {$x - 1}]]
  88                }
  89            }
  90        } else {
  91            if {$comment == {}} {
  92                set headline $line
  93            } else {
  94                append comment "\n"
  95            }
  96            append comment $line
  97        }
  98    }
  99    if {$audate != {}} {
 100        set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"]
 101    }
 102    if {$comdate != {}} {
 103        set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
 104    }
 105    set commitinfo($id) [list $comment $auname $audate $comname $comdate]
 106    return [list $headline $auname $audate]
 107}
 108
 109proc makewindow {} {
 110    global canv linespc charspc ctext
 111    frame .clist
 112    set canv .clist.canv
 113    canvas $canv -height [expr 30 * $linespc + 4] -width [expr 90 * $charspc] \
 114        -bg white -relief sunk -bd 1 \
 115        -yscrollincr $linespc -yscrollcommand ".clist.csb set"
 116    scrollbar .clist.csb -command "$canv yview" -highlightthickness 0
 117    pack .clist.csb -side right -fill y
 118    pack $canv -side bottom -fill both -expand 1
 119    pack .clist -side top -fill both -expand 1
 120    set ctext .ctext
 121    text $ctext -bg white
 122    pack $ctext -side top -fill x -expand 1
 123
 124    bind $canv <1> {selcanvline %x %y}
 125    bind $canv <B1-Motion> {selcanvline %x %y}
 126    bind $canv <ButtonRelease-4> "$canv yview scroll -5 u"
 127    bind $canv <ButtonRelease-5> "$canv yview scroll 5 u"
 128    bind $canv <2> "$canv scan mark 0 %y"
 129    bind $canv <B2-Motion> "$canv scan dragto 0 %y"
 130    bind . <Key-Prior> "$canv yview scroll -1 p"
 131    bind . <Key-Next> "$canv yview scroll 1 p"
 132    bind . <Key-Delete> "$canv yview scroll -1 p"
 133    bind . <Key-BackSpace> "$canv yview scroll -1 p"
 134    bind . <Key-space> "$canv yview scroll 1 p"
 135    bind . <Key-Up> "$canv yview scroll -1 u"
 136    bind . <Key-Down> "$canv yview scroll 1 u"
 137    bind . Q "set stopped 1; destroy ."
 138}
 139
 140proc truncatetofit {str width font} {
 141    if {[font measure $font $str] <= $width} {
 142        return $str
 143    }
 144    set best 0
 145    set bad [string length $str]
 146    set tmp $str
 147    while {$best < $bad - 1} {
 148        set try [expr {int(($best + $bad) / 2)}]
 149        set tmp "[string range $str 0 [expr $try-1]]..."
 150        if {[font measure $font $tmp] <= $width} {
 151            set best $try
 152        } else {
 153            set bad $try
 154        }
 155    }
 156    return $tmp
 157}
 158
 159proc drawgraph {start} {
 160    global parents children nparents nchildren commits
 161    global canv mainfont namefont canvx0 canvy0 linespc namex datex
 162    global datemode cdate
 163    global lineid linehtag linentag linedtag
 164
 165    set colors {green red blue magenta darkgrey brown orange}
 166    set ncolors [llength $colors]
 167    set nextcolor 0
 168    set colormap($start) [lindex $colors 0]
 169    foreach id $commits {
 170        set ncleft($id) $nchildren($id)
 171    }
 172    set todo [list $start]
 173    set level 0
 174    set canvy $canvy0
 175    set linestarty(0) $canvy
 176    set nullentry -1
 177    set lineno -1
 178    while 1 {
 179        incr lineno
 180        set nlines [llength $todo]
 181        set id [lindex $todo $level]
 182        set lineid($lineno) $id
 183        foreach p $parents($id) {
 184            incr ncleft($p) -1
 185        }
 186        set cinfo [readcommit $id]
 187        set x [expr $canvx0 + $level * $linespc]
 188        set y2 [expr $canvy + $linespc]
 189        if {$linestarty($level) < $canvy} {
 190            set t [$canv create line $x $linestarty($level) $x $canvy \
 191                       -width 2 -fill $colormap($id)]
 192            $canv lower $t
 193            set linestarty($level) $canvy
 194        }
 195        set t [$canv create oval [expr $x - 4] [expr $canvy - 4] \
 196                   [expr $x + 3] [expr $canvy + 3] \
 197                   -fill blue -outline black -width 1]
 198        $canv raise $t
 199        set xt [expr $canvx0 + $nlines * $linespc]
 200        set headline [lindex $cinfo 0]
 201        set name [lindex $cinfo 1]
 202        set date [lindex $cinfo 2]
 203        set headline [truncatetofit $headline [expr $namex-$xt-$linespc] \
 204                         $mainfont]
 205        set linehtag($lineno) [$canv create text $xt $canvy -anchor w \
 206                                   -text $headline -font $mainfont ]
 207        set name [truncatetofit $name [expr $datex-$namex-$linespc] $namefont]
 208        set linentag($lineno) [$canv create text $namex $canvy -anchor w \
 209                                   -text $name -font $namefont]
 210        set linedtag($lineno) [$canv create text $datex $canvy -anchor w \
 211                                 -text $date -font $mainfont]
 212        if {!$datemode && $nparents($id) == 1} {
 213            set p [lindex $parents($id) 0]
 214            if {$ncleft($p) == 0 && [lsearch -exact $todo $p] < 0} {
 215                set todo [lreplace $todo $level $level $p]
 216                set colormap($p) $colormap($id)
 217                set canvy $y2
 218                $canv conf -scrollregion [list 0 0 0 $canvy]
 219                update
 220                continue
 221            }
 222        }
 223
 224        set oldtodo $todo
 225        set oldlevel $level
 226        set lines {}
 227        for {set i 0} {$i < $nlines} {incr i} {
 228            if {[lindex $todo $i] == {}} continue
 229            set oldstarty($i) $linestarty($i)
 230            if {$i != $level} {
 231                lappend lines [list $i [lindex $todo $i]]
 232            }
 233        }
 234        unset linestarty
 235        if {$nullentry >= 0} {
 236            set todo [lreplace $todo $nullentry $nullentry]
 237            if {$nullentry < $level} {
 238                incr level -1
 239            }
 240        }
 241
 242        set badcolors [list $colormap($id)]
 243        foreach p $parents($id) {
 244            if {[info exists colormap($p)]} {
 245                lappend badcolors $colormap($p)
 246            }
 247        }
 248        set todo [lreplace $todo $level $level]
 249        if {$nullentry > $level} {
 250            incr nullentry -1
 251        }
 252        set i $level
 253        foreach p $parents($id) {
 254            set k [lsearch -exact $todo $p]
 255            if {$k < 0} {
 256                set todo [linsert $todo $i $p]
 257                if {$nullentry >= $i} {
 258                    incr nullentry
 259                }
 260                if {$nparents($id) == 1 && $nparents($p) == 1
 261                    && $nchildren($p) == 1} {
 262                    set colormap($p) $colormap($id)
 263                } else {
 264                    for {set j 0} {$j <= $ncolors} {incr j} {
 265                        if {[incr nextcolor] >= $ncolors} {
 266                            set nextcolor 0
 267                        }
 268                        set c [lindex $colors $nextcolor]
 269                        # make sure the incoming and outgoing colors differ
 270                        if {[lsearch -exact $badcolors $c] < 0} break
 271                    }
 272                    set colormap($p) $c
 273                    lappend badcolors $c
 274                }
 275            }
 276            lappend lines [list $oldlevel $p]
 277        }
 278
 279        # choose which one to do next time around
 280        set todol [llength $todo]
 281        set level -1
 282        set latest {}
 283        for {set k $todol} {[incr k -1] >= 0} {} {
 284            set p [lindex $todo $k]
 285            if {$p == {}} continue
 286            if {$ncleft($p) == 0} {
 287                if {$datemode} {
 288                    if {$latest == {} || $cdate($p) > $latest} {
 289                        set level $k
 290                        set latest $cdate($p)
 291                    }
 292                } else {
 293                    set level $k
 294                    break
 295                }
 296            }
 297        }
 298        if {$level < 0} {
 299            if {$todo != {}} {
 300                puts "ERROR: none of the pending commits can be done yet:"
 301                foreach p $todo {
 302                    puts "  $p"
 303                }
 304            }
 305            break
 306        }
 307
 308        # If we are reducing, put in a null entry
 309        if {$todol < $nlines} {
 310            if {$nullentry >= 0} {
 311                set i $nullentry
 312                while {$i < $todol
 313                       && [lindex $oldtodo $i] == [lindex $todo $i]} {
 314                    incr i
 315                }
 316            } else {
 317                set i $oldlevel
 318                if {$level >= $i} {
 319                    incr i
 320                }
 321            }
 322            if {$i >= $todol} {
 323                set nullentry -1
 324            } else {
 325                set nullentry $i
 326                set todo [linsert $todo $nullentry {}]
 327                if {$level >= $i} {
 328                    incr level
 329                }
 330            }
 331        } else {
 332            set nullentry -1
 333        }
 334
 335        foreach l $lines {
 336            set i [lindex $l 0]
 337            set dst [lindex $l 1]
 338            set j [lsearch -exact $todo $dst]
 339            if {$i == $j} {
 340                set linestarty($i) $oldstarty($i)
 341                continue
 342            }
 343            set xi [expr {$canvx0 + $i * $linespc}]
 344            set xj [expr {$canvx0 + $j * $linespc}]
 345            set coords {}
 346            if {$oldstarty($i) < $canvy} {
 347                lappend coords $xi $oldstarty($i)
 348            }
 349            lappend coords $xi $canvy
 350            if {$j < $i - 1} {
 351                lappend coords [expr $xj + $linespc] $canvy
 352            } elseif {$j > $i + 1} {
 353                lappend coords [expr $xj - $linespc] $canvy
 354            }
 355            lappend coords $xj $y2
 356            set t [$canv create line $coords -width 2 -fill $colormap($dst)]
 357            $canv lower $t
 358            if {![info exists linestarty($j)]} {
 359                set linestarty($j) $y2
 360            }
 361        }
 362        set canvy $y2
 363        $canv conf -scrollregion [list 0 0 0 $canvy]
 364        update
 365    }
 366}
 367
 368proc selcanvline {x y} {
 369    global canv canvy0 ctext linespc selectedline
 370    global lineid linehtag linentag linedtag commitinfo
 371    set ymax [lindex [$canv cget -scrollregion] 3]
 372    set yfrac [lindex [$canv yview] 0]
 373    set y [expr {$y + $yfrac * $ymax}]
 374    set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
 375    if {$l < 0} {
 376        set l 0
 377    }
 378    if {[info exists selectedline] && $selectedline == $l} return
 379    if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
 380    $canv select clear
 381    $canv select from $linehtag($l) 0
 382    $canv select to $linehtag($l) end
 383    set id $lineid($l)
 384    $ctext delete 0.0 end
 385    set info $commitinfo($id)
 386    $ctext insert end "Author: [lindex $info 1]  \t[lindex $info 2]\n"
 387    $ctext insert end "Committer: [lindex $info 3]  \t[lindex $info 4]\n"
 388    $ctext insert end "\n"
 389    $ctext insert end [lindex $info 0]
 390}
 391
 392getcommits $revtreeargs
 393
 394set mainfont {Helvetica 9}
 395set namefont $mainfont
 396if {$boldnames} {
 397    lappend namefont bold
 398}
 399set linespc [font metrics $mainfont -linespace]
 400set charspc [font measure $mainfont "m"]
 401
 402set canvy0 [expr 3 + 0.5 * $linespc]
 403set canvx0 [expr 3 + 0.5 * $linespc]
 404set namex [expr 45 * $charspc]
 405set datex [expr 75 * $charspc]
 406
 407makewindow
 408
 409set start {}
 410foreach id $commits {
 411    if {$nchildren($id) == 0} {
 412        set start $id
 413        break
 414    }
 415}
 416if {$start != {}} {
 417    drawgraph $start
 418}