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