gitkon commit More fixes for geometry restoration (1738606)
   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.15 $
  11
  12proc getcommits {rargs} {
  13    global commits commfd phase canv mainfont
  14    if {$rargs == {}} {
  15        set rargs HEAD
  16    }
  17    set commits {}
  18    set phase getcommits
  19    if [catch {set commfd [open "|git-rev-tree $rargs" r]} err] {
  20        puts stderr "Error executing git-rev-tree: $err"
  21        exit 1
  22    }
  23    fconfigure $commfd -blocking 0
  24    fileevent $commfd readable "getcommitline $commfd"
  25    $canv delete all
  26    $canv create text 3 3 -anchor nw -text "Reading commits..." \
  27        -font $mainfont -tags textitems
  28}
  29
  30proc getcommitline {commfd}  {
  31    global commits parents cdate nparents children nchildren
  32    set n [gets $commfd line]
  33    if {$n < 0} {
  34        if {![eof $commfd]} return
  35        # this works around what is apparently a bug in Tcl...
  36        fconfigure $commfd -blocking 1
  37        if {![catch {close $commfd} err]} {
  38            after idle drawgraph
  39            return
  40        }
  41        if {[string range $err 0 4] == "usage"} {
  42            set err "\
  43Gitk: error reading commits: bad arguments to git-rev-tree.\n\
  44(Note: arguments to gitk are passed to git-rev-tree\
  45to allow selection of commits to be displayed.)"
  46        } else {
  47            set err "Error reading commits: $err"
  48        }
  49        error_popup $err
  50        exit 1
  51    }
  52
  53    set i 0
  54    set cid {}
  55    foreach f $line {
  56        if {$i == 0} {
  57            set d $f
  58        } else {
  59            set id [lindex [split $f :] 0]
  60            if {![info exists nchildren($id)]} {
  61                set children($id) {}
  62                set nchildren($id) 0
  63            }
  64            if {$i == 1} {
  65                set cid $id
  66                lappend commits $id
  67                set parents($id) {}
  68                set cdate($id) $d
  69                set nparents($id) 0
  70            } else {
  71                lappend parents($cid) $id
  72                incr nparents($cid)
  73                incr nchildren($id)
  74                lappend children($id) $cid
  75            }
  76        }
  77        incr i
  78    }
  79}
  80
  81proc readcommit {id} {
  82    global commitinfo
  83    set inhdr 1
  84    set comment {}
  85    set headline {}
  86    set auname {}
  87    set audate {}
  88    set comname {}
  89    set comdate {}
  90    if [catch {set contents [exec git-cat-file commit $id]}] return
  91    foreach line [split $contents "\n"] {
  92        if {$inhdr} {
  93            if {$line == {}} {
  94                set inhdr 0
  95            } else {
  96                set tag [lindex $line 0]
  97                if {$tag == "author"} {
  98                    set x [expr {[llength $line] - 2}]
  99                    set audate [lindex $line $x]
 100                    set auname [lrange $line 1 [expr {$x - 1}]]
 101                } elseif {$tag == "committer"} {
 102                    set x [expr {[llength $line] - 2}]
 103                    set comdate [lindex $line $x]
 104                    set comname [lrange $line 1 [expr {$x - 1}]]
 105                }
 106            }
 107        } else {
 108            if {$comment == {}} {
 109                set headline $line
 110            } else {
 111                append comment "\n"
 112            }
 113            append comment $line
 114        }
 115    }
 116    if {$audate != {}} {
 117        set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"]
 118    }
 119    if {$comdate != {}} {
 120        set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
 121    }
 122    set commitinfo($id) [list $headline $auname $audate \
 123                             $comname $comdate $comment]
 124}
 125
 126proc error_popup msg {
 127    set w .error
 128    toplevel $w
 129    wm transient $w .
 130    message $w.m -text $msg -justify center -aspect 400
 131    pack $w.m -side top -fill x -padx 20 -pady 20
 132    button $w.ok -text OK -command "destroy $w"
 133    pack $w.ok -side bottom -fill x
 134    bind $w <Visibility> "grab $w; focus $w"
 135    tkwait window $w
 136}
 137
 138proc makewindow {} {
 139    global canv canv2 canv3 linespc charspc ctext cflist textfont
 140    global sha1entry findtype findloc findstring fstring geometry
 141
 142    menu .bar
 143    .bar add cascade -label "File" -menu .bar.file
 144    menu .bar.file
 145    .bar.file add command -label "Quit" -command doquit
 146    menu .bar.help
 147    .bar add cascade -label "Help" -menu .bar.help
 148    .bar.help add command -label "About gitk" -command about
 149    . configure -menu .bar
 150
 151    if {![info exists geometry(canv1)]} {
 152        set geometry(canv1) [expr 45 * $charspc]
 153        set geometry(canv2) [expr 30 * $charspc]
 154        set geometry(canv3) [expr 15 * $charspc]
 155        set geometry(canvh) [expr 25 * $linespc + 4]
 156        set geometry(ctextw) 80
 157        set geometry(ctexth) 30
 158        set geometry(cflistw) 30
 159    }
 160    panedwindow .ctop -orient vertical
 161    if {[info exists geometry(width)]} {
 162        .ctop conf -width $geometry(width) -height $geometry(height)
 163        set texth [expr {$geometry(height) - $geometry(canvh) - 56}]
 164        set geometry(ctexth) [expr {($texth - 8) /
 165                                    [font metrics $textfont -linespace]}]
 166    }
 167    frame .ctop.top
 168    frame .ctop.top.bar
 169    pack .ctop.top.bar -side bottom -fill x
 170    set cscroll .ctop.top.csb
 171    scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
 172    pack $cscroll -side right -fill y
 173    panedwindow .ctop.top.clist -orient horizontal -sashpad 0 -handlesize 4
 174    pack .ctop.top.clist -side top -fill both -expand 1
 175    .ctop add .ctop.top
 176    set canv .ctop.top.clist.canv
 177    canvas $canv -height $geometry(canvh) -width $geometry(canv1) \
 178        -bg white -bd 0 \
 179        -yscrollincr $linespc -yscrollcommand "$cscroll set"
 180    .ctop.top.clist add $canv
 181    set canv2 .ctop.top.clist.canv2
 182    canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \
 183        -bg white -bd 0 -yscrollincr $linespc
 184    .ctop.top.clist add $canv2
 185    set canv3 .ctop.top.clist.canv3
 186    canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \
 187        -bg white -bd 0 -yscrollincr $linespc
 188    .ctop.top.clist add $canv3
 189    bind .ctop.top.clist <Configure> {resizeclistpanes %W %w}
 190
 191    set sha1entry .ctop.top.bar.sha1
 192    label .ctop.top.bar.sha1label -text "SHA1 ID: "
 193    pack .ctop.top.bar.sha1label -side left
 194    entry $sha1entry -width 40 -font $textfont -state readonly
 195    pack $sha1entry -side left -pady 2
 196    button .ctop.top.bar.findbut -text "Find" -command dofind
 197    pack .ctop.top.bar.findbut -side left
 198    set findstring {}
 199    set fstring .ctop.top.bar.findstring
 200    entry $fstring -width 30 -font $textfont -textvariable findstring
 201    pack $fstring -side left -expand 1 -fill x
 202    set findtype Exact
 203    tk_optionMenu .ctop.top.bar.findtype findtype Exact IgnCase Regexp
 204    set findloc "All fields"
 205    tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \
 206        Comments Author Committer
 207    pack .ctop.top.bar.findloc -side right
 208    pack .ctop.top.bar.findtype -side right
 209
 210    panedwindow .ctop.cdet -orient horizontal
 211    .ctop add .ctop.cdet
 212    frame .ctop.cdet.left
 213    set ctext .ctop.cdet.left.ctext
 214    text $ctext -bg white -state disabled -font $textfont \
 215        -width $geometry(ctextw) -height $geometry(ctexth) \
 216        -yscrollcommand ".ctop.cdet.left.sb set"
 217    scrollbar .ctop.cdet.left.sb -command "$ctext yview"
 218    pack .ctop.cdet.left.sb -side right -fill y
 219    pack $ctext -side left -fill both -expand 1
 220    .ctop.cdet add .ctop.cdet.left
 221
 222    $ctext tag conf filesep -font [concat $textfont bold]
 223    $ctext tag conf hunksep -back blue -fore white
 224    $ctext tag conf d0 -back "#ff8080"
 225    $ctext tag conf d1 -back green
 226    $ctext tag conf found -back yellow
 227
 228    frame .ctop.cdet.right
 229    set cflist .ctop.cdet.right.cfiles
 230    listbox $cflist -bg white -selectmode extended -width $geometry(cflistw) \
 231        -yscrollcommand ".ctop.cdet.right.sb set"
 232    scrollbar .ctop.cdet.right.sb -command "$cflist yview"
 233    pack .ctop.cdet.right.sb -side right -fill y
 234    pack $cflist -side left -fill both -expand 1
 235    .ctop.cdet add .ctop.cdet.right
 236    bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
 237
 238    pack .ctop -side top -fill both -expand 1
 239
 240    bindall <1> {selcanvline %x %y}
 241    bindall <B1-Motion> {selcanvline %x %y}
 242    bindall <ButtonRelease-4> "allcanvs yview scroll -5 u"
 243    bindall <ButtonRelease-5> "allcanvs yview scroll 5 u"
 244    bindall <2> "allcanvs scan mark 0 %y"
 245    bindall <B2-Motion> "allcanvs scan dragto 0 %y"
 246    bind . <Key-Up> "selnextline -1"
 247    bind . <Key-Down> "selnextline 1"
 248    bind . <Key-Prior> "allcanvs yview scroll -1 p"
 249    bind . <Key-Next> "allcanvs yview scroll 1 p"
 250    bindkey <Key-Delete> "$ctext yview scroll -1 p"
 251    bindkey <Key-BackSpace> "$ctext yview scroll -1 p"
 252    bindkey <Key-space> "$ctext yview scroll 1 p"
 253    bindkey p "selnextline -1"
 254    bindkey n "selnextline 1"
 255    bindkey b "$ctext yview scroll -1 p"
 256    bindkey d "$ctext yview scroll 18 u"
 257    bindkey u "$ctext yview scroll -18 u"
 258    bindkey / findnext
 259    bindkey ? findprev
 260    bind . <Control-q> doquit
 261    bind . <Control-f> dofind
 262    bind . <Control-g> findnext
 263    bind . <Control-r> findprev
 264    bind . <Control-equal> {incrfont 1}
 265    bind . <Control-KP_Add> {incrfont 1}
 266    bind . <Control-minus> {incrfont -1}
 267    bind . <Control-KP_Subtract> {incrfont -1}
 268    bind $cflist <<ListboxSelect>> listboxsel
 269    bind . <Destroy> {savestuff %W}
 270    bind . <Button-1> "click %W"
 271    bind $fstring <Key-Return> dofind
 272}
 273
 274# when we make a key binding for the toplevel, make sure
 275# it doesn't get triggered when that key is pressed in the
 276# find string entry widget.
 277proc bindkey {ev script} {
 278    global fstring
 279    bind . $ev $script
 280    set escript [bind Entry $ev]
 281    if {$escript == {}} {
 282        set escript [bind Entry <Key>]
 283    }
 284    bind $fstring $ev "$escript; break"
 285}
 286
 287# set the focus back to the toplevel for any click outside
 288# the find string entry widget
 289proc click {w} {
 290    global fstring
 291    if {$w != $fstring} {
 292        focus .
 293    }
 294}
 295
 296proc savestuff {w} {
 297    global canv canv2 canv3 ctext cflist mainfont textfont
 298    global stuffsaved
 299    if {$stuffsaved} return
 300    if {![winfo viewable .]} return
 301    catch {
 302        set f [open "~/.gitk-new" w]
 303        puts $f "set mainfont {$mainfont}"
 304        puts $f "set textfont {$textfont}"
 305        puts $f "set geometry(width) [winfo width .ctop]"
 306        puts $f "set geometry(height) [winfo height .ctop]"
 307        puts $f "set geometry(canv1) [expr [winfo width $canv]-2]"
 308        puts $f "set geometry(canv2) [expr [winfo width $canv2]-2]"
 309        puts $f "set geometry(canv3) [expr [winfo width $canv3]-2]"
 310        puts $f "set geometry(canvh) [expr [winfo height $canv]-2]"
 311        set wid [expr {([winfo width $ctext] - 8) \
 312                           / [font measure $textfont "0"]}]
 313        puts $f "set geometry(ctextw) $wid"
 314        set wid [expr {([winfo width $cflist] - 11) \
 315                           / [font measure [$cflist cget -font] "0"]}]
 316        puts $f "set geometry(cflistw) $wid"
 317        close $f
 318        file rename -force "~/.gitk-new" "~/.gitk"
 319    }
 320    set stuffsaved 1
 321}
 322
 323proc resizeclistpanes {win w} {
 324    global oldwidth
 325    if [info exists oldwidth($win)] {
 326        set s0 [$win sash coord 0]
 327        set s1 [$win sash coord 1]
 328        if {$w < 60} {
 329            set sash0 [expr {int($w/2 - 2)}]
 330            set sash1 [expr {int($w*5/6 - 2)}]
 331        } else {
 332            set factor [expr {1.0 * $w / $oldwidth($win)}]
 333            set sash0 [expr {int($factor * [lindex $s0 0])}]
 334            set sash1 [expr {int($factor * [lindex $s1 0])}]
 335            if {$sash0 < 30} {
 336                set sash0 30
 337            }
 338            if {$sash1 < $sash0 + 20} {
 339                set sash1 [expr $sash0 + 20]
 340            }
 341            if {$sash1 > $w - 10} {
 342                set sash1 [expr $w - 10]
 343                if {$sash0 > $sash1 - 20} {
 344                    set sash0 [expr $sash1 - 20]
 345                }
 346            }
 347        }
 348        $win sash place 0 $sash0 [lindex $s0 1]
 349        $win sash place 1 $sash1 [lindex $s1 1]
 350    }
 351    set oldwidth($win) $w
 352}
 353
 354proc resizecdetpanes {win w} {
 355    global oldwidth
 356    if [info exists oldwidth($win)] {
 357        set s0 [$win sash coord 0]
 358        if {$w < 60} {
 359            set sash0 [expr {int($w*3/4 - 2)}]
 360        } else {
 361            set factor [expr {1.0 * $w / $oldwidth($win)}]
 362            set sash0 [expr {int($factor * [lindex $s0 0])}]
 363            if {$sash0 < 45} {
 364                set sash0 45
 365            }
 366            if {$sash0 > $w - 15} {
 367                set sash0 [expr $w - 15]
 368            }
 369        }
 370        $win sash place 0 $sash0 [lindex $s0 1]
 371    }
 372    set oldwidth($win) $w
 373}
 374
 375proc allcanvs args {
 376    global canv canv2 canv3
 377    eval $canv $args
 378    eval $canv2 $args
 379    eval $canv3 $args
 380}
 381
 382proc bindall {event action} {
 383    global canv canv2 canv3
 384    bind $canv $event $action
 385    bind $canv2 $event $action
 386    bind $canv3 $event $action
 387}
 388
 389proc about {} {
 390    set w .about
 391    if {[winfo exists $w]} {
 392        raise $w
 393        return
 394    }
 395    toplevel $w
 396    wm title $w "About gitk"
 397    message $w.m -text {
 398Gitk version 0.95
 399
 400Copyright © 2005 Paul Mackerras
 401
 402Use and redistribute under the terms of the GNU General Public License
 403
 404(CVS $Revision: 1.15 $)} \
 405            -justify center -aspect 400
 406    pack $w.m -side top -fill x -padx 20 -pady 20
 407    button $w.ok -text Close -command "destroy $w"
 408    pack $w.ok -side bottom
 409}
 410
 411proc truncatetofit {str width font} {
 412    if {[font measure $font $str] <= $width} {
 413        return $str
 414    }
 415    set best 0
 416    set bad [string length $str]
 417    set tmp $str
 418    while {$best < $bad - 1} {
 419        set try [expr {int(($best + $bad) / 2)}]
 420        set tmp "[string range $str 0 [expr $try-1]]..."
 421        if {[font measure $font $tmp] <= $width} {
 422            set best $try
 423        } else {
 424            set bad $try
 425        }
 426    }
 427    return $tmp
 428}
 429
 430proc assigncolor {id} {
 431    global commitinfo colormap commcolors colors nextcolor
 432    global colorbycommitter
 433    global parents nparents children nchildren
 434    if [info exists colormap($id)] return
 435    set ncolors [llength $colors]
 436    if {$colorbycommitter} {
 437        if {![info exists commitinfo($id)]} {
 438            readcommit $id
 439        }
 440        set comm [lindex $commitinfo($id) 3]
 441        if {![info exists commcolors($comm)]} {
 442            set commcolors($comm) [lindex $colors $nextcolor]
 443            if {[incr nextcolor] >= $ncolors} {
 444                set nextcolor 0
 445            }
 446        }
 447        set colormap($id) $commcolors($comm)
 448    } else {
 449        if {$nparents($id) == 1 && $nchildren($id) == 1} {
 450            set child [lindex $children($id) 0]
 451            if {[info exists colormap($child)]
 452                && $nparents($child) == 1} {
 453                set colormap($id) $colormap($child)
 454                return
 455            }
 456        }
 457        set badcolors {}
 458        foreach child $children($id) {
 459            if {[info exists colormap($child)]
 460                && [lsearch -exact $badcolors $colormap($child)] < 0} {
 461                lappend badcolors $colormap($child)
 462            }
 463            if {[info exists parents($child)]} {
 464                foreach p $parents($child) {
 465                    if {[info exists colormap($p)]
 466                        && [lsearch -exact $badcolors $colormap($p)] < 0} {
 467                        lappend badcolors $colormap($p)
 468                    }
 469                }
 470            }
 471        }
 472        if {[llength $badcolors] >= $ncolors} {
 473            set badcolors {}
 474        }
 475        for {set i 0} {$i <= $ncolors} {incr i} {
 476            set c [lindex $colors $nextcolor]
 477            if {[incr nextcolor] >= $ncolors} {
 478                set nextcolor 0
 479            }
 480            if {[lsearch -exact $badcolors $c]} break
 481        }
 482        set colormap($id) $c
 483    }
 484}
 485
 486proc drawgraph {} {
 487    global parents children nparents nchildren commits
 488    global canv canv2 canv3 mainfont namefont canvx0 canvy0 canvy linespc
 489    global datemode cdate
 490    global lineid linehtag linentag linedtag commitinfo
 491    global nextcolor colormap numcommits
 492    global stopped phase redisplaying selectedline
 493
 494    allcanvs delete all
 495    set start {}
 496    foreach id [array names nchildren] {
 497        if {$nchildren($id) == 0} {
 498            lappend start $id
 499        }
 500        set ncleft($id) $nchildren($id)
 501        if {![info exists nparents($id)]} {
 502            set nparents($id) 0
 503        }
 504    }
 505    if {$start == {}} {
 506        error_popup "Gitk: ERROR: No starting commits found"
 507        exit 1
 508    }
 509
 510    set nextcolor 0
 511    foreach id $start {
 512        assigncolor $id
 513    }
 514    set todo $start
 515    set level [expr [llength $todo] - 1]
 516    set y2 $canvy0
 517    set nullentry -1
 518    set lineno -1
 519    set numcommits 0
 520    set phase drawgraph
 521    set lthickness [expr {($linespc / 9) + 1}]
 522    while 1 {
 523        set canvy $y2
 524        allcanvs conf -scrollregion \
 525            [list 0 0 0 [expr $canvy + 0.5 * $linespc + 2]]
 526        update
 527        if {$stopped} break
 528        incr numcommits
 529        incr lineno
 530        set nlines [llength $todo]
 531        set id [lindex $todo $level]
 532        set lineid($lineno) $id
 533        set actualparents {}
 534        if {[info exists parents($id)]} {
 535            foreach p $parents($id) {
 536                incr ncleft($p) -1
 537                if {![info exists commitinfo($p)]} {
 538                    readcommit $p
 539                    if {![info exists commitinfo($p)]} continue
 540                }
 541                lappend actualparents $p
 542            }
 543        }
 544        if {![info exists commitinfo($id)]} {
 545            readcommit $id
 546            if {![info exists commitinfo($id)]} {
 547                set commitinfo($id) {"No commit information available"}
 548            }
 549        }
 550        set x [expr $canvx0 + $level * $linespc]
 551        set y2 [expr $canvy + $linespc]
 552        if {[info exists linestarty($level)] && $linestarty($level) < $canvy} {
 553            set t [$canv create line $x $linestarty($level) $x $canvy \
 554                       -width $lthickness -fill $colormap($id)]
 555            $canv lower $t
 556        }
 557        set linestarty($level) $canvy
 558        set ofill [expr {[info exists parents($id)]? "blue": "white"}]
 559        set orad [expr {$linespc / 3}]
 560        set t [$canv create oval [expr $x - $orad] [expr $canvy - $orad] \
 561                   [expr $x + $orad - 1] [expr $canvy + $orad - 1] \
 562                   -fill $ofill -outline black -width 1]
 563        $canv raise $t
 564        set xt [expr $canvx0 + $nlines * $linespc]
 565        set headline [lindex $commitinfo($id) 0]
 566        set name [lindex $commitinfo($id) 1]
 567        set date [lindex $commitinfo($id) 2]
 568        set linehtag($lineno) [$canv create text $xt $canvy -anchor w \
 569                                   -text $headline -font $mainfont ]
 570        set linentag($lineno) [$canv2 create text 3 $canvy -anchor w \
 571                                   -text $name -font $namefont]
 572        set linedtag($lineno) [$canv3 create text 3 $canvy -anchor w \
 573                                 -text $date -font $mainfont]
 574        if {!$datemode && [llength $actualparents] == 1} {
 575            set p [lindex $actualparents 0]
 576            if {$ncleft($p) == 0 && [lsearch -exact $todo $p] < 0} {
 577                assigncolor $p
 578                set todo [lreplace $todo $level $level $p]
 579                continue
 580            }
 581        }
 582
 583        set oldtodo $todo
 584        set oldlevel $level
 585        set lines {}
 586        for {set i 0} {$i < $nlines} {incr i} {
 587            if {[lindex $todo $i] == {}} continue
 588            if {[info exists linestarty($i)]} {
 589                set oldstarty($i) $linestarty($i)
 590                unset linestarty($i)
 591            }
 592            if {$i != $level} {
 593                lappend lines [list $i [lindex $todo $i]]
 594            }
 595        }
 596        if {$nullentry >= 0} {
 597            set todo [lreplace $todo $nullentry $nullentry]
 598            if {$nullentry < $level} {
 599                incr level -1
 600            }
 601        }
 602
 603        set todo [lreplace $todo $level $level]
 604        if {$nullentry > $level} {
 605            incr nullentry -1
 606        }
 607        set i $level
 608        foreach p $actualparents {
 609            set k [lsearch -exact $todo $p]
 610            if {$k < 0} {
 611                assigncolor $p
 612                set todo [linsert $todo $i $p]
 613                if {$nullentry >= $i} {
 614                    incr nullentry
 615                }
 616            }
 617            lappend lines [list $oldlevel $p]
 618        }
 619
 620        # choose which one to do next time around
 621        set todol [llength $todo]
 622        set level -1
 623        set latest {}
 624        for {set k $todol} {[incr k -1] >= 0} {} {
 625            set p [lindex $todo $k]
 626            if {$p == {}} continue
 627            if {$ncleft($p) == 0} {
 628                if {$datemode} {
 629                    if {$latest == {} || $cdate($p) > $latest} {
 630                        set level $k
 631                        set latest $cdate($p)
 632                    }
 633                } else {
 634                    set level $k
 635                    break
 636                }
 637            }
 638        }
 639        if {$level < 0} {
 640            if {$todo != {}} {
 641                puts "ERROR: none of the pending commits can be done yet:"
 642                foreach p $todo {
 643                    puts "  $p"
 644                }
 645            }
 646            break
 647        }
 648
 649        # If we are reducing, put in a null entry
 650        if {$todol < $nlines} {
 651            if {$nullentry >= 0} {
 652                set i $nullentry
 653                while {$i < $todol
 654                       && [lindex $oldtodo $i] == [lindex $todo $i]} {
 655                    incr i
 656                }
 657            } else {
 658                set i $oldlevel
 659                if {$level >= $i} {
 660                    incr i
 661                }
 662            }
 663            if {$i >= $todol} {
 664                set nullentry -1
 665            } else {
 666                set nullentry $i
 667                set todo [linsert $todo $nullentry {}]
 668                if {$level >= $i} {
 669                    incr level
 670                }
 671            }
 672        } else {
 673            set nullentry -1
 674        }
 675
 676        foreach l $lines {
 677            set i [lindex $l 0]
 678            set dst [lindex $l 1]
 679            set j [lsearch -exact $todo $dst]
 680            if {$i == $j} {
 681                if {[info exists oldstarty($i)]} {
 682                    set linestarty($i) $oldstarty($i)
 683                }
 684                continue
 685            }
 686            set xi [expr {$canvx0 + $i * $linespc}]
 687            set xj [expr {$canvx0 + $j * $linespc}]
 688            set coords {}
 689            if {[info exists oldstarty($i)] && $oldstarty($i) < $canvy} {
 690                lappend coords $xi $oldstarty($i)
 691            }
 692            lappend coords $xi $canvy
 693            if {$j < $i - 1} {
 694                lappend coords [expr $xj + $linespc] $canvy
 695            } elseif {$j > $i + 1} {
 696                lappend coords [expr $xj - $linespc] $canvy
 697            }
 698            lappend coords $xj $y2
 699            set t [$canv create line $coords -width $lthickness \
 700                       -fill $colormap($dst)]
 701            $canv lower $t
 702            if {![info exists linestarty($j)]} {
 703                set linestarty($j) $y2
 704            }
 705        }
 706    }
 707    set phase {}
 708    if {$redisplaying} {
 709        if {$stopped == 0 && [info exists selectedline]} {
 710            selectline $selectedline
 711        }
 712        if {$stopped == 1} {
 713            set stopped 0
 714            after idle drawgraph
 715        } else {
 716            set redisplaying 0
 717        }
 718    }
 719}
 720
 721proc findmatches {f} {
 722    global findtype foundstring foundstrlen
 723    if {$findtype == "Regexp"} {
 724        set matches [regexp -indices -all -inline $foundstring $f]
 725    } else {
 726        if {$findtype == "IgnCase"} {
 727            set str [string tolower $f]
 728        } else {
 729            set str $f
 730        }
 731        set matches {}
 732        set i 0
 733        while {[set j [string first $foundstring $str $i]] >= 0} {
 734            lappend matches [list $j [expr $j+$foundstrlen-1]]
 735            set i [expr $j + $foundstrlen]
 736        }
 737    }
 738    return $matches
 739}
 740
 741proc dofind {} {
 742    global findtype findloc findstring markedmatches commitinfo
 743    global numcommits lineid linehtag linentag linedtag
 744    global mainfont namefont canv canv2 canv3 selectedline
 745    global matchinglines foundstring foundstrlen
 746    unmarkmatches
 747    focus .
 748    set matchinglines {}
 749    set fldtypes {Headline Author Date Committer CDate Comment}
 750    if {$findtype == "IgnCase"} {
 751        set foundstring [string tolower $findstring]
 752    } else {
 753        set foundstring $findstring
 754    }
 755    set foundstrlen [string length $findstring]
 756    if {$foundstrlen == 0} return
 757    if {![info exists selectedline]} {
 758        set oldsel -1
 759    } else {
 760        set oldsel $selectedline
 761    }
 762    set didsel 0
 763    for {set l 0} {$l < $numcommits} {incr l} {
 764        set id $lineid($l)
 765        set info $commitinfo($id)
 766        set doesmatch 0
 767        foreach f $info ty $fldtypes {
 768            if {$findloc != "All fields" && $findloc != $ty} {
 769                continue
 770            }
 771            set matches [findmatches $f]
 772            if {$matches == {}} continue
 773            set doesmatch 1
 774            if {$ty == "Headline"} {
 775                markmatches $canv $l $f $linehtag($l) $matches $mainfont
 776            } elseif {$ty == "Author"} {
 777                markmatches $canv2 $l $f $linentag($l) $matches $namefont
 778            } elseif {$ty == "Date"} {
 779                markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
 780            }
 781        }
 782        if {$doesmatch} {
 783            lappend matchinglines $l
 784            if {!$didsel && $l > $oldsel} {
 785                findselectline $l
 786                set didsel 1
 787            }
 788        }
 789    }
 790    if {$matchinglines == {}} {
 791        bell
 792    } elseif {!$didsel} {
 793        findselectline [lindex $matchinglines 0]
 794    }
 795}
 796
 797proc findselectline {l} {
 798    global findloc commentend ctext
 799    selectline $l
 800    if {$findloc == "All fields" || $findloc == "Comments"} {
 801        # highlight the matches in the comments
 802        set f [$ctext get 1.0 $commentend]
 803        set matches [findmatches $f]
 804        foreach match $matches {
 805            set start [lindex $match 0]
 806            set end [expr [lindex $match 1] + 1]
 807            $ctext tag add found "1.0 + $start c" "1.0 + $end c"
 808        }
 809    }
 810}
 811
 812proc findnext {} {
 813    global matchinglines selectedline
 814    if {![info exists matchinglines]} {
 815        dofind
 816        return
 817    }
 818    if {![info exists selectedline]} return
 819    foreach l $matchinglines {
 820        if {$l > $selectedline} {
 821            findselectline $l
 822            return
 823        }
 824    }
 825    bell
 826}
 827
 828proc findprev {} {
 829    global matchinglines selectedline
 830    if {![info exists matchinglines]} {
 831        dofind
 832        return
 833    }
 834    if {![info exists selectedline]} return
 835    set prev {}
 836    foreach l $matchinglines {
 837        if {$l >= $selectedline} break
 838        set prev $l
 839    }
 840    if {$prev != {}} {
 841        findselectline $prev
 842    } else {
 843        bell
 844    }
 845}
 846
 847proc markmatches {canv l str tag matches font} {
 848    set bbox [$canv bbox $tag]
 849    set x0 [lindex $bbox 0]
 850    set y0 [lindex $bbox 1]
 851    set y1 [lindex $bbox 3]
 852    foreach match $matches {
 853        set start [lindex $match 0]
 854        set end [lindex $match 1]
 855        if {$start > $end} continue
 856        set xoff [font measure $font [string range $str 0 [expr $start-1]]]
 857        set xlen [font measure $font [string range $str 0 [expr $end]]]
 858        set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \
 859                   -outline {} -tags matches -fill yellow]
 860        $canv lower $t
 861    }
 862}
 863
 864proc unmarkmatches {} {
 865    global matchinglines
 866    allcanvs delete matches
 867    catch {unset matchinglines}
 868}
 869
 870proc selcanvline {x y} {
 871    global canv canvy0 ctext linespc selectedline
 872    global lineid linehtag linentag linedtag
 873    set ymax [lindex [$canv cget -scrollregion] 3]
 874    set yfrac [lindex [$canv yview] 0]
 875    set y [expr {$y + $yfrac * $ymax}]
 876    set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
 877    if {$l < 0} {
 878        set l 0
 879    }
 880    if {[info exists selectedline] && $selectedline == $l} return
 881    unmarkmatches
 882    selectline $l
 883}
 884
 885proc selectline {l} {
 886    global canv canv2 canv3 ctext commitinfo selectedline
 887    global lineid linehtag linentag linedtag
 888    global canvy0 linespc nparents treepending
 889    global cflist treediffs currentid sha1entry
 890    global commentend seenfile numcommits
 891    if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
 892    $canv delete secsel
 893    set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
 894               -tags secsel -fill [$canv cget -selectbackground]]
 895    $canv lower $t
 896    $canv2 delete secsel
 897    set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
 898               -tags secsel -fill [$canv2 cget -selectbackground]]
 899    $canv2 lower $t
 900    $canv3 delete secsel
 901    set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
 902               -tags secsel -fill [$canv3 cget -selectbackground]]
 903    $canv3 lower $t
 904    set y [expr {$canvy0 + $l * $linespc}]
 905    set ymax [lindex [$canv cget -scrollregion] 3]
 906    set ytop [expr {($y - $linespc / 2.0 - 1) / $ymax}]
 907    set ybot [expr {($y + $linespc / 2.0 + 1) / $ymax}]
 908    set wnow [$canv yview]
 909    set scrincr [expr {$linespc * 1.0 / $ymax}]
 910    set wtop [lindex $wnow 0]
 911    if {$ytop < $wtop} {
 912        if {$ytop > $wtop - $scrincr} {
 913            set ytop [expr {$wtop - $scrincr}]
 914        }
 915        allcanvs yview moveto $ytop
 916    } elseif {$ybot > [lindex $wnow 1]} {
 917        set wh [expr {[lindex $wnow 1] - $wtop}]
 918        set ytop [expr {$ybot - $wh}]
 919        if {$ytop < $wtop + $scrincr} {
 920            set ytop [expr {$wtop + $scrincr}]
 921        }
 922        allcanvs yview moveto $ytop
 923    }
 924    set selectedline $l
 925
 926    set id $lineid($l)
 927    $sha1entry conf -state normal
 928    $sha1entry delete 0 end
 929    $sha1entry insert 0 $id
 930    $sha1entry selection from 0
 931    $sha1entry selection to end
 932    $sha1entry conf -state readonly
 933
 934    $ctext conf -state normal
 935    $ctext delete 0.0 end
 936    set info $commitinfo($id)
 937    $ctext insert end "Author: [lindex $info 1]  [lindex $info 2]\n"
 938    $ctext insert end "Committer: [lindex $info 3]  [lindex $info 4]\n"
 939    $ctext insert end "\n"
 940    $ctext insert end [lindex $info 5]
 941    $ctext insert end "\n"
 942    $ctext tag delete Comments
 943    $ctext tag remove found 1.0 end
 944    $ctext conf -state disabled
 945    set commentend [$ctext index "end - 1c"]
 946
 947    $cflist delete 0 end
 948    set currentid $id
 949    if {$nparents($id) == 1} {
 950        if {![info exists treediffs($id)]} {
 951            if {![info exists treepending]} {
 952                gettreediffs $id
 953            }
 954        } else {
 955            addtocflist $id
 956        }
 957    }
 958    catch {unset seenfile}
 959}
 960
 961proc selnextline {dir} {
 962    global selectedline
 963    if {![info exists selectedline]} return
 964    set l [expr $selectedline + $dir]
 965    unmarkmatches
 966    selectline $l
 967}
 968
 969proc addtocflist {id} {
 970    global currentid treediffs cflist treepending
 971    if {$id != $currentid} {
 972        gettreediffs $currentid
 973        return
 974    }
 975    $cflist insert end "All files"
 976    foreach f $treediffs($currentid) {
 977        $cflist insert end $f
 978    }
 979    getblobdiffs $id
 980}
 981
 982proc gettreediffs {id} {
 983    global treediffs parents treepending
 984    set treepending $id
 985    set treediffs($id) {}
 986    set p [lindex $parents($id) 0]
 987    if [catch {set gdtf [open "|git-diff-tree -r $p $id" r]}] return
 988    fconfigure $gdtf -blocking 0
 989    fileevent $gdtf readable "gettreediffline $gdtf $id"
 990}
 991
 992proc gettreediffline {gdtf id} {
 993    global treediffs treepending
 994    set n [gets $gdtf line]
 995    if {$n < 0} {
 996        if {![eof $gdtf]} return
 997        close $gdtf
 998        unset treepending
 999        addtocflist $id
1000        return
1001    }
1002    set type [lindex $line 1]
1003    set file [lindex $line 3]
1004    if {$type == "blob"} {
1005        lappend treediffs($id) $file
1006    }
1007}
1008
1009proc getblobdiffs {id} {
1010    global parents diffopts blobdifffd env curdifftag curtagstart
1011    set p [lindex $parents($id) 0]
1012    set env(GIT_DIFF_OPTS) $diffopts
1013    if [catch {set bdf [open "|git-diff-tree -r -p $p $id" r]} err] {
1014        puts "error getting diffs: $err"
1015        return
1016    }
1017    fconfigure $bdf -blocking 0
1018    set blobdifffd($id) $bdf
1019    set curdifftag Comments
1020    set curtagstart 0.0
1021    fileevent $bdf readable "getblobdiffline $bdf $id"
1022}
1023
1024proc getblobdiffline {bdf id} {
1025    global currentid blobdifffd ctext curdifftag curtagstart seenfile
1026    global diffnexthead
1027    set n [gets $bdf line]
1028    if {$n < 0} {
1029        if {[eof $bdf]} {
1030            close $bdf
1031            if {$id == $currentid && $bdf == $blobdifffd($id)} {
1032                $ctext tag add $curdifftag $curtagstart end
1033                set seenfile($curdifftag) 1
1034            }
1035        }
1036        return
1037    }
1038    if {$id != $currentid || $bdf != $blobdifffd($id)} {
1039        return
1040    }
1041    $ctext conf -state normal
1042    if {[regexp {^---[ \t]+([^/])*/(.*)} $line match s1 fname]} {
1043        # start of a new file
1044        $ctext insert end "\n"
1045        $ctext tag add $curdifftag $curtagstart end
1046        set seenfile($curdifftag) 1
1047        set curtagstart [$ctext index "end - 1c"]
1048        if {[info exists diffnexthead]} {
1049            set fname $diffnexthead
1050            unset diffnexthead
1051        }
1052        set curdifftag "f:$fname"
1053        $ctext tag delete $curdifftag
1054        set l [expr {(78 - [string length $fname]) / 2}]
1055        set pad [string range "----------------------------------------" 1 $l]
1056        $ctext insert end "$pad $fname $pad\n" filesep
1057    } elseif {[string range $line 0 2] == "+++"} {
1058        # no need to do anything with this
1059    } elseif {[regexp {^Created: (.*) \(mode: *[0-7]*\)} $line match fn]} {
1060        set diffnexthead $fn
1061    } elseif {[string range $line 0 8] == "Deleted: "} {
1062        set diffnexthead [string range $line 9 end]
1063    } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
1064                   $line match f1l f1c f2l f2c rest]} {
1065        $ctext insert end "\t" hunksep
1066        $ctext insert end "    $f1l    " d0 "    $f2l    " d1
1067        $ctext insert end "    $rest \n" hunksep
1068    } else {
1069        set x [string range $line 0 0]
1070        if {$x == "-" || $x == "+"} {
1071            set tag [expr {$x == "+"}]
1072            set line [string range $line 1 end]
1073            $ctext insert end "$line\n" d$tag
1074        } elseif {$x == " "} {
1075            set line [string range $line 1 end]
1076            $ctext insert end "$line\n"
1077        } else {
1078            # Something else we don't recognize
1079            if {$curdifftag != "Comments"} {
1080                $ctext insert end "\n"
1081                $ctext tag add $curdifftag $curtagstart end
1082                set seenfile($curdifftag) 1
1083                set curtagstart [$ctext index "end - 1c"]
1084                set curdifftag Comments
1085            }
1086            $ctext insert end "$line\n" filesep
1087        }
1088    }
1089    $ctext conf -state disabled
1090}
1091
1092proc listboxsel {} {
1093    global ctext cflist currentid treediffs seenfile
1094    if {![info exists currentid]} return
1095    set sel [$cflist curselection]
1096    if {$sel == {} || [lsearch -exact $sel 0] >= 0} {
1097        # show everything
1098        $ctext tag conf Comments -elide 0
1099        foreach f $treediffs($currentid) {
1100            if [info exists seenfile(f:$f)] {
1101                $ctext tag conf "f:$f" -elide 0
1102            }
1103        }
1104    } else {
1105        # just show selected files
1106        $ctext tag conf Comments -elide 1
1107        set i 1
1108        foreach f $treediffs($currentid) {
1109            set elide [expr {[lsearch -exact $sel $i] < 0}]
1110            if [info exists seenfile(f:$f)] {
1111                $ctext tag conf "f:$f" -elide $elide
1112            }
1113            incr i
1114        }
1115    }
1116}
1117
1118proc setcoords {} {
1119    global linespc charspc canvx0 canvy0 mainfont
1120    set linespc [font metrics $mainfont -linespace]
1121    set charspc [font measure $mainfont "m"]
1122    set canvy0 [expr 3 + 0.5 * $linespc]
1123    set canvx0 [expr 3 + 0.5 * $linespc]
1124}
1125
1126proc redisplay {} {
1127    global selectedline stopped redisplaying phase
1128    if {$stopped > 1} return
1129    if {$phase == "getcommits"} return
1130    set redisplaying 1
1131    if {$phase == "drawgraph"} {
1132        set stopped 1
1133    } else {
1134        drawgraph
1135    }
1136}
1137
1138proc incrfont {inc} {
1139    global mainfont namefont textfont selectedline ctext canv phase
1140    global stopped
1141    unmarkmatches
1142    set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
1143    set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
1144    set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
1145    setcoords
1146    $ctext conf -font $textfont
1147    $ctext tag conf filesep -font [concat $textfont bold]
1148    if {$phase == "getcommits"} {
1149        $canv itemconf textitems -font $mainfont
1150    }
1151    redisplay
1152}
1153
1154proc doquit {} {
1155    global stopped
1156    set stopped 100
1157    destroy .
1158}
1159
1160# defaults...
1161set datemode 0
1162set boldnames 0
1163set diffopts "-U 5 -p"
1164
1165set mainfont {Helvetica 9}
1166set textfont {Courier 9}
1167
1168set colors {green red blue magenta darkgrey brown orange}
1169set colorbycommitter false
1170
1171catch {source ~/.gitk}
1172
1173set namefont $mainfont
1174if {$boldnames} {
1175    lappend namefont bold
1176}
1177
1178set revtreeargs {}
1179foreach arg $argv {
1180    switch -regexp -- $arg {
1181        "^$" { }
1182        "^-b" { set boldnames 1 }
1183        "^-c" { set colorbycommitter 1 }
1184        "^-d" { set datemode 1 }
1185        "^-.*" {
1186            puts stderr "unrecognized option $arg"
1187            exit 1
1188        }
1189        default {
1190            lappend revtreeargs $arg
1191        }
1192    }
1193}
1194
1195set stopped 0
1196set redisplaying 0
1197set stuffsaved 0
1198setcoords
1199makewindow
1200getcommits $revtreeargs