gitkon commit Save the maxwidth setting in the ~/.gitk file. (04c13d3)
   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
  10proc gitdir {} {
  11    global env
  12    if {[info exists env(GIT_DIR)]} {
  13        return $env(GIT_DIR)
  14    } else {
  15        return ".git"
  16    }
  17}
  18
  19proc getcommits {rargs} {
  20    global commits commfd phase canv mainfont env
  21    global startmsecs nextupdate ncmupdate
  22    global ctext maincursor textcursor leftover
  23
  24    # check that we can find a .git directory somewhere...
  25    set gitdir [gitdir]
  26    if {![file isdirectory $gitdir]} {
  27        error_popup "Cannot find the git directory \"$gitdir\"."
  28        exit 1
  29    }
  30    set commits {}
  31    set phase getcommits
  32    set startmsecs [clock clicks -milliseconds]
  33    set nextupdate [expr $startmsecs + 100]
  34    set ncmupdate 1
  35    if [catch {
  36        set parse_args [concat --default HEAD $rargs]
  37        set parsed_args [split [eval exec git-rev-parse $parse_args] "\n"]
  38    }] {
  39        # if git-rev-parse failed for some reason...
  40        if {$rargs == {}} {
  41            set rargs HEAD
  42        }
  43        set parsed_args $rargs
  44    }
  45    if [catch {
  46        set commfd [open "|git-rev-list --header --topo-order --parents $parsed_args" r]
  47    } err] {
  48        puts stderr "Error executing git-rev-list: $err"
  49        exit 1
  50    }
  51    set leftover {}
  52    fconfigure $commfd -blocking 0 -translation lf
  53    fileevent $commfd readable [list getcommitlines $commfd]
  54    $canv delete all
  55    $canv create text 3 3 -anchor nw -text "Reading commits..." \
  56        -font $mainfont -tags textitems
  57    . config -cursor watch
  58    settextcursor watch
  59}
  60
  61proc getcommitlines {commfd}  {
  62    global commits parents cdate children
  63    global commitlisted phase commitinfo nextupdate
  64    global stopped redisplaying leftover
  65
  66    set stuff [read $commfd]
  67    if {$stuff == {}} {
  68        if {![eof $commfd]} return
  69        # set it blocking so we wait for the process to terminate
  70        fconfigure $commfd -blocking 1
  71        if {![catch {close $commfd} err]} {
  72            after idle finishcommits
  73            return
  74        }
  75        if {[string range $err 0 4] == "usage"} {
  76            set err \
  77{Gitk: error reading commits: bad arguments to git-rev-list.
  78(Note: arguments to gitk are passed to git-rev-list
  79to allow selection of commits to be displayed.)}
  80        } else {
  81            set err "Error reading commits: $err"
  82        }
  83        error_popup $err
  84        exit 1
  85    }
  86    set start 0
  87    while 1 {
  88        set i [string first "\0" $stuff $start]
  89        if {$i < 0} {
  90            append leftover [string range $stuff $start end]
  91            return
  92        }
  93        set cmit [string range $stuff $start [expr {$i - 1}]]
  94        if {$start == 0} {
  95            set cmit "$leftover$cmit"
  96            set leftover {}
  97        }
  98        set start [expr {$i + 1}]
  99        set j [string first "\n" $cmit]
 100        set ok 0
 101        if {$j >= 0} {
 102            set ids [string range $cmit 0 [expr {$j - 1}]]
 103            set ok 1
 104            foreach id $ids {
 105                if {![regexp {^[0-9a-f]{40}$} $id]} {
 106                    set ok 0
 107                    break
 108                }
 109            }
 110        }
 111        if {!$ok} {
 112            set shortcmit $cmit
 113            if {[string length $shortcmit] > 80} {
 114                set shortcmit "[string range $shortcmit 0 80]..."
 115            }
 116            error_popup "Can't parse git-rev-list output: {$shortcmit}"
 117            exit 1
 118        }
 119        set id [lindex $ids 0]
 120        set olds [lrange $ids 1 end]
 121        set cmit [string range $cmit [expr {$j + 1}] end]
 122        lappend commits $id
 123        set commitlisted($id) 1
 124        parsecommit $id $cmit 1 [lrange $ids 1 end]
 125        drawcommit $id
 126        if {[clock clicks -milliseconds] >= $nextupdate} {
 127            doupdate 1
 128        }
 129        while {$redisplaying} {
 130            set redisplaying 0
 131            if {$stopped == 1} {
 132                set stopped 0
 133                set phase "getcommits"
 134                foreach id $commits {
 135                    drawcommit $id
 136                    if {$stopped} break
 137                    if {[clock clicks -milliseconds] >= $nextupdate} {
 138                        doupdate 1
 139                    }
 140                }
 141            }
 142        }
 143    }
 144}
 145
 146proc doupdate {reading} {
 147    global commfd nextupdate numcommits ncmupdate
 148
 149    if {$reading} {
 150        fileevent $commfd readable {}
 151    }
 152    update
 153    set nextupdate [expr {[clock clicks -milliseconds] + 100}]
 154    if {$numcommits < 100} {
 155        set ncmupdate [expr {$numcommits + 1}]
 156    } elseif {$numcommits < 10000} {
 157        set ncmupdate [expr {$numcommits + 10}]
 158    } else {
 159        set ncmupdate [expr {$numcommits + 100}]
 160    }
 161    if {$reading} {
 162        fileevent $commfd readable [list getcommitlines $commfd]
 163    }
 164}
 165
 166proc readcommit {id} {
 167    if [catch {set contents [exec git-cat-file commit $id]}] return
 168    parsecommit $id $contents 0 {}
 169}
 170
 171proc parsecommit {id contents listed olds} {
 172    global commitinfo children nchildren parents nparents cdate ncleft
 173
 174    set inhdr 1
 175    set comment {}
 176    set headline {}
 177    set auname {}
 178    set audate {}
 179    set comname {}
 180    set comdate {}
 181    if {![info exists nchildren($id)]} {
 182        set children($id) {}
 183        set nchildren($id) 0
 184        set ncleft($id) 0
 185    }
 186    set parents($id) $olds
 187    set nparents($id) [llength $olds]
 188    foreach p $olds {
 189        if {![info exists nchildren($p)]} {
 190            set children($p) [list $id]
 191            set nchildren($p) 1
 192            set ncleft($p) 1
 193        } elseif {[lsearch -exact $children($p) $id] < 0} {
 194            lappend children($p) $id
 195            incr nchildren($p)
 196            incr ncleft($p)
 197        }
 198    }
 199    foreach line [split $contents "\n"] {
 200        if {$inhdr} {
 201            if {$line == {}} {
 202                set inhdr 0
 203            } else {
 204                set tag [lindex $line 0]
 205                if {$tag == "author"} {
 206                    set x [expr {[llength $line] - 2}]
 207                    set audate [lindex $line $x]
 208                    set auname [lrange $line 1 [expr {$x - 1}]]
 209                } elseif {$tag == "committer"} {
 210                    set x [expr {[llength $line] - 2}]
 211                    set comdate [lindex $line $x]
 212                    set comname [lrange $line 1 [expr {$x - 1}]]
 213                }
 214            }
 215        } else {
 216            if {$comment == {}} {
 217                set headline [string trim $line]
 218            } else {
 219                append comment "\n"
 220            }
 221            if {!$listed} {
 222                # git-rev-list indents the comment by 4 spaces;
 223                # if we got this via git-cat-file, add the indentation
 224                append comment "    "
 225            }
 226            append comment $line
 227        }
 228    }
 229    if {$audate != {}} {
 230        set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"]
 231    }
 232    if {$comdate != {}} {
 233        set cdate($id) $comdate
 234        set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
 235    }
 236    set commitinfo($id) [list $headline $auname $audate \
 237                             $comname $comdate $comment]
 238}
 239
 240proc readrefs {} {
 241    global tagids idtags headids idheads
 242    set tags [glob -nocomplain -types f [gitdir]/refs/tags/*]
 243    foreach f $tags {
 244        catch {
 245            set fd [open $f r]
 246            set line [read $fd]
 247            if {[regexp {^[0-9a-f]{40}} $line id]} {
 248                set direct [file tail $f]
 249                set tagids($direct) $id
 250                lappend idtags($id) $direct
 251                set contents [split [exec git-cat-file tag $id] "\n"]
 252                set obj {}
 253                set type {}
 254                set tag {}
 255                foreach l $contents {
 256                    if {$l == {}} break
 257                    switch -- [lindex $l 0] {
 258                        "object" {set obj [lindex $l 1]}
 259                        "type" {set type [lindex $l 1]}
 260                        "tag" {set tag [string range $l 4 end]}
 261                    }
 262                }
 263                if {$obj != {} && $type == "commit" && $tag != {}} {
 264                    set tagids($tag) $obj
 265                    lappend idtags($obj) $tag
 266                }
 267            }
 268            close $fd
 269        }
 270    }
 271    set heads [glob -nocomplain -types f [gitdir]/refs/heads/*]
 272    foreach f $heads {
 273        catch {
 274            set fd [open $f r]
 275            set line [read $fd 40]
 276            if {[regexp {^[0-9a-f]{40}} $line id]} {
 277                set head [file tail $f]
 278                set headids($head) $line
 279                lappend idheads($line) $head
 280            }
 281            close $fd
 282        }
 283    }
 284}
 285
 286proc error_popup msg {
 287    set w .error
 288    toplevel $w
 289    wm transient $w .
 290    message $w.m -text $msg -justify center -aspect 400
 291    pack $w.m -side top -fill x -padx 20 -pady 20
 292    button $w.ok -text OK -command "destroy $w"
 293    pack $w.ok -side bottom -fill x
 294    bind $w <Visibility> "grab $w; focus $w"
 295    tkwait window $w
 296}
 297
 298proc makewindow {} {
 299    global canv canv2 canv3 linespc charspc ctext cflist textfont
 300    global findtype findtypemenu findloc findstring fstring geometry
 301    global entries sha1entry sha1string sha1but
 302    global maincursor textcursor curtextcursor
 303    global rowctxmenu gaudydiff mergemax
 304
 305    menu .bar
 306    .bar add cascade -label "File" -menu .bar.file
 307    menu .bar.file
 308    .bar.file add command -label "Quit" -command doquit
 309    menu .bar.help
 310    .bar add cascade -label "Help" -menu .bar.help
 311    .bar.help add command -label "About gitk" -command about
 312    . configure -menu .bar
 313
 314    if {![info exists geometry(canv1)]} {
 315        set geometry(canv1) [expr 45 * $charspc]
 316        set geometry(canv2) [expr 30 * $charspc]
 317        set geometry(canv3) [expr 15 * $charspc]
 318        set geometry(canvh) [expr 25 * $linespc + 4]
 319        set geometry(ctextw) 80
 320        set geometry(ctexth) 30
 321        set geometry(cflistw) 30
 322    }
 323    panedwindow .ctop -orient vertical
 324    if {[info exists geometry(width)]} {
 325        .ctop conf -width $geometry(width) -height $geometry(height)
 326        set texth [expr {$geometry(height) - $geometry(canvh) - 56}]
 327        set geometry(ctexth) [expr {($texth - 8) /
 328                                    [font metrics $textfont -linespace]}]
 329    }
 330    frame .ctop.top
 331    frame .ctop.top.bar
 332    pack .ctop.top.bar -side bottom -fill x
 333    set cscroll .ctop.top.csb
 334    scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
 335    pack $cscroll -side right -fill y
 336    panedwindow .ctop.top.clist -orient horizontal -sashpad 0 -handlesize 4
 337    pack .ctop.top.clist -side top -fill both -expand 1
 338    .ctop add .ctop.top
 339    set canv .ctop.top.clist.canv
 340    canvas $canv -height $geometry(canvh) -width $geometry(canv1) \
 341        -bg white -bd 0 \
 342        -yscrollincr $linespc -yscrollcommand "$cscroll set"
 343    .ctop.top.clist add $canv
 344    set canv2 .ctop.top.clist.canv2
 345    canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \
 346        -bg white -bd 0 -yscrollincr $linespc
 347    .ctop.top.clist add $canv2
 348    set canv3 .ctop.top.clist.canv3
 349    canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \
 350        -bg white -bd 0 -yscrollincr $linespc
 351    .ctop.top.clist add $canv3
 352    bind .ctop.top.clist <Configure> {resizeclistpanes %W %w}
 353
 354    set sha1entry .ctop.top.bar.sha1
 355    set entries $sha1entry
 356    set sha1but .ctop.top.bar.sha1label
 357    button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
 358        -command gotocommit -width 8
 359    $sha1but conf -disabledforeground [$sha1but cget -foreground]
 360    pack .ctop.top.bar.sha1label -side left
 361    entry $sha1entry -width 40 -font $textfont -textvariable sha1string
 362    trace add variable sha1string write sha1change
 363    pack $sha1entry -side left -pady 2
 364
 365    image create bitmap bm-left -data {
 366        #define left_width 16
 367        #define left_height 16
 368        static unsigned char left_bits[] = {
 369        0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
 370        0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
 371        0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
 372    }
 373    image create bitmap bm-right -data {
 374        #define right_width 16
 375        #define right_height 16
 376        static unsigned char right_bits[] = {
 377        0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
 378        0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
 379        0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
 380    }
 381    button .ctop.top.bar.leftbut -image bm-left -command goback \
 382        -state disabled -width 26
 383    pack .ctop.top.bar.leftbut -side left -fill y
 384    button .ctop.top.bar.rightbut -image bm-right -command goforw \
 385        -state disabled -width 26
 386    pack .ctop.top.bar.rightbut -side left -fill y
 387
 388    button .ctop.top.bar.findbut -text "Find" -command dofind
 389    pack .ctop.top.bar.findbut -side left
 390    set findstring {}
 391    set fstring .ctop.top.bar.findstring
 392    lappend entries $fstring
 393    entry $fstring -width 30 -font $textfont -textvariable findstring
 394    pack $fstring -side left -expand 1 -fill x
 395    set findtype Exact
 396    set findtypemenu [tk_optionMenu .ctop.top.bar.findtype \
 397                          findtype Exact IgnCase Regexp]
 398    set findloc "All fields"
 399    tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \
 400        Comments Author Committer Files Pickaxe
 401    pack .ctop.top.bar.findloc -side right
 402    pack .ctop.top.bar.findtype -side right
 403    # for making sure type==Exact whenever loc==Pickaxe
 404    trace add variable findloc write findlocchange
 405
 406    panedwindow .ctop.cdet -orient horizontal
 407    .ctop add .ctop.cdet
 408    frame .ctop.cdet.left
 409    set ctext .ctop.cdet.left.ctext
 410    text $ctext -bg white -state disabled -font $textfont \
 411        -width $geometry(ctextw) -height $geometry(ctexth) \
 412        -yscrollcommand ".ctop.cdet.left.sb set" -wrap none
 413    scrollbar .ctop.cdet.left.sb -command "$ctext yview"
 414    pack .ctop.cdet.left.sb -side right -fill y
 415    pack $ctext -side left -fill both -expand 1
 416    .ctop.cdet add .ctop.cdet.left
 417
 418    $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
 419    if {$gaudydiff} {
 420        $ctext tag conf hunksep -back blue -fore white
 421        $ctext tag conf d0 -back "#ff8080"
 422        $ctext tag conf d1 -back green
 423    } else {
 424        $ctext tag conf hunksep -fore blue
 425        $ctext tag conf d0 -fore red
 426        $ctext tag conf d1 -fore "#00a000"
 427        $ctext tag conf m0 -fore red
 428        $ctext tag conf m1 -fore blue
 429        $ctext tag conf m2 -fore green
 430        $ctext tag conf m3 -fore purple
 431        $ctext tag conf m4 -fore brown
 432        $ctext tag conf mmax -fore darkgrey
 433        set mergemax 5
 434        $ctext tag conf mresult -font [concat $textfont bold]
 435        $ctext tag conf msep -font [concat $textfont bold]
 436        $ctext tag conf found -back yellow
 437    }
 438
 439    frame .ctop.cdet.right
 440    set cflist .ctop.cdet.right.cfiles
 441    listbox $cflist -bg white -selectmode extended -width $geometry(cflistw) \
 442        -yscrollcommand ".ctop.cdet.right.sb set"
 443    scrollbar .ctop.cdet.right.sb -command "$cflist yview"
 444    pack .ctop.cdet.right.sb -side right -fill y
 445    pack $cflist -side left -fill both -expand 1
 446    .ctop.cdet add .ctop.cdet.right
 447    bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
 448
 449    pack .ctop -side top -fill both -expand 1
 450
 451    bindall <1> {selcanvline %W %x %y}
 452    #bindall <B1-Motion> {selcanvline %W %x %y}
 453    bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
 454    bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
 455    bindall <2> "allcanvs scan mark 0 %y"
 456    bindall <B2-Motion> "allcanvs scan dragto 0 %y"
 457    bind . <Key-Up> "selnextline -1"
 458    bind . <Key-Down> "selnextline 1"
 459    bind . <Key-Prior> "allcanvs yview scroll -1 pages"
 460    bind . <Key-Next> "allcanvs yview scroll 1 pages"
 461    bindkey <Key-Delete> "$ctext yview scroll -1 pages"
 462    bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
 463    bindkey <Key-space> "$ctext yview scroll 1 pages"
 464    bindkey p "selnextline -1"
 465    bindkey n "selnextline 1"
 466    bindkey b "$ctext yview scroll -1 pages"
 467    bindkey d "$ctext yview scroll 18 units"
 468    bindkey u "$ctext yview scroll -18 units"
 469    bindkey / {findnext 1}
 470    bindkey <Key-Return> {findnext 0}
 471    bindkey ? findprev
 472    bindkey f nextfile
 473    bind . <Control-q> doquit
 474    bind . <Control-f> dofind
 475    bind . <Control-g> {findnext 0}
 476    bind . <Control-r> findprev
 477    bind . <Control-equal> {incrfont 1}
 478    bind . <Control-KP_Add> {incrfont 1}
 479    bind . <Control-minus> {incrfont -1}
 480    bind . <Control-KP_Subtract> {incrfont -1}
 481    bind $cflist <<ListboxSelect>> listboxsel
 482    bind . <Destroy> {savestuff %W}
 483    bind . <Button-1> "click %W"
 484    bind $fstring <Key-Return> dofind
 485    bind $sha1entry <Key-Return> gotocommit
 486    bind $sha1entry <<PasteSelection>> clearsha1
 487
 488    set maincursor [. cget -cursor]
 489    set textcursor [$ctext cget -cursor]
 490    set curtextcursor $textcursor
 491
 492    set rowctxmenu .rowctxmenu
 493    menu $rowctxmenu -tearoff 0
 494    $rowctxmenu add command -label "Diff this -> selected" \
 495        -command {diffvssel 0}
 496    $rowctxmenu add command -label "Diff selected -> this" \
 497        -command {diffvssel 1}
 498    $rowctxmenu add command -label "Make patch" -command mkpatch
 499    $rowctxmenu add command -label "Create tag" -command mktag
 500    $rowctxmenu add command -label "Write commit to file" -command writecommit
 501}
 502
 503# when we make a key binding for the toplevel, make sure
 504# it doesn't get triggered when that key is pressed in the
 505# find string entry widget.
 506proc bindkey {ev script} {
 507    global entries
 508    bind . $ev $script
 509    set escript [bind Entry $ev]
 510    if {$escript == {}} {
 511        set escript [bind Entry <Key>]
 512    }
 513    foreach e $entries {
 514        bind $e $ev "$escript; break"
 515    }
 516}
 517
 518# set the focus back to the toplevel for any click outside
 519# the entry widgets
 520proc click {w} {
 521    global entries
 522    foreach e $entries {
 523        if {$w == $e} return
 524    }
 525    focus .
 526}
 527
 528proc savestuff {w} {
 529    global canv canv2 canv3 ctext cflist mainfont textfont
 530    global stuffsaved findmergefiles gaudydiff maxgraphpct
 531    global maxwidth
 532
 533    if {$stuffsaved} return
 534    if {![winfo viewable .]} return
 535    catch {
 536        set f [open "~/.gitk-new" w]
 537        puts $f [list set mainfont $mainfont]
 538        puts $f [list set textfont $textfont]
 539        puts $f [list set findmergefiles $findmergefiles]
 540        puts $f [list set gaudydiff $gaudydiff]
 541        puts $f [list set maxgraphpct $maxgraphpct]
 542        puts $f [list set maxwidth $maxwidth]
 543        puts $f "set geometry(width) [winfo width .ctop]"
 544        puts $f "set geometry(height) [winfo height .ctop]"
 545        puts $f "set geometry(canv1) [expr [winfo width $canv]-2]"
 546        puts $f "set geometry(canv2) [expr [winfo width $canv2]-2]"
 547        puts $f "set geometry(canv3) [expr [winfo width $canv3]-2]"
 548        puts $f "set geometry(canvh) [expr [winfo height $canv]-2]"
 549        set wid [expr {([winfo width $ctext] - 8) \
 550                           / [font measure $textfont "0"]}]
 551        puts $f "set geometry(ctextw) $wid"
 552        set wid [expr {([winfo width $cflist] - 11) \
 553                           / [font measure [$cflist cget -font] "0"]}]
 554        puts $f "set geometry(cflistw) $wid"
 555        close $f
 556        file rename -force "~/.gitk-new" "~/.gitk"
 557    }
 558    set stuffsaved 1
 559}
 560
 561proc resizeclistpanes {win w} {
 562    global oldwidth
 563    if [info exists oldwidth($win)] {
 564        set s0 [$win sash coord 0]
 565        set s1 [$win sash coord 1]
 566        if {$w < 60} {
 567            set sash0 [expr {int($w/2 - 2)}]
 568            set sash1 [expr {int($w*5/6 - 2)}]
 569        } else {
 570            set factor [expr {1.0 * $w / $oldwidth($win)}]
 571            set sash0 [expr {int($factor * [lindex $s0 0])}]
 572            set sash1 [expr {int($factor * [lindex $s1 0])}]
 573            if {$sash0 < 30} {
 574                set sash0 30
 575            }
 576            if {$sash1 < $sash0 + 20} {
 577                set sash1 [expr $sash0 + 20]
 578            }
 579            if {$sash1 > $w - 10} {
 580                set sash1 [expr $w - 10]
 581                if {$sash0 > $sash1 - 20} {
 582                    set sash0 [expr $sash1 - 20]
 583                }
 584            }
 585        }
 586        $win sash place 0 $sash0 [lindex $s0 1]
 587        $win sash place 1 $sash1 [lindex $s1 1]
 588    }
 589    set oldwidth($win) $w
 590}
 591
 592proc resizecdetpanes {win w} {
 593    global oldwidth
 594    if [info exists oldwidth($win)] {
 595        set s0 [$win sash coord 0]
 596        if {$w < 60} {
 597            set sash0 [expr {int($w*3/4 - 2)}]
 598        } else {
 599            set factor [expr {1.0 * $w / $oldwidth($win)}]
 600            set sash0 [expr {int($factor * [lindex $s0 0])}]
 601            if {$sash0 < 45} {
 602                set sash0 45
 603            }
 604            if {$sash0 > $w - 15} {
 605                set sash0 [expr $w - 15]
 606            }
 607        }
 608        $win sash place 0 $sash0 [lindex $s0 1]
 609    }
 610    set oldwidth($win) $w
 611}
 612
 613proc allcanvs args {
 614    global canv canv2 canv3
 615    eval $canv $args
 616    eval $canv2 $args
 617    eval $canv3 $args
 618}
 619
 620proc bindall {event action} {
 621    global canv canv2 canv3
 622    bind $canv $event $action
 623    bind $canv2 $event $action
 624    bind $canv3 $event $action
 625}
 626
 627proc about {} {
 628    set w .about
 629    if {[winfo exists $w]} {
 630        raise $w
 631        return
 632    }
 633    toplevel $w
 634    wm title $w "About gitk"
 635    message $w.m -text {
 636Gitk version 1.2
 637
 638Copyright © 2005 Paul Mackerras
 639
 640Use and redistribute under the terms of the GNU General Public License} \
 641            -justify center -aspect 400
 642    pack $w.m -side top -fill x -padx 20 -pady 20
 643    button $w.ok -text Close -command "destroy $w"
 644    pack $w.ok -side bottom
 645}
 646
 647proc assigncolor {id} {
 648    global commitinfo colormap commcolors colors nextcolor
 649    global parents nparents children nchildren
 650    global cornercrossings crossings
 651
 652    if [info exists colormap($id)] return
 653    set ncolors [llength $colors]
 654    if {$nparents($id) <= 1 && $nchildren($id) == 1} {
 655        set child [lindex $children($id) 0]
 656        if {[info exists colormap($child)]
 657            && $nparents($child) == 1} {
 658            set colormap($id) $colormap($child)
 659            return
 660        }
 661    }
 662    set badcolors {}
 663    if {[info exists cornercrossings($id)]} {
 664        foreach x $cornercrossings($id) {
 665            if {[info exists colormap($x)]
 666                && [lsearch -exact $badcolors $colormap($x)] < 0} {
 667                lappend badcolors $colormap($x)
 668            }
 669        }
 670        if {[llength $badcolors] >= $ncolors} {
 671            set badcolors {}
 672        }
 673    }
 674    set origbad $badcolors
 675    if {[llength $badcolors] < $ncolors - 1} {
 676        if {[info exists crossings($id)]} {
 677            foreach x $crossings($id) {
 678                if {[info exists colormap($x)]
 679                    && [lsearch -exact $badcolors $colormap($x)] < 0} {
 680                    lappend badcolors $colormap($x)
 681                }
 682            }
 683            if {[llength $badcolors] >= $ncolors} {
 684                set badcolors $origbad
 685            }
 686        }
 687        set origbad $badcolors
 688    }
 689    if {[llength $badcolors] < $ncolors - 1} {
 690        foreach child $children($id) {
 691            if {[info exists colormap($child)]
 692                && [lsearch -exact $badcolors $colormap($child)] < 0} {
 693                lappend badcolors $colormap($child)
 694            }
 695            if {[info exists parents($child)]} {
 696                foreach p $parents($child) {
 697                    if {[info exists colormap($p)]
 698                        && [lsearch -exact $badcolors $colormap($p)] < 0} {
 699                        lappend badcolors $colormap($p)
 700                    }
 701                }
 702            }
 703        }
 704        if {[llength $badcolors] >= $ncolors} {
 705            set badcolors $origbad
 706        }
 707    }
 708    for {set i 0} {$i <= $ncolors} {incr i} {
 709        set c [lindex $colors $nextcolor]
 710        if {[incr nextcolor] >= $ncolors} {
 711            set nextcolor 0
 712        }
 713        if {[lsearch -exact $badcolors $c]} break
 714    }
 715    set colormap($id) $c
 716}
 717
 718proc initgraph {} {
 719    global canvy canvy0 lineno numcommits nextcolor linespc
 720    global mainline mainlinearrow sidelines
 721    global nchildren ncleft
 722    global displist nhyperspace
 723
 724    allcanvs delete all
 725    set nextcolor 0
 726    set canvy $canvy0
 727    set lineno -1
 728    set numcommits 0
 729    catch {unset mainline}
 730    catch {unset mainlinearrow}
 731    catch {unset sidelines}
 732    foreach id [array names nchildren] {
 733        set ncleft($id) $nchildren($id)
 734    }
 735    set displist {}
 736    set nhyperspace 0
 737}
 738
 739proc bindline {t id} {
 740    global canv
 741
 742    $canv bind $t <Enter> "lineenter %x %y $id"
 743    $canv bind $t <Motion> "linemotion %x %y $id"
 744    $canv bind $t <Leave> "lineleave $id"
 745    $canv bind $t <Button-1> "lineclick %x %y $id 1"
 746}
 747
 748# level here is an index in displist
 749proc drawcommitline {level} {
 750    global parents children nparents displist
 751    global canv canv2 canv3 mainfont namefont canvy linespc
 752    global lineid linehtag linentag linedtag commitinfo
 753    global colormap numcommits currentparents dupparents
 754    global idtags idline idheads
 755    global lineno lthickness mainline mainlinearrow sidelines
 756    global commitlisted rowtextx idpos lastuse displist
 757    global oldnlines olddlevel olddisplist
 758
 759    incr numcommits
 760    incr lineno
 761    set id [lindex $displist $level]
 762    set lastuse($id) $lineno
 763    set lineid($lineno) $id
 764    set idline($id) $lineno
 765    set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}]
 766    if {![info exists commitinfo($id)]} {
 767        readcommit $id
 768        if {![info exists commitinfo($id)]} {
 769            set commitinfo($id) {"No commit information available"}
 770            set nparents($id) 0
 771        }
 772    }
 773    assigncolor $id
 774    set currentparents {}
 775    set dupparents {}
 776    if {[info exists commitlisted($id)] && [info exists parents($id)]} {
 777        foreach p $parents($id) {
 778            if {[lsearch -exact $currentparents $p] < 0} {
 779                lappend currentparents $p
 780            } else {
 781                # remember that this parent was listed twice
 782                lappend dupparents $p
 783            }
 784        }
 785    }
 786    set x [xcoord $level $level $lineno]
 787    set y1 $canvy
 788    set canvy [expr $canvy + $linespc]
 789    allcanvs conf -scrollregion \
 790        [list 0 0 0 [expr $y1 + 0.5 * $linespc + 2]]
 791    if {[info exists mainline($id)]} {
 792        lappend mainline($id) $x $y1
 793        if {$mainlinearrow($id) ne "none"} {
 794            set mainline($id) [trimdiagstart $mainline($id)]
 795        }
 796        set t [$canv create line $mainline($id) \
 797                   -width $lthickness -fill $colormap($id) \
 798                   -arrow $mainlinearrow($id)]
 799        $canv lower $t
 800        bindline $t $id
 801    }
 802    if {[info exists sidelines($id)]} {
 803        foreach ls $sidelines($id) {
 804            set coords [lindex $ls 0]
 805            set thick [lindex $ls 1]
 806            set arrow [lindex $ls 2]
 807            set t [$canv create line $coords -fill $colormap($id) \
 808                       -width [expr {$thick * $lthickness}] -arrow $arrow]
 809            $canv lower $t
 810            bindline $t $id
 811        }
 812    }
 813    set orad [expr {$linespc / 3}]
 814    set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \
 815               [expr $x + $orad - 1] [expr $y1 + $orad - 1] \
 816               -fill $ofill -outline black -width 1]
 817    $canv raise $t
 818    $canv bind $t <1> {selcanvline {} %x %y}
 819    set xt [xcoord [llength $displist] $level $lineno]
 820    if {[llength $currentparents] > 2} {
 821        set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}]
 822    }
 823    set rowtextx($lineno) $xt
 824    set idpos($id) [list $x $xt $y1]
 825    if {[info exists idtags($id)] || [info exists idheads($id)]} {
 826        set xt [drawtags $id $x $xt $y1]
 827    }
 828    set headline [lindex $commitinfo($id) 0]
 829    set name [lindex $commitinfo($id) 1]
 830    set date [lindex $commitinfo($id) 2]
 831    set linehtag($lineno) [$canv create text $xt $y1 -anchor w \
 832                               -text $headline -font $mainfont ]
 833    $canv bind $linehtag($lineno) <Button-3> "rowmenu %X %Y $id"
 834    set linentag($lineno) [$canv2 create text 3 $y1 -anchor w \
 835                               -text $name -font $namefont]
 836    set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \
 837                               -text $date -font $mainfont]
 838
 839    set olddlevel $level
 840    set olddisplist $displist
 841    set oldnlines [llength $displist]
 842}
 843
 844proc drawtags {id x xt y1} {
 845    global idtags idheads
 846    global linespc lthickness
 847    global canv mainfont
 848
 849    set marks {}
 850    set ntags 0
 851    if {[info exists idtags($id)]} {
 852        set marks $idtags($id)
 853        set ntags [llength $marks]
 854    }
 855    if {[info exists idheads($id)]} {
 856        set marks [concat $marks $idheads($id)]
 857    }
 858    if {$marks eq {}} {
 859        return $xt
 860    }
 861
 862    set delta [expr {int(0.5 * ($linespc - $lthickness))}]
 863    set yt [expr $y1 - 0.5 * $linespc]
 864    set yb [expr $yt + $linespc - 1]
 865    set xvals {}
 866    set wvals {}
 867    foreach tag $marks {
 868        set wid [font measure $mainfont $tag]
 869        lappend xvals $xt
 870        lappend wvals $wid
 871        set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
 872    }
 873    set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
 874               -width $lthickness -fill black -tags tag.$id]
 875    $canv lower $t
 876    foreach tag $marks x $xvals wid $wvals {
 877        set xl [expr $x + $delta]
 878        set xr [expr $x + $delta + $wid + $lthickness]
 879        if {[incr ntags -1] >= 0} {
 880            # draw a tag
 881            $canv create polygon $x [expr $yt + $delta] $xl $yt\
 882                $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \
 883                -width 1 -outline black -fill yellow -tags tag.$id
 884        } else {
 885            # draw a head
 886            set xl [expr $xl - $delta/2]
 887            $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
 888                -width 1 -outline black -fill green -tags tag.$id
 889        }
 890        $canv create text $xl $y1 -anchor w -text $tag \
 891            -font $mainfont -tags tag.$id
 892    }
 893    return $xt
 894}
 895
 896proc notecrossings {id lo hi corner} {
 897    global olddisplist crossings cornercrossings
 898
 899    for {set i $lo} {[incr i] < $hi} {} {
 900        set p [lindex $olddisplist $i]
 901        if {$p == {}} continue
 902        if {$i == $corner} {
 903            if {![info exists cornercrossings($id)]
 904                || [lsearch -exact $cornercrossings($id) $p] < 0} {
 905                lappend cornercrossings($id) $p
 906            }
 907            if {![info exists cornercrossings($p)]
 908                || [lsearch -exact $cornercrossings($p) $id] < 0} {
 909                lappend cornercrossings($p) $id
 910            }
 911        } else {
 912            if {![info exists crossings($id)]
 913                || [lsearch -exact $crossings($id) $p] < 0} {
 914                lappend crossings($id) $p
 915            }
 916            if {![info exists crossings($p)]
 917                || [lsearch -exact $crossings($p) $id] < 0} {
 918                lappend crossings($p) $id
 919            }
 920        }
 921    }
 922}
 923
 924proc xcoord {i level ln} {
 925    global canvx0 xspc1 xspc2
 926
 927    set x [expr {$canvx0 + $i * $xspc1($ln)}]
 928    if {$i > 0 && $i == $level} {
 929        set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
 930    } elseif {$i > $level} {
 931        set x [expr {$x + $xspc2 - $xspc1($ln)}]
 932    }
 933    return $x
 934}
 935
 936# it seems Tk can't draw arrows on the end of diagonal line segments...
 937proc trimdiagend {line} {
 938    while {[llength $line] > 4} {
 939        set x1 [lindex $line end-3]
 940        set y1 [lindex $line end-2]
 941        set x2 [lindex $line end-1]
 942        set y2 [lindex $line end]
 943        if {($x1 == $x2) != ($y1 == $y2)} break
 944        set line [lreplace $line end-1 end]
 945    }
 946    return $line
 947}
 948
 949proc trimdiagstart {line} {
 950    while {[llength $line] > 4} {
 951        set x1 [lindex $line 0]
 952        set y1 [lindex $line 1]
 953        set x2 [lindex $line 2]
 954        set y2 [lindex $line 3]
 955        if {($x1 == $x2) != ($y1 == $y2)} break
 956        set line [lreplace $line 0 1]
 957    }
 958    return $line
 959}
 960
 961proc drawslants {id needonscreen nohs} {
 962    global canv mainline mainlinearrow sidelines
 963    global canvx0 canvy xspc1 xspc2 lthickness
 964    global currentparents dupparents
 965    global lthickness linespc canvy colormap lineno geometry
 966    global maxgraphpct maxwidth
 967    global displist onscreen lastuse
 968    global parents commitlisted
 969    global oldnlines olddlevel olddisplist
 970    global nhyperspace numcommits nnewparents
 971
 972    if {$lineno < 0} {
 973        lappend displist $id
 974        set onscreen($id) 1
 975        return 0
 976    }
 977
 978    set y1 [expr {$canvy - $linespc}]
 979    set y2 $canvy
 980
 981    # work out what we need to get back on screen
 982    set reins {}
 983    if {$onscreen($id) < 0} {
 984        # next to do isn't displayed, better get it on screen...
 985        lappend reins [list $id 0]
 986    }
 987    # make sure all the previous commits's parents are on the screen
 988    foreach p $currentparents {
 989        if {$onscreen($p) < 0} {
 990            lappend reins [list $p 0]
 991        }
 992    }
 993    # bring back anything requested by caller
 994    if {$needonscreen ne {}} {
 995        lappend reins $needonscreen
 996    }
 997
 998    # try the shortcut
 999    if {$currentparents == $id && $onscreen($id) == 0 && $reins eq {}} {
1000        set dlevel $olddlevel
1001        set x [xcoord $dlevel $dlevel $lineno]
1002        set mainline($id) [list $x $y1]
1003        set mainlinearrow($id) none
1004        set lastuse($id) $lineno
1005        set displist [lreplace $displist $dlevel $dlevel $id]
1006        set onscreen($id) 1
1007        set xspc1([expr {$lineno + 1}]) $xspc1($lineno)
1008        return $dlevel
1009    }
1010
1011    # update displist
1012    set displist [lreplace $displist $olddlevel $olddlevel]
1013    set j $olddlevel
1014    foreach p $currentparents {
1015        set lastuse($p) $lineno
1016        if {$onscreen($p) == 0} {
1017            set displist [linsert $displist $j $p]
1018            set onscreen($p) 1
1019            incr j
1020        }
1021    }
1022    if {$onscreen($id) == 0} {
1023        lappend displist $id
1024        set onscreen($id) 1
1025    }
1026
1027    # remove the null entry if present
1028    set nullentry [lsearch -exact $displist {}]
1029    if {$nullentry >= 0} {
1030        set displist [lreplace $displist $nullentry $nullentry]
1031    }
1032
1033    # bring back the ones we need now (if we did it earlier
1034    # it would change displist and invalidate olddlevel)
1035    foreach pi $reins {
1036        # test again in case of duplicates in reins
1037        set p [lindex $pi 0]
1038        if {$onscreen($p) < 0} {
1039            set onscreen($p) 1
1040            set lastuse($p) $lineno
1041            set displist [linsert $displist [lindex $pi 1] $p]
1042            incr nhyperspace -1
1043        }
1044    }
1045
1046    set lastuse($id) $lineno
1047
1048    # see if we need to make any lines jump off into hyperspace
1049    set displ [llength $displist]
1050    if {$displ > $maxwidth} {
1051        set ages {}
1052        foreach x $displist {
1053            lappend ages [list $lastuse($x) $x]
1054        }
1055        set ages [lsort -integer -index 0 $ages]
1056        set k 0
1057        while {$displ > $maxwidth} {
1058            set use [lindex $ages $k 0]
1059            set victim [lindex $ages $k 1]
1060            if {$use >= $lineno - 5} break
1061            incr k
1062            if {[lsearch -exact $nohs $victim] >= 0} continue
1063            set i [lsearch -exact $displist $victim]
1064            set displist [lreplace $displist $i $i]
1065            set onscreen($victim) -1
1066            incr nhyperspace
1067            incr displ -1
1068            if {$i < $nullentry} {
1069                incr nullentry -1
1070            }
1071            set x [lindex $mainline($victim) end-1]
1072            lappend mainline($victim) $x $y1
1073            set line [trimdiagend $mainline($victim)]
1074            set arrow "last"
1075            if {$mainlinearrow($victim) ne "none"} {
1076                set line [trimdiagstart $line]
1077                set arrow "both"
1078            }
1079            lappend sidelines($victim) [list $line 1 $arrow]
1080            unset mainline($victim)
1081        }
1082    }
1083
1084    set dlevel [lsearch -exact $displist $id]
1085
1086    # If we are reducing, put in a null entry
1087    if {$displ < $oldnlines} {
1088        # does the next line look like a merge?
1089        # i.e. does it have > 1 new parent?
1090        if {$nnewparents($id) > 1} {
1091            set i [expr {$dlevel + 1}]
1092        } elseif {$nnewparents([lindex $olddisplist $olddlevel]) == 0} {
1093            set i $olddlevel
1094            if {$nullentry >= 0 && $nullentry < $i} {
1095                incr i -1
1096            }
1097        } elseif {$nullentry >= 0} {
1098            set i $nullentry
1099            while {$i < $displ
1100                   && [lindex $olddisplist $i] == [lindex $displist $i]} {
1101                incr i
1102            }
1103        } else {
1104            set i $olddlevel
1105            if {$dlevel >= $i} {
1106                incr i
1107            }
1108        }
1109        if {$i < $displ} {
1110            set displist [linsert $displist $i {}]
1111            incr displ
1112            if {$dlevel >= $i} {
1113                incr dlevel
1114            }
1115        }
1116    }
1117
1118    # decide on the line spacing for the next line
1119    set lj [expr {$lineno + 1}]
1120    set maxw [expr {$maxgraphpct * $geometry(canv1) / 100}]
1121    if {$displ <= 1 || $canvx0 + $displ * $xspc2 <= $maxw} {
1122        set xspc1($lj) $xspc2
1123    } else {
1124        set xspc1($lj) [expr {($maxw - $canvx0 - $xspc2) / ($displ - 1)}]
1125        if {$xspc1($lj) < $lthickness} {
1126            set xspc1($lj) $lthickness
1127        }
1128    }
1129
1130    foreach idi $reins {
1131        set id [lindex $idi 0]
1132        set j [lsearch -exact $displist $id]
1133        set xj [xcoord $j $dlevel $lj]
1134        set mainline($id) [list $xj $y2]
1135        set mainlinearrow($id) first
1136    }
1137
1138    set i -1
1139    foreach id $olddisplist {
1140        incr i
1141        if {$id == {}} continue
1142        if {$onscreen($id) <= 0} continue
1143        set xi [xcoord $i $olddlevel $lineno]
1144        if {$i == $olddlevel} {
1145            foreach p $currentparents {
1146                set j [lsearch -exact $displist $p]
1147                set coords [list $xi $y1]
1148                set xj [xcoord $j $dlevel $lj]
1149                if {$xj < $xi - $linespc} {
1150                    lappend coords [expr {$xj + $linespc}] $y1
1151                    notecrossings $p $j $i [expr {$j + 1}]
1152                } elseif {$xj > $xi + $linespc} {
1153                    lappend coords [expr {$xj - $linespc}] $y1
1154                    notecrossings $p $i $j [expr {$j - 1}]
1155                }
1156                if {[lsearch -exact $dupparents $p] >= 0} {
1157                    # draw a double-width line to indicate the doubled parent
1158                    lappend coords $xj $y2
1159                    lappend sidelines($p) [list $coords 2 none]
1160                    if {![info exists mainline($p)]} {
1161                        set mainline($p) [list $xj $y2]
1162                        set mainlinearrow($p) none
1163                    }
1164                } else {
1165                    # normal case, no parent duplicated
1166                    set yb $y2
1167                    set dx [expr {abs($xi - $xj)}]
1168                    if {0 && $dx < $linespc} {
1169                        set yb [expr {$y1 + $dx}]
1170                    }
1171                    if {![info exists mainline($p)]} {
1172                        if {$xi != $xj} {
1173                            lappend coords $xj $yb
1174                        }
1175                        set mainline($p) $coords
1176                        set mainlinearrow($p) none
1177                    } else {
1178                        lappend coords $xj $yb
1179                        if {$yb < $y2} {
1180                            lappend coords $xj $y2
1181                        }
1182                        lappend sidelines($p) [list $coords 1 none]
1183                    }
1184                }
1185            }
1186        } else {
1187            set j $i
1188            if {[lindex $displist $i] != $id} {
1189                set j [lsearch -exact $displist $id]
1190            }
1191            if {$j != $i || $xspc1($lineno) != $xspc1($lj)
1192                || ($olddlevel < $i && $i < $dlevel)
1193                || ($dlevel < $i && $i < $olddlevel)} {
1194                set xj [xcoord $j $dlevel $lj]
1195                lappend mainline($id) $xi $y1 $xj $y2
1196            }
1197        }
1198    }
1199    return $dlevel
1200}
1201
1202# search for x in a list of lists
1203proc llsearch {llist x} {
1204    set i 0
1205    foreach l $llist {
1206        if {$l == $x || [lsearch -exact $l $x] >= 0} {
1207            return $i
1208        }
1209        incr i
1210    }
1211    return -1
1212}
1213
1214proc drawmore {reading} {
1215    global displayorder numcommits ncmupdate nextupdate
1216    global stopped nhyperspace parents commitlisted
1217    global maxwidth onscreen displist currentparents olddlevel
1218
1219    set n [llength $displayorder]
1220    while {$numcommits < $n} {
1221        set id [lindex $displayorder $numcommits]
1222        set ctxend [expr {$numcommits + 10}]
1223        if {!$reading && $ctxend > $n} {
1224            set ctxend $n
1225        }
1226        set dlist {}
1227        if {$numcommits > 0} {
1228            set dlist [lreplace $displist $olddlevel $olddlevel]
1229            set i $olddlevel
1230            foreach p $currentparents {
1231                if {$onscreen($p) == 0} {
1232                    set dlist [linsert $dlist $i $p]
1233                    incr i
1234                }
1235            }
1236        }
1237        set nohs {}
1238        set reins {}
1239        set isfat [expr {[llength $dlist] > $maxwidth}]
1240        if {$nhyperspace > 0 || $isfat} {
1241            if {$ctxend > $n} break
1242            # work out what to bring back and
1243            # what we want to don't want to send into hyperspace
1244            set room 1
1245            for {set k $numcommits} {$k < $ctxend} {incr k} {
1246                set x [lindex $displayorder $k]
1247                set i [llsearch $dlist $x]
1248                if {$i < 0} {
1249                    set i [llength $dlist]
1250                    lappend dlist $x
1251                }
1252                if {[lsearch -exact $nohs $x] < 0} {
1253                    lappend nohs $x
1254                }
1255                if {$reins eq {} && $onscreen($x) < 0 && $room} {
1256                    set reins [list $x $i]
1257                }
1258                set newp {}
1259                if {[info exists commitlisted($x)]} {
1260                    set right 0
1261                    foreach p $parents($x) {
1262                        if {[llsearch $dlist $p] < 0} {
1263                            lappend newp $p
1264                            if {[lsearch -exact $nohs $p] < 0} {
1265                                lappend nohs $p
1266                            }
1267                            if {$reins eq {} && $onscreen($p) < 0 && $room} {
1268                                set reins [list $p [expr {$i + $right}]]
1269                            }
1270                        }
1271                        set right 1
1272                    }
1273                }
1274                set l [lindex $dlist $i]
1275                if {[llength $l] == 1} {
1276                    set l $newp
1277                } else {
1278                    set j [lsearch -exact $l $x]
1279                    set l [concat [lreplace $l $j $j] $newp]
1280                }
1281                set dlist [lreplace $dlist $i $i $l]
1282                if {$room && $isfat && [llength $newp] <= 1} {
1283                    set room 0
1284                }
1285            }
1286        }
1287
1288        set dlevel [drawslants $id $reins $nohs]
1289        drawcommitline $dlevel
1290        if {[clock clicks -milliseconds] >= $nextupdate
1291            && $numcommits >= $ncmupdate} {
1292            doupdate $reading
1293            if {$stopped} break
1294        }
1295    }
1296}
1297
1298# level here is an index in todo
1299proc updatetodo {level noshortcut} {
1300    global ncleft todo nnewparents
1301    global commitlisted parents onscreen
1302
1303    set id [lindex $todo $level]
1304    set olds {}
1305    if {[info exists commitlisted($id)]} {
1306        foreach p $parents($id) {
1307            if {[lsearch -exact $olds $p] < 0} {
1308                lappend olds $p
1309            }
1310        }
1311    }
1312    if {!$noshortcut && [llength $olds] == 1} {
1313        set p [lindex $olds 0]
1314        if {$ncleft($p) == 1 && [lsearch -exact $todo $p] < 0} {
1315            set ncleft($p) 0
1316            set todo [lreplace $todo $level $level $p]
1317            set onscreen($p) 0
1318            set nnewparents($id) 1
1319            return 0
1320        }
1321    }
1322
1323    set todo [lreplace $todo $level $level]
1324    set i $level
1325    set n 0
1326    foreach p $olds {
1327        incr ncleft($p) -1
1328        set k [lsearch -exact $todo $p]
1329        if {$k < 0} {
1330            set todo [linsert $todo $i $p]
1331            set onscreen($p) 0
1332            incr i
1333            incr n
1334        }
1335    }
1336    set nnewparents($id) $n
1337
1338    return 1
1339}
1340
1341proc decidenext {{noread 0}} {
1342    global ncleft todo
1343    global datemode cdate
1344    global commitinfo
1345
1346    # choose which one to do next time around
1347    set todol [llength $todo]
1348    set level -1
1349    set latest {}
1350    for {set k $todol} {[incr k -1] >= 0} {} {
1351        set p [lindex $todo $k]
1352        if {$ncleft($p) == 0} {
1353            if {$datemode} {
1354                if {![info exists commitinfo($p)]} {
1355                    if {$noread} {
1356                        return {}
1357                    }
1358                    readcommit $p
1359                }
1360                if {$latest == {} || $cdate($p) > $latest} {
1361                    set level $k
1362                    set latest $cdate($p)
1363                }
1364            } else {
1365                set level $k
1366                break
1367            }
1368        }
1369    }
1370    if {$level < 0} {
1371        if {$todo != {}} {
1372            puts "ERROR: none of the pending commits can be done yet:"
1373            foreach p $todo {
1374                puts "  $p ($ncleft($p))"
1375            }
1376        }
1377        return -1
1378    }
1379
1380    return $level
1381}
1382
1383proc drawcommit {id} {
1384    global phase todo nchildren datemode nextupdate
1385    global numcommits ncmupdate displayorder todo onscreen
1386
1387    if {$phase != "incrdraw"} {
1388        set phase incrdraw
1389        set displayorder {}
1390        set todo {}
1391        initgraph
1392    }
1393    if {$nchildren($id) == 0} {
1394        lappend todo $id
1395        set onscreen($id) 0
1396    }
1397    set level [decidenext 1]
1398    if {$level == {} || $id != [lindex $todo $level]} {
1399        return
1400    }
1401    while 1 {
1402        lappend displayorder [lindex $todo $level]
1403        if {[updatetodo $level $datemode]} {
1404            set level [decidenext 1]
1405            if {$level == {}} break
1406        }
1407        set id [lindex $todo $level]
1408        if {![info exists commitlisted($id)]} {
1409            break
1410        }
1411    }
1412    drawmore 1
1413}
1414
1415proc finishcommits {} {
1416    global phase
1417    global canv mainfont ctext maincursor textcursor
1418
1419    if {$phase != "incrdraw"} {
1420        $canv delete all
1421        $canv create text 3 3 -anchor nw -text "No commits selected" \
1422            -font $mainfont -tags textitems
1423        set phase {}
1424    } else {
1425        drawrest
1426    }
1427    . config -cursor $maincursor
1428    settextcursor $textcursor
1429}
1430
1431# Don't change the text pane cursor if it is currently the hand cursor,
1432# showing that we are over a sha1 ID link.
1433proc settextcursor {c} {
1434    global ctext curtextcursor
1435
1436    if {[$ctext cget -cursor] == $curtextcursor} {
1437        $ctext config -cursor $c
1438    }
1439    set curtextcursor $c
1440}
1441
1442proc drawgraph {} {
1443    global nextupdate startmsecs ncmupdate
1444    global displayorder onscreen
1445
1446    if {$displayorder == {}} return
1447    set startmsecs [clock clicks -milliseconds]
1448    set nextupdate [expr $startmsecs + 100]
1449    set ncmupdate 1
1450    initgraph
1451    foreach id $displayorder {
1452        set onscreen($id) 0
1453    }
1454    drawmore 0
1455}
1456
1457proc drawrest {} {
1458    global phase stopped redisplaying selectedline
1459    global datemode todo displayorder
1460    global numcommits ncmupdate
1461    global nextupdate startmsecs idline
1462
1463    set level [decidenext]
1464    if {$level >= 0} {
1465        set phase drawgraph
1466        while 1 {
1467            lappend displayorder [lindex $todo $level]
1468            set hard [updatetodo $level $datemode]
1469            if {$hard} {
1470                set level [decidenext]
1471                if {$level < 0} break
1472            }
1473        }
1474        drawmore 0
1475    }
1476    set phase {}
1477    set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
1478    #puts "overall $drawmsecs ms for $numcommits commits"
1479    if {$redisplaying} {
1480        if {$stopped == 0 && [info exists selectedline]} {
1481            selectline $selectedline 0
1482        }
1483        if {$stopped == 1} {
1484            set stopped 0
1485            after idle drawgraph
1486        } else {
1487            set redisplaying 0
1488        }
1489    }
1490}
1491
1492proc findmatches {f} {
1493    global findtype foundstring foundstrlen
1494    if {$findtype == "Regexp"} {
1495        set matches [regexp -indices -all -inline $foundstring $f]
1496    } else {
1497        if {$findtype == "IgnCase"} {
1498            set str [string tolower $f]
1499        } else {
1500            set str $f
1501        }
1502        set matches {}
1503        set i 0
1504        while {[set j [string first $foundstring $str $i]] >= 0} {
1505            lappend matches [list $j [expr $j+$foundstrlen-1]]
1506            set i [expr $j + $foundstrlen]
1507        }
1508    }
1509    return $matches
1510}
1511
1512proc dofind {} {
1513    global findtype findloc findstring markedmatches commitinfo
1514    global numcommits lineid linehtag linentag linedtag
1515    global mainfont namefont canv canv2 canv3 selectedline
1516    global matchinglines foundstring foundstrlen
1517
1518    stopfindproc
1519    unmarkmatches
1520    focus .
1521    set matchinglines {}
1522    if {$findloc == "Pickaxe"} {
1523        findpatches
1524        return
1525    }
1526    if {$findtype == "IgnCase"} {
1527        set foundstring [string tolower $findstring]
1528    } else {
1529        set foundstring $findstring
1530    }
1531    set foundstrlen [string length $findstring]
1532    if {$foundstrlen == 0} return
1533    if {$findloc == "Files"} {
1534        findfiles
1535        return
1536    }
1537    if {![info exists selectedline]} {
1538        set oldsel -1
1539    } else {
1540        set oldsel $selectedline
1541    }
1542    set didsel 0
1543    set fldtypes {Headline Author Date Committer CDate Comment}
1544    for {set l 0} {$l < $numcommits} {incr l} {
1545        set id $lineid($l)
1546        set info $commitinfo($id)
1547        set doesmatch 0
1548        foreach f $info ty $fldtypes {
1549            if {$findloc != "All fields" && $findloc != $ty} {
1550                continue
1551            }
1552            set matches [findmatches $f]
1553            if {$matches == {}} continue
1554            set doesmatch 1
1555            if {$ty == "Headline"} {
1556                markmatches $canv $l $f $linehtag($l) $matches $mainfont
1557            } elseif {$ty == "Author"} {
1558                markmatches $canv2 $l $f $linentag($l) $matches $namefont
1559            } elseif {$ty == "Date"} {
1560                markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
1561            }
1562        }
1563        if {$doesmatch} {
1564            lappend matchinglines $l
1565            if {!$didsel && $l > $oldsel} {
1566                findselectline $l
1567                set didsel 1
1568            }
1569        }
1570    }
1571    if {$matchinglines == {}} {
1572        bell
1573    } elseif {!$didsel} {
1574        findselectline [lindex $matchinglines 0]
1575    }
1576}
1577
1578proc findselectline {l} {
1579    global findloc commentend ctext
1580    selectline $l 1
1581    if {$findloc == "All fields" || $findloc == "Comments"} {
1582        # highlight the matches in the comments
1583        set f [$ctext get 1.0 $commentend]
1584        set matches [findmatches $f]
1585        foreach match $matches {
1586            set start [lindex $match 0]
1587            set end [expr [lindex $match 1] + 1]
1588            $ctext tag add found "1.0 + $start c" "1.0 + $end c"
1589        }
1590    }
1591}
1592
1593proc findnext {restart} {
1594    global matchinglines selectedline
1595    if {![info exists matchinglines]} {
1596        if {$restart} {
1597            dofind
1598        }
1599        return
1600    }
1601    if {![info exists selectedline]} return
1602    foreach l $matchinglines {
1603        if {$l > $selectedline} {
1604            findselectline $l
1605            return
1606        }
1607    }
1608    bell
1609}
1610
1611proc findprev {} {
1612    global matchinglines selectedline
1613    if {![info exists matchinglines]} {
1614        dofind
1615        return
1616    }
1617    if {![info exists selectedline]} return
1618    set prev {}
1619    foreach l $matchinglines {
1620        if {$l >= $selectedline} break
1621        set prev $l
1622    }
1623    if {$prev != {}} {
1624        findselectline $prev
1625    } else {
1626        bell
1627    }
1628}
1629
1630proc findlocchange {name ix op} {
1631    global findloc findtype findtypemenu
1632    if {$findloc == "Pickaxe"} {
1633        set findtype Exact
1634        set state disabled
1635    } else {
1636        set state normal
1637    }
1638    $findtypemenu entryconf 1 -state $state
1639    $findtypemenu entryconf 2 -state $state
1640}
1641
1642proc stopfindproc {{done 0}} {
1643    global findprocpid findprocfile findids
1644    global ctext findoldcursor phase maincursor textcursor
1645    global findinprogress
1646
1647    catch {unset findids}
1648    if {[info exists findprocpid]} {
1649        if {!$done} {
1650            catch {exec kill $findprocpid}
1651        }
1652        catch {close $findprocfile}
1653        unset findprocpid
1654    }
1655    if {[info exists findinprogress]} {
1656        unset findinprogress
1657        if {$phase != "incrdraw"} {
1658            . config -cursor $maincursor
1659            settextcursor $textcursor
1660        }
1661    }
1662}
1663
1664proc findpatches {} {
1665    global findstring selectedline numcommits
1666    global findprocpid findprocfile
1667    global finddidsel ctext lineid findinprogress
1668    global findinsertpos
1669
1670    if {$numcommits == 0} return
1671
1672    # make a list of all the ids to search, starting at the one
1673    # after the selected line (if any)
1674    if {[info exists selectedline]} {
1675        set l $selectedline
1676    } else {
1677        set l -1
1678    }
1679    set inputids {}
1680    for {set i 0} {$i < $numcommits} {incr i} {
1681        if {[incr l] >= $numcommits} {
1682            set l 0
1683        }
1684        append inputids $lineid($l) "\n"
1685    }
1686
1687    if {[catch {
1688        set f [open [list | git-diff-tree --stdin -s -r -S$findstring \
1689                         << $inputids] r]
1690    } err]} {
1691        error_popup "Error starting search process: $err"
1692        return
1693    }
1694
1695    set findinsertpos end
1696    set findprocfile $f
1697    set findprocpid [pid $f]
1698    fconfigure $f -blocking 0
1699    fileevent $f readable readfindproc
1700    set finddidsel 0
1701    . config -cursor watch
1702    settextcursor watch
1703    set findinprogress 1
1704}
1705
1706proc readfindproc {} {
1707    global findprocfile finddidsel
1708    global idline matchinglines findinsertpos
1709
1710    set n [gets $findprocfile line]
1711    if {$n < 0} {
1712        if {[eof $findprocfile]} {
1713            stopfindproc 1
1714            if {!$finddidsel} {
1715                bell
1716            }
1717        }
1718        return
1719    }
1720    if {![regexp {^[0-9a-f]{40}} $line id]} {
1721        error_popup "Can't parse git-diff-tree output: $line"
1722        stopfindproc
1723        return
1724    }
1725    if {![info exists idline($id)]} {
1726        puts stderr "spurious id: $id"
1727        return
1728    }
1729    set l $idline($id)
1730    insertmatch $l $id
1731}
1732
1733proc insertmatch {l id} {
1734    global matchinglines findinsertpos finddidsel
1735
1736    if {$findinsertpos == "end"} {
1737        if {$matchinglines != {} && $l < [lindex $matchinglines 0]} {
1738            set matchinglines [linsert $matchinglines 0 $l]
1739            set findinsertpos 1
1740        } else {
1741            lappend matchinglines $l
1742        }
1743    } else {
1744        set matchinglines [linsert $matchinglines $findinsertpos $l]
1745        incr findinsertpos
1746    }
1747    markheadline $l $id
1748    if {!$finddidsel} {
1749        findselectline $l
1750        set finddidsel 1
1751    }
1752}
1753
1754proc findfiles {} {
1755    global selectedline numcommits lineid ctext
1756    global ffileline finddidsel parents nparents
1757    global findinprogress findstartline findinsertpos
1758    global treediffs fdiffids fdiffsneeded fdiffpos
1759    global findmergefiles
1760
1761    if {$numcommits == 0} return
1762
1763    if {[info exists selectedline]} {
1764        set l [expr {$selectedline + 1}]
1765    } else {
1766        set l 0
1767    }
1768    set ffileline $l
1769    set findstartline $l
1770    set diffsneeded {}
1771    set fdiffsneeded {}
1772    while 1 {
1773        set id $lineid($l)
1774        if {$findmergefiles || $nparents($id) == 1} {
1775            foreach p $parents($id) {
1776                if {![info exists treediffs([list $id $p])]} {
1777                    append diffsneeded "$id $p\n"
1778                    lappend fdiffsneeded [list $id $p]
1779                }
1780            }
1781        }
1782        if {[incr l] >= $numcommits} {
1783            set l 0
1784        }
1785        if {$l == $findstartline} break
1786    }
1787
1788    # start off a git-diff-tree process if needed
1789    if {$diffsneeded ne {}} {
1790        if {[catch {
1791            set df [open [list | git-diff-tree -r --stdin << $diffsneeded] r]
1792        } err ]} {
1793            error_popup "Error starting search process: $err"
1794            return
1795        }
1796        catch {unset fdiffids}
1797        set fdiffpos 0
1798        fconfigure $df -blocking 0
1799        fileevent $df readable [list readfilediffs $df]
1800    }
1801
1802    set finddidsel 0
1803    set findinsertpos end
1804    set id $lineid($l)
1805    set p [lindex $parents($id) 0]
1806    . config -cursor watch
1807    settextcursor watch
1808    set findinprogress 1
1809    findcont [list $id $p]
1810    update
1811}
1812
1813proc readfilediffs {df} {
1814    global findids fdiffids fdiffs
1815
1816    set n [gets $df line]
1817    if {$n < 0} {
1818        if {[eof $df]} {
1819            donefilediff
1820            if {[catch {close $df} err]} {
1821                stopfindproc
1822                bell
1823                error_popup "Error in git-diff-tree: $err"
1824            } elseif {[info exists findids]} {
1825                set ids $findids
1826                stopfindproc
1827                bell
1828                error_popup "Couldn't find diffs for {$ids}"
1829            }
1830        }
1831        return
1832    }
1833    if {[regexp {^([0-9a-f]{40}) \(from ([0-9a-f]{40})\)} $line match id p]} {
1834        # start of a new string of diffs
1835        donefilediff
1836        set fdiffids [list $id $p]
1837        set fdiffs {}
1838    } elseif {[string match ":*" $line]} {
1839        lappend fdiffs [lindex $line 5]
1840    }
1841}
1842
1843proc donefilediff {} {
1844    global fdiffids fdiffs treediffs findids
1845    global fdiffsneeded fdiffpos
1846
1847    if {[info exists fdiffids]} {
1848        while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffids
1849               && $fdiffpos < [llength $fdiffsneeded]} {
1850            # git-diff-tree doesn't output anything for a commit
1851            # which doesn't change anything
1852            set nullids [lindex $fdiffsneeded $fdiffpos]
1853            set treediffs($nullids) {}
1854            if {[info exists findids] && $nullids eq $findids} {
1855                unset findids
1856                findcont $nullids
1857            }
1858            incr fdiffpos
1859        }
1860        incr fdiffpos
1861
1862        if {![info exists treediffs($fdiffids)]} {
1863            set treediffs($fdiffids) $fdiffs
1864        }
1865        if {[info exists findids] && $fdiffids eq $findids} {
1866            unset findids
1867            findcont $fdiffids
1868        }
1869    }
1870}
1871
1872proc findcont {ids} {
1873    global findids treediffs parents nparents
1874    global ffileline findstartline finddidsel
1875    global lineid numcommits matchinglines findinprogress
1876    global findmergefiles
1877
1878    set id [lindex $ids 0]
1879    set p [lindex $ids 1]
1880    set pi [lsearch -exact $parents($id) $p]
1881    set l $ffileline
1882    while 1 {
1883        if {$findmergefiles || $nparents($id) == 1} {
1884            if {![info exists treediffs($ids)]} {
1885                set findids $ids
1886                set ffileline $l
1887                return
1888            }
1889            set doesmatch 0
1890            foreach f $treediffs($ids) {
1891                set x [findmatches $f]
1892                if {$x != {}} {
1893                    set doesmatch 1
1894                    break
1895                }
1896            }
1897            if {$doesmatch} {
1898                insertmatch $l $id
1899                set pi $nparents($id)
1900            }
1901        } else {
1902            set pi $nparents($id)
1903        }
1904        if {[incr pi] >= $nparents($id)} {
1905            set pi 0
1906            if {[incr l] >= $numcommits} {
1907                set l 0
1908            }
1909            if {$l == $findstartline} break
1910            set id $lineid($l)
1911        }
1912        set p [lindex $parents($id) $pi]
1913        set ids [list $id $p]
1914    }
1915    stopfindproc
1916    if {!$finddidsel} {
1917        bell
1918    }
1919}
1920
1921# mark a commit as matching by putting a yellow background
1922# behind the headline
1923proc markheadline {l id} {
1924    global canv mainfont linehtag commitinfo
1925
1926    set bbox [$canv bbox $linehtag($l)]
1927    set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
1928    $canv lower $t
1929}
1930
1931# mark the bits of a headline, author or date that match a find string
1932proc markmatches {canv l str tag matches font} {
1933    set bbox [$canv bbox $tag]
1934    set x0 [lindex $bbox 0]
1935    set y0 [lindex $bbox 1]
1936    set y1 [lindex $bbox 3]
1937    foreach match $matches {
1938        set start [lindex $match 0]
1939        set end [lindex $match 1]
1940        if {$start > $end} continue
1941        set xoff [font measure $font [string range $str 0 [expr $start-1]]]
1942        set xlen [font measure $font [string range $str 0 [expr $end]]]
1943        set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \
1944                   -outline {} -tags matches -fill yellow]
1945        $canv lower $t
1946    }
1947}
1948
1949proc unmarkmatches {} {
1950    global matchinglines findids
1951    allcanvs delete matches
1952    catch {unset matchinglines}
1953    catch {unset findids}
1954}
1955
1956proc selcanvline {w x y} {
1957    global canv canvy0 ctext linespc
1958    global lineid linehtag linentag linedtag rowtextx
1959    set ymax [lindex [$canv cget -scrollregion] 3]
1960    if {$ymax == {}} return
1961    set yfrac [lindex [$canv yview] 0]
1962    set y [expr {$y + $yfrac * $ymax}]
1963    set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
1964    if {$l < 0} {
1965        set l 0
1966    }
1967    if {$w eq $canv} {
1968        if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
1969    }
1970    unmarkmatches
1971    selectline $l 1
1972}
1973
1974proc commit_descriptor {p} {
1975    global commitinfo
1976    set l "..."
1977    if {[info exists commitinfo($p)]} {
1978        set l [lindex $commitinfo($p) 0]
1979    }
1980    return "$p ($l)"
1981}
1982
1983proc selectline {l isnew} {
1984    global canv canv2 canv3 ctext commitinfo selectedline
1985    global lineid linehtag linentag linedtag
1986    global canvy0 linespc parents nparents children
1987    global cflist currentid sha1entry
1988    global commentend idtags idline
1989
1990    $canv delete hover
1991    if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
1992    $canv delete secsel
1993    set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
1994               -tags secsel -fill [$canv cget -selectbackground]]
1995    $canv lower $t
1996    $canv2 delete secsel
1997    set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
1998               -tags secsel -fill [$canv2 cget -selectbackground]]
1999    $canv2 lower $t
2000    $canv3 delete secsel
2001    set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
2002               -tags secsel -fill [$canv3 cget -selectbackground]]
2003    $canv3 lower $t
2004    set y [expr {$canvy0 + $l * $linespc}]
2005    set ymax [lindex [$canv cget -scrollregion] 3]
2006    set ytop [expr {$y - $linespc - 1}]
2007    set ybot [expr {$y + $linespc + 1}]
2008    set wnow [$canv yview]
2009    set wtop [expr [lindex $wnow 0] * $ymax]
2010    set wbot [expr [lindex $wnow 1] * $ymax]
2011    set wh [expr {$wbot - $wtop}]
2012    set newtop $wtop
2013    if {$ytop < $wtop} {
2014        if {$ybot < $wtop} {
2015            set newtop [expr {$y - $wh / 2.0}]
2016        } else {
2017            set newtop $ytop
2018            if {$newtop > $wtop - $linespc} {
2019                set newtop [expr {$wtop - $linespc}]
2020            }
2021        }
2022    } elseif {$ybot > $wbot} {
2023        if {$ytop > $wbot} {
2024            set newtop [expr {$y - $wh / 2.0}]
2025        } else {
2026            set newtop [expr {$ybot - $wh}]
2027            if {$newtop < $wtop + $linespc} {
2028                set newtop [expr {$wtop + $linespc}]
2029            }
2030        }
2031    }
2032    if {$newtop != $wtop} {
2033        if {$newtop < 0} {
2034            set newtop 0
2035        }
2036        allcanvs yview moveto [expr $newtop * 1.0 / $ymax]
2037    }
2038
2039    if {$isnew} {
2040        addtohistory [list selectline $l 0]
2041    }
2042
2043    set selectedline $l
2044
2045    set id $lineid($l)
2046    set currentid $id
2047    $sha1entry delete 0 end
2048    $sha1entry insert 0 $id
2049    $sha1entry selection from 0
2050    $sha1entry selection to end
2051
2052    $ctext conf -state normal
2053    $ctext delete 0.0 end
2054    $ctext mark set fmark.0 0.0
2055    $ctext mark gravity fmark.0 left
2056    set info $commitinfo($id)
2057    $ctext insert end "Author: [lindex $info 1]  [lindex $info 2]\n"
2058    $ctext insert end "Committer: [lindex $info 3]  [lindex $info 4]\n"
2059    if {[info exists idtags($id)]} {
2060        $ctext insert end "Tags:"
2061        foreach tag $idtags($id) {
2062            $ctext insert end " $tag"
2063        }
2064        $ctext insert end "\n"
2065    }
2066 
2067    set commentstart [$ctext index "end - 1c"]
2068    set comment {}
2069    if {[info exists parents($id)]} {
2070        foreach p $parents($id) {
2071            append comment "Parent: [commit_descriptor $p]\n"
2072        }
2073    }
2074    if {[info exists children($id)]} {
2075        foreach c $children($id) {
2076            append comment "Child:  [commit_descriptor $c]\n"
2077        }
2078    }
2079    append comment "\n"
2080    append comment [lindex $info 5]
2081    $ctext insert end $comment
2082    $ctext insert end "\n"
2083
2084    # make anything that looks like a SHA1 ID be a clickable link
2085    set links [regexp -indices -all -inline {[0-9a-f]{40}} $comment]
2086    set i 0
2087    foreach l $links {
2088        set s [lindex $l 0]
2089        set e [lindex $l 1]
2090        set linkid [string range $comment $s $e]
2091        if {![info exists idline($linkid)]} continue
2092        incr e
2093        $ctext tag add link "$commentstart + $s c" "$commentstart + $e c"
2094        $ctext tag add link$i "$commentstart + $s c" "$commentstart + $e c"
2095        $ctext tag bind link$i <1> [list selectline $idline($linkid) 1]
2096        incr i
2097    }
2098    $ctext tag conf link -foreground blue -underline 1
2099    $ctext tag bind link <Enter> { %W configure -cursor hand2 }
2100    $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
2101
2102    $ctext tag delete Comments
2103    $ctext tag remove found 1.0 end
2104    $ctext conf -state disabled
2105    set commentend [$ctext index "end - 1c"]
2106
2107    $cflist delete 0 end
2108    $cflist insert end "Comments"
2109    if {$nparents($id) == 1} {
2110        startdiff [concat $id $parents($id)]
2111    } elseif {$nparents($id) > 1} {
2112        mergediff $id
2113    }
2114}
2115
2116proc selnextline {dir} {
2117    global selectedline
2118    if {![info exists selectedline]} return
2119    set l [expr $selectedline + $dir]
2120    unmarkmatches
2121    selectline $l 1
2122}
2123
2124proc unselectline {} {
2125    global selectedline
2126
2127    catch {unset selectedline}
2128    allcanvs delete secsel
2129}
2130
2131proc addtohistory {cmd} {
2132    global history historyindex
2133
2134    if {$historyindex > 0
2135        && [lindex $history [expr {$historyindex - 1}]] == $cmd} {
2136        return
2137    }
2138
2139    if {$historyindex < [llength $history]} {
2140        set history [lreplace $history $historyindex end $cmd]
2141    } else {
2142        lappend history $cmd
2143    }
2144    incr historyindex
2145    if {$historyindex > 1} {
2146        .ctop.top.bar.leftbut conf -state normal
2147    } else {
2148        .ctop.top.bar.leftbut conf -state disabled
2149    }
2150    .ctop.top.bar.rightbut conf -state disabled
2151}
2152
2153proc goback {} {
2154    global history historyindex
2155
2156    if {$historyindex > 1} {
2157        incr historyindex -1
2158        set cmd [lindex $history [expr {$historyindex - 1}]]
2159        eval $cmd
2160        .ctop.top.bar.rightbut conf -state normal
2161    }
2162    if {$historyindex <= 1} {
2163        .ctop.top.bar.leftbut conf -state disabled
2164    }
2165}
2166
2167proc goforw {} {
2168    global history historyindex
2169
2170    if {$historyindex < [llength $history]} {
2171        set cmd [lindex $history $historyindex]
2172        incr historyindex
2173        eval $cmd
2174        .ctop.top.bar.leftbut conf -state normal
2175    }
2176    if {$historyindex >= [llength $history]} {
2177        .ctop.top.bar.rightbut conf -state disabled
2178    }
2179}
2180
2181proc mergediff {id} {
2182    global parents diffmergeid diffmergegca mergefilelist diffpindex
2183
2184    set diffmergeid $id
2185    set diffpindex -1
2186    set diffmergegca [findgca $parents($id)]
2187    if {[info exists mergefilelist($id)]} {
2188        if {$mergefilelist($id) ne {}} {
2189            showmergediff
2190        }
2191    } else {
2192        contmergediff {}
2193    }
2194}
2195
2196proc findgca {ids} {
2197    set gca {}
2198    foreach id $ids {
2199        if {$gca eq {}} {
2200            set gca $id
2201        } else {
2202            if {[catch {
2203                set gca [exec git-merge-base $gca $id]
2204            } err]} {
2205                return {}
2206            }
2207        }
2208    }
2209    return $gca
2210}
2211
2212proc contmergediff {ids} {
2213    global diffmergeid diffpindex parents nparents diffmergegca
2214    global treediffs mergefilelist diffids treepending
2215
2216    # diff the child against each of the parents, and diff
2217    # each of the parents against the GCA.
2218    while 1 {
2219        if {[lindex $ids 0] == $diffmergeid && $diffmergegca ne {}} {
2220            set ids [list [lindex $ids 1] $diffmergegca]
2221        } else {
2222            if {[incr diffpindex] >= $nparents($diffmergeid)} break
2223            set p [lindex $parents($diffmergeid) $diffpindex]
2224            set ids [list $diffmergeid $p]
2225        }
2226        if {![info exists treediffs($ids)]} {
2227            set diffids $ids
2228            if {![info exists treepending]} {
2229                gettreediffs $ids
2230            }
2231            return
2232        }
2233    }
2234
2235    # If a file in some parent is different from the child and also
2236    # different from the GCA, then it's interesting.
2237    # If we don't have a GCA, then a file is interesting if it is
2238    # different from the child in all the parents.
2239    if {$diffmergegca ne {}} {
2240        set files {}
2241        foreach p $parents($diffmergeid) {
2242            set gcadiffs $treediffs([list $p $diffmergegca])
2243            foreach f $treediffs([list $diffmergeid $p]) {
2244                if {[lsearch -exact $files $f] < 0
2245                    && [lsearch -exact $gcadiffs $f] >= 0} {
2246                    lappend files $f
2247                }
2248            }
2249        }
2250        set files [lsort $files]
2251    } else {
2252        set p [lindex $parents($diffmergeid) 0]
2253        set files $treediffs([list $diffmergeid $p])
2254        for {set i 1} {$i < $nparents($diffmergeid) && $files ne {}} {incr i} {
2255            set p [lindex $parents($diffmergeid) $i]
2256            set df $treediffs([list $diffmergeid $p])
2257            set nf {}
2258            foreach f $files {
2259                if {[lsearch -exact $df $f] >= 0} {
2260                    lappend nf $f
2261                }
2262            }
2263            set files $nf
2264        }
2265    }
2266
2267    set mergefilelist($diffmergeid) $files
2268    if {$files ne {}} {
2269        showmergediff
2270    }
2271}
2272
2273proc showmergediff {} {
2274    global cflist diffmergeid mergefilelist parents
2275    global diffopts diffinhunk currentfile currenthunk filelines
2276    global diffblocked groupfilelast mergefds groupfilenum grouphunks
2277
2278    set files $mergefilelist($diffmergeid)
2279    foreach f $files {
2280        $cflist insert end $f
2281    }
2282    set env(GIT_DIFF_OPTS) $diffopts
2283    set flist {}
2284    catch {unset currentfile}
2285    catch {unset currenthunk}
2286    catch {unset filelines}
2287    catch {unset groupfilenum}
2288    catch {unset grouphunks}
2289    set groupfilelast -1
2290    foreach p $parents($diffmergeid) {
2291        set cmd [list | git-diff-tree -p $p $diffmergeid]
2292        set cmd [concat $cmd $mergefilelist($diffmergeid)]
2293        if {[catch {set f [open $cmd r]} err]} {
2294            error_popup "Error getting diffs: $err"
2295            foreach f $flist {
2296                catch {close $f}
2297            }
2298            return
2299        }
2300        lappend flist $f
2301        set ids [list $diffmergeid $p]
2302        set mergefds($ids) $f
2303        set diffinhunk($ids) 0
2304        set diffblocked($ids) 0
2305        fconfigure $f -blocking 0
2306        fileevent $f readable [list getmergediffline $f $ids $diffmergeid]
2307    }
2308}
2309
2310proc getmergediffline {f ids id} {
2311    global diffmergeid diffinhunk diffoldlines diffnewlines
2312    global currentfile currenthunk
2313    global diffoldstart diffnewstart diffoldlno diffnewlno
2314    global diffblocked mergefilelist
2315    global noldlines nnewlines difflcounts filelines
2316
2317    set n [gets $f line]
2318    if {$n < 0} {
2319        if {![eof $f]} return
2320    }
2321
2322    if {!([info exists diffmergeid] && $diffmergeid == $id)} {
2323        if {$n < 0} {
2324            close $f
2325        }
2326        return
2327    }
2328
2329    if {$diffinhunk($ids) != 0} {
2330        set fi $currentfile($ids)
2331        if {$n > 0 && [regexp {^[-+ \\]} $line match]} {
2332            # continuing an existing hunk
2333            set line [string range $line 1 end]
2334            set p [lindex $ids 1]
2335            if {$match eq "-" || $match eq " "} {
2336                set filelines($p,$fi,$diffoldlno($ids)) $line
2337                incr diffoldlno($ids)
2338            }
2339            if {$match eq "+" || $match eq " "} {
2340                set filelines($id,$fi,$diffnewlno($ids)) $line
2341                incr diffnewlno($ids)
2342            }
2343            if {$match eq " "} {
2344                if {$diffinhunk($ids) == 2} {
2345                    lappend difflcounts($ids) \
2346                        [list $noldlines($ids) $nnewlines($ids)]
2347                    set noldlines($ids) 0
2348                    set diffinhunk($ids) 1
2349                }
2350                incr noldlines($ids)
2351            } elseif {$match eq "-" || $match eq "+"} {
2352                if {$diffinhunk($ids) == 1} {
2353                    lappend difflcounts($ids) [list $noldlines($ids)]
2354                    set noldlines($ids) 0
2355                    set nnewlines($ids) 0
2356                    set diffinhunk($ids) 2
2357                }
2358                if {$match eq "-"} {
2359                    incr noldlines($ids)
2360                } else {
2361                    incr nnewlines($ids)
2362                }
2363            }
2364            # and if it's \ No newline at end of line, then what?
2365            return
2366        }
2367        # end of a hunk
2368        if {$diffinhunk($ids) == 1 && $noldlines($ids) != 0} {
2369            lappend difflcounts($ids) [list $noldlines($ids)]
2370        } elseif {$diffinhunk($ids) == 2
2371                  && ($noldlines($ids) != 0 || $nnewlines($ids) != 0)} {
2372            lappend difflcounts($ids) [list $noldlines($ids) $nnewlines($ids)]
2373        }
2374        set currenthunk($ids) [list $currentfile($ids) \
2375                                   $diffoldstart($ids) $diffnewstart($ids) \
2376                                   $diffoldlno($ids) $diffnewlno($ids) \
2377                                   $difflcounts($ids)]
2378        set diffinhunk($ids) 0
2379        # -1 = need to block, 0 = unblocked, 1 = is blocked
2380        set diffblocked($ids) -1
2381        processhunks
2382        if {$diffblocked($ids) == -1} {
2383            fileevent $f readable {}
2384            set diffblocked($ids) 1
2385        }
2386    }
2387
2388    if {$n < 0} {
2389        # eof
2390        if {!$diffblocked($ids)} {
2391            close $f
2392            set currentfile($ids) [llength $mergefilelist($diffmergeid)]
2393            set currenthunk($ids) [list $currentfile($ids) 0 0 0 0 {}]
2394            processhunks
2395        }
2396    } elseif {[regexp {^diff --git a/(.*) b/} $line match fname]} {
2397        # start of a new file
2398        set currentfile($ids) \
2399            [lsearch -exact $mergefilelist($diffmergeid) $fname]
2400    } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2401                   $line match f1l f1c f2l f2c rest]} {
2402        if {[info exists currentfile($ids)] && $currentfile($ids) >= 0} {
2403            # start of a new hunk
2404            if {$f1l == 0 && $f1c == 0} {
2405                set f1l 1
2406            }
2407            if {$f2l == 0 && $f2c == 0} {
2408                set f2l 1
2409            }
2410            set diffinhunk($ids) 1
2411            set diffoldstart($ids) $f1l
2412            set diffnewstart($ids) $f2l
2413            set diffoldlno($ids) $f1l
2414            set diffnewlno($ids) $f2l
2415            set difflcounts($ids) {}
2416            set noldlines($ids) 0
2417            set nnewlines($ids) 0
2418        }
2419    }
2420}
2421
2422proc processhunks {} {
2423    global diffmergeid parents nparents currenthunk
2424    global mergefilelist diffblocked mergefds
2425    global grouphunks grouplinestart grouplineend groupfilenum
2426
2427    set nfiles [llength $mergefilelist($diffmergeid)]
2428    while 1 {
2429        set fi $nfiles
2430        set lno 0
2431        # look for the earliest hunk
2432        foreach p $parents($diffmergeid) {
2433            set ids [list $diffmergeid $p]
2434            if {![info exists currenthunk($ids)]} return
2435            set i [lindex $currenthunk($ids) 0]
2436            set l [lindex $currenthunk($ids) 2]
2437            if {$i < $fi || ($i == $fi && $l < $lno)} {
2438                set fi $i
2439                set lno $l
2440                set pi $p
2441            }
2442        }
2443
2444        if {$fi < $nfiles} {
2445            set ids [list $diffmergeid $pi]
2446            set hunk $currenthunk($ids)
2447            unset currenthunk($ids)
2448            if {$diffblocked($ids) > 0} {
2449                fileevent $mergefds($ids) readable \
2450                    [list getmergediffline $mergefds($ids) $ids $diffmergeid]
2451            }
2452            set diffblocked($ids) 0
2453
2454            if {[info exists groupfilenum] && $groupfilenum == $fi
2455                && $lno <= $grouplineend} {
2456                # add this hunk to the pending group
2457                lappend grouphunks($pi) $hunk
2458                set endln [lindex $hunk 4]
2459                if {$endln > $grouplineend} {
2460                    set grouplineend $endln
2461                }
2462                continue
2463            }
2464        }
2465
2466        # succeeding stuff doesn't belong in this group, so
2467        # process the group now
2468        if {[info exists groupfilenum]} {
2469            processgroup
2470            unset groupfilenum
2471            unset grouphunks
2472        }
2473
2474        if {$fi >= $nfiles} break
2475
2476        # start a new group
2477        set groupfilenum $fi
2478        set grouphunks($pi) [list $hunk]
2479        set grouplinestart $lno
2480        set grouplineend [lindex $hunk 4]
2481    }
2482}
2483
2484proc processgroup {} {
2485    global groupfilelast groupfilenum difffilestart
2486    global mergefilelist diffmergeid ctext filelines
2487    global parents diffmergeid diffoffset
2488    global grouphunks grouplinestart grouplineend nparents
2489    global mergemax
2490
2491    $ctext conf -state normal
2492    set id $diffmergeid
2493    set f $groupfilenum
2494    if {$groupfilelast != $f} {
2495        $ctext insert end "\n"
2496        set here [$ctext index "end - 1c"]
2497        set difffilestart($f) $here
2498        set mark fmark.[expr {$f + 1}]
2499        $ctext mark set $mark $here
2500        $ctext mark gravity $mark left
2501        set header [lindex $mergefilelist($id) $f]
2502        set l [expr {(78 - [string length $header]) / 2}]
2503        set pad [string range "----------------------------------------" 1 $l]
2504        $ctext insert end "$pad $header $pad\n" filesep
2505        set groupfilelast $f
2506        foreach p $parents($id) {
2507            set diffoffset($p) 0
2508        }
2509    }
2510
2511    $ctext insert end "@@" msep
2512    set nlines [expr {$grouplineend - $grouplinestart}]
2513    set events {}
2514    set pnum 0
2515    foreach p $parents($id) {
2516        set startline [expr {$grouplinestart + $diffoffset($p)}]
2517        set ol $startline
2518        set nl $grouplinestart
2519        if {[info exists grouphunks($p)]} {
2520            foreach h $grouphunks($p) {
2521                set l [lindex $h 2]
2522                if {$nl < $l} {
2523                    for {} {$nl < $l} {incr nl} {
2524                        set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2525                        incr ol
2526                    }
2527                }
2528                foreach chunk [lindex $h 5] {
2529                    if {[llength $chunk] == 2} {
2530                        set olc [lindex $chunk 0]
2531                        set nlc [lindex $chunk 1]
2532                        set nnl [expr {$nl + $nlc}]
2533                        lappend events [list $nl $nnl $pnum $olc $nlc]
2534                        incr ol $olc
2535                        set nl $nnl
2536                    } else {
2537                        incr ol [lindex $chunk 0]
2538                        incr nl [lindex $chunk 0]
2539                    }
2540                }
2541            }
2542        }
2543        if {$nl < $grouplineend} {
2544            for {} {$nl < $grouplineend} {incr nl} {
2545                set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2546                incr ol
2547            }
2548        }
2549        set nlines [expr {$ol - $startline}]
2550        $ctext insert end " -$startline,$nlines" msep
2551        incr pnum
2552    }
2553
2554    set nlines [expr {$grouplineend - $grouplinestart}]
2555    $ctext insert end " +$grouplinestart,$nlines @@\n" msep
2556
2557    set events [lsort -integer -index 0 $events]
2558    set nevents [llength $events]
2559    set nmerge $nparents($diffmergeid)
2560    set l $grouplinestart
2561    for {set i 0} {$i < $nevents} {set i $j} {
2562        set nl [lindex $events $i 0]
2563        while {$l < $nl} {
2564            $ctext insert end " $filelines($id,$f,$l)\n"
2565            incr l
2566        }
2567        set e [lindex $events $i]
2568        set enl [lindex $e 1]
2569        set j $i
2570        set active {}
2571        while 1 {
2572            set pnum [lindex $e 2]
2573            set olc [lindex $e 3]
2574            set nlc [lindex $e 4]
2575            if {![info exists delta($pnum)]} {
2576                set delta($pnum) [expr {$olc - $nlc}]
2577                lappend active $pnum
2578            } else {
2579                incr delta($pnum) [expr {$olc - $nlc}]
2580            }
2581            if {[incr j] >= $nevents} break
2582            set e [lindex $events $j]
2583            if {[lindex $e 0] >= $enl} break
2584            if {[lindex $e 1] > $enl} {
2585                set enl [lindex $e 1]
2586            }
2587        }
2588        set nlc [expr {$enl - $l}]
2589        set ncol mresult
2590        set bestpn -1
2591        if {[llength $active] == $nmerge - 1} {
2592            # no diff for one of the parents, i.e. it's identical
2593            for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2594                if {![info exists delta($pnum)]} {
2595                    if {$pnum < $mergemax} {
2596                        lappend ncol m$pnum
2597                    } else {
2598                        lappend ncol mmax
2599                    }
2600                    break
2601                }
2602            }
2603        } elseif {[llength $active] == $nmerge} {
2604            # all parents are different, see if one is very similar
2605            set bestsim 30
2606            for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2607                set sim [similarity $pnum $l $nlc $f \
2608                             [lrange $events $i [expr {$j-1}]]]
2609                if {$sim > $bestsim} {
2610                    set bestsim $sim
2611                    set bestpn $pnum
2612                }
2613            }
2614            if {$bestpn >= 0} {
2615                lappend ncol m$bestpn
2616            }
2617        }
2618        set pnum -1
2619        foreach p $parents($id) {
2620            incr pnum
2621            if {![info exists delta($pnum)] || $pnum == $bestpn} continue
2622            set olc [expr {$nlc + $delta($pnum)}]
2623            set ol [expr {$l + $diffoffset($p)}]
2624            incr diffoffset($p) $delta($pnum)
2625            unset delta($pnum)
2626            for {} {$olc > 0} {incr olc -1} {
2627                $ctext insert end "-$filelines($p,$f,$ol)\n" m$pnum
2628                incr ol
2629            }
2630        }
2631        set endl [expr {$l + $nlc}]
2632        if {$bestpn >= 0} {
2633            # show this pretty much as a normal diff
2634            set p [lindex $parents($id) $bestpn]
2635            set ol [expr {$l + $diffoffset($p)}]
2636            incr diffoffset($p) $delta($bestpn)
2637            unset delta($bestpn)
2638            for {set k $i} {$k < $j} {incr k} {
2639                set e [lindex $events $k]
2640                if {[lindex $e 2] != $bestpn} continue
2641                set nl [lindex $e 0]
2642                set ol [expr {$ol + $nl - $l}]
2643                for {} {$l < $nl} {incr l} {
2644                    $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2645                }
2646                set c [lindex $e 3]
2647                for {} {$c > 0} {incr c -1} {
2648                    $ctext insert end "-$filelines($p,$f,$ol)\n" m$bestpn
2649                    incr ol
2650                }
2651                set nl [lindex $e 1]
2652                for {} {$l < $nl} {incr l} {
2653                    $ctext insert end "+$filelines($id,$f,$l)\n" mresult
2654                }
2655            }
2656        }
2657        for {} {$l < $endl} {incr l} {
2658            $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2659        }
2660    }
2661    while {$l < $grouplineend} {
2662        $ctext insert end " $filelines($id,$f,$l)\n"
2663        incr l
2664    }
2665    $ctext conf -state disabled
2666}
2667
2668proc similarity {pnum l nlc f events} {
2669    global diffmergeid parents diffoffset filelines
2670
2671    set id $diffmergeid
2672    set p [lindex $parents($id) $pnum]
2673    set ol [expr {$l + $diffoffset($p)}]
2674    set endl [expr {$l + $nlc}]
2675    set same 0
2676    set diff 0
2677    foreach e $events {
2678        if {[lindex $e 2] != $pnum} continue
2679        set nl [lindex $e 0]
2680        set ol [expr {$ol + $nl - $l}]
2681        for {} {$l < $nl} {incr l} {
2682            incr same [string length $filelines($id,$f,$l)]
2683            incr same
2684        }
2685        set oc [lindex $e 3]
2686        for {} {$oc > 0} {incr oc -1} {
2687            incr diff [string length $filelines($p,$f,$ol)]
2688            incr diff
2689            incr ol
2690        }
2691        set nl [lindex $e 1]
2692        for {} {$l < $nl} {incr l} {
2693            incr diff [string length $filelines($id,$f,$l)]
2694            incr diff
2695        }
2696    }
2697    for {} {$l < $endl} {incr l} {
2698        incr same [string length $filelines($id,$f,$l)]
2699        incr same
2700    }
2701    if {$same == 0} {
2702        return 0
2703    }
2704    return [expr {200 * $same / (2 * $same + $diff)}]
2705}
2706
2707proc startdiff {ids} {
2708    global treediffs diffids treepending diffmergeid
2709
2710    set diffids $ids
2711    catch {unset diffmergeid}
2712    if {![info exists treediffs($ids)]} {
2713        if {![info exists treepending]} {
2714            gettreediffs $ids
2715        }
2716    } else {
2717        addtocflist $ids
2718    }
2719}
2720
2721proc addtocflist {ids} {
2722    global treediffs cflist
2723    foreach f $treediffs($ids) {
2724        $cflist insert end $f
2725    }
2726    getblobdiffs $ids
2727}
2728
2729proc gettreediffs {ids} {
2730    global treediff parents treepending
2731    set treepending $ids
2732    set treediff {}
2733    set id [lindex $ids 0]
2734    set p [lindex $ids 1]
2735    if [catch {set gdtf [open "|git-diff-tree -r $p $id" r]}] return
2736    fconfigure $gdtf -blocking 0
2737    fileevent $gdtf readable [list gettreediffline $gdtf $ids]
2738}
2739
2740proc gettreediffline {gdtf ids} {
2741    global treediff treediffs treepending diffids diffmergeid
2742
2743    set n [gets $gdtf line]
2744    if {$n < 0} {
2745        if {![eof $gdtf]} return
2746        close $gdtf
2747        set treediffs($ids) $treediff
2748        unset treepending
2749        if {$ids != $diffids} {
2750            gettreediffs $diffids
2751        } else {
2752            if {[info exists diffmergeid]} {
2753                contmergediff $ids
2754            } else {
2755                addtocflist $ids
2756            }
2757        }
2758        return
2759    }
2760    set file [lindex $line 5]
2761    lappend treediff $file
2762}
2763
2764proc getblobdiffs {ids} {
2765    global diffopts blobdifffd diffids env curdifftag curtagstart
2766    global difffilestart nextupdate diffinhdr treediffs
2767
2768    set id [lindex $ids 0]
2769    set p [lindex $ids 1]
2770    set env(GIT_DIFF_OPTS) $diffopts
2771    set cmd [list | git-diff-tree -r -p -C $p $id]
2772    if {[catch {set bdf [open $cmd r]} err]} {
2773        puts "error getting diffs: $err"
2774        return
2775    }
2776    set diffinhdr 0
2777    fconfigure $bdf -blocking 0
2778    set blobdifffd($ids) $bdf
2779    set curdifftag Comments
2780    set curtagstart 0.0
2781    catch {unset difffilestart}
2782    fileevent $bdf readable [list getblobdiffline $bdf $diffids]
2783    set nextupdate [expr {[clock clicks -milliseconds] + 100}]
2784}
2785
2786proc getblobdiffline {bdf ids} {
2787    global diffids blobdifffd ctext curdifftag curtagstart
2788    global diffnexthead diffnextnote difffilestart
2789    global nextupdate diffinhdr treediffs
2790    global gaudydiff
2791
2792    set n [gets $bdf line]
2793    if {$n < 0} {
2794        if {[eof $bdf]} {
2795            close $bdf
2796            if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
2797                $ctext tag add $curdifftag $curtagstart end
2798            }
2799        }
2800        return
2801    }
2802    if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
2803        return
2804    }
2805    $ctext conf -state normal
2806    if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} {
2807        # start of a new file
2808        $ctext insert end "\n"
2809        $ctext tag add $curdifftag $curtagstart end
2810        set curtagstart [$ctext index "end - 1c"]
2811        set header $newname
2812        set here [$ctext index "end - 1c"]
2813        set i [lsearch -exact $treediffs($diffids) $fname]
2814        if {$i >= 0} {
2815            set difffilestart($i) $here
2816            incr i
2817            $ctext mark set fmark.$i $here
2818            $ctext mark gravity fmark.$i left
2819        }
2820        if {$newname != $fname} {
2821            set i [lsearch -exact $treediffs($diffids) $newname]
2822            if {$i >= 0} {
2823                set difffilestart($i) $here
2824                incr i
2825                $ctext mark set fmark.$i $here
2826                $ctext mark gravity fmark.$i left
2827            }
2828        }
2829        set curdifftag "f:$fname"
2830        $ctext tag delete $curdifftag
2831        set l [expr {(78 - [string length $header]) / 2}]
2832        set pad [string range "----------------------------------------" 1 $l]
2833        $ctext insert end "$pad $header $pad\n" filesep
2834        set diffinhdr 1
2835    } elseif {[regexp {^(---|\+\+\+)} $line]} {
2836        set diffinhdr 0
2837    } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2838                   $line match f1l f1c f2l f2c rest]} {
2839        if {$gaudydiff} {
2840            $ctext insert end "\t" hunksep
2841            $ctext insert end "    $f1l    " d0 "    $f2l    " d1
2842            $ctext insert end "    $rest \n" hunksep
2843        } else {
2844            $ctext insert end "$line\n" hunksep
2845        }
2846        set diffinhdr 0
2847    } else {
2848        set x [string range $line 0 0]
2849        if {$x == "-" || $x == "+"} {
2850            set tag [expr {$x == "+"}]
2851            if {$gaudydiff} {
2852                set line [string range $line 1 end]
2853            }
2854            $ctext insert end "$line\n" d$tag
2855        } elseif {$x == " "} {
2856            if {$gaudydiff} {
2857                set line [string range $line 1 end]
2858            }
2859            $ctext insert end "$line\n"
2860        } elseif {$diffinhdr || $x == "\\"} {
2861            # e.g. "\ No newline at end of file"
2862            $ctext insert end "$line\n" filesep
2863        } else {
2864            # Something else we don't recognize
2865            if {$curdifftag != "Comments"} {
2866                $ctext insert end "\n"
2867                $ctext tag add $curdifftag $curtagstart end
2868                set curtagstart [$ctext index "end - 1c"]
2869                set curdifftag Comments
2870            }
2871            $ctext insert end "$line\n" filesep
2872        }
2873    }
2874    $ctext conf -state disabled
2875    if {[clock clicks -milliseconds] >= $nextupdate} {
2876        incr nextupdate 100
2877        fileevent $bdf readable {}
2878        update
2879        fileevent $bdf readable "getblobdiffline $bdf {$ids}"
2880    }
2881}
2882
2883proc nextfile {} {
2884    global difffilestart ctext
2885    set here [$ctext index @0,0]
2886    for {set i 0} {[info exists difffilestart($i)]} {incr i} {
2887        if {[$ctext compare $difffilestart($i) > $here]} {
2888            if {![info exists pos]
2889                || [$ctext compare $difffilestart($i) < $pos]} {
2890                set pos $difffilestart($i)
2891            }
2892        }
2893    }
2894    if {[info exists pos]} {
2895        $ctext yview $pos
2896    }
2897}
2898
2899proc listboxsel {} {
2900    global ctext cflist currentid
2901    if {![info exists currentid]} return
2902    set sel [lsort [$cflist curselection]]
2903    if {$sel eq {}} return
2904    set first [lindex $sel 0]
2905    catch {$ctext yview fmark.$first}
2906}
2907
2908proc setcoords {} {
2909    global linespc charspc canvx0 canvy0 mainfont
2910    global xspc1 xspc2 lthickness
2911
2912    set linespc [font metrics $mainfont -linespace]
2913    set charspc [font measure $mainfont "m"]
2914    set canvy0 [expr 3 + 0.5 * $linespc]
2915    set canvx0 [expr 3 + 0.5 * $linespc]
2916    set lthickness [expr {int($linespc / 9) + 1}]
2917    set xspc1(0) $linespc
2918    set xspc2 $linespc
2919}
2920
2921proc redisplay {} {
2922    global stopped redisplaying phase
2923    if {$stopped > 1} return
2924    if {$phase == "getcommits"} return
2925    set redisplaying 1
2926    if {$phase == "drawgraph" || $phase == "incrdraw"} {
2927        set stopped 1
2928    } else {
2929        drawgraph
2930    }
2931}
2932
2933proc incrfont {inc} {
2934    global mainfont namefont textfont ctext canv phase
2935    global stopped entries
2936    unmarkmatches
2937    set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
2938    set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
2939    set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
2940    setcoords
2941    $ctext conf -font $textfont
2942    $ctext tag conf filesep -font [concat $textfont bold]
2943    foreach e $entries {
2944        $e conf -font $mainfont
2945    }
2946    if {$phase == "getcommits"} {
2947        $canv itemconf textitems -font $mainfont
2948    }
2949    redisplay
2950}
2951
2952proc clearsha1 {} {
2953    global sha1entry sha1string
2954    if {[string length $sha1string] == 40} {
2955        $sha1entry delete 0 end
2956    }
2957}
2958
2959proc sha1change {n1 n2 op} {
2960    global sha1string currentid sha1but
2961    if {$sha1string == {}
2962        || ([info exists currentid] && $sha1string == $currentid)} {
2963        set state disabled
2964    } else {
2965        set state normal
2966    }
2967    if {[$sha1but cget -state] == $state} return
2968    if {$state == "normal"} {
2969        $sha1but conf -state normal -relief raised -text "Goto: "
2970    } else {
2971        $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
2972    }
2973}
2974
2975proc gotocommit {} {
2976    global sha1string currentid idline tagids
2977    global lineid numcommits
2978
2979    if {$sha1string == {}
2980        || ([info exists currentid] && $sha1string == $currentid)} return
2981    if {[info exists tagids($sha1string)]} {
2982        set id $tagids($sha1string)
2983    } else {
2984        set id [string tolower $sha1string]
2985        if {[regexp {^[0-9a-f]{4,39}$} $id]} {
2986            set matches {}
2987            for {set l 0} {$l < $numcommits} {incr l} {
2988                if {[string match $id* $lineid($l)]} {
2989                    lappend matches $lineid($l)
2990                }
2991            }
2992            if {$matches ne {}} {
2993                if {[llength $matches] > 1} {
2994                    error_popup "Short SHA1 id $id is ambiguous"
2995                    return
2996                }
2997                set id [lindex $matches 0]
2998            }
2999        }
3000    }
3001    if {[info exists idline($id)]} {
3002        selectline $idline($id) 1
3003        return
3004    }
3005    if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
3006        set type "SHA1 id"
3007    } else {
3008        set type "Tag"
3009    }
3010    error_popup "$type $sha1string is not known"
3011}
3012
3013proc lineenter {x y id} {
3014    global hoverx hovery hoverid hovertimer
3015    global commitinfo canv
3016
3017    if {![info exists commitinfo($id)]} return
3018    set hoverx $x
3019    set hovery $y
3020    set hoverid $id
3021    if {[info exists hovertimer]} {
3022        after cancel $hovertimer
3023    }
3024    set hovertimer [after 500 linehover]
3025    $canv delete hover
3026}
3027
3028proc linemotion {x y id} {
3029    global hoverx hovery hoverid hovertimer
3030
3031    if {[info exists hoverid] && $id == $hoverid} {
3032        set hoverx $x
3033        set hovery $y
3034        if {[info exists hovertimer]} {
3035            after cancel $hovertimer
3036        }
3037        set hovertimer [after 500 linehover]
3038    }
3039}
3040
3041proc lineleave {id} {
3042    global hoverid hovertimer canv
3043
3044    if {[info exists hoverid] && $id == $hoverid} {
3045        $canv delete hover
3046        if {[info exists hovertimer]} {
3047            after cancel $hovertimer
3048            unset hovertimer
3049        }
3050        unset hoverid
3051    }
3052}
3053
3054proc linehover {} {
3055    global hoverx hovery hoverid hovertimer
3056    global canv linespc lthickness
3057    global commitinfo mainfont
3058
3059    set text [lindex $commitinfo($hoverid) 0]
3060    set ymax [lindex [$canv cget -scrollregion] 3]
3061    if {$ymax == {}} return
3062    set yfrac [lindex [$canv yview] 0]
3063    set x [expr {$hoverx + 2 * $linespc}]
3064    set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
3065    set x0 [expr {$x - 2 * $lthickness}]
3066    set y0 [expr {$y - 2 * $lthickness}]
3067    set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
3068    set y1 [expr {$y + $linespc + 2 * $lthickness}]
3069    set t [$canv create rectangle $x0 $y0 $x1 $y1 \
3070               -fill \#ffff80 -outline black -width 1 -tags hover]
3071    $canv raise $t
3072    set t [$canv create text $x $y -anchor nw -text $text -tags hover]
3073    $canv raise $t
3074}
3075
3076proc lineclick {x y id isnew} {
3077    global ctext commitinfo children cflist canv
3078
3079    unmarkmatches
3080    unselectline
3081    if {$isnew} {
3082        addtohistory [list lineclick $x $x $id 0]
3083    }
3084    $canv delete hover
3085    # fill the details pane with info about this line
3086    $ctext conf -state normal
3087    $ctext delete 0.0 end
3088    $ctext tag conf link -foreground blue -underline 1
3089    $ctext tag bind link <Enter> { %W configure -cursor hand2 }
3090    $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
3091    $ctext insert end "Parent:\t"
3092    $ctext insert end $id [list link link0]
3093    $ctext tag bind link0 <1> [list selbyid $id]
3094    set info $commitinfo($id)
3095    $ctext insert end "\n\t[lindex $info 0]\n"
3096    $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
3097    $ctext insert end "\tDate:\t[lindex $info 2]\n"
3098    if {[info exists children($id)]} {
3099        $ctext insert end "\nChildren:"
3100        set i 0
3101        foreach child $children($id) {
3102            incr i
3103            set info $commitinfo($child)
3104            $ctext insert end "\n\t"
3105            $ctext insert end $child [list link link$i]
3106            $ctext tag bind link$i <1> [list selbyid $child]
3107            $ctext insert end "\n\t[lindex $info 0]"
3108            $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
3109            $ctext insert end "\n\tDate:\t[lindex $info 2]\n"
3110        }
3111    }
3112    $ctext conf -state disabled
3113
3114    $cflist delete 0 end
3115}
3116
3117proc selbyid {id} {
3118    global idline
3119    if {[info exists idline($id)]} {
3120        selectline $idline($id) 1
3121    }
3122}
3123
3124proc mstime {} {
3125    global startmstime
3126    if {![info exists startmstime]} {
3127        set startmstime [clock clicks -milliseconds]
3128    }
3129    return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
3130}
3131
3132proc rowmenu {x y id} {
3133    global rowctxmenu idline selectedline rowmenuid
3134
3135    if {![info exists selectedline] || $idline($id) eq $selectedline} {
3136        set state disabled
3137    } else {
3138        set state normal
3139    }
3140    $rowctxmenu entryconfigure 0 -state $state
3141    $rowctxmenu entryconfigure 1 -state $state
3142    $rowctxmenu entryconfigure 2 -state $state
3143    set rowmenuid $id
3144    tk_popup $rowctxmenu $x $y
3145}
3146
3147proc diffvssel {dirn} {
3148    global rowmenuid selectedline lineid
3149
3150    if {![info exists selectedline]} return
3151    if {$dirn} {
3152        set oldid $lineid($selectedline)
3153        set newid $rowmenuid
3154    } else {
3155        set oldid $rowmenuid
3156        set newid $lineid($selectedline)
3157    }
3158    addtohistory [list doseldiff $oldid $newid]
3159    doseldiff $oldid $newid
3160}
3161
3162proc doseldiff {oldid newid} {
3163    global ctext cflist
3164    global commitinfo
3165
3166    $ctext conf -state normal
3167    $ctext delete 0.0 end
3168    $ctext mark set fmark.0 0.0
3169    $ctext mark gravity fmark.0 left
3170    $cflist delete 0 end
3171    $cflist insert end "Top"
3172    $ctext insert end "From "
3173    $ctext tag conf link -foreground blue -underline 1
3174    $ctext tag bind link <Enter> { %W configure -cursor hand2 }
3175    $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
3176    $ctext tag bind link0 <1> [list selbyid $oldid]
3177    $ctext insert end $oldid [list link link0]
3178    $ctext insert end "\n     "
3179    $ctext insert end [lindex $commitinfo($oldid) 0]
3180    $ctext insert end "\n\nTo   "
3181    $ctext tag bind link1 <1> [list selbyid $newid]
3182    $ctext insert end $newid [list link link1]
3183    $ctext insert end "\n     "
3184    $ctext insert end [lindex $commitinfo($newid) 0]
3185    $ctext insert end "\n"
3186    $ctext conf -state disabled
3187    $ctext tag delete Comments
3188    $ctext tag remove found 1.0 end
3189    startdiff [list $newid $oldid]
3190}
3191
3192proc mkpatch {} {
3193    global rowmenuid currentid commitinfo patchtop patchnum
3194
3195    if {![info exists currentid]} return
3196    set oldid $currentid
3197    set oldhead [lindex $commitinfo($oldid) 0]
3198    set newid $rowmenuid
3199    set newhead [lindex $commitinfo($newid) 0]
3200    set top .patch
3201    set patchtop $top
3202    catch {destroy $top}
3203    toplevel $top
3204    label $top.title -text "Generate patch"
3205    grid $top.title - -pady 10
3206    label $top.from -text "From:"
3207    entry $top.fromsha1 -width 40 -relief flat
3208    $top.fromsha1 insert 0 $oldid
3209    $top.fromsha1 conf -state readonly
3210    grid $top.from $top.fromsha1 -sticky w
3211    entry $top.fromhead -width 60 -relief flat
3212    $top.fromhead insert 0 $oldhead
3213    $top.fromhead conf -state readonly
3214    grid x $top.fromhead -sticky w
3215    label $top.to -text "To:"
3216    entry $top.tosha1 -width 40 -relief flat
3217    $top.tosha1 insert 0 $newid
3218    $top.tosha1 conf -state readonly
3219    grid $top.to $top.tosha1 -sticky w
3220    entry $top.tohead -width 60 -relief flat
3221    $top.tohead insert 0 $newhead
3222    $top.tohead conf -state readonly
3223    grid x $top.tohead -sticky w
3224    button $top.rev -text "Reverse" -command mkpatchrev -padx 5
3225    grid $top.rev x -pady 10
3226    label $top.flab -text "Output file:"
3227    entry $top.fname -width 60
3228    $top.fname insert 0 [file normalize "patch$patchnum.patch"]
3229    incr patchnum
3230    grid $top.flab $top.fname -sticky w
3231    frame $top.buts
3232    button $top.buts.gen -text "Generate" -command mkpatchgo
3233    button $top.buts.can -text "Cancel" -command mkpatchcan
3234    grid $top.buts.gen $top.buts.can
3235    grid columnconfigure $top.buts 0 -weight 1 -uniform a
3236    grid columnconfigure $top.buts 1 -weight 1 -uniform a
3237    grid $top.buts - -pady 10 -sticky ew
3238    focus $top.fname
3239}
3240
3241proc mkpatchrev {} {
3242    global patchtop
3243
3244    set oldid [$patchtop.fromsha1 get]
3245    set oldhead [$patchtop.fromhead get]
3246    set newid [$patchtop.tosha1 get]
3247    set newhead [$patchtop.tohead get]
3248    foreach e [list fromsha1 fromhead tosha1 tohead] \
3249            v [list $newid $newhead $oldid $oldhead] {
3250        $patchtop.$e conf -state normal
3251        $patchtop.$e delete 0 end
3252        $patchtop.$e insert 0 $v
3253        $patchtop.$e conf -state readonly
3254    }
3255}
3256
3257proc mkpatchgo {} {
3258    global patchtop
3259
3260    set oldid [$patchtop.fromsha1 get]
3261    set newid [$patchtop.tosha1 get]
3262    set fname [$patchtop.fname get]
3263    if {[catch {exec git-diff-tree -p $oldid $newid >$fname &} err]} {
3264        error_popup "Error creating patch: $err"
3265    }
3266    catch {destroy $patchtop}
3267    unset patchtop
3268}
3269
3270proc mkpatchcan {} {
3271    global patchtop
3272
3273    catch {destroy $patchtop}
3274    unset patchtop
3275}
3276
3277proc mktag {} {
3278    global rowmenuid mktagtop commitinfo
3279
3280    set top .maketag
3281    set mktagtop $top
3282    catch {destroy $top}
3283    toplevel $top
3284    label $top.title -text "Create tag"
3285    grid $top.title - -pady 10
3286    label $top.id -text "ID:"
3287    entry $top.sha1 -width 40 -relief flat
3288    $top.sha1 insert 0 $rowmenuid
3289    $top.sha1 conf -state readonly
3290    grid $top.id $top.sha1 -sticky w
3291    entry $top.head -width 60 -relief flat
3292    $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3293    $top.head conf -state readonly
3294    grid x $top.head -sticky w
3295    label $top.tlab -text "Tag name:"
3296    entry $top.tag -width 60
3297    grid $top.tlab $top.tag -sticky w
3298    frame $top.buts
3299    button $top.buts.gen -text "Create" -command mktaggo
3300    button $top.buts.can -text "Cancel" -command mktagcan
3301    grid $top.buts.gen $top.buts.can
3302    grid columnconfigure $top.buts 0 -weight 1 -uniform a
3303    grid columnconfigure $top.buts 1 -weight 1 -uniform a
3304    grid $top.buts - -pady 10 -sticky ew
3305    focus $top.tag
3306}
3307
3308proc domktag {} {
3309    global mktagtop env tagids idtags
3310    global idpos idline linehtag canv selectedline
3311
3312    set id [$mktagtop.sha1 get]
3313    set tag [$mktagtop.tag get]
3314    if {$tag == {}} {
3315        error_popup "No tag name specified"
3316        return
3317    }
3318    if {[info exists tagids($tag)]} {
3319        error_popup "Tag \"$tag\" already exists"
3320        return
3321    }
3322    if {[catch {
3323        set dir [gitdir]
3324        set fname [file join $dir "refs/tags" $tag]
3325        set f [open $fname w]
3326        puts $f $id
3327        close $f
3328    } err]} {
3329        error_popup "Error creating tag: $err"
3330        return
3331    }
3332
3333    set tagids($tag) $id
3334    lappend idtags($id) $tag
3335    $canv delete tag.$id
3336    set xt [eval drawtags $id $idpos($id)]
3337    $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2]
3338    if {[info exists selectedline] && $selectedline == $idline($id)} {
3339        selectline $selectedline 0
3340    }
3341}
3342
3343proc mktagcan {} {
3344    global mktagtop
3345
3346    catch {destroy $mktagtop}
3347    unset mktagtop
3348}
3349
3350proc mktaggo {} {
3351    domktag
3352    mktagcan
3353}
3354
3355proc writecommit {} {
3356    global rowmenuid wrcomtop commitinfo wrcomcmd
3357
3358    set top .writecommit
3359    set wrcomtop $top
3360    catch {destroy $top}
3361    toplevel $top
3362    label $top.title -text "Write commit to file"
3363    grid $top.title - -pady 10
3364    label $top.id -text "ID:"
3365    entry $top.sha1 -width 40 -relief flat
3366    $top.sha1 insert 0 $rowmenuid
3367    $top.sha1 conf -state readonly
3368    grid $top.id $top.sha1 -sticky w
3369    entry $top.head -width 60 -relief flat
3370    $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3371    $top.head conf -state readonly
3372    grid x $top.head -sticky w
3373    label $top.clab -text "Command:"
3374    entry $top.cmd -width 60 -textvariable wrcomcmd
3375    grid $top.clab $top.cmd -sticky w -pady 10
3376    label $top.flab -text "Output file:"
3377    entry $top.fname -width 60
3378    $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
3379    grid $top.flab $top.fname -sticky w
3380    frame $top.buts
3381    button $top.buts.gen -text "Write" -command wrcomgo
3382    button $top.buts.can -text "Cancel" -command wrcomcan
3383    grid $top.buts.gen $top.buts.can
3384    grid columnconfigure $top.buts 0 -weight 1 -uniform a
3385    grid columnconfigure $top.buts 1 -weight 1 -uniform a
3386    grid $top.buts - -pady 10 -sticky ew
3387    focus $top.fname
3388}
3389
3390proc wrcomgo {} {
3391    global wrcomtop
3392
3393    set id [$wrcomtop.sha1 get]
3394    set cmd "echo $id | [$wrcomtop.cmd get]"
3395    set fname [$wrcomtop.fname get]
3396    if {[catch {exec sh -c $cmd >$fname &} err]} {
3397        error_popup "Error writing commit: $err"
3398    }
3399    catch {destroy $wrcomtop}
3400    unset wrcomtop
3401}
3402
3403proc wrcomcan {} {
3404    global wrcomtop
3405
3406    catch {destroy $wrcomtop}
3407    unset wrcomtop
3408}
3409
3410proc doquit {} {
3411    global stopped
3412    set stopped 100
3413    destroy .
3414}
3415
3416# defaults...
3417set datemode 0
3418set boldnames 0
3419set diffopts "-U 5 -p"
3420set wrcomcmd "git-diff-tree --stdin -p --pretty"
3421
3422set mainfont {Helvetica 9}
3423set textfont {Courier 9}
3424set findmergefiles 0
3425set gaudydiff 0
3426set maxgraphpct 50
3427set maxwidth 16
3428
3429set colors {green red blue magenta darkgrey brown orange}
3430
3431catch {source ~/.gitk}
3432
3433set namefont $mainfont
3434if {$boldnames} {
3435    lappend namefont bold
3436}
3437
3438set revtreeargs {}
3439foreach arg $argv {
3440    switch -regexp -- $arg {
3441        "^$" { }
3442        "^-b" { set boldnames 1 }
3443        "^-d" { set datemode 1 }
3444        default {
3445            lappend revtreeargs $arg
3446        }
3447    }
3448}
3449
3450set history {}
3451set historyindex 0
3452
3453set stopped 0
3454set redisplaying 0
3455set stuffsaved 0
3456set patchnum 0
3457setcoords
3458makewindow
3459readrefs
3460getcommits $revtreeargs