gitkon commit gitk: Implement date mode in the new framework (f3ea5ed)
   1#!/bin/sh
   2# Tcl ignores the next line -*- tcl -*- \
   3exec wish "$0" -- "$@"
   4
   5# Copyright (C) 2005-2006 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 [exec git rev-parse --git-dir]
  16    }
  17}
  18
  19# A simple scheduler for compute-intensive stuff.
  20# The aim is to make sure that event handlers for GUI actions can
  21# run at least every 50-100 ms.  Unfortunately fileevent handlers are
  22# run before X event handlers, so reading from a fast source can
  23# make the GUI completely unresponsive.
  24proc run args {
  25    global isonrunq runq
  26
  27    set script $args
  28    if {[info exists isonrunq($script)]} return
  29    if {$runq eq {}} {
  30        after idle dorunq
  31    }
  32    lappend runq [list {} $script]
  33    set isonrunq($script) 1
  34}
  35
  36proc filerun {fd script} {
  37    fileevent $fd readable [list filereadable $fd $script]
  38}
  39
  40proc filereadable {fd script} {
  41    global runq
  42
  43    fileevent $fd readable {}
  44    if {$runq eq {}} {
  45        after idle dorunq
  46    }
  47    lappend runq [list $fd $script]
  48}
  49
  50proc nukefile {fd} {
  51    global runq
  52
  53    for {set i 0} {$i < [llength $runq]} {} {
  54        if {[lindex $runq $i 0] eq $fd} {
  55            set runq [lreplace $runq $i $i]
  56        } else {
  57            incr i
  58        }
  59    }
  60}
  61
  62proc dorunq {} {
  63    global isonrunq runq
  64
  65    set tstart [clock clicks -milliseconds]
  66    set t0 $tstart
  67    while {[llength $runq] > 0} {
  68        set fd [lindex $runq 0 0]
  69        set script [lindex $runq 0 1]
  70        set repeat [eval $script]
  71        set t1 [clock clicks -milliseconds]
  72        set t [expr {$t1 - $t0}]
  73        set runq [lrange $runq 1 end]
  74        if {$repeat ne {} && $repeat} {
  75            if {$fd eq {} || $repeat == 2} {
  76                # script returns 1 if it wants to be readded
  77                # file readers return 2 if they could do more straight away
  78                lappend runq [list $fd $script]
  79            } else {
  80                fileevent $fd readable [list filereadable $fd $script]
  81            }
  82        } elseif {$fd eq {}} {
  83            unset isonrunq($script)
  84        }
  85        set t0 $t1
  86        if {$t1 - $tstart >= 80} break
  87    }
  88    if {$runq ne {}} {
  89        after idle dorunq
  90    }
  91}
  92
  93# Start off a git rev-list process and arrange to read its output
  94proc start_rev_list {view} {
  95    global startmsecs
  96    global commfd leftover tclencoding datemode
  97    global viewargs viewfiles commitidx viewcomplete vnextroot
  98    global showlocalchanges commitinterest mainheadid
  99    global progressdirn progresscoords proglastnc curview
 100    global viewincl viewactive loginstance viewinstances
 101
 102    set startmsecs [clock clicks -milliseconds]
 103    set commitidx($view) 0
 104    set viewcomplete($view) 0
 105    set viewactive($view) 1
 106    set vnextroot($view) 0
 107    varcinit $view
 108
 109    set commits [eval exec git rev-parse --default HEAD --revs-only \
 110                     $viewargs($view)]
 111    set viewincl($view) {}
 112    foreach c $commits {
 113        if {![string match "^*" $c]} {
 114            lappend viewincl($view) $c
 115        }
 116    }
 117    if {[catch {
 118        set fd [open [concat | git log --no-color -z --pretty=raw --parents \
 119                         --boundary $commits "--" $viewfiles($view)] r]
 120    } err]} {
 121        error_popup "Error executing git log: $err"
 122        exit 1
 123    }
 124    set i [incr loginstance]
 125    set viewinstances($view) [list $i]
 126    set commfd($i) $fd
 127    set leftover($i) {}
 128    if {$showlocalchanges} {
 129        lappend commitinterest($mainheadid) {dodiffindex}
 130    }
 131    fconfigure $fd -blocking 0 -translation lf -eofchar {}
 132    if {$tclencoding != {}} {
 133        fconfigure $fd -encoding $tclencoding
 134    }
 135    filerun $fd [list getcommitlines $fd $i $view]
 136    nowbusy $view "Reading"
 137    if {$view == $curview} {
 138        set progressdirn 1
 139        set progresscoords {0 0}
 140        set proglastnc 0
 141    }
 142}
 143
 144proc stop_rev_list {view} {
 145    global commfd viewinstances leftover
 146
 147    foreach inst $viewinstances($view) {
 148        set fd $commfd($inst)
 149        catch {
 150            set pid [pid $fd]
 151            exec kill $pid
 152        }
 153        catch {close $fd}
 154        nukefile $fd
 155        unset commfd($inst)
 156        unset leftover($inst)
 157    }
 158    set viewinstances($view) {}
 159}
 160
 161proc getcommits {} {
 162    global canv curview
 163
 164    initlayout
 165    start_rev_list $curview
 166    show_status "Reading commits..."
 167}
 168
 169proc updatecommits {} {
 170    global curview viewargs viewfiles viewincl viewinstances
 171    global viewactive viewcomplete loginstance tclencoding mainheadid
 172    global varcid startmsecs commfd showneartags showlocalchanges leftover
 173
 174    if {$showlocalchanges && [commitinview $mainheadid $curview]} {
 175        dodiffindex
 176    }
 177    set view $curview
 178    set commits [exec git rev-parse --default HEAD --revs-only \
 179                     $viewargs($view)]
 180    set pos {}
 181    set neg {}
 182    foreach c $commits {
 183        if {[string match "^*" $c]} {
 184            lappend neg $c
 185        } else {
 186            if {!([info exists varcid($view,$c)] ||
 187                  [lsearch -exact $viewincl($view) $c] >= 0)} {
 188                lappend pos $c
 189            }
 190        }
 191    }
 192    if {$pos eq {}} {
 193        return
 194    }
 195    foreach id $viewincl($view) {
 196        lappend neg "^$id"
 197    }
 198    set viewincl($view) [concat $viewincl($view) $pos]
 199    if {[catch {
 200        set fd [open [concat | git log --no-color -z --pretty=raw --parents \
 201                         --boundary $pos $neg "--" $viewfiles($view)] r]
 202    } err]} {
 203        error_popup "Error executing git log: $err"
 204        exit 1
 205    }
 206    if {$viewactive($view) == 0} {
 207        set startmsecs [clock clicks -milliseconds]
 208    }
 209    set i [incr loginstance]
 210    lappend viewinstances($view) $i
 211    set commfd($i) $fd
 212    set leftover($i) {}
 213    fconfigure $fd -blocking 0 -translation lf -eofchar {}
 214    if {$tclencoding != {}} {
 215        fconfigure $fd -encoding $tclencoding
 216    }
 217    filerun $fd [list getcommitlines $fd $i $view]
 218    incr viewactive($view)
 219    set viewcomplete($view) 0
 220    nowbusy $view "Reading"
 221    readrefs
 222    changedrefs
 223    if {$showneartags} {
 224        getallcommits
 225    }
 226}
 227
 228proc reloadcommits {} {
 229    global curview viewcomplete selectedline currentid thickerline
 230    global showneartags treediffs commitinterest cached_commitrow
 231    global progresscoords
 232
 233    if {!$viewcomplete($curview)} {
 234        stop_rev_list $curview
 235        set progresscoords {0 0}
 236        adjustprogress
 237    }
 238    resetvarcs $curview
 239    catch {unset selectedline}
 240    catch {unset currentid}
 241    catch {unset thickerline}
 242    catch {unset treediffs}
 243    readrefs
 244    changedrefs
 245    if {$showneartags} {
 246        getallcommits
 247    }
 248    clear_display
 249    catch {unset commitinterest}
 250    catch {unset cached_commitrow}
 251    setcanvscroll
 252    getcommits
 253}
 254
 255# This makes a string representation of a positive integer which
 256# sorts as a string in numerical order
 257proc strrep {n} {
 258    if {$n < 16} {
 259        return [format "%x" $n]
 260    } elseif {$n < 256} {
 261        return [format "x%.2x" $n]
 262    } elseif {$n < 65536} {
 263        return [format "y%.4x" $n]
 264    }
 265    return [format "z%.8x" $n]
 266}
 267
 268# Procedures used in reordering commits from git log (without
 269# --topo-order) into the order for display.
 270
 271proc varcinit {view} {
 272    global varcstart vupptr vdownptr vleftptr vbackptr varctok varcrow
 273    global vtokmod varcmod vrowmod varcix vlastins
 274
 275    set varcstart($view) {{}}
 276    set vupptr($view) {0}
 277    set vdownptr($view) {0}
 278    set vleftptr($view) {0}
 279    set vbackptr($view) {0}
 280    set varctok($view) {{}}
 281    set varcrow($view) {{}}
 282    set vtokmod($view) {}
 283    set varcmod($view) 0
 284    set vrowmod($view) 0
 285    set varcix($view) {{}}
 286    set vlastins($view) {0}
 287}
 288
 289proc resetvarcs {view} {
 290    global varcid varccommits parents children vseedcount ordertok
 291
 292    foreach vid [array names varcid $view,*] {
 293        unset varcid($vid)
 294        unset children($vid)
 295        unset parents($vid)
 296    }
 297    # some commits might have children but haven't been seen yet
 298    foreach vid [array names children $view,*] {
 299        unset children($vid)
 300    }
 301    foreach va [array names varccommits $view,*] {
 302        unset varccommits($va)
 303    }
 304    foreach vd [array names vseedcount $view,*] {
 305        unset vseedcount($vd)
 306    }
 307    catch {unset ordertok}
 308}
 309
 310proc newvarc {view id} {
 311    global varcid varctok parents children datemode
 312    global vupptr vdownptr vleftptr vbackptr varcrow varcix varcstart
 313    global commitdata commitinfo vseedcount varccommits vlastins
 314
 315    set a [llength $varctok($view)]
 316    set vid $view,$id
 317    if {[llength $children($vid)] == 0 || $datemode} {
 318        if {![info exists commitinfo($id)]} {
 319            parsecommit $id $commitdata($id) 1
 320        }
 321        set cdate [lindex $commitinfo($id) 4]
 322        if {![string is integer -strict $cdate]} {
 323            set cdate 0
 324        }
 325        if {![info exists vseedcount($view,$cdate)]} {
 326            set vseedcount($view,$cdate) -1
 327        }
 328        set c [incr vseedcount($view,$cdate)]
 329        set cdate [expr {$cdate ^ 0xffffffff}]
 330        set tok "s[strrep $cdate][strrep $c]"
 331    } else {
 332        set tok {}
 333    }
 334    set ka 0
 335    if {[llength $children($vid)] > 0} {
 336        set kid [lindex $children($vid) end]
 337        set k $varcid($view,$kid)
 338        if {[string compare [lindex $varctok($view) $k] $tok] > 0} {
 339            set ki $kid
 340            set ka $k
 341            set tok [lindex $varctok($view) $k]
 342        }
 343    }
 344    if {$ka != 0} {
 345        set i [lsearch -exact $parents($view,$ki) $id]
 346        set j [expr {[llength $parents($view,$ki)] - 1 - $i}]
 347        append tok [strrep $j]
 348    }
 349    set c [lindex $vlastins($view) $ka]
 350    if {$c == 0 || [string compare $tok [lindex $varctok($view) $c]] < 0} {
 351        set c $ka
 352        set b [lindex $vdownptr($view) $ka]
 353    } else {
 354        set b [lindex $vleftptr($view) $c]
 355    }
 356    while {$b != 0 && [string compare $tok [lindex $varctok($view) $b]] >= 0} {
 357        set c $b
 358        set b [lindex $vleftptr($view) $c]
 359    }
 360    if {$c == $ka} {
 361        lset vdownptr($view) $ka $a
 362        lappend vbackptr($view) 0
 363    } else {
 364        lset vleftptr($view) $c $a
 365        lappend vbackptr($view) $c
 366    }
 367    lset vlastins($view) $ka $a
 368    lappend vupptr($view) $ka
 369    lappend vleftptr($view) $b
 370    if {$b != 0} {
 371        lset vbackptr($view) $b $a
 372    }
 373    lappend varctok($view) $tok
 374    lappend varcstart($view) $id
 375    lappend vdownptr($view) 0
 376    lappend varcrow($view) {}
 377    lappend varcix($view) {}
 378    set varccommits($view,$a) {}
 379    lappend vlastins($view) 0
 380    return $a
 381}
 382
 383proc splitvarc {p v} {
 384    global varcid varcstart varccommits varctok
 385    global vupptr vdownptr vleftptr vbackptr varcix varcrow vlastins
 386
 387    set oa $varcid($v,$p)
 388    set ac $varccommits($v,$oa)
 389    set i [lsearch -exact $varccommits($v,$oa) $p]
 390    if {$i <= 0} return
 391    set na [llength $varctok($v)]
 392    # "%" sorts before "0"...
 393    set tok "[lindex $varctok($v) $oa]%[strrep $i]"
 394    lappend varctok($v) $tok
 395    lappend varcrow($v) {}
 396    lappend varcix($v) {}
 397    set varccommits($v,$oa) [lrange $ac 0 [expr {$i - 1}]]
 398    set varccommits($v,$na) [lrange $ac $i end]
 399    lappend varcstart($v) $p
 400    foreach id $varccommits($v,$na) {
 401        set varcid($v,$id) $na
 402    }
 403    lappend vdownptr($v) [lindex $vdownptr($v) $oa]
 404    lset vdownptr($v) $oa $na
 405    lappend vupptr($v) $oa
 406    lappend vleftptr($v) 0
 407    lappend vbackptr($v) 0
 408    lappend vlastins($v) 0
 409    for {set b [lindex $vdownptr($v) $na]} {$b != 0} {set b [lindex $vleftptr($v) $b]} {
 410        lset vupptr($v) $b $na
 411    }
 412}
 413
 414proc renumbervarc {a v} {
 415    global parents children varctok varcstart varccommits
 416    global vupptr vdownptr vleftptr vbackptr vlastins varcid vtokmod datemode
 417
 418    set t1 [clock clicks -milliseconds]
 419    set todo {}
 420    set isrelated($a) 1
 421    set kidchanged($a) 1
 422    set ntot 0
 423    while {$a != 0} {
 424        if {[info exists isrelated($a)]} {
 425            lappend todo $a
 426            set id [lindex $varccommits($v,$a) end]
 427            foreach p $parents($v,$id) {
 428                if {[info exists varcid($v,$p)]} {
 429                    set isrelated($varcid($v,$p)) 1
 430                }
 431            }
 432        }
 433        incr ntot
 434        set b [lindex $vdownptr($v) $a]
 435        if {$b == 0} {
 436            while {$a != 0} {
 437                set b [lindex $vleftptr($v) $a]
 438                if {$b != 0} break
 439                set a [lindex $vupptr($v) $a]
 440            }
 441        }
 442        set a $b
 443    }
 444    foreach a $todo {
 445        if {![info exists kidchanged($a)]} continue
 446        set id [lindex $varcstart($v) $a]
 447        if {[llength $children($v,$id)] > 1} {
 448            set children($v,$id) [lsort -command [list vtokcmp $v] \
 449                                      $children($v,$id)]
 450        }
 451        set oldtok [lindex $varctok($v) $a]
 452        if {!$datemode} {
 453            set tok {}
 454        } else {
 455            set tok $oldtok
 456        }
 457        set ka 0
 458        if {[llength $children($v,$id)] > 0} {
 459            set kid [lindex $children($v,$id) end]
 460            set k $varcid($v,$kid)
 461            if {[string compare [lindex $varctok($v) $k] $tok] > 0} {
 462                set ki $kid
 463                set ka $k
 464                set tok [lindex $varctok($v) $k]
 465            }
 466        }
 467        if {$ka != 0} {
 468            set i [lsearch -exact $parents($v,$ki) $id]
 469            set j [expr {[llength $parents($v,$ki)] - 1 - $i}]
 470            append tok [strrep $j]
 471        }
 472        if {$tok eq $oldtok} {
 473            continue
 474        }
 475        set id [lindex $varccommits($v,$a) end]
 476        foreach p $parents($v,$id) {
 477            if {[info exists varcid($v,$p)]} {
 478                set kidchanged($varcid($v,$p)) 1
 479            } else {
 480                set sortkids($p) 1
 481            }
 482        }
 483        lset varctok($v) $a $tok
 484        set b [lindex $vupptr($v) $a]
 485        if {$b != $ka} {
 486            if {[string compare [lindex $varctok($v) $ka] $vtokmod($v)] < 0} {
 487                modify_arc $v $ka
 488            }
 489            if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
 490                modify_arc $v $b
 491            }
 492            set c [lindex $vbackptr($v) $a]
 493            set d [lindex $vleftptr($v) $a]
 494            if {$c == 0} {
 495                lset vdownptr($v) $b $d
 496            } else {
 497                lset vleftptr($v) $c $d
 498            }
 499            if {$d != 0} {
 500                lset vbackptr($v) $d $c
 501            }
 502            lset vupptr($v) $a $ka
 503            set c [lindex $vlastins($v) $ka]
 504            if {$c == 0 || \
 505                    [string compare $tok [lindex $varctok($v) $c]] < 0} {
 506                set c $ka
 507                set b [lindex $vdownptr($v) $ka]
 508            } else {
 509                set b [lindex $vleftptr($v) $c]
 510            }
 511            while {$b != 0 && \
 512                      [string compare $tok [lindex $varctok($v) $b]] >= 0} {
 513                set c $b
 514                set b [lindex $vleftptr($v) $c]
 515            }
 516            if {$c == $ka} {
 517                lset vdownptr($v) $ka $a
 518                lset vbackptr($v) $a 0
 519            } else {
 520                lset vleftptr($v) $c $a
 521                lset vbackptr($v) $a $c
 522            }
 523            lset vleftptr($v) $a $b
 524            if {$b != 0} {
 525                lset vbackptr($v) $b $a
 526            }
 527            lset vlastins($v) $ka $a
 528        }
 529    }
 530    foreach id [array names sortkids] {
 531        if {[llength $children($v,$id)] > 1} {
 532            set children($v,$id) [lsort -command [list vtokcmp $v] \
 533                                      $children($v,$id)]
 534        }
 535    }
 536    set t2 [clock clicks -milliseconds]
 537    #puts "renumbervarc did [llength $todo] of $ntot arcs in [expr {$t2-$t1}]ms"
 538}
 539
 540proc fix_reversal {p a v} {
 541    global varcid varcstart varctok vupptr
 542
 543    set pa $varcid($v,$p)
 544    if {$p ne [lindex $varcstart($v) $pa]} {
 545        splitvarc $p $v
 546        set pa $varcid($v,$p)
 547    }
 548    # seeds always need to be renumbered
 549    if {[lindex $vupptr($v) $pa] == 0 ||
 550        [string compare [lindex $varctok($v) $a] \
 551             [lindex $varctok($v) $pa]] > 0} {
 552        renumbervarc $pa $v
 553    }
 554}
 555
 556proc insertrow {id p v} {
 557    global varcid varccommits parents children cmitlisted
 558    global commitidx varctok vtokmod
 559
 560    set a $varcid($v,$p)
 561    set i [lsearch -exact $varccommits($v,$a) $p]
 562    if {$i < 0} {
 563        puts "oops: insertrow can't find [shortids $p] on arc $a"
 564        return
 565    }
 566    set children($v,$id) {}
 567    set parents($v,$id) [list $p]
 568    set varcid($v,$id) $a
 569    lappend children($v,$p) $id
 570    set cmitlisted($v,$id) 1
 571    incr commitidx($v)
 572    # note we deliberately don't update varcstart($v) even if $i == 0
 573    set varccommits($v,$a) [linsert $varccommits($v,$a) $i $id]
 574    if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] < 0} {
 575        modify_arc $v $a $i
 576    }
 577    drawvisible
 578}
 579
 580proc removerow {id v} {
 581    global varcid varccommits parents children commitidx
 582    global varctok vtokmod cmitlisted
 583
 584    if {[llength $parents($v,$id)] != 1} {
 585        puts "oops: removerow [shortids $id] has [llength $parents($v,$id)] parents"
 586        return
 587    }
 588    set p [lindex $parents($v,$id) 0]
 589    set a $varcid($v,$id)
 590    set i [lsearch -exact $varccommits($v,$a) $id]
 591    if {$i < 0} {
 592        puts "oops: removerow can't find [shortids $id] on arc $a"
 593        return
 594    }
 595    unset varcid($v,$id)
 596    set varccommits($v,$a) [lreplace $varccommits($v,$a) $i $i]
 597    unset parents($v,$id)
 598    unset children($v,$id)
 599    unset cmitlisted($v,$id)
 600    incr commitidx($v) -1
 601    set j [lsearch -exact $children($v,$p) $id]
 602    if {$j >= 0} {
 603        set children($v,$p) [lreplace $children($v,$p) $j $j]
 604    }
 605    if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] < 0} {
 606        modify_arc $v $a $i
 607    }
 608    drawvisible
 609}
 610
 611proc vtokcmp {v a b} {
 612    global varctok varcid
 613
 614    return [string compare [lindex $varctok($v) $varcid($v,$a)] \
 615                [lindex $varctok($v) $varcid($v,$b)]]
 616}
 617
 618proc modify_arc {v a {lim {}}} {
 619    global varctok vtokmod varcmod varcrow vupptr curview vrowmod varccommits
 620    global vhighlights nhighlights fhighlights rhighlights
 621
 622    set vtokmod($v) [lindex $varctok($v) $a]
 623    set varcmod($v) $a
 624    if {$v == $curview} {
 625        while {$a != 0 && [lindex $varcrow($v) $a] eq {}} {
 626            set a [lindex $vupptr($v) $a]
 627            set lim {}
 628        }
 629        set r 0
 630        if {$a != 0} {
 631            if {$lim eq {}} {
 632                set lim [llength $varccommits($v,$a)]
 633            }
 634            set r [expr {[lindex $varcrow($v) $a] + $lim}]
 635        }
 636        set vrowmod($v) $r
 637        undolayout $r
 638    }
 639    catch {unset nhighlights}
 640    catch {unset fhighlights}
 641    catch {unset vhighlights}
 642    catch {unset rhighlights}
 643}
 644
 645proc update_arcrows {v} {
 646    global vtokmod varcmod vrowmod varcrow commitidx currentid selectedline
 647    global varcid vrownum varcorder varcix varccommits
 648    global vupptr vdownptr vleftptr varctok
 649    global displayorder parentlist curview cached_commitrow
 650
 651    set narctot [expr {[llength $varctok($v)] - 1}]
 652    set a $varcmod($v)
 653    while {$a != 0 && [lindex $varcix($v) $a] eq {}} {
 654        # go up the tree until we find something that has a row number,
 655        # or we get to a seed
 656        set a [lindex $vupptr($v) $a]
 657    }
 658    if {$a == 0} {
 659        set a [lindex $vdownptr($v) 0]
 660        if {$a == 0} return
 661        set vrownum($v) {0}
 662        set varcorder($v) [list $a]
 663        lset varcix($v) $a 0
 664        lset varcrow($v) $a 0
 665        set arcn 0
 666        set row 0
 667    } else {
 668        set arcn [lindex $varcix($v) $a]
 669        # see if a is the last arc; if so, nothing to do
 670        if {$arcn == $narctot - 1} {
 671            return
 672        }
 673        if {[llength $vrownum($v)] > $arcn + 1} {
 674            set vrownum($v) [lrange $vrownum($v) 0 $arcn]
 675            set varcorder($v) [lrange $varcorder($v) 0 $arcn]
 676        }
 677        set row [lindex $varcrow($v) $a]
 678    }
 679    if {$v == $curview} {
 680        if {[llength $displayorder] > $vrowmod($v)} {
 681            set displayorder [lrange $displayorder 0 [expr {$vrowmod($v) - 1}]]
 682            set parentlist [lrange $parentlist 0 [expr {$vrowmod($v) - 1}]]
 683        }
 684        catch {unset cached_commitrow}
 685    }
 686    while {1} {
 687        set p $a
 688        incr row [llength $varccommits($v,$a)]
 689        # go down if possible
 690        set b [lindex $vdownptr($v) $a]
 691        if {$b == 0} {
 692            # if not, go left, or go up until we can go left
 693            while {$a != 0} {
 694                set b [lindex $vleftptr($v) $a]
 695                if {$b != 0} break
 696                set a [lindex $vupptr($v) $a]
 697            }
 698            if {$a == 0} break
 699        }
 700        set a $b
 701        incr arcn
 702        lappend vrownum($v) $row
 703        lappend varcorder($v) $a
 704        lset varcix($v) $a $arcn
 705        lset varcrow($v) $a $row
 706    }
 707    set vtokmod($v) [lindex $varctok($v) $p]
 708    set varcmod($v) $p
 709    set vrowmod($v) $row
 710    if {[info exists currentid]} {
 711        set selectedline [rowofcommit $currentid]
 712    }
 713}
 714
 715# Test whether view $v contains commit $id
 716proc commitinview {id v} {
 717    global varcid
 718
 719    return [info exists varcid($v,$id)]
 720}
 721
 722# Return the row number for commit $id in the current view
 723proc rowofcommit {id} {
 724    global varcid varccommits varcrow curview cached_commitrow
 725    global varctok vtokmod
 726
 727    if {[info exists cached_commitrow($id)]} {
 728        return $cached_commitrow($id)
 729    }
 730    set v $curview
 731    if {![info exists varcid($v,$id)]} {
 732        puts "oops rowofcommit no arc for [shortids $id]"
 733        return {}
 734    }
 735    set a $varcid($v,$id)
 736    if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] > 0} {
 737        update_arcrows $v
 738    }
 739    set i [lsearch -exact $varccommits($v,$a) $id]
 740    if {$i < 0} {
 741        puts "oops didn't find commit [shortids $id] in arc $a"
 742        return {}
 743    }
 744    incr i [lindex $varcrow($v) $a]
 745    set cached_commitrow($id) $i
 746    return $i
 747}
 748
 749proc bsearch {l elt} {
 750    if {[llength $l] == 0 || $elt <= [lindex $l 0]} {
 751        return 0
 752    }
 753    set lo 0
 754    set hi [llength $l]
 755    while {$hi - $lo > 1} {
 756        set mid [expr {int(($lo + $hi) / 2)}]
 757        set t [lindex $l $mid]
 758        if {$elt < $t} {
 759            set hi $mid
 760        } elseif {$elt > $t} {
 761            set lo $mid
 762        } else {
 763            return $mid
 764        }
 765    }
 766    return $lo
 767}
 768
 769# Make sure rows $start..$end-1 are valid in displayorder and parentlist
 770proc make_disporder {start end} {
 771    global vrownum curview commitidx displayorder parentlist
 772    global varccommits varcorder parents vrowmod varcrow
 773    global d_valid_start d_valid_end
 774
 775    if {$end > $vrowmod($curview)} {
 776        update_arcrows $curview
 777    }
 778    set ai [bsearch $vrownum($curview) $start]
 779    set start [lindex $vrownum($curview) $ai]
 780    set narc [llength $vrownum($curview)]
 781    for {set r $start} {$ai < $narc && $r < $end} {incr ai} {
 782        set a [lindex $varcorder($curview) $ai]
 783        set l [llength $displayorder]
 784        set al [llength $varccommits($curview,$a)]
 785        if {$l < $r + $al} {
 786            if {$l < $r} {
 787                set pad [ntimes [expr {$r - $l}] {}]
 788                set displayorder [concat $displayorder $pad]
 789                set parentlist [concat $parentlist $pad]
 790            } elseif {$l > $r} {
 791                set displayorder [lrange $displayorder 0 [expr {$r - 1}]]
 792                set parentlist [lrange $parentlist 0 [expr {$r - 1}]]
 793            }
 794            foreach id $varccommits($curview,$a) {
 795                lappend displayorder $id
 796                lappend parentlist $parents($curview,$id)
 797            }
 798        } elseif {[lindex $displayorder $r] eq {}} {
 799            set i $r
 800            foreach id $varccommits($curview,$a) {
 801                lset displayorder $i $id
 802                lset parentlist $i $parents($curview,$id)
 803                incr i
 804            }
 805        }
 806        incr r $al
 807    }
 808}
 809
 810proc commitonrow {row} {
 811    global displayorder
 812
 813    set id [lindex $displayorder $row]
 814    if {$id eq {}} {
 815        make_disporder $row [expr {$row + 1}]
 816        set id [lindex $displayorder $row]
 817    }
 818    return $id
 819}
 820
 821proc closevarcs {v} {
 822    global varctok varccommits varcid parents children
 823    global cmitlisted commitidx commitinterest vtokmod
 824
 825    set missing_parents 0
 826    set scripts {}
 827    set narcs [llength $varctok($v)]
 828    for {set a 1} {$a < $narcs} {incr a} {
 829        set id [lindex $varccommits($v,$a) end]
 830        foreach p $parents($v,$id) {
 831            if {[info exists varcid($v,$p)]} continue
 832            # add p as a new commit
 833            incr missing_parents
 834            set cmitlisted($v,$p) 0
 835            set parents($v,$p) {}
 836            if {[llength $children($v,$p)] == 1 &&
 837                [llength $parents($v,$id)] == 1} {
 838                set b $a
 839            } else {
 840                set b [newvarc $v $p]
 841            }
 842            set varcid($v,$p) $b
 843            if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
 844                modify_arc $v $b
 845            }
 846            lappend varccommits($v,$b) $p
 847            incr commitidx($v)
 848            if {[info exists commitinterest($p)]} {
 849                foreach script $commitinterest($p) {
 850                    lappend scripts [string map [list "%I" $p] $script]
 851                }
 852                unset commitinterest($id)
 853            }
 854        }
 855    }
 856    if {$missing_parents > 0} {
 857        foreach s $scripts {
 858            eval $s
 859        }
 860    }
 861}
 862
 863proc getcommitlines {fd inst view}  {
 864    global cmitlisted commitinterest leftover
 865    global commitidx commitdata datemode
 866    global parents children curview hlview
 867    global vnextroot idpending ordertok
 868    global varccommits varcid varctok vtokmod
 869
 870    set stuff [read $fd 500000]
 871    # git log doesn't terminate the last commit with a null...
 872    if {$stuff == {} && $leftover($inst) ne {} && [eof $fd]} {
 873        set stuff "\0"
 874    }
 875    if {$stuff == {}} {
 876        if {![eof $fd]} {
 877            return 1
 878        }
 879        global commfd viewcomplete viewactive viewname progresscoords
 880        global viewinstances
 881        unset commfd($inst)
 882        set i [lsearch -exact $viewinstances($view) $inst]
 883        if {$i >= 0} {
 884            set viewinstances($view) [lreplace $viewinstances($view) $i $i]
 885        }
 886        # set it blocking so we wait for the process to terminate
 887        fconfigure $fd -blocking 1
 888        if {[catch {close $fd} err]} {
 889            set fv {}
 890            if {$view != $curview} {
 891                set fv " for the \"$viewname($view)\" view"
 892            }
 893            if {[string range $err 0 4] == "usage"} {
 894                set err "Gitk: error reading commits$fv:\
 895                        bad arguments to git rev-list."
 896                if {$viewname($view) eq "Command line"} {
 897                    append err \
 898                        "  (Note: arguments to gitk are passed to git rev-list\
 899                         to allow selection of commits to be displayed.)"
 900                }
 901            } else {
 902                set err "Error reading commits$fv: $err"
 903            }
 904            error_popup $err
 905        }
 906        if {[incr viewactive($view) -1] <= 0} {
 907            set viewcomplete($view) 1
 908            # Check if we have seen any ids listed as parents that haven't
 909            # appeared in the list
 910            closevarcs $view
 911            notbusy $view
 912            set progresscoords {0 0}
 913            adjustprogress
 914        }
 915        if {$view == $curview} {
 916            run chewcommits $view
 917        }
 918        return 0
 919    }
 920    set start 0
 921    set gotsome 0
 922    set scripts {}
 923    while 1 {
 924        set i [string first "\0" $stuff $start]
 925        if {$i < 0} {
 926            append leftover($inst) [string range $stuff $start end]
 927            break
 928        }
 929        if {$start == 0} {
 930            set cmit $leftover($inst)
 931            append cmit [string range $stuff 0 [expr {$i - 1}]]
 932            set leftover($inst) {}
 933        } else {
 934            set cmit [string range $stuff $start [expr {$i - 1}]]
 935        }
 936        set start [expr {$i + 1}]
 937        set j [string first "\n" $cmit]
 938        set ok 0
 939        set listed 1
 940        if {$j >= 0 && [string match "commit *" $cmit]} {
 941            set ids [string range $cmit 7 [expr {$j - 1}]]
 942            if {[string match {[-<>]*} $ids]} {
 943                switch -- [string index $ids 0] {
 944                    "-" {set listed 0}
 945                    "<" {set listed 2}
 946                    ">" {set listed 3}
 947                }
 948                set ids [string range $ids 1 end]
 949            }
 950            set ok 1
 951            foreach id $ids {
 952                if {[string length $id] != 40} {
 953                    set ok 0
 954                    break
 955                }
 956            }
 957        }
 958        if {!$ok} {
 959            set shortcmit $cmit
 960            if {[string length $shortcmit] > 80} {
 961                set shortcmit "[string range $shortcmit 0 80]..."
 962            }
 963            error_popup "Can't parse git log output: {$shortcmit}"
 964            exit 1
 965        }
 966        set id [lindex $ids 0]
 967        set vid $view,$id
 968        if {!$listed && [info exists parents($vid)]} continue
 969        if {$listed} {
 970            set olds [lrange $ids 1 end]
 971        } else {
 972            set olds {}
 973        }
 974        set commitdata($id) [string range $cmit [expr {$j + 1}] end]
 975        set cmitlisted($vid) $listed
 976        set parents($vid) $olds
 977        set a 0
 978        if {![info exists children($vid)]} {
 979            set children($vid) {}
 980        } elseif {[llength $children($vid)] == 1} {
 981            set k [lindex $children($vid) 0]
 982            if {[llength $parents($view,$k)] == 1 &&
 983                (!$datemode ||
 984                 $varcid($view,$k) == [llength $varctok($view)] - 1)} {
 985                set a $varcid($view,$k)
 986            }
 987        }
 988        if {$a == 0} {
 989            # new arc
 990            set a [newvarc $view $id]
 991        }
 992        set varcid($vid) $a
 993        if {[string compare [lindex $varctok($view) $a] $vtokmod($view)] < 0} {
 994            modify_arc $view $a
 995        }
 996        lappend varccommits($view,$a) $id
 997
 998        set i 0
 999        foreach p $olds {
1000            if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
1001                set vp $view,$p
1002                if {[llength [lappend children($vp) $id]] > 1 &&
1003                    [vtokcmp $view [lindex $children($vp) end-1] $id] > 0} {
1004                    set children($vp) [lsort -command [list vtokcmp $view] \
1005                                           $children($vp)]
1006                    catch {unset ordertok}
1007                }
1008                if {[info exists varcid($view,$p)]} {
1009                    fix_reversal $p $a $view
1010                }
1011            }
1012            incr i
1013        }
1014
1015        incr commitidx($view)
1016        if {[info exists commitinterest($id)]} {
1017            foreach script $commitinterest($id) {
1018                lappend scripts [string map [list "%I" $id] $script]
1019            }
1020            unset commitinterest($id)
1021        }
1022        set gotsome 1
1023    }
1024    if {$gotsome} {
1025        run chewcommits $view
1026        foreach s $scripts {
1027            eval $s
1028        }
1029        if {$view == $curview} {
1030            # update progress bar
1031            global progressdirn progresscoords proglastnc
1032            set inc [expr {($commitidx($view) - $proglastnc) * 0.0002}]
1033            set proglastnc $commitidx($view)
1034            set l [lindex $progresscoords 0]
1035            set r [lindex $progresscoords 1]
1036            if {$progressdirn} {
1037                set r [expr {$r + $inc}]
1038                if {$r >= 1.0} {
1039                    set r 1.0
1040                    set progressdirn 0
1041                }
1042                if {$r > 0.2} {
1043                    set l [expr {$r - 0.2}]
1044                }
1045            } else {
1046                set l [expr {$l - $inc}]
1047                if {$l <= 0.0} {
1048                    set l 0.0
1049                    set progressdirn 1
1050                }
1051                set r [expr {$l + 0.2}]
1052            }
1053            set progresscoords [list $l $r]
1054            adjustprogress
1055        }
1056    }
1057    return 2
1058}
1059
1060proc chewcommits {view} {
1061    global curview hlview viewcomplete
1062    global pending_select
1063
1064    if {$view == $curview} {
1065        layoutmore
1066        if {$viewcomplete($view)} {
1067            global commitidx varctok
1068            global numcommits startmsecs
1069            global mainheadid commitinfo nullid
1070
1071            if {[info exists pending_select]} {
1072                set row [first_real_row]
1073                selectline $row 1
1074            }
1075            if {$commitidx($curview) > 0} {
1076                #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
1077                #puts "overall $ms ms for $numcommits commits"
1078                #puts "[llength $varctok($view)] arcs, $commitidx($view) commits"
1079            } else {
1080                show_status "No commits selected"
1081            }
1082            notbusy layout
1083        }
1084    }
1085    if {[info exists hlview] && $view == $hlview} {
1086        vhighlightmore
1087    }
1088    return 0
1089}
1090
1091proc readcommit {id} {
1092    if {[catch {set contents [exec git cat-file commit $id]}]} return
1093    parsecommit $id $contents 0
1094}
1095
1096proc parsecommit {id contents listed} {
1097    global commitinfo cdate
1098
1099    set inhdr 1
1100    set comment {}
1101    set headline {}
1102    set auname {}
1103    set audate {}
1104    set comname {}
1105    set comdate {}
1106    set hdrend [string first "\n\n" $contents]
1107    if {$hdrend < 0} {
1108        # should never happen...
1109        set hdrend [string length $contents]
1110    }
1111    set header [string range $contents 0 [expr {$hdrend - 1}]]
1112    set comment [string range $contents [expr {$hdrend + 2}] end]
1113    foreach line [split $header "\n"] {
1114        set tag [lindex $line 0]
1115        if {$tag == "author"} {
1116            set audate [lindex $line end-1]
1117            set auname [lrange $line 1 end-2]
1118        } elseif {$tag == "committer"} {
1119            set comdate [lindex $line end-1]
1120            set comname [lrange $line 1 end-2]
1121        }
1122    }
1123    set headline {}
1124    # take the first non-blank line of the comment as the headline
1125    set headline [string trimleft $comment]
1126    set i [string first "\n" $headline]
1127    if {$i >= 0} {
1128        set headline [string range $headline 0 $i]
1129    }
1130    set headline [string trimright $headline]
1131    set i [string first "\r" $headline]
1132    if {$i >= 0} {
1133        set headline [string trimright [string range $headline 0 $i]]
1134    }
1135    if {!$listed} {
1136        # git rev-list indents the comment by 4 spaces;
1137        # if we got this via git cat-file, add the indentation
1138        set newcomment {}
1139        foreach line [split $comment "\n"] {
1140            append newcomment "    "
1141            append newcomment $line
1142            append newcomment "\n"
1143        }
1144        set comment $newcomment
1145    }
1146    if {$comdate != {}} {
1147        set cdate($id) $comdate
1148    }
1149    set commitinfo($id) [list $headline $auname $audate \
1150                             $comname $comdate $comment]
1151}
1152
1153proc getcommit {id} {
1154    global commitdata commitinfo
1155
1156    if {[info exists commitdata($id)]} {
1157        parsecommit $id $commitdata($id) 1
1158    } else {
1159        readcommit $id
1160        if {![info exists commitinfo($id)]} {
1161            set commitinfo($id) {"No commit information available"}
1162        }
1163    }
1164    return 1
1165}
1166
1167proc readrefs {} {
1168    global tagids idtags headids idheads tagobjid
1169    global otherrefids idotherrefs mainhead mainheadid
1170
1171    foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
1172        catch {unset $v}
1173    }
1174    set refd [open [list | git show-ref -d] r]
1175    while {[gets $refd line] >= 0} {
1176        if {[string index $line 40] ne " "} continue
1177        set id [string range $line 0 39]
1178        set ref [string range $line 41 end]
1179        if {![string match "refs/*" $ref]} continue
1180        set name [string range $ref 5 end]
1181        if {[string match "remotes/*" $name]} {
1182            if {![string match "*/HEAD" $name]} {
1183                set headids($name) $id
1184                lappend idheads($id) $name
1185            }
1186        } elseif {[string match "heads/*" $name]} {
1187            set name [string range $name 6 end]
1188            set headids($name) $id
1189            lappend idheads($id) $name
1190        } elseif {[string match "tags/*" $name]} {
1191            # this lets refs/tags/foo^{} overwrite refs/tags/foo,
1192            # which is what we want since the former is the commit ID
1193            set name [string range $name 5 end]
1194            if {[string match "*^{}" $name]} {
1195                set name [string range $name 0 end-3]
1196            } else {
1197                set tagobjid($name) $id
1198            }
1199            set tagids($name) $id
1200            lappend idtags($id) $name
1201        } else {
1202            set otherrefids($name) $id
1203            lappend idotherrefs($id) $name
1204        }
1205    }
1206    catch {close $refd}
1207    set mainhead {}
1208    set mainheadid {}
1209    catch {
1210        set thehead [exec git symbolic-ref HEAD]
1211        if {[string match "refs/heads/*" $thehead]} {
1212            set mainhead [string range $thehead 11 end]
1213            if {[info exists headids($mainhead)]} {
1214                set mainheadid $headids($mainhead)
1215            }
1216        }
1217    }
1218}
1219
1220# skip over fake commits
1221proc first_real_row {} {
1222    global nullid nullid2 numcommits
1223
1224    for {set row 0} {$row < $numcommits} {incr row} {
1225        set id [commitonrow $row]
1226        if {$id ne $nullid && $id ne $nullid2} {
1227            break
1228        }
1229    }
1230    return $row
1231}
1232
1233# update things for a head moved to a child of its previous location
1234proc movehead {id name} {
1235    global headids idheads
1236
1237    removehead $headids($name) $name
1238    set headids($name) $id
1239    lappend idheads($id) $name
1240}
1241
1242# update things when a head has been removed
1243proc removehead {id name} {
1244    global headids idheads
1245
1246    if {$idheads($id) eq $name} {
1247        unset idheads($id)
1248    } else {
1249        set i [lsearch -exact $idheads($id) $name]
1250        if {$i >= 0} {
1251            set idheads($id) [lreplace $idheads($id) $i $i]
1252        }
1253    }
1254    unset headids($name)
1255}
1256
1257proc show_error {w top msg} {
1258    message $w.m -text $msg -justify center -aspect 400
1259    pack $w.m -side top -fill x -padx 20 -pady 20
1260    button $w.ok -text OK -command "destroy $top"
1261    pack $w.ok -side bottom -fill x
1262    bind $top <Visibility> "grab $top; focus $top"
1263    bind $top <Key-Return> "destroy $top"
1264    tkwait window $top
1265}
1266
1267proc error_popup msg {
1268    set w .error
1269    toplevel $w
1270    wm transient $w .
1271    show_error $w $w $msg
1272}
1273
1274proc confirm_popup msg {
1275    global confirm_ok
1276    set confirm_ok 0
1277    set w .confirm
1278    toplevel $w
1279    wm transient $w .
1280    message $w.m -text $msg -justify center -aspect 400
1281    pack $w.m -side top -fill x -padx 20 -pady 20
1282    button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
1283    pack $w.ok -side left -fill x
1284    button $w.cancel -text Cancel -command "destroy $w"
1285    pack $w.cancel -side right -fill x
1286    bind $w <Visibility> "grab $w; focus $w"
1287    tkwait window $w
1288    return $confirm_ok
1289}
1290
1291proc makewindow {} {
1292    global canv canv2 canv3 linespc charspc ctext cflist
1293    global tabstop
1294    global findtype findtypemenu findloc findstring fstring geometry
1295    global entries sha1entry sha1string sha1but
1296    global diffcontextstring diffcontext
1297    global maincursor textcursor curtextcursor
1298    global rowctxmenu fakerowmenu mergemax wrapcomment
1299    global highlight_files gdttype
1300    global searchstring sstring
1301    global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
1302    global headctxmenu progresscanv progressitem progresscoords statusw
1303    global fprogitem fprogcoord lastprogupdate progupdatepending
1304    global rprogitem rprogcoord
1305    global have_tk85
1306
1307    menu .bar
1308    .bar add cascade -label "File" -menu .bar.file
1309    .bar configure -font uifont
1310    menu .bar.file
1311    .bar.file add command -label "Update" -command updatecommits
1312    .bar.file add command -label "Reload" -command reloadcommits
1313    .bar.file add command -label "Reread references" -command rereadrefs
1314    .bar.file add command -label "List references" -command showrefs
1315    .bar.file add command -label "Quit" -command doquit
1316    .bar.file configure -font uifont
1317    menu .bar.edit
1318    .bar add cascade -label "Edit" -menu .bar.edit
1319    .bar.edit add command -label "Preferences" -command doprefs
1320    .bar.edit configure -font uifont
1321
1322    menu .bar.view -font uifont
1323    .bar add cascade -label "View" -menu .bar.view
1324    .bar.view add command -label "New view..." -command {newview 0}
1325    .bar.view add command -label "Edit view..." -command editview \
1326        -state disabled
1327    .bar.view add command -label "Delete view" -command delview -state disabled
1328    .bar.view add separator
1329    .bar.view add radiobutton -label "All files" -command {showview 0} \
1330        -variable selectedview -value 0
1331
1332    menu .bar.help
1333    .bar add cascade -label "Help" -menu .bar.help
1334    .bar.help add command -label "About gitk" -command about
1335    .bar.help add command -label "Key bindings" -command keys
1336    .bar.help configure -font uifont
1337    . configure -menu .bar
1338
1339    # the gui has upper and lower half, parts of a paned window.
1340    panedwindow .ctop -orient vertical
1341
1342    # possibly use assumed geometry
1343    if {![info exists geometry(pwsash0)]} {
1344        set geometry(topheight) [expr {15 * $linespc}]
1345        set geometry(topwidth) [expr {80 * $charspc}]
1346        set geometry(botheight) [expr {15 * $linespc}]
1347        set geometry(botwidth) [expr {50 * $charspc}]
1348        set geometry(pwsash0) "[expr {40 * $charspc}] 2"
1349        set geometry(pwsash1) "[expr {60 * $charspc}] 2"
1350    }
1351
1352    # the upper half will have a paned window, a scroll bar to the right, and some stuff below
1353    frame .tf -height $geometry(topheight) -width $geometry(topwidth)
1354    frame .tf.histframe
1355    panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
1356
1357    # create three canvases
1358    set cscroll .tf.histframe.csb
1359    set canv .tf.histframe.pwclist.canv
1360    canvas $canv \
1361        -selectbackground $selectbgcolor \
1362        -background $bgcolor -bd 0 \
1363        -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
1364    .tf.histframe.pwclist add $canv
1365    set canv2 .tf.histframe.pwclist.canv2
1366    canvas $canv2 \
1367        -selectbackground $selectbgcolor \
1368        -background $bgcolor -bd 0 -yscrollincr $linespc
1369    .tf.histframe.pwclist add $canv2
1370    set canv3 .tf.histframe.pwclist.canv3
1371    canvas $canv3 \
1372        -selectbackground $selectbgcolor \
1373        -background $bgcolor -bd 0 -yscrollincr $linespc
1374    .tf.histframe.pwclist add $canv3
1375    eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
1376    eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
1377
1378    # a scroll bar to rule them
1379    scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
1380    pack $cscroll -side right -fill y
1381    bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
1382    lappend bglist $canv $canv2 $canv3
1383    pack .tf.histframe.pwclist -fill both -expand 1 -side left
1384
1385    # we have two button bars at bottom of top frame. Bar 1
1386    frame .tf.bar
1387    frame .tf.lbar -height 15
1388
1389    set sha1entry .tf.bar.sha1
1390    set entries $sha1entry
1391    set sha1but .tf.bar.sha1label
1392    button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
1393        -command gotocommit -width 8 -font uifont
1394    $sha1but conf -disabledforeground [$sha1but cget -foreground]
1395    pack .tf.bar.sha1label -side left
1396    entry $sha1entry -width 40 -font textfont -textvariable sha1string
1397    trace add variable sha1string write sha1change
1398    pack $sha1entry -side left -pady 2
1399
1400    image create bitmap bm-left -data {
1401        #define left_width 16
1402        #define left_height 16
1403        static unsigned char left_bits[] = {
1404        0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
1405        0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
1406        0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
1407    }
1408    image create bitmap bm-right -data {
1409        #define right_width 16
1410        #define right_height 16
1411        static unsigned char right_bits[] = {
1412        0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
1413        0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
1414        0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
1415    }
1416    button .tf.bar.leftbut -image bm-left -command goback \
1417        -state disabled -width 26
1418    pack .tf.bar.leftbut -side left -fill y
1419    button .tf.bar.rightbut -image bm-right -command goforw \
1420        -state disabled -width 26
1421    pack .tf.bar.rightbut -side left -fill y
1422
1423    # Status label and progress bar
1424    set statusw .tf.bar.status
1425    label $statusw -width 15 -relief sunken -font uifont
1426    pack $statusw -side left -padx 5
1427    set h [expr {[font metrics uifont -linespace] + 2}]
1428    set progresscanv .tf.bar.progress
1429    canvas $progresscanv -relief sunken -height $h -borderwidth 2
1430    set progressitem [$progresscanv create rect -1 0 0 $h -fill green]
1431    set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow]
1432    set rprogitem [$progresscanv create rect -1 0 0 $h -fill red]
1433    pack $progresscanv -side right -expand 1 -fill x
1434    set progresscoords {0 0}
1435    set fprogcoord 0
1436    set rprogcoord 0
1437    bind $progresscanv <Configure> adjustprogress
1438    set lastprogupdate [clock clicks -milliseconds]
1439    set progupdatepending 0
1440
1441    # build up the bottom bar of upper window
1442    label .tf.lbar.flabel -text "Find " -font uifont
1443    button .tf.lbar.fnext -text "next" -command {dofind 1 1} -font uifont
1444    button .tf.lbar.fprev -text "prev" -command {dofind -1 1} -font uifont
1445    label .tf.lbar.flab2 -text " commit " -font uifont
1446    pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \
1447        -side left -fill y
1448    set gdttype "containing:"
1449    set gm [tk_optionMenu .tf.lbar.gdttype gdttype \
1450                "containing:" \
1451                "touching paths:" \
1452                "adding/removing string:"]
1453    trace add variable gdttype write gdttype_change
1454    $gm conf -font uifont
1455    .tf.lbar.gdttype conf -font uifont
1456    pack .tf.lbar.gdttype -side left -fill y
1457
1458    set findstring {}
1459    set fstring .tf.lbar.findstring
1460    lappend entries $fstring
1461    entry $fstring -width 30 -font textfont -textvariable findstring
1462    trace add variable findstring write find_change
1463    set findtype Exact
1464    set findtypemenu [tk_optionMenu .tf.lbar.findtype \
1465                      findtype Exact IgnCase Regexp]
1466    trace add variable findtype write findcom_change
1467    .tf.lbar.findtype configure -font uifont
1468    .tf.lbar.findtype.menu configure -font uifont
1469    set findloc "All fields"
1470    tk_optionMenu .tf.lbar.findloc findloc "All fields" Headline \
1471        Comments Author Committer
1472    trace add variable findloc write find_change
1473    .tf.lbar.findloc configure -font uifont
1474    .tf.lbar.findloc.menu configure -font uifont
1475    pack .tf.lbar.findloc -side right
1476    pack .tf.lbar.findtype -side right
1477    pack $fstring -side left -expand 1 -fill x
1478
1479    # Finish putting the upper half of the viewer together
1480    pack .tf.lbar -in .tf -side bottom -fill x
1481    pack .tf.bar -in .tf -side bottom -fill x
1482    pack .tf.histframe -fill both -side top -expand 1
1483    .ctop add .tf
1484    .ctop paneconfigure .tf -height $geometry(topheight)
1485    .ctop paneconfigure .tf -width $geometry(topwidth)
1486
1487    # now build up the bottom
1488    panedwindow .pwbottom -orient horizontal
1489
1490    # lower left, a text box over search bar, scroll bar to the right
1491    # if we know window height, then that will set the lower text height, otherwise
1492    # we set lower text height which will drive window height
1493    if {[info exists geometry(main)]} {
1494        frame .bleft -width $geometry(botwidth)
1495    } else {
1496        frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
1497    }
1498    frame .bleft.top
1499    frame .bleft.mid
1500
1501    button .bleft.top.search -text "Search" -command dosearch \
1502        -font uifont
1503    pack .bleft.top.search -side left -padx 5
1504    set sstring .bleft.top.sstring
1505    entry $sstring -width 20 -font textfont -textvariable searchstring
1506    lappend entries $sstring
1507    trace add variable searchstring write incrsearch
1508    pack $sstring -side left -expand 1 -fill x
1509    radiobutton .bleft.mid.diff -text "Diff" -font uifont \
1510        -command changediffdisp -variable diffelide -value {0 0}
1511    radiobutton .bleft.mid.old -text "Old version" -font uifont \
1512        -command changediffdisp -variable diffelide -value {0 1}
1513    radiobutton .bleft.mid.new -text "New version" -font uifont \
1514        -command changediffdisp -variable diffelide -value {1 0}
1515    label .bleft.mid.labeldiffcontext -text "      Lines of context: " \
1516        -font uifont
1517    pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
1518    spinbox .bleft.mid.diffcontext -width 5 -font textfont \
1519        -from 1 -increment 1 -to 10000000 \
1520        -validate all -validatecommand "diffcontextvalidate %P" \
1521        -textvariable diffcontextstring
1522    .bleft.mid.diffcontext set $diffcontext
1523    trace add variable diffcontextstring write diffcontextchange
1524    lappend entries .bleft.mid.diffcontext
1525    pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left
1526    set ctext .bleft.ctext
1527    text $ctext -background $bgcolor -foreground $fgcolor \
1528        -state disabled -font textfont \
1529        -yscrollcommand scrolltext -wrap none
1530    if {$have_tk85} {
1531        $ctext conf -tabstyle wordprocessor
1532    }
1533    scrollbar .bleft.sb -command "$ctext yview"
1534    pack .bleft.top -side top -fill x
1535    pack .bleft.mid -side top -fill x
1536    pack .bleft.sb -side right -fill y
1537    pack $ctext -side left -fill both -expand 1
1538    lappend bglist $ctext
1539    lappend fglist $ctext
1540
1541    $ctext tag conf comment -wrap $wrapcomment
1542    $ctext tag conf filesep -font textfontbold -back "#aaaaaa"
1543    $ctext tag conf hunksep -fore [lindex $diffcolors 2]
1544    $ctext tag conf d0 -fore [lindex $diffcolors 0]
1545    $ctext tag conf d1 -fore [lindex $diffcolors 1]
1546    $ctext tag conf m0 -fore red
1547    $ctext tag conf m1 -fore blue
1548    $ctext tag conf m2 -fore green
1549    $ctext tag conf m3 -fore purple
1550    $ctext tag conf m4 -fore brown
1551    $ctext tag conf m5 -fore "#009090"
1552    $ctext tag conf m6 -fore magenta
1553    $ctext tag conf m7 -fore "#808000"
1554    $ctext tag conf m8 -fore "#009000"
1555    $ctext tag conf m9 -fore "#ff0080"
1556    $ctext tag conf m10 -fore cyan
1557    $ctext tag conf m11 -fore "#b07070"
1558    $ctext tag conf m12 -fore "#70b0f0"
1559    $ctext tag conf m13 -fore "#70f0b0"
1560    $ctext tag conf m14 -fore "#f0b070"
1561    $ctext tag conf m15 -fore "#ff70b0"
1562    $ctext tag conf mmax -fore darkgrey
1563    set mergemax 16
1564    $ctext tag conf mresult -font textfontbold
1565    $ctext tag conf msep -font textfontbold
1566    $ctext tag conf found -back yellow
1567
1568    .pwbottom add .bleft
1569    .pwbottom paneconfigure .bleft -width $geometry(botwidth)
1570
1571    # lower right
1572    frame .bright
1573    frame .bright.mode
1574    radiobutton .bright.mode.patch -text "Patch" \
1575        -command reselectline -variable cmitmode -value "patch"
1576    .bright.mode.patch configure -font uifont
1577    radiobutton .bright.mode.tree -text "Tree" \
1578        -command reselectline -variable cmitmode -value "tree"
1579    .bright.mode.tree configure -font uifont
1580    grid .bright.mode.patch .bright.mode.tree -sticky ew
1581    pack .bright.mode -side top -fill x
1582    set cflist .bright.cfiles
1583    set indent [font measure mainfont "nn"]
1584    text $cflist \
1585        -selectbackground $selectbgcolor \
1586        -background $bgcolor -foreground $fgcolor \
1587        -font mainfont \
1588        -tabs [list $indent [expr {2 * $indent}]] \
1589        -yscrollcommand ".bright.sb set" \
1590        -cursor [. cget -cursor] \
1591        -spacing1 1 -spacing3 1
1592    lappend bglist $cflist
1593    lappend fglist $cflist
1594    scrollbar .bright.sb -command "$cflist yview"
1595    pack .bright.sb -side right -fill y
1596    pack $cflist -side left -fill both -expand 1
1597    $cflist tag configure highlight \
1598        -background [$cflist cget -selectbackground]
1599    $cflist tag configure bold -font mainfontbold
1600
1601    .pwbottom add .bright
1602    .ctop add .pwbottom
1603
1604    # restore window position if known
1605    if {[info exists geometry(main)]} {
1606        wm geometry . "$geometry(main)"
1607    }
1608
1609    if {[tk windowingsystem] eq {aqua}} {
1610        set M1B M1
1611    } else {
1612        set M1B Control
1613    }
1614
1615    bind .pwbottom <Configure> {resizecdetpanes %W %w}
1616    pack .ctop -fill both -expand 1
1617    bindall <1> {selcanvline %W %x %y}
1618    #bindall <B1-Motion> {selcanvline %W %x %y}
1619    if {[tk windowingsystem] == "win32"} {
1620        bind . <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D }
1621        bind $ctext <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D ; break }
1622    } else {
1623        bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
1624        bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
1625        if {[tk windowingsystem] eq "aqua"} {
1626            bindall <MouseWheel> {
1627                set delta [expr {- (%D)}]
1628                allcanvs yview scroll $delta units
1629            }
1630        }
1631    }
1632    bindall <2> "canvscan mark %W %x %y"
1633    bindall <B2-Motion> "canvscan dragto %W %x %y"
1634    bindkey <Home> selfirstline
1635    bindkey <End> sellastline
1636    bind . <Key-Up> "selnextline -1"
1637    bind . <Key-Down> "selnextline 1"
1638    bind . <Shift-Key-Up> "dofind -1 0"
1639    bind . <Shift-Key-Down> "dofind 1 0"
1640    bindkey <Key-Right> "goforw"
1641    bindkey <Key-Left> "goback"
1642    bind . <Key-Prior> "selnextpage -1"
1643    bind . <Key-Next> "selnextpage 1"
1644    bind . <$M1B-Home> "allcanvs yview moveto 0.0"
1645    bind . <$M1B-End> "allcanvs yview moveto 1.0"
1646    bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
1647    bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
1648    bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
1649    bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
1650    bindkey <Key-Delete> "$ctext yview scroll -1 pages"
1651    bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
1652    bindkey <Key-space> "$ctext yview scroll 1 pages"
1653    bindkey p "selnextline -1"
1654    bindkey n "selnextline 1"
1655    bindkey z "goback"
1656    bindkey x "goforw"
1657    bindkey i "selnextline -1"
1658    bindkey k "selnextline 1"
1659    bindkey j "goback"
1660    bindkey l "goforw"
1661    bindkey b "$ctext yview scroll -1 pages"
1662    bindkey d "$ctext yview scroll 18 units"
1663    bindkey u "$ctext yview scroll -18 units"
1664    bindkey / {dofind 1 1}
1665    bindkey <Key-Return> {dofind 1 1}
1666    bindkey ? {dofind -1 1}
1667    bindkey f nextfile
1668    bindkey <F5> updatecommits
1669    bind . <$M1B-q> doquit
1670    bind . <$M1B-f> {dofind 1 1}
1671    bind . <$M1B-g> {dofind 1 0}
1672    bind . <$M1B-r> dosearchback
1673    bind . <$M1B-s> dosearch
1674    bind . <$M1B-equal> {incrfont 1}
1675    bind . <$M1B-KP_Add> {incrfont 1}
1676    bind . <$M1B-minus> {incrfont -1}
1677    bind . <$M1B-KP_Subtract> {incrfont -1}
1678    wm protocol . WM_DELETE_WINDOW doquit
1679    bind . <Button-1> "click %W"
1680    bind $fstring <Key-Return> {dofind 1 1}
1681    bind $sha1entry <Key-Return> gotocommit
1682    bind $sha1entry <<PasteSelection>> clearsha1
1683    bind $cflist <1> {sel_flist %W %x %y; break}
1684    bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
1685    bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
1686    bind $cflist <Button-3> {pop_flist_menu %W %X %Y %x %y}
1687
1688    set maincursor [. cget -cursor]
1689    set textcursor [$ctext cget -cursor]
1690    set curtextcursor $textcursor
1691
1692    set rowctxmenu .rowctxmenu
1693    menu $rowctxmenu -tearoff 0
1694    $rowctxmenu add command -label "Diff this -> selected" \
1695        -command {diffvssel 0}
1696    $rowctxmenu add command -label "Diff selected -> this" \
1697        -command {diffvssel 1}
1698    $rowctxmenu add command -label "Make patch" -command mkpatch
1699    $rowctxmenu add command -label "Create tag" -command mktag
1700    $rowctxmenu add command -label "Write commit to file" -command writecommit
1701    $rowctxmenu add command -label "Create new branch" -command mkbranch
1702    $rowctxmenu add command -label "Cherry-pick this commit" \
1703        -command cherrypick
1704    $rowctxmenu add command -label "Reset HEAD branch to here" \
1705        -command resethead
1706
1707    set fakerowmenu .fakerowmenu
1708    menu $fakerowmenu -tearoff 0
1709    $fakerowmenu add command -label "Diff this -> selected" \
1710        -command {diffvssel 0}
1711    $fakerowmenu add command -label "Diff selected -> this" \
1712        -command {diffvssel 1}
1713    $fakerowmenu add command -label "Make patch" -command mkpatch
1714#    $fakerowmenu add command -label "Commit" -command {mkcommit 0}
1715#    $fakerowmenu add command -label "Commit all" -command {mkcommit 1}
1716#    $fakerowmenu add command -label "Revert local changes" -command revertlocal
1717
1718    set headctxmenu .headctxmenu
1719    menu $headctxmenu -tearoff 0
1720    $headctxmenu add command -label "Check out this branch" \
1721        -command cobranch
1722    $headctxmenu add command -label "Remove this branch" \
1723        -command rmbranch
1724
1725    global flist_menu
1726    set flist_menu .flistctxmenu
1727    menu $flist_menu -tearoff 0
1728    $flist_menu add command -label "Highlight this too" \
1729        -command {flist_hl 0}
1730    $flist_menu add command -label "Highlight this only" \
1731        -command {flist_hl 1}
1732}
1733
1734# Windows sends all mouse wheel events to the current focused window, not
1735# the one where the mouse hovers, so bind those events here and redirect
1736# to the correct window
1737proc windows_mousewheel_redirector {W X Y D} {
1738    global canv canv2 canv3
1739    set w [winfo containing -displayof $W $X $Y]
1740    if {$w ne ""} {
1741        set u [expr {$D < 0 ? 5 : -5}]
1742        if {$w == $canv || $w == $canv2 || $w == $canv3} {
1743            allcanvs yview scroll $u units
1744        } else {
1745            catch {
1746                $w yview scroll $u units
1747            }
1748        }
1749    }
1750}
1751
1752# mouse-2 makes all windows scan vertically, but only the one
1753# the cursor is in scans horizontally
1754proc canvscan {op w x y} {
1755    global canv canv2 canv3
1756    foreach c [list $canv $canv2 $canv3] {
1757        if {$c == $w} {
1758            $c scan $op $x $y
1759        } else {
1760            $c scan $op 0 $y
1761        }
1762    }
1763}
1764
1765proc scrollcanv {cscroll f0 f1} {
1766    $cscroll set $f0 $f1
1767    drawfrac $f0 $f1
1768    flushhighlights
1769}
1770
1771# when we make a key binding for the toplevel, make sure
1772# it doesn't get triggered when that key is pressed in the
1773# find string entry widget.
1774proc bindkey {ev script} {
1775    global entries
1776    bind . $ev $script
1777    set escript [bind Entry $ev]
1778    if {$escript == {}} {
1779        set escript [bind Entry <Key>]
1780    }
1781    foreach e $entries {
1782        bind $e $ev "$escript; break"
1783    }
1784}
1785
1786# set the focus back to the toplevel for any click outside
1787# the entry widgets
1788proc click {w} {
1789    global ctext entries
1790    foreach e [concat $entries $ctext] {
1791        if {$w == $e} return
1792    }
1793    focus .
1794}
1795
1796# Adjust the progress bar for a change in requested extent or canvas size
1797proc adjustprogress {} {
1798    global progresscanv progressitem progresscoords
1799    global fprogitem fprogcoord lastprogupdate progupdatepending
1800    global rprogitem rprogcoord
1801
1802    set w [expr {[winfo width $progresscanv] - 4}]
1803    set x0 [expr {$w * [lindex $progresscoords 0]}]
1804    set x1 [expr {$w * [lindex $progresscoords 1]}]
1805    set h [winfo height $progresscanv]
1806    $progresscanv coords $progressitem $x0 0 $x1 $h
1807    $progresscanv coords $fprogitem 0 0 [expr {$w * $fprogcoord}] $h
1808    $progresscanv coords $rprogitem 0 0 [expr {$w * $rprogcoord}] $h
1809    set now [clock clicks -milliseconds]
1810    if {$now >= $lastprogupdate + 100} {
1811        set progupdatepending 0
1812        update
1813    } elseif {!$progupdatepending} {
1814        set progupdatepending 1
1815        after [expr {$lastprogupdate + 100 - $now}] doprogupdate
1816    }
1817}
1818
1819proc doprogupdate {} {
1820    global lastprogupdate progupdatepending
1821
1822    if {$progupdatepending} {
1823        set progupdatepending 0
1824        set lastprogupdate [clock clicks -milliseconds]
1825        update
1826    }
1827}
1828
1829proc savestuff {w} {
1830    global canv canv2 canv3 mainfont textfont uifont tabstop
1831    global stuffsaved findmergefiles maxgraphpct
1832    global maxwidth showneartags showlocalchanges
1833    global viewname viewfiles viewargs viewperm nextviewnum
1834    global cmitmode wrapcomment datetimeformat limitdiffs
1835    global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
1836
1837    if {$stuffsaved} return
1838    if {![winfo viewable .]} return
1839    catch {
1840        set f [open "~/.gitk-new" w]
1841        puts $f [list set mainfont $mainfont]
1842        puts $f [list set textfont $textfont]
1843        puts $f [list set uifont $uifont]
1844        puts $f [list set tabstop $tabstop]
1845        puts $f [list set findmergefiles $findmergefiles]
1846        puts $f [list set maxgraphpct $maxgraphpct]
1847        puts $f [list set maxwidth $maxwidth]
1848        puts $f [list set cmitmode $cmitmode]
1849        puts $f [list set wrapcomment $wrapcomment]
1850        puts $f [list set showneartags $showneartags]
1851        puts $f [list set showlocalchanges $showlocalchanges]
1852        puts $f [list set datetimeformat $datetimeformat]
1853        puts $f [list set limitdiffs $limitdiffs]
1854        puts $f [list set bgcolor $bgcolor]
1855        puts $f [list set fgcolor $fgcolor]
1856        puts $f [list set colors $colors]
1857        puts $f [list set diffcolors $diffcolors]
1858        puts $f [list set diffcontext $diffcontext]
1859        puts $f [list set selectbgcolor $selectbgcolor]
1860
1861        puts $f "set geometry(main) [wm geometry .]"
1862        puts $f "set geometry(topwidth) [winfo width .tf]"
1863        puts $f "set geometry(topheight) [winfo height .tf]"
1864        puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
1865        puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
1866        puts $f "set geometry(botwidth) [winfo width .bleft]"
1867        puts $f "set geometry(botheight) [winfo height .bleft]"
1868
1869        puts -nonewline $f "set permviews {"
1870        for {set v 0} {$v < $nextviewnum} {incr v} {
1871            if {$viewperm($v)} {
1872                puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v)]}"
1873            }
1874        }
1875        puts $f "}"
1876        close $f
1877        file rename -force "~/.gitk-new" "~/.gitk"
1878    }
1879    set stuffsaved 1
1880}
1881
1882proc resizeclistpanes {win w} {
1883    global oldwidth
1884    if {[info exists oldwidth($win)]} {
1885        set s0 [$win sash coord 0]
1886        set s1 [$win sash coord 1]
1887        if {$w < 60} {
1888            set sash0 [expr {int($w/2 - 2)}]
1889            set sash1 [expr {int($w*5/6 - 2)}]
1890        } else {
1891            set factor [expr {1.0 * $w / $oldwidth($win)}]
1892            set sash0 [expr {int($factor * [lindex $s0 0])}]
1893            set sash1 [expr {int($factor * [lindex $s1 0])}]
1894            if {$sash0 < 30} {
1895                set sash0 30
1896            }
1897            if {$sash1 < $sash0 + 20} {
1898                set sash1 [expr {$sash0 + 20}]
1899            }
1900            if {$sash1 > $w - 10} {
1901                set sash1 [expr {$w - 10}]
1902                if {$sash0 > $sash1 - 20} {
1903                    set sash0 [expr {$sash1 - 20}]
1904                }
1905            }
1906        }
1907        $win sash place 0 $sash0 [lindex $s0 1]
1908        $win sash place 1 $sash1 [lindex $s1 1]
1909    }
1910    set oldwidth($win) $w
1911}
1912
1913proc resizecdetpanes {win w} {
1914    global oldwidth
1915    if {[info exists oldwidth($win)]} {
1916        set s0 [$win sash coord 0]
1917        if {$w < 60} {
1918            set sash0 [expr {int($w*3/4 - 2)}]
1919        } else {
1920            set factor [expr {1.0 * $w / $oldwidth($win)}]
1921            set sash0 [expr {int($factor * [lindex $s0 0])}]
1922            if {$sash0 < 45} {
1923                set sash0 45
1924            }
1925            if {$sash0 > $w - 15} {
1926                set sash0 [expr {$w - 15}]
1927            }
1928        }
1929        $win sash place 0 $sash0 [lindex $s0 1]
1930    }
1931    set oldwidth($win) $w
1932}
1933
1934proc allcanvs args {
1935    global canv canv2 canv3
1936    eval $canv $args
1937    eval $canv2 $args
1938    eval $canv3 $args
1939}
1940
1941proc bindall {event action} {
1942    global canv canv2 canv3
1943    bind $canv $event $action
1944    bind $canv2 $event $action
1945    bind $canv3 $event $action
1946}
1947
1948proc about {} {
1949    global uifont
1950    set w .about
1951    if {[winfo exists $w]} {
1952        raise $w
1953        return
1954    }
1955    toplevel $w
1956    wm title $w "About gitk"
1957    message $w.m -text {
1958Gitk - a commit viewer for git
1959
1960Copyright © 2005-2007 Paul Mackerras
1961
1962Use and redistribute under the terms of the GNU General Public License} \
1963            -justify center -aspect 400 -border 2 -bg white -relief groove
1964    pack $w.m -side top -fill x -padx 2 -pady 2
1965    $w.m configure -font uifont
1966    button $w.ok -text Close -command "destroy $w" -default active
1967    pack $w.ok -side bottom
1968    $w.ok configure -font uifont
1969    bind $w <Visibility> "focus $w.ok"
1970    bind $w <Key-Escape> "destroy $w"
1971    bind $w <Key-Return> "destroy $w"
1972}
1973
1974proc keys {} {
1975    global uifont
1976    set w .keys
1977    if {[winfo exists $w]} {
1978        raise $w
1979        return
1980    }
1981    if {[tk windowingsystem] eq {aqua}} {
1982        set M1T Cmd
1983    } else {
1984        set M1T Ctrl
1985    }
1986    toplevel $w
1987    wm title $w "Gitk key bindings"
1988    message $w.m -text "
1989Gitk key bindings:
1990
1991<$M1T-Q>                Quit
1992<Home>          Move to first commit
1993<End>           Move to last commit
1994<Up>, p, i      Move up one commit
1995<Down>, n, k    Move down one commit
1996<Left>, z, j    Go back in history list
1997<Right>, x, l   Go forward in history list
1998<PageUp>        Move up one page in commit list
1999<PageDown>      Move down one page in commit list
2000<$M1T-Home>     Scroll to top of commit list
2001<$M1T-End>      Scroll to bottom of commit list
2002<$M1T-Up>       Scroll commit list up one line
2003<$M1T-Down>     Scroll commit list down one line
2004<$M1T-PageUp>   Scroll commit list up one page
2005<$M1T-PageDown> Scroll commit list down one page
2006<Shift-Up>      Find backwards (upwards, later commits)
2007<Shift-Down>    Find forwards (downwards, earlier commits)
2008<Delete>, b     Scroll diff view up one page
2009<Backspace>     Scroll diff view up one page
2010<Space>         Scroll diff view down one page
2011u               Scroll diff view up 18 lines
2012d               Scroll diff view down 18 lines
2013<$M1T-F>                Find
2014<$M1T-G>                Move to next find hit
2015<Return>        Move to next find hit
2016/               Move to next find hit, or redo find
2017?               Move to previous find hit
2018f               Scroll diff view to next file
2019<$M1T-S>                Search for next hit in diff view
2020<$M1T-R>                Search for previous hit in diff view
2021<$M1T-KP+>      Increase font size
2022<$M1T-plus>     Increase font size
2023<$M1T-KP->      Decrease font size
2024<$M1T-minus>    Decrease font size
2025<F5>            Update
2026" \
2027            -justify left -bg white -border 2 -relief groove
2028    pack $w.m -side top -fill both -padx 2 -pady 2
2029    $w.m configure -font uifont
2030    button $w.ok -text Close -command "destroy $w" -default active
2031    pack $w.ok -side bottom
2032    $w.ok configure -font uifont
2033    bind $w <Visibility> "focus $w.ok"
2034    bind $w <Key-Escape> "destroy $w"
2035    bind $w <Key-Return> "destroy $w"
2036}
2037
2038# Procedures for manipulating the file list window at the
2039# bottom right of the overall window.
2040
2041proc treeview {w l openlevs} {
2042    global treecontents treediropen treeheight treeparent treeindex
2043
2044    set ix 0
2045    set treeindex() 0
2046    set lev 0
2047    set prefix {}
2048    set prefixend -1
2049    set prefendstack {}
2050    set htstack {}
2051    set ht 0
2052    set treecontents() {}
2053    $w conf -state normal
2054    foreach f $l {
2055        while {[string range $f 0 $prefixend] ne $prefix} {
2056            if {$lev <= $openlevs} {
2057                $w mark set e:$treeindex($prefix) "end -1c"
2058                $w mark gravity e:$treeindex($prefix) left
2059            }
2060            set treeheight($prefix) $ht
2061            incr ht [lindex $htstack end]
2062            set htstack [lreplace $htstack end end]
2063            set prefixend [lindex $prefendstack end]
2064            set prefendstack [lreplace $prefendstack end end]
2065            set prefix [string range $prefix 0 $prefixend]
2066            incr lev -1
2067        }
2068        set tail [string range $f [expr {$prefixend+1}] end]
2069        while {[set slash [string first "/" $tail]] >= 0} {
2070            lappend htstack $ht
2071            set ht 0
2072            lappend prefendstack $prefixend
2073            incr prefixend [expr {$slash + 1}]
2074            set d [string range $tail 0 $slash]
2075            lappend treecontents($prefix) $d
2076            set oldprefix $prefix
2077            append prefix $d
2078            set treecontents($prefix) {}
2079            set treeindex($prefix) [incr ix]
2080            set treeparent($prefix) $oldprefix
2081            set tail [string range $tail [expr {$slash+1}] end]
2082            if {$lev <= $openlevs} {
2083                set ht 1
2084                set treediropen($prefix) [expr {$lev < $openlevs}]
2085                set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
2086                $w mark set d:$ix "end -1c"
2087                $w mark gravity d:$ix left
2088                set str "\n"
2089                for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2090                $w insert end $str
2091                $w image create end -align center -image $bm -padx 1 \
2092                    -name a:$ix
2093                $w insert end $d [highlight_tag $prefix]
2094                $w mark set s:$ix "end -1c"
2095                $w mark gravity s:$ix left
2096            }
2097            incr lev
2098        }
2099        if {$tail ne {}} {
2100            if {$lev <= $openlevs} {
2101                incr ht
2102                set str "\n"
2103                for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2104                $w insert end $str
2105                $w insert end $tail [highlight_tag $f]
2106            }
2107            lappend treecontents($prefix) $tail
2108        }
2109    }
2110    while {$htstack ne {}} {
2111        set treeheight($prefix) $ht
2112        incr ht [lindex $htstack end]
2113        set htstack [lreplace $htstack end end]
2114        set prefixend [lindex $prefendstack end]
2115        set prefendstack [lreplace $prefendstack end end]
2116        set prefix [string range $prefix 0 $prefixend]
2117    }
2118    $w conf -state disabled
2119}
2120
2121proc linetoelt {l} {
2122    global treeheight treecontents
2123
2124    set y 2
2125    set prefix {}
2126    while {1} {
2127        foreach e $treecontents($prefix) {
2128            if {$y == $l} {
2129                return "$prefix$e"
2130            }
2131            set n 1
2132            if {[string index $e end] eq "/"} {
2133                set n $treeheight($prefix$e)
2134                if {$y + $n > $l} {
2135                    append prefix $e
2136                    incr y
2137                    break
2138                }
2139            }
2140            incr y $n
2141        }
2142    }
2143}
2144
2145proc highlight_tree {y prefix} {
2146    global treeheight treecontents cflist
2147
2148    foreach e $treecontents($prefix) {
2149        set path $prefix$e
2150        if {[highlight_tag $path] ne {}} {
2151            $cflist tag add bold $y.0 "$y.0 lineend"
2152        }
2153        incr y
2154        if {[string index $e end] eq "/" && $treeheight($path) > 1} {
2155            set y [highlight_tree $y $path]
2156        }
2157    }
2158    return $y
2159}
2160
2161proc treeclosedir {w dir} {
2162    global treediropen treeheight treeparent treeindex
2163
2164    set ix $treeindex($dir)
2165    $w conf -state normal
2166    $w delete s:$ix e:$ix
2167    set treediropen($dir) 0
2168    $w image configure a:$ix -image tri-rt
2169    $w conf -state disabled
2170    set n [expr {1 - $treeheight($dir)}]
2171    while {$dir ne {}} {
2172        incr treeheight($dir) $n
2173        set dir $treeparent($dir)
2174    }
2175}
2176
2177proc treeopendir {w dir} {
2178    global treediropen treeheight treeparent treecontents treeindex
2179
2180    set ix $treeindex($dir)
2181    $w conf -state normal
2182    $w image configure a:$ix -image tri-dn
2183    $w mark set e:$ix s:$ix
2184    $w mark gravity e:$ix right
2185    set lev 0
2186    set str "\n"
2187    set n [llength $treecontents($dir)]
2188    for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
2189        incr lev
2190        append str "\t"
2191        incr treeheight($x) $n
2192    }
2193    foreach e $treecontents($dir) {
2194        set de $dir$e
2195        if {[string index $e end] eq "/"} {
2196            set iy $treeindex($de)
2197            $w mark set d:$iy e:$ix
2198            $w mark gravity d:$iy left
2199            $w insert e:$ix $str
2200            set treediropen($de) 0
2201            $w image create e:$ix -align center -image tri-rt -padx 1 \
2202                -name a:$iy
2203            $w insert e:$ix $e [highlight_tag $de]
2204            $w mark set s:$iy e:$ix
2205            $w mark gravity s:$iy left
2206            set treeheight($de) 1
2207        } else {
2208            $w insert e:$ix $str
2209            $w insert e:$ix $e [highlight_tag $de]
2210        }
2211    }
2212    $w mark gravity e:$ix left
2213    $w conf -state disabled
2214    set treediropen($dir) 1
2215    set top [lindex [split [$w index @0,0] .] 0]
2216    set ht [$w cget -height]
2217    set l [lindex [split [$w index s:$ix] .] 0]
2218    if {$l < $top} {
2219        $w yview $l.0
2220    } elseif {$l + $n + 1 > $top + $ht} {
2221        set top [expr {$l + $n + 2 - $ht}]
2222        if {$l < $top} {
2223            set top $l
2224        }
2225        $w yview $top.0
2226    }
2227}
2228
2229proc treeclick {w x y} {
2230    global treediropen cmitmode ctext cflist cflist_top
2231
2232    if {$cmitmode ne "tree"} return
2233    if {![info exists cflist_top]} return
2234    set l [lindex [split [$w index "@$x,$y"] "."] 0]
2235    $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2236    $cflist tag add highlight $l.0 "$l.0 lineend"
2237    set cflist_top $l
2238    if {$l == 1} {
2239        $ctext yview 1.0
2240        return
2241    }
2242    set e [linetoelt $l]
2243    if {[string index $e end] ne "/"} {
2244        showfile $e
2245    } elseif {$treediropen($e)} {
2246        treeclosedir $w $e
2247    } else {
2248        treeopendir $w $e
2249    }
2250}
2251
2252proc setfilelist {id} {
2253    global treefilelist cflist
2254
2255    treeview $cflist $treefilelist($id) 0
2256}
2257
2258image create bitmap tri-rt -background black -foreground blue -data {
2259    #define tri-rt_width 13
2260    #define tri-rt_height 13
2261    static unsigned char tri-rt_bits[] = {
2262       0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
2263       0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
2264       0x00, 0x00};
2265} -maskdata {
2266    #define tri-rt-mask_width 13
2267    #define tri-rt-mask_height 13
2268    static unsigned char tri-rt-mask_bits[] = {
2269       0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
2270       0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
2271       0x08, 0x00};
2272}
2273image create bitmap tri-dn -background black -foreground blue -data {
2274    #define tri-dn_width 13
2275    #define tri-dn_height 13
2276    static unsigned char tri-dn_bits[] = {
2277       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
2278       0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2279       0x00, 0x00};
2280} -maskdata {
2281    #define tri-dn-mask_width 13
2282    #define tri-dn-mask_height 13
2283    static unsigned char tri-dn-mask_bits[] = {
2284       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
2285       0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
2286       0x00, 0x00};
2287}
2288
2289image create bitmap reficon-T -background black -foreground yellow -data {
2290    #define tagicon_width 13
2291    #define tagicon_height 9
2292    static unsigned char tagicon_bits[] = {
2293       0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0xf8, 0x07,
2294       0xfc, 0x07, 0xf8, 0x07, 0xf0, 0x07, 0x00, 0x00, 0x00, 0x00};
2295} -maskdata {
2296    #define tagicon-mask_width 13
2297    #define tagicon-mask_height 9
2298    static unsigned char tagicon-mask_bits[] = {
2299       0x00, 0x00, 0xf0, 0x0f, 0xf8, 0x0f, 0xfc, 0x0f,
2300       0xfe, 0x0f, 0xfc, 0x0f, 0xf8, 0x0f, 0xf0, 0x0f, 0x00, 0x00};
2301}
2302set rectdata {
2303    #define headicon_width 13
2304    #define headicon_height 9
2305    static unsigned char headicon_bits[] = {
2306       0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0xf8, 0x07,
2307       0xf8, 0x07, 0xf8, 0x07, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x00};
2308}
2309set rectmask {
2310    #define headicon-mask_width 13
2311    #define headicon-mask_height 9
2312    static unsigned char headicon-mask_bits[] = {
2313       0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f,
2314       0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00};
2315}
2316image create bitmap reficon-H -background black -foreground green \
2317    -data $rectdata -maskdata $rectmask
2318image create bitmap reficon-o -background black -foreground "#ddddff" \
2319    -data $rectdata -maskdata $rectmask
2320
2321proc init_flist {first} {
2322    global cflist cflist_top difffilestart
2323
2324    $cflist conf -state normal
2325    $cflist delete 0.0 end
2326    if {$first ne {}} {
2327        $cflist insert end $first
2328        set cflist_top 1
2329        $cflist tag add highlight 1.0 "1.0 lineend"
2330    } else {
2331        catch {unset cflist_top}
2332    }
2333    $cflist conf -state disabled
2334    set difffilestart {}
2335}
2336
2337proc highlight_tag {f} {
2338    global highlight_paths
2339
2340    foreach p $highlight_paths {
2341        if {[string match $p $f]} {
2342            return "bold"
2343        }
2344    }
2345    return {}
2346}
2347
2348proc highlight_filelist {} {
2349    global cmitmode cflist
2350
2351    $cflist conf -state normal
2352    if {$cmitmode ne "tree"} {
2353        set end [lindex [split [$cflist index end] .] 0]
2354        for {set l 2} {$l < $end} {incr l} {
2355            set line [$cflist get $l.0 "$l.0 lineend"]
2356            if {[highlight_tag $line] ne {}} {
2357                $cflist tag add bold $l.0 "$l.0 lineend"
2358            }
2359        }
2360    } else {
2361        highlight_tree 2 {}
2362    }
2363    $cflist conf -state disabled
2364}
2365
2366proc unhighlight_filelist {} {
2367    global cflist
2368
2369    $cflist conf -state normal
2370    $cflist tag remove bold 1.0 end
2371    $cflist conf -state disabled
2372}
2373
2374proc add_flist {fl} {
2375    global cflist
2376
2377    $cflist conf -state normal
2378    foreach f $fl {
2379        $cflist insert end "\n"
2380        $cflist insert end $f [highlight_tag $f]
2381    }
2382    $cflist conf -state disabled
2383}
2384
2385proc sel_flist {w x y} {
2386    global ctext difffilestart cflist cflist_top cmitmode
2387
2388    if {$cmitmode eq "tree"} return
2389    if {![info exists cflist_top]} return
2390    set l [lindex [split [$w index "@$x,$y"] "."] 0]
2391    $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2392    $cflist tag add highlight $l.0 "$l.0 lineend"
2393    set cflist_top $l
2394    if {$l == 1} {
2395        $ctext yview 1.0
2396    } else {
2397        catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
2398    }
2399}
2400
2401proc pop_flist_menu {w X Y x y} {
2402    global ctext cflist cmitmode flist_menu flist_menu_file
2403    global treediffs diffids
2404
2405    stopfinding
2406    set l [lindex [split [$w index "@$x,$y"] "."] 0]
2407    if {$l <= 1} return
2408    if {$cmitmode eq "tree"} {
2409        set e [linetoelt $l]
2410        if {[string index $e end] eq "/"} return
2411    } else {
2412        set e [lindex $treediffs($diffids) [expr {$l-2}]]
2413    }
2414    set flist_menu_file $e
2415    tk_popup $flist_menu $X $Y
2416}
2417
2418proc flist_hl {only} {
2419    global flist_menu_file findstring gdttype
2420
2421    set x [shellquote $flist_menu_file]
2422    if {$only || $findstring eq {} || $gdttype ne "touching paths:"} {
2423        set findstring $x
2424    } else {
2425        append findstring " " $x
2426    }
2427    set gdttype "touching paths:"
2428}
2429
2430# Functions for adding and removing shell-type quoting
2431
2432proc shellquote {str} {
2433    if {![string match "*\['\"\\ \t]*" $str]} {
2434        return $str
2435    }
2436    if {![string match "*\['\"\\]*" $str]} {
2437        return "\"$str\""
2438    }
2439    if {![string match "*'*" $str]} {
2440        return "'$str'"
2441    }
2442    return "\"[string map {\" \\\" \\ \\\\} $str]\""
2443}
2444
2445proc shellarglist {l} {
2446    set str {}
2447    foreach a $l {
2448        if {$str ne {}} {
2449            append str " "
2450        }
2451        append str [shellquote $a]
2452    }
2453    return $str
2454}
2455
2456proc shelldequote {str} {
2457    set ret {}
2458    set used -1
2459    while {1} {
2460        incr used
2461        if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
2462            append ret [string range $str $used end]
2463            set used [string length $str]
2464            break
2465        }
2466        set first [lindex $first 0]
2467        set ch [string index $str $first]
2468        if {$first > $used} {
2469            append ret [string range $str $used [expr {$first - 1}]]
2470            set used $first
2471        }
2472        if {$ch eq " " || $ch eq "\t"} break
2473        incr used
2474        if {$ch eq "'"} {
2475            set first [string first "'" $str $used]
2476            if {$first < 0} {
2477                error "unmatched single-quote"
2478            }
2479            append ret [string range $str $used [expr {$first - 1}]]
2480            set used $first
2481            continue
2482        }
2483        if {$ch eq "\\"} {
2484            if {$used >= [string length $str]} {
2485                error "trailing backslash"
2486            }
2487            append ret [string index $str $used]
2488            continue
2489        }
2490        # here ch == "\""
2491        while {1} {
2492            if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
2493                error "unmatched double-quote"
2494            }
2495            set first [lindex $first 0]
2496            set ch [string index $str $first]
2497            if {$first > $used} {
2498                append ret [string range $str $used [expr {$first - 1}]]
2499                set used $first
2500            }
2501            if {$ch eq "\""} break
2502            incr used
2503            append ret [string index $str $used]
2504            incr used
2505        }
2506    }
2507    return [list $used $ret]
2508}
2509
2510proc shellsplit {str} {
2511    set l {}
2512    while {1} {
2513        set str [string trimleft $str]
2514        if {$str eq {}} break
2515        set dq [shelldequote $str]
2516        set n [lindex $dq 0]
2517        set word [lindex $dq 1]
2518        set str [string range $str $n end]
2519        lappend l $word
2520    }
2521    return $l
2522}
2523
2524# Code to implement multiple views
2525
2526proc newview {ishighlight} {
2527    global nextviewnum newviewname newviewperm uifont newishighlight
2528    global newviewargs revtreeargs
2529
2530    set newishighlight $ishighlight
2531    set top .gitkview
2532    if {[winfo exists $top]} {
2533        raise $top
2534        return
2535    }
2536    set newviewname($nextviewnum) "View $nextviewnum"
2537    set newviewperm($nextviewnum) 0
2538    set newviewargs($nextviewnum) [shellarglist $revtreeargs]
2539    vieweditor $top $nextviewnum "Gitk view definition"
2540}
2541
2542proc editview {} {
2543    global curview
2544    global viewname viewperm newviewname newviewperm
2545    global viewargs newviewargs
2546
2547    set top .gitkvedit-$curview
2548    if {[winfo exists $top]} {
2549        raise $top
2550        return
2551    }
2552    set newviewname($curview) $viewname($curview)
2553    set newviewperm($curview) $viewperm($curview)
2554    set newviewargs($curview) [shellarglist $viewargs($curview)]
2555    vieweditor $top $curview "Gitk: edit view $viewname($curview)"
2556}
2557
2558proc vieweditor {top n title} {
2559    global newviewname newviewperm viewfiles
2560    global uifont
2561
2562    toplevel $top
2563    wm title $top $title
2564    label $top.nl -text "Name" -font uifont
2565    entry $top.name -width 20 -textvariable newviewname($n) -font uifont
2566    grid $top.nl $top.name -sticky w -pady 5
2567    checkbutton $top.perm -text "Remember this view" -variable newviewperm($n) \
2568        -font uifont
2569    grid $top.perm - -pady 5 -sticky w
2570    message $top.al -aspect 1000 -font uifont \
2571        -text "Commits to include (arguments to git rev-list):"
2572    grid $top.al - -sticky w -pady 5
2573    entry $top.args -width 50 -textvariable newviewargs($n) \
2574        -background white -font uifont
2575    grid $top.args - -sticky ew -padx 5
2576    message $top.l -aspect 1000 -font uifont \
2577        -text "Enter files and directories to include, one per line:"
2578    grid $top.l - -sticky w
2579    text $top.t -width 40 -height 10 -background white -font uifont
2580    if {[info exists viewfiles($n)]} {
2581        foreach f $viewfiles($n) {
2582            $top.t insert end $f
2583            $top.t insert end "\n"
2584        }
2585        $top.t delete {end - 1c} end
2586        $top.t mark set insert 0.0
2587    }
2588    grid $top.t - -sticky ew -padx 5
2589    frame $top.buts
2590    button $top.buts.ok -text "OK" -command [list newviewok $top $n] \
2591        -font uifont
2592    button $top.buts.can -text "Cancel" -command [list destroy $top] \
2593        -font uifont
2594    grid $top.buts.ok $top.buts.can
2595    grid columnconfigure $top.buts 0 -weight 1 -uniform a
2596    grid columnconfigure $top.buts 1 -weight 1 -uniform a
2597    grid $top.buts - -pady 10 -sticky ew
2598    focus $top.t
2599}
2600
2601proc doviewmenu {m first cmd op argv} {
2602    set nmenu [$m index end]
2603    for {set i $first} {$i <= $nmenu} {incr i} {
2604        if {[$m entrycget $i -command] eq $cmd} {
2605            eval $m $op $i $argv
2606            break
2607        }
2608    }
2609}
2610
2611proc allviewmenus {n op args} {
2612    # global viewhlmenu
2613
2614    doviewmenu .bar.view 5 [list showview $n] $op $args
2615    # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
2616}
2617
2618proc newviewok {top n} {
2619    global nextviewnum newviewperm newviewname newishighlight
2620    global viewname viewfiles viewperm selectedview curview
2621    global viewargs newviewargs viewhlmenu
2622
2623    if {[catch {
2624        set newargs [shellsplit $newviewargs($n)]
2625    } err]} {
2626        error_popup "Error in commit selection arguments: $err"
2627        wm raise $top
2628        focus $top
2629        return
2630    }
2631    set files {}
2632    foreach f [split [$top.t get 0.0 end] "\n"] {
2633        set ft [string trim $f]
2634        if {$ft ne {}} {
2635            lappend files $ft
2636        }
2637    }
2638    if {![info exists viewfiles($n)]} {
2639        # creating a new view
2640        incr nextviewnum
2641        set viewname($n) $newviewname($n)
2642        set viewperm($n) $newviewperm($n)
2643        set viewfiles($n) $files
2644        set viewargs($n) $newargs
2645        addviewmenu $n
2646        if {!$newishighlight} {
2647            run showview $n
2648        } else {
2649            run addvhighlight $n
2650        }
2651    } else {
2652        # editing an existing view
2653        set viewperm($n) $newviewperm($n)
2654        if {$newviewname($n) ne $viewname($n)} {
2655            set viewname($n) $newviewname($n)
2656            doviewmenu .bar.view 5 [list showview $n] \
2657                entryconf [list -label $viewname($n)]
2658            # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
2659                # entryconf [list -label $viewname($n) -value $viewname($n)]
2660        }
2661        if {$files ne $viewfiles($n) || $newargs ne $viewargs($n)} {
2662            set viewfiles($n) $files
2663            set viewargs($n) $newargs
2664            if {$curview == $n} {
2665                run reloadcommits
2666            }
2667        }
2668    }
2669    catch {destroy $top}
2670}
2671
2672proc delview {} {
2673    global curview viewperm hlview selectedhlview
2674
2675    if {$curview == 0} return
2676    if {[info exists hlview] && $hlview == $curview} {
2677        set selectedhlview None
2678        unset hlview
2679    }
2680    allviewmenus $curview delete
2681    set viewperm($curview) 0
2682    showview 0
2683}
2684
2685proc addviewmenu {n} {
2686    global viewname viewhlmenu
2687
2688    .bar.view add radiobutton -label $viewname($n) \
2689        -command [list showview $n] -variable selectedview -value $n
2690    #$viewhlmenu add radiobutton -label $viewname($n) \
2691    #   -command [list addvhighlight $n] -variable selectedhlview
2692}
2693
2694proc showview {n} {
2695    global curview viewfiles cached_commitrow ordertok
2696    global displayorder parentlist rowidlist rowisopt rowfinal
2697    global colormap rowtextx nextcolor canvxmax
2698    global numcommits viewcomplete
2699    global selectedline currentid canv canvy0
2700    global treediffs
2701    global pending_select
2702    global commitidx
2703    global selectedview selectfirst
2704    global hlview selectedhlview commitinterest
2705
2706    if {$n == $curview} return
2707    set selid {}
2708    set ymax [lindex [$canv cget -scrollregion] 3]
2709    set span [$canv yview]
2710    set ytop [expr {[lindex $span 0] * $ymax}]
2711    set ybot [expr {[lindex $span 1] * $ymax}]
2712    set yscreen [expr {($ybot - $ytop) / 2}]
2713    if {[info exists selectedline]} {
2714        set selid $currentid
2715        set y [yc $selectedline]
2716        if {$ytop < $y && $y < $ybot} {
2717            set yscreen [expr {$y - $ytop}]
2718        }
2719    } elseif {[info exists pending_select]} {
2720        set selid $pending_select
2721        unset pending_select
2722    }
2723    unselectline
2724    normalline
2725    catch {unset treediffs}
2726    clear_display
2727    if {[info exists hlview] && $hlview == $n} {
2728        unset hlview
2729        set selectedhlview None
2730    }
2731    catch {unset commitinterest}
2732    catch {unset cached_commitrow}
2733    catch {unset ordertok}
2734
2735    set curview $n
2736    set selectedview $n
2737    .bar.view entryconf Edit* -state [expr {$n == 0? "disabled": "normal"}]
2738    .bar.view entryconf Delete* -state [expr {$n == 0? "disabled": "normal"}]
2739
2740    run refill_reflist
2741    if {![info exists viewcomplete($n)]} {
2742        if {$selid ne {}} {
2743            set pending_select $selid
2744        }
2745        getcommits
2746        return
2747    }
2748
2749    set displayorder {}
2750    set parentlist {}
2751    set rowidlist {}
2752    set rowisopt {}
2753    set rowfinal {}
2754    set numcommits $commitidx($n)
2755
2756    catch {unset colormap}
2757    catch {unset rowtextx}
2758    set nextcolor 0
2759    set canvxmax [$canv cget -width]
2760    set curview $n
2761    set row 0
2762    setcanvscroll
2763    set yf 0
2764    set row {}
2765    set selectfirst 0
2766    if {$selid ne {} && [commitinview $selid $n]} {
2767        set row [rowofcommit $selid]
2768        # try to get the selected row in the same position on the screen
2769        set ymax [lindex [$canv cget -scrollregion] 3]
2770        set ytop [expr {[yc $row] - $yscreen}]
2771        if {$ytop < 0} {
2772            set ytop 0
2773        }
2774        set yf [expr {$ytop * 1.0 / $ymax}]
2775    }
2776    allcanvs yview moveto $yf
2777    drawvisible
2778    if {$row ne {}} {
2779        selectline $row 0
2780    } elseif {$selid ne {}} {
2781        set pending_select $selid
2782    } else {
2783        set row [first_real_row]
2784        if {$row < $numcommits} {
2785            selectline $row 0
2786        } else {
2787            set selectfirst 1
2788        }
2789    }
2790    if {!$viewcomplete($n)} {
2791        if {$numcommits == 0} {
2792            show_status "Reading commits..."
2793        } else {
2794            run chewcommits $n
2795        }
2796    } elseif {$numcommits == 0} {
2797        show_status "No commits selected"
2798    }
2799}
2800
2801# Stuff relating to the highlighting facility
2802
2803proc ishighlighted {row} {
2804    global vhighlights fhighlights nhighlights rhighlights
2805
2806    if {[info exists nhighlights($row)] && $nhighlights($row) > 0} {
2807        return $nhighlights($row)
2808    }
2809    if {[info exists vhighlights($row)] && $vhighlights($row) > 0} {
2810        return $vhighlights($row)
2811    }
2812    if {[info exists fhighlights($row)] && $fhighlights($row) > 0} {
2813        return $fhighlights($row)
2814    }
2815    if {[info exists rhighlights($row)] && $rhighlights($row) > 0} {
2816        return $rhighlights($row)
2817    }
2818    return 0
2819}
2820
2821proc bolden {row font} {
2822    global canv linehtag selectedline boldrows
2823
2824    lappend boldrows $row
2825    $canv itemconf $linehtag($row) -font $font
2826    if {[info exists selectedline] && $row == $selectedline} {
2827        $canv delete secsel
2828        set t [eval $canv create rect [$canv bbox $linehtag($row)] \
2829                   -outline {{}} -tags secsel \
2830                   -fill [$canv cget -selectbackground]]
2831        $canv lower $t
2832    }
2833}
2834
2835proc bolden_name {row font} {
2836    global canv2 linentag selectedline boldnamerows
2837
2838    lappend boldnamerows $row
2839    $canv2 itemconf $linentag($row) -font $font
2840    if {[info exists selectedline] && $row == $selectedline} {
2841        $canv2 delete secsel
2842        set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
2843                   -outline {{}} -tags secsel \
2844                   -fill [$canv2 cget -selectbackground]]
2845        $canv2 lower $t
2846    }
2847}
2848
2849proc unbolden {} {
2850    global boldrows
2851
2852    set stillbold {}
2853    foreach row $boldrows {
2854        if {![ishighlighted $row]} {
2855            bolden $row mainfont
2856        } else {
2857            lappend stillbold $row
2858        }
2859    }
2860    set boldrows $stillbold
2861}
2862
2863proc addvhighlight {n} {
2864    global hlview viewcomplete curview vhl_done vhighlights commitidx
2865
2866    if {[info exists hlview]} {
2867        delvhighlight
2868    }
2869    set hlview $n
2870    if {$n != $curview && ![info exists viewcomplete($n)]} {
2871        start_rev_list $n
2872    }
2873    set vhl_done $commitidx($hlview)
2874    if {$vhl_done > 0} {
2875        drawvisible
2876    }
2877}
2878
2879proc delvhighlight {} {
2880    global hlview vhighlights
2881
2882    if {![info exists hlview]} return
2883    unset hlview
2884    catch {unset vhighlights}
2885    unbolden
2886}
2887
2888proc vhighlightmore {} {
2889    global hlview vhl_done commitidx vhighlights curview
2890
2891    set max $commitidx($hlview)
2892    set vr [visiblerows]
2893    set r0 [lindex $vr 0]
2894    set r1 [lindex $vr 1]
2895    for {set i $vhl_done} {$i < $max} {incr i} {
2896        set id [commitonrow $i $hlview]
2897        if {[commitinview $id $curview]} {
2898            set row [rowofcommit $id]
2899            if {$r0 <= $row && $row <= $r1} {
2900                if {![highlighted $row]} {
2901                    bolden $row mainfontbold
2902                }
2903                set vhighlights($row) 1
2904            }
2905        }
2906    }
2907    set vhl_done $max
2908}
2909
2910proc askvhighlight {row id} {
2911    global hlview vhighlights iddrawn
2912
2913    if {[commitinview $id $hlview]} {
2914        if {[info exists iddrawn($id)] && ![ishighlighted $row]} {
2915            bolden $row mainfontbold
2916        }
2917        set vhighlights($row) 1
2918    } else {
2919        set vhighlights($row) 0
2920    }
2921}
2922
2923proc hfiles_change {} {
2924    global highlight_files filehighlight fhighlights fh_serial
2925    global highlight_paths gdttype
2926
2927    if {[info exists filehighlight]} {
2928        # delete previous highlights
2929        catch {close $filehighlight}
2930        unset filehighlight
2931        catch {unset fhighlights}
2932        unbolden
2933        unhighlight_filelist
2934    }
2935    set highlight_paths {}
2936    after cancel do_file_hl $fh_serial
2937    incr fh_serial
2938    if {$highlight_files ne {}} {
2939        after 300 do_file_hl $fh_serial
2940    }
2941}
2942
2943proc gdttype_change {name ix op} {
2944    global gdttype highlight_files findstring findpattern
2945
2946    stopfinding
2947    if {$findstring ne {}} {
2948        if {$gdttype eq "containing:"} {
2949            if {$highlight_files ne {}} {
2950                set highlight_files {}
2951                hfiles_change
2952            }
2953            findcom_change
2954        } else {
2955            if {$findpattern ne {}} {
2956                set findpattern {}
2957                findcom_change
2958            }
2959            set highlight_files $findstring
2960            hfiles_change
2961        }
2962        drawvisible
2963    }
2964    # enable/disable findtype/findloc menus too
2965}
2966
2967proc find_change {name ix op} {
2968    global gdttype findstring highlight_files
2969
2970    stopfinding
2971    if {$gdttype eq "containing:"} {
2972        findcom_change
2973    } else {
2974        if {$highlight_files ne $findstring} {
2975            set highlight_files $findstring
2976            hfiles_change
2977        }
2978    }
2979    drawvisible
2980}
2981
2982proc findcom_change args {
2983    global nhighlights boldnamerows
2984    global findpattern findtype findstring gdttype
2985
2986    stopfinding
2987    # delete previous highlights, if any
2988    foreach row $boldnamerows {
2989        bolden_name $row mainfont
2990    }
2991    set boldnamerows {}
2992    catch {unset nhighlights}
2993    unbolden
2994    unmarkmatches
2995    if {$gdttype ne "containing:" || $findstring eq {}} {
2996        set findpattern {}
2997    } elseif {$findtype eq "Regexp"} {
2998        set findpattern $findstring
2999    } else {
3000        set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
3001                   $findstring]
3002        set findpattern "*$e*"
3003    }
3004}
3005
3006proc makepatterns {l} {
3007    set ret {}
3008    foreach e $l {
3009        set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
3010        if {[string index $ee end] eq "/"} {
3011            lappend ret "$ee*"
3012        } else {
3013            lappend ret $ee
3014            lappend ret "$ee/*"
3015        }
3016    }
3017    return $ret
3018}
3019
3020proc do_file_hl {serial} {
3021    global highlight_files filehighlight highlight_paths gdttype fhl_list
3022
3023    if {$gdttype eq "touching paths:"} {
3024        if {[catch {set paths [shellsplit $highlight_files]}]} return
3025        set highlight_paths [makepatterns $paths]
3026        highlight_filelist
3027        set gdtargs [concat -- $paths]
3028    } elseif {$gdttype eq "adding/removing string:"} {
3029        set gdtargs [list "-S$highlight_files"]
3030    } else {
3031        # must be "containing:", i.e. we're searching commit info
3032        return
3033    }
3034    set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
3035    set filehighlight [open $cmd r+]
3036    fconfigure $filehighlight -blocking 0
3037    filerun $filehighlight readfhighlight
3038    set fhl_list {}
3039    drawvisible
3040    flushhighlights
3041}
3042
3043proc flushhighlights {} {
3044    global filehighlight fhl_list
3045
3046    if {[info exists filehighlight]} {
3047        lappend fhl_list {}
3048        puts $filehighlight ""
3049        flush $filehighlight
3050    }
3051}
3052
3053proc askfilehighlight {row id} {
3054    global filehighlight fhighlights fhl_list
3055
3056    lappend fhl_list $id
3057    set fhighlights($row) -1
3058    puts $filehighlight $id
3059}
3060
3061proc readfhighlight {} {
3062    global filehighlight fhighlights curview iddrawn
3063    global fhl_list find_dirn
3064
3065    if {![info exists filehighlight]} {
3066        return 0
3067    }
3068    set nr 0
3069    while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
3070        set line [string trim $line]
3071        set i [lsearch -exact $fhl_list $line]
3072        if {$i < 0} continue
3073        for {set j 0} {$j < $i} {incr j} {
3074            set id [lindex $fhl_list $j]
3075            if {[commitinview $id $curview]} {
3076                set fhighlights([rowofcommit $id]) 0
3077            }
3078        }
3079        set fhl_list [lrange $fhl_list [expr {$i+1}] end]
3080        if {$line eq {}} continue
3081        if {![commitinview $line $curview]} continue
3082        set row [rowofcommit $line]
3083        if {[info exists iddrawn($line)] && ![ishighlighted $row]} {
3084            bolden $row mainfontbold
3085        }
3086        set fhighlights($row) 1
3087    }
3088    if {[eof $filehighlight]} {
3089        # strange...
3090        puts "oops, git diff-tree died"
3091        catch {close $filehighlight}
3092        unset filehighlight
3093        return 0
3094    }
3095    if {[info exists find_dirn]} {
3096        run findmore
3097    }
3098    return 1
3099}
3100
3101proc doesmatch {f} {
3102    global findtype findpattern
3103
3104    if {$findtype eq "Regexp"} {
3105        return [regexp $findpattern $f]
3106    } elseif {$findtype eq "IgnCase"} {
3107        return [string match -nocase $findpattern $f]
3108    } else {
3109        return [string match $findpattern $f]
3110    }
3111}
3112
3113proc askfindhighlight {row id} {
3114    global nhighlights commitinfo iddrawn
3115    global findloc
3116    global markingmatches
3117
3118    if {![info exists commitinfo($id)]} {
3119        getcommit $id
3120    }
3121    set info $commitinfo($id)
3122    set isbold 0
3123    set fldtypes {Headline Author Date Committer CDate Comments}
3124    foreach f $info ty $fldtypes {
3125        if {($findloc eq "All fields" || $findloc eq $ty) &&
3126            [doesmatch $f]} {
3127            if {$ty eq "Author"} {
3128                set isbold 2
3129                break
3130            }
3131            set isbold 1
3132        }
3133    }
3134    if {$isbold && [info exists iddrawn($id)]} {
3135        if {![ishighlighted $row]} {
3136            bolden $row mainfontbold
3137            if {$isbold > 1} {
3138                bolden_name $row mainfontbold
3139            }
3140        }
3141        if {$markingmatches} {
3142            markrowmatches $row $id
3143        }
3144    }
3145    set nhighlights($row) $isbold
3146}
3147
3148proc markrowmatches {row id} {
3149    global canv canv2 linehtag linentag commitinfo findloc
3150
3151    set headline [lindex $commitinfo($id) 0]
3152    set author [lindex $commitinfo($id) 1]
3153    $canv delete match$row
3154    $canv2 delete match$row
3155    if {$findloc eq "All fields" || $findloc eq "Headline"} {
3156        set m [findmatches $headline]
3157        if {$m ne {}} {
3158            markmatches $canv $row $headline $linehtag($row) $m \
3159                [$canv itemcget $linehtag($row) -font] $row
3160        }
3161    }
3162    if {$findloc eq "All fields" || $findloc eq "Author"} {
3163        set m [findmatches $author]
3164        if {$m ne {}} {
3165            markmatches $canv2 $row $author $linentag($row) $m \
3166                [$canv2 itemcget $linentag($row) -font] $row
3167        }
3168    }
3169}
3170
3171proc vrel_change {name ix op} {
3172    global highlight_related
3173
3174    rhighlight_none
3175    if {$highlight_related ne "None"} {
3176        run drawvisible
3177    }
3178}
3179
3180# prepare for testing whether commits are descendents or ancestors of a
3181proc rhighlight_sel {a} {
3182    global descendent desc_todo ancestor anc_todo
3183    global highlight_related rhighlights
3184
3185    catch {unset descendent}
3186    set desc_todo [list $a]
3187    catch {unset ancestor}
3188    set anc_todo [list $a]
3189    if {$highlight_related ne "None"} {
3190        rhighlight_none
3191        run drawvisible
3192    }
3193}
3194
3195proc rhighlight_none {} {
3196    global rhighlights
3197
3198    catch {unset rhighlights}
3199    unbolden
3200}
3201
3202proc is_descendent {a} {
3203    global curview children descendent desc_todo
3204
3205    set v $curview
3206    set la [rowofcommit $a]
3207    set todo $desc_todo
3208    set leftover {}
3209    set done 0
3210    for {set i 0} {$i < [llength $todo]} {incr i} {
3211        set do [lindex $todo $i]
3212        if {[rowofcommit $do] < $la} {
3213            lappend leftover $do
3214            continue
3215        }
3216        foreach nk $children($v,$do) {
3217            if {![info exists descendent($nk)]} {
3218                set descendent($nk) 1
3219                lappend todo $nk
3220                if {$nk eq $a} {
3221                    set done 1
3222                }
3223            }
3224        }
3225        if {$done} {
3226            set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
3227            return
3228        }
3229    }
3230    set descendent($a) 0
3231    set desc_todo $leftover
3232}
3233
3234proc is_ancestor {a} {
3235    global curview parents ancestor anc_todo
3236
3237    set v $curview
3238    set la [rowofcommit $a]
3239    set todo $anc_todo
3240    set leftover {}
3241    set done 0
3242    for {set i 0} {$i < [llength $todo]} {incr i} {
3243        set do [lindex $todo $i]
3244        if {![commitinview $do $v] || [rowofcommit $do] > $la} {
3245            lappend leftover $do
3246            continue
3247        }
3248        foreach np $parents($v,$do) {
3249            if {![info exists ancestor($np)]} {
3250                set ancestor($np) 1
3251                lappend todo $np
3252                if {$np eq $a} {
3253                    set done 1
3254                }
3255            }
3256        }
3257        if {$done} {
3258            set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
3259            return
3260        }
3261    }
3262    set ancestor($a) 0
3263    set anc_todo $leftover
3264}
3265
3266proc askrelhighlight {row id} {
3267    global descendent highlight_related iddrawn rhighlights
3268    global selectedline ancestor
3269
3270    if {![info exists selectedline]} return
3271    set isbold 0
3272    if {$highlight_related eq "Descendent" ||
3273        $highlight_related eq "Not descendent"} {
3274        if {![info exists descendent($id)]} {
3275            is_descendent $id
3276        }
3277        if {$descendent($id) == ($highlight_related eq "Descendent")} {
3278            set isbold 1
3279        }
3280    } elseif {$highlight_related eq "Ancestor" ||
3281              $highlight_related eq "Not ancestor"} {
3282        if {![info exists ancestor($id)]} {
3283            is_ancestor $id
3284        }
3285        if {$ancestor($id) == ($highlight_related eq "Ancestor")} {
3286            set isbold 1
3287        }
3288    }
3289    if {[info exists iddrawn($id)]} {
3290        if {$isbold && ![ishighlighted $row]} {
3291            bolden $row mainfontbold
3292        }
3293    }
3294    set rhighlights($row) $isbold
3295}
3296
3297# Graph layout functions
3298
3299proc shortids {ids} {
3300    set res {}
3301    foreach id $ids {
3302        if {[llength $id] > 1} {
3303            lappend res [shortids $id]
3304        } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
3305            lappend res [string range $id 0 7]
3306        } else {
3307            lappend res $id
3308        }
3309    }
3310    return $res
3311}
3312
3313proc ntimes {n o} {
3314    set ret {}
3315    set o [list $o]
3316    for {set mask 1} {$mask <= $n} {incr mask $mask} {
3317        if {($n & $mask) != 0} {
3318            set ret [concat $ret $o]
3319        }
3320        set o [concat $o $o]
3321    }
3322    return $ret
3323}
3324
3325proc ordertoken {id} {
3326    global ordertok curview varcid varcstart varctok curview parents children
3327    global nullid nullid2
3328
3329    if {[info exists ordertok($id)]} {
3330        return $ordertok($id)
3331    }
3332    set origid $id
3333    set todo {}
3334    while {1} {
3335        if {[info exists varcid($curview,$id)]} {
3336            set a $varcid($curview,$id)
3337            set p [lindex $varcstart($curview) $a]
3338        } else {
3339            set p [lindex $children($curview,$id) 0]
3340        }
3341        if {[info exists ordertok($p)]} {
3342            set tok $ordertok($p)
3343            break
3344        }
3345        if {[llength $children($curview,$p)] == 0} {
3346            # it's a root
3347            set tok [lindex $varctok($curview) $a]
3348            break
3349        }
3350        set id [lindex $children($curview,$p) 0]
3351        if {$id eq $nullid || $id eq $nullid2} {
3352            # XXX treat it as a root
3353            set tok [lindex $varctok($curview) $a]
3354            break
3355        }
3356        if {[llength $parents($curview,$id)] == 1} {
3357            lappend todo [list $p {}]
3358        } else {
3359            set j [lsearch -exact $parents($curview,$id) $p]
3360            if {$j < 0} {
3361                puts "oops didn't find [shortids $p] in parents of [shortids $id]"
3362            }
3363            lappend todo [list $p [strrep $j]]
3364        }
3365    }
3366    for {set i [llength $todo]} {[incr i -1] >= 0} {} {
3367        set p [lindex $todo $i 0]
3368        append tok [lindex $todo $i 1]
3369        set ordertok($p) $tok
3370    }
3371    set ordertok($origid) $tok
3372    return $tok
3373}
3374
3375# Work out where id should go in idlist so that order-token
3376# values increase from left to right
3377proc idcol {idlist id {i 0}} {
3378    set t [ordertoken $id]
3379    if {$i < 0} {
3380        set i 0
3381    }
3382    if {$i >= [llength $idlist] || $t < [ordertoken [lindex $idlist $i]]} {
3383        if {$i > [llength $idlist]} {
3384            set i [llength $idlist]
3385        }
3386        while {[incr i -1] >= 0 && $t < [ordertoken [lindex $idlist $i]]} {}
3387        incr i
3388    } else {
3389        if {$t > [ordertoken [lindex $idlist $i]]} {
3390            while {[incr i] < [llength $idlist] &&
3391                   $t >= [ordertoken [lindex $idlist $i]]} {}
3392        }
3393    }
3394    return $i
3395}
3396
3397proc initlayout {} {
3398    global rowidlist rowisopt rowfinal displayorder parentlist
3399    global numcommits canvxmax canv
3400    global nextcolor
3401    global colormap rowtextx
3402    global selectfirst
3403
3404    set numcommits 0
3405    set displayorder {}
3406    set parentlist {}
3407    set nextcolor 0
3408    set rowidlist {}
3409    set rowisopt {}
3410    set rowfinal {}
3411    set canvxmax [$canv cget -width]
3412    catch {unset colormap}
3413    catch {unset rowtextx}
3414    set selectfirst 1
3415}
3416
3417proc setcanvscroll {} {
3418    global canv canv2 canv3 numcommits linespc canvxmax canvy0
3419
3420    set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
3421    $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
3422    $canv2 conf -scrollregion [list 0 0 0 $ymax]
3423    $canv3 conf -scrollregion [list 0 0 0 $ymax]
3424}
3425
3426proc visiblerows {} {
3427    global canv numcommits linespc
3428
3429    set ymax [lindex [$canv cget -scrollregion] 3]
3430    if {$ymax eq {} || $ymax == 0} return
3431    set f [$canv yview]
3432    set y0 [expr {int([lindex $f 0] * $ymax)}]
3433    set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
3434    if {$r0 < 0} {
3435        set r0 0
3436    }
3437    set y1 [expr {int([lindex $f 1] * $ymax)}]
3438    set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
3439    if {$r1 >= $numcommits} {
3440        set r1 [expr {$numcommits - 1}]
3441    }
3442    return [list $r0 $r1]
3443}
3444
3445proc layoutmore {} {
3446    global commitidx viewcomplete curview
3447    global numcommits pending_select selectedline curview
3448    global selectfirst lastscrollset commitinterest
3449
3450    set canshow $commitidx($curview)
3451    if {$canshow <= $numcommits && !$viewcomplete($curview)} return
3452    if {$numcommits == 0} {
3453        allcanvs delete all
3454    }
3455    set r0 $numcommits
3456    set prev $numcommits
3457    set numcommits $canshow
3458    set t [clock clicks -milliseconds]
3459    if {$prev < 100 || $viewcomplete($curview) || $t - $lastscrollset > 500} {
3460        set lastscrollset $t
3461        setcanvscroll
3462    }
3463    set rows [visiblerows]
3464    set r1 [lindex $rows 1]
3465    if {$r1 >= $canshow} {
3466        set r1 [expr {$canshow - 1}]
3467    }
3468    if {$r0 <= $r1} {
3469        drawcommits $r0 $r1
3470    }
3471    if {[info exists pending_select] &&
3472        [commitinview $pending_select $curview]} {
3473        selectline [rowofcommit $pending_select] 1
3474    }
3475    if {$selectfirst} {
3476        if {[info exists selectedline] || [info exists pending_select]} {
3477            set selectfirst 0
3478        } else {
3479            set l [first_real_row]
3480            selectline $l 1
3481            set selectfirst 0
3482        }
3483    }
3484}
3485
3486proc doshowlocalchanges {} {
3487    global curview mainheadid
3488
3489    if {[commitinview $mainheadid $curview]} {
3490        dodiffindex
3491    } else {
3492        lappend commitinterest($mainheadid) {dodiffindex}
3493    }
3494}
3495
3496proc dohidelocalchanges {} {
3497    global nullid nullid2 lserial curview
3498
3499    if {[commitinview $nullid $curview]} {
3500        removerow $nullid $curview
3501    }
3502    if {[commitinview $nullid2 $curview]} {
3503        removerow $nullid2 $curview
3504    }
3505    incr lserial
3506}
3507
3508# spawn off a process to do git diff-index --cached HEAD
3509proc dodiffindex {} {
3510    global lserial showlocalchanges
3511
3512    if {!$showlocalchanges} return
3513    incr lserial
3514    set fd [open "|git diff-index --cached HEAD" r]
3515    fconfigure $fd -blocking 0
3516    filerun $fd [list readdiffindex $fd $lserial]
3517}
3518
3519proc readdiffindex {fd serial} {
3520    global mainheadid nullid2 curview commitinfo commitdata lserial
3521
3522    set isdiff 1
3523    if {[gets $fd line] < 0} {
3524        if {![eof $fd]} {
3525            return 1
3526        }
3527        set isdiff 0
3528    }
3529    # we only need to see one line and we don't really care what it says...
3530    close $fd
3531
3532    if {$serial != $lserial} {
3533        return 0
3534    }
3535
3536    # now see if there are any local changes not checked in to the index
3537    set fd [open "|git diff-files" r]
3538    fconfigure $fd -blocking 0
3539    filerun $fd [list readdifffiles $fd $serial]
3540
3541    if {$isdiff && ![commitinview $nullid2 $curview]} {
3542        # add the line for the changes in the index to the graph
3543        set hl "Local changes checked in to index but not committed"
3544        set commitinfo($nullid2) [list  $hl {} {} {} {} "    $hl\n"]
3545        set commitdata($nullid2) "\n    $hl\n"
3546        insertrow $nullid2 $mainheadid $curview
3547    } elseif {!$isdiff && [commitinview $nullid2 $curview]} {
3548        removerow $nullid2 $curview
3549    }
3550    return 0
3551}
3552
3553proc readdifffiles {fd serial} {
3554    global mainheadid nullid nullid2 curview
3555    global commitinfo commitdata lserial
3556
3557    set isdiff 1
3558    if {[gets $fd line] < 0} {
3559        if {![eof $fd]} {
3560            return 1
3561        }
3562        set isdiff 0
3563    }
3564    # we only need to see one line and we don't really care what it says...
3565    close $fd
3566
3567    if {$serial != $lserial} {
3568        return 0
3569    }
3570
3571    if {$isdiff && ![commitinview $nullid $curview]} {
3572        # add the line for the local diff to the graph
3573        set hl "Local uncommitted changes, not checked in to index"
3574        set commitinfo($nullid) [list  $hl {} {} {} {} "    $hl\n"]
3575        set commitdata($nullid) "\n    $hl\n"
3576        if {[commitinview $nullid2 $curview]} {
3577            set p $nullid2
3578        } else {
3579            set p $mainheadid
3580        }
3581        insertrow $nullid $p $curview
3582    } elseif {!$isdiff && [commitinview $nullid $curview]} {
3583        removerow $nullid $curview
3584    }
3585    return 0
3586}
3587
3588proc nextuse {id row} {
3589    global curview children
3590
3591    if {[info exists children($curview,$id)]} {
3592        foreach kid $children($curview,$id) {
3593            if {![commitinview $kid $curview]} {
3594                return -1
3595            }
3596            if {[rowofcommit $kid] > $row} {
3597                return [rowofcommit $kid]
3598            }
3599        }
3600    }
3601    if {[commitinview $id $curview]} {
3602        return [rowofcommit $id]
3603    }
3604    return -1
3605}
3606
3607proc prevuse {id row} {
3608    global curview children
3609
3610    set ret -1
3611    if {[info exists children($curview,$id)]} {
3612        foreach kid $children($curview,$id) {
3613            if {![commitinview $kid $curview]} break
3614            if {[rowofcommit $kid] < $row} {
3615                set ret [rowofcommit $kid]
3616            }
3617        }
3618    }
3619    return $ret
3620}
3621
3622proc make_idlist {row} {
3623    global displayorder parentlist uparrowlen downarrowlen mingaplen
3624    global commitidx curview children
3625
3626    set r [expr {$row - $mingaplen - $downarrowlen - 1}]
3627    if {$r < 0} {
3628        set r 0
3629    }
3630    set ra [expr {$row - $downarrowlen}]
3631    if {$ra < 0} {
3632        set ra 0
3633    }
3634    set rb [expr {$row + $uparrowlen}]
3635    if {$rb > $commitidx($curview)} {
3636        set rb $commitidx($curview)
3637    }
3638    make_disporder $r [expr {$rb + 1}]
3639    set ids {}
3640    for {} {$r < $ra} {incr r} {
3641        set nextid [lindex $displayorder [expr {$r + 1}]]
3642        foreach p [lindex $parentlist $r] {
3643            if {$p eq $nextid} continue
3644            set rn [nextuse $p $r]
3645            if {$rn >= $row &&
3646                $rn <= $r + $downarrowlen + $mingaplen + $uparrowlen} {
3647                lappend ids [list [ordertoken $p] $p]
3648            }
3649        }
3650    }
3651    for {} {$r < $row} {incr r} {
3652        set nextid [lindex $displayorder [expr {$r + 1}]]
3653        foreach p [lindex $parentlist $r] {
3654            if {$p eq $nextid} continue
3655            set rn [nextuse $p $r]
3656            if {$rn < 0 || $rn >= $row} {
3657                lappend ids [list [ordertoken $p] $p]
3658            }
3659        }
3660    }
3661    set id [lindex $displayorder $row]
3662    lappend ids [list [ordertoken $id] $id]
3663    while {$r < $rb} {
3664        foreach p [lindex $parentlist $r] {
3665            set firstkid [lindex $children($curview,$p) 0]
3666            if {[rowofcommit $firstkid] < $row} {
3667                lappend ids [list [ordertoken $p] $p]
3668            }
3669        }
3670        incr r
3671        set id [lindex $displayorder $r]
3672        if {$id ne {}} {
3673            set firstkid [lindex $children($curview,$id) 0]
3674            if {$firstkid ne {} && [rowofcommit $firstkid] < $row} {
3675                lappend ids [list [ordertoken $id] $id]
3676            }
3677        }
3678    }
3679    set idlist {}
3680    foreach idx [lsort -unique $ids] {
3681        lappend idlist [lindex $idx 1]
3682    }
3683    return $idlist
3684}
3685
3686proc rowsequal {a b} {
3687    while {[set i [lsearch -exact $a {}]] >= 0} {
3688        set a [lreplace $a $i $i]
3689    }
3690    while {[set i [lsearch -exact $b {}]] >= 0} {
3691        set b [lreplace $b $i $i]
3692    }
3693    return [expr {$a eq $b}]
3694}
3695
3696proc makeupline {id row rend col} {
3697    global rowidlist uparrowlen downarrowlen mingaplen
3698
3699    for {set r $rend} {1} {set r $rstart} {
3700        set rstart [prevuse $id $r]
3701        if {$rstart < 0} return
3702        if {$rstart < $row} break
3703    }
3704    if {$rstart + $uparrowlen + $mingaplen + $downarrowlen < $rend} {
3705        set rstart [expr {$rend - $uparrowlen - 1}]
3706    }
3707    for {set r $rstart} {[incr r] <= $row} {} {
3708        set idlist [lindex $rowidlist $r]
3709        if {$idlist ne {} && [lsearch -exact $idlist $id] < 0} {
3710            set col [idcol $idlist $id $col]
3711            lset rowidlist $r [linsert $idlist $col $id]
3712            changedrow $r
3713        }
3714    }
3715}
3716
3717proc layoutrows {row endrow} {
3718    global rowidlist rowisopt rowfinal displayorder
3719    global uparrowlen downarrowlen maxwidth mingaplen
3720    global children parentlist
3721    global commitidx viewcomplete curview
3722
3723    make_disporder [expr {$row - 1}] [expr {$endrow + $uparrowlen}]
3724    set idlist {}
3725    if {$row > 0} {
3726        set rm1 [expr {$row - 1}]
3727        foreach id [lindex $rowidlist $rm1] {
3728            if {$id ne {}} {
3729                lappend idlist $id
3730            }
3731        }
3732        set final [lindex $rowfinal $rm1]
3733    }
3734    for {} {$row < $endrow} {incr row} {
3735        set rm1 [expr {$row - 1}]
3736        if {$rm1 < 0 || $idlist eq {}} {
3737            set idlist [make_idlist $row]
3738            set final 1
3739        } else {
3740            set id [lindex $displayorder $rm1]
3741            set col [lsearch -exact $idlist $id]
3742            set idlist [lreplace $idlist $col $col]
3743            foreach p [lindex $parentlist $rm1] {
3744                if {[lsearch -exact $idlist $p] < 0} {
3745                    set col [idcol $idlist $p $col]
3746                    set idlist [linsert $idlist $col $p]
3747                    # if not the first child, we have to insert a line going up
3748                    if {$id ne [lindex $children($curview,$p) 0]} {
3749                        makeupline $p $rm1 $row $col
3750                    }
3751                }
3752            }
3753            set id [lindex $displayorder $row]
3754            if {$row > $downarrowlen} {
3755                set termrow [expr {$row - $downarrowlen - 1}]
3756                foreach p [lindex $parentlist $termrow] {
3757                    set i [lsearch -exact $idlist $p]
3758                    if {$i < 0} continue
3759                    set nr [nextuse $p $termrow]
3760                    if {$nr < 0 || $nr >= $row + $mingaplen + $uparrowlen} {
3761                        set idlist [lreplace $idlist $i $i]
3762                    }
3763                }
3764            }
3765            set col [lsearch -exact $idlist $id]
3766            if {$col < 0} {
3767                set col [idcol $idlist $id]
3768                set idlist [linsert $idlist $col $id]
3769                if {$children($curview,$id) ne {}} {
3770                    makeupline $id $rm1 $row $col
3771                }
3772            }
3773            set r [expr {$row + $uparrowlen - 1}]
3774            if {$r < $commitidx($curview)} {
3775                set x $col
3776                foreach p [lindex $parentlist $r] {
3777                    if {[lsearch -exact $idlist $p] >= 0} continue
3778                    set fk [lindex $children($curview,$p) 0]
3779                    if {[rowofcommit $fk] < $row} {
3780                        set x [idcol $idlist $p $x]
3781                        set idlist [linsert $idlist $x $p]
3782                    }
3783                }
3784                if {[incr r] < $commitidx($curview)} {
3785                    set p [lindex $displayorder $r]
3786                    if {[lsearch -exact $idlist $p] < 0} {
3787                        set fk [lindex $children($curview,$p) 0]
3788                        if {$fk ne {} && [rowofcommit $fk] < $row} {
3789                            set x [idcol $idlist $p $x]
3790                            set idlist [linsert $idlist $x $p]
3791                        }
3792                    }
3793                }
3794            }
3795        }
3796        if {$final && !$viewcomplete($curview) &&
3797            $row + $uparrowlen + $mingaplen + $downarrowlen
3798                >= $commitidx($curview)} {
3799            set final 0
3800        }
3801        set l [llength $rowidlist]
3802        if {$row == $l} {
3803            lappend rowidlist $idlist
3804            lappend rowisopt 0
3805            lappend rowfinal $final
3806        } elseif {$row < $l} {
3807            if {![rowsequal $idlist [lindex $rowidlist $row]]} {
3808                lset rowidlist $row $idlist
3809                changedrow $row
3810            }
3811            lset rowfinal $row $final
3812        } else {
3813            set pad [ntimes [expr {$row - $l}] {}]
3814            set rowidlist [concat $rowidlist $pad]
3815            lappend rowidlist $idlist
3816            set rowfinal [concat $rowfinal $pad]
3817            lappend rowfinal $final
3818            set rowisopt [concat $rowisopt [ntimes [expr {$row - $l + 1}] 0]]
3819        }
3820    }
3821    return $row
3822}
3823
3824proc changedrow {row} {
3825    global displayorder iddrawn rowisopt need_redisplay
3826
3827    set l [llength $rowisopt]
3828    if {$row < $l} {
3829        lset rowisopt $row 0
3830        if {$row + 1 < $l} {
3831            lset rowisopt [expr {$row + 1}] 0
3832            if {$row + 2 < $l} {
3833                lset rowisopt [expr {$row + 2}] 0
3834            }
3835        }
3836    }
3837    set id [lindex $displayorder $row]
3838    if {[info exists iddrawn($id)]} {
3839        set need_redisplay 1
3840    }
3841}
3842
3843proc insert_pad {row col npad} {
3844    global rowidlist
3845
3846    set pad [ntimes $npad {}]
3847    set idlist [lindex $rowidlist $row]
3848    set bef [lrange $idlist 0 [expr {$col - 1}]]
3849    set aft [lrange $idlist $col end]
3850    set i [lsearch -exact $aft {}]
3851    if {$i > 0} {
3852        set aft [lreplace $aft $i $i]
3853    }
3854    lset rowidlist $row [concat $bef $pad $aft]
3855    changedrow $row
3856}
3857
3858proc optimize_rows {row col endrow} {
3859    global rowidlist rowisopt displayorder curview children
3860
3861    if {$row < 1} {
3862        set row 1
3863    }
3864    for {} {$row < $endrow} {incr row; set col 0} {
3865        if {[lindex $rowisopt $row]} continue
3866        set haspad 0
3867        set y0 [expr {$row - 1}]
3868        set ym [expr {$row - 2}]
3869        set idlist [lindex $rowidlist $row]
3870        set previdlist [lindex $rowidlist $y0]
3871        if {$idlist eq {} || $previdlist eq {}} continue
3872        if {$ym >= 0} {
3873            set pprevidlist [lindex $rowidlist $ym]
3874            if {$pprevidlist eq {}} continue
3875        } else {
3876            set pprevidlist {}
3877        }
3878        set x0 -1
3879        set xm -1
3880        for {} {$col < [llength $idlist]} {incr col} {
3881            set id [lindex $idlist $col]
3882            if {[lindex $previdlist $col] eq $id} continue
3883            if {$id eq {}} {
3884                set haspad 1
3885                continue
3886            }
3887            set x0 [lsearch -exact $previdlist $id]
3888            if {$x0 < 0} continue
3889            set z [expr {$x0 - $col}]
3890            set isarrow 0
3891            set z0 {}
3892            if {$ym >= 0} {
3893                set xm [lsearch -exact $pprevidlist $id]
3894                if {$xm >= 0} {
3895                    set z0 [expr {$xm - $x0}]
3896                }
3897            }
3898            if {$z0 eq {}} {
3899                # if row y0 is the first child of $id then it's not an arrow
3900                if {[lindex $children($curview,$id) 0] ne
3901                    [lindex $displayorder $y0]} {
3902                    set isarrow 1
3903                }
3904            }
3905            if {!$isarrow && $id ne [lindex $displayorder $row] &&
3906                [lsearch -exact [lindex $rowidlist [expr {$row+1}]] $id] < 0} {
3907                set isarrow 1
3908            }
3909            # Looking at lines from this row to the previous row,
3910            # make them go straight up if they end in an arrow on
3911            # the previous row; otherwise make them go straight up
3912            # or at 45 degrees.
3913            if {$z < -1 || ($z < 0 && $isarrow)} {
3914                # Line currently goes left too much;
3915                # insert pads in the previous row, then optimize it
3916                set npad [expr {-1 - $z + $isarrow}]
3917                insert_pad $y0 $x0 $npad
3918                if {$y0 > 0} {
3919                    optimize_rows $y0 $x0 $row
3920                }
3921                set previdlist [lindex $rowidlist $y0]
3922                set x0 [lsearch -exact $previdlist $id]
3923                set z [expr {$x0 - $col}]
3924                if {$z0 ne {}} {
3925                    set pprevidlist [lindex $rowidlist $ym]
3926                    set xm [lsearch -exact $pprevidlist $id]
3927                    set z0 [expr {$xm - $x0}]
3928                }
3929            } elseif {$z > 1 || ($z > 0 && $isarrow)} {
3930                # Line currently goes right too much;
3931                # insert pads in this line
3932                set npad [expr {$z - 1 + $isarrow}]
3933                insert_pad $row $col $npad
3934                set idlist [lindex $rowidlist $row]
3935                incr col $npad
3936                set z [expr {$x0 - $col}]
3937                set haspad 1
3938            }
3939            if {$z0 eq {} && !$isarrow && $ym >= 0} {
3940                # this line links to its first child on row $row-2
3941                set id [lindex $displayorder $ym]
3942                set xc [lsearch -exact $pprevidlist $id]
3943                if {$xc >= 0} {
3944                    set z0 [expr {$xc - $x0}]
3945                }
3946            }
3947            # avoid lines jigging left then immediately right
3948            if {$z0 ne {} && $z < 0 && $z0 > 0} {
3949                insert_pad $y0 $x0 1
3950                incr x0
3951                optimize_rows $y0 $x0 $row
3952                set previdlist [lindex $rowidlist $y0]
3953            }
3954        }
3955        if {!$haspad} {
3956            # Find the first column that doesn't have a line going right
3957            for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
3958                set id [lindex $idlist $col]
3959                if {$id eq {}} break
3960                set x0 [lsearch -exact $previdlist $id]
3961                if {$x0 < 0} {
3962                    # check if this is the link to the first child
3963                    set kid [lindex $displayorder $y0]
3964                    if {[lindex $children($curview,$id) 0] eq $kid} {
3965                        # it is, work out offset to child
3966                        set x0 [lsearch -exact $previdlist $kid]
3967                    }
3968                }
3969                if {$x0 <= $col} break
3970            }
3971            # Insert a pad at that column as long as it has a line and
3972            # isn't the last column
3973            if {$x0 >= 0 && [incr col] < [llength $idlist]} {
3974                set idlist [linsert $idlist $col {}]
3975                lset rowidlist $row $idlist
3976                changedrow $row
3977            }
3978        }
3979    }
3980}
3981
3982proc xc {row col} {
3983    global canvx0 linespc
3984    return [expr {$canvx0 + $col * $linespc}]
3985}
3986
3987proc yc {row} {
3988    global canvy0 linespc
3989    return [expr {$canvy0 + $row * $linespc}]
3990}
3991
3992proc linewidth {id} {
3993    global thickerline lthickness
3994
3995    set wid $lthickness
3996    if {[info exists thickerline] && $id eq $thickerline} {
3997        set wid [expr {2 * $lthickness}]
3998    }
3999    return $wid
4000}
4001
4002proc rowranges {id} {
4003    global curview children uparrowlen downarrowlen
4004    global rowidlist
4005
4006    set kids $children($curview,$id)
4007    if {$kids eq {}} {
4008        return {}
4009    }
4010    set ret {}
4011    lappend kids $id
4012    foreach child $kids {
4013        if {![commitinview $child $curview]} break
4014        set row [rowofcommit $child]
4015        if {![info exists prev]} {
4016            lappend ret [expr {$row + 1}]
4017        } else {
4018            if {$row <= $prevrow} {
4019                puts "oops children of [shortids $id] out of order [shortids $child] $row <= [shortids $prev] $prevrow"
4020            }
4021            # see if the line extends the whole way from prevrow to row
4022            if {$row > $prevrow + $uparrowlen + $downarrowlen &&
4023                [lsearch -exact [lindex $rowidlist \
4024                            [expr {int(($row + $prevrow) / 2)}]] $id] < 0} {
4025                # it doesn't, see where it ends
4026                set r [expr {$prevrow + $downarrowlen}]
4027                if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
4028                    while {[incr r -1] > $prevrow &&
4029                           [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
4030                } else {
4031                    while {[incr r] <= $row &&
4032                           [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
4033                    incr r -1
4034                }
4035                lappend ret $r
4036                # see where it starts up again
4037                set r [expr {$row - $uparrowlen}]
4038                if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
4039                    while {[incr r] < $row &&
4040                           [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
4041                } else {
4042                    while {[incr r -1] >= $prevrow &&
4043                           [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
4044                    incr r
4045                }
4046                lappend ret $r
4047            }
4048        }
4049        if {$child eq $id} {
4050            lappend ret $row
4051        }
4052        set prev $child
4053        set prevrow $row
4054    }
4055    return $ret
4056}
4057
4058proc drawlineseg {id row endrow arrowlow} {
4059    global rowidlist displayorder iddrawn linesegs
4060    global canv colormap linespc curview maxlinelen parentlist
4061
4062    set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
4063    set le [expr {$row + 1}]
4064    set arrowhigh 1
4065    while {1} {
4066        set c [lsearch -exact [lindex $rowidlist $le] $id]
4067        if {$c < 0} {
4068            incr le -1
4069            break
4070        }
4071        lappend cols $c
4072        set x [lindex $displayorder $le]
4073        if {$x eq $id} {
4074            set arrowhigh 0
4075            break
4076        }
4077        if {[info exists iddrawn($x)] || $le == $endrow} {
4078            set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
4079            if {$c >= 0} {
4080                lappend cols $c
4081                set arrowhigh 0
4082            }
4083            break
4084        }
4085        incr le
4086    }
4087    if {$le <= $row} {
4088        return $row
4089    }
4090
4091    set lines {}
4092    set i 0
4093    set joinhigh 0
4094    if {[info exists linesegs($id)]} {
4095        set lines $linesegs($id)
4096        foreach li $lines {
4097            set r0 [lindex $li 0]
4098            if {$r0 > $row} {
4099                if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
4100                    set joinhigh 1
4101                }
4102                break
4103            }
4104            incr i
4105        }
4106    }
4107    set joinlow 0
4108    if {$i > 0} {
4109        set li [lindex $lines [expr {$i-1}]]
4110        set r1 [lindex $li 1]
4111        if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
4112            set joinlow 1
4113        }
4114    }
4115
4116    set x [lindex $cols [expr {$le - $row}]]
4117    set xp [lindex $cols [expr {$le - 1 - $row}]]
4118    set dir [expr {$xp - $x}]
4119    if {$joinhigh} {
4120        set ith [lindex $lines $i 2]
4121        set coords [$canv coords $ith]
4122        set ah [$canv itemcget $ith -arrow]
4123        set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
4124        set x2 [lindex $cols [expr {$le + 1 - $row}]]
4125        if {$x2 ne {} && $x - $x2 == $dir} {
4126            set coords [lrange $coords 0 end-2]
4127        }
4128    } else {
4129        set coords [list [xc $le $x] [yc $le]]
4130    }
4131    if {$joinlow} {
4132        set itl [lindex $lines [expr {$i-1}] 2]
4133        set al [$canv itemcget $itl -arrow]
4134        set arrowlow [expr {$al eq "last" || $al eq "both"}]
4135    } elseif {$arrowlow} {
4136        if {[lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0 ||
4137            [lsearch -exact [lindex $parentlist [expr {$row-1}]] $id] >= 0} {
4138            set arrowlow 0
4139        }
4140    }
4141    set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
4142    for {set y $le} {[incr y -1] > $row} {} {
4143        set x $xp
4144        set xp [lindex $cols [expr {$y - 1 - $row}]]
4145        set ndir [expr {$xp - $x}]
4146        if {$dir != $ndir || $xp < 0} {
4147            lappend coords [xc $y $x] [yc $y]
4148        }
4149        set dir $ndir
4150    }
4151    if {!$joinlow} {
4152        if {$xp < 0} {
4153            # join parent line to first child
4154            set ch [lindex $displayorder $row]
4155            set xc [lsearch -exact [lindex $rowidlist $row] $ch]
4156            if {$xc < 0} {
4157                puts "oops: drawlineseg: child $ch not on row $row"
4158            } elseif {$xc != $x} {
4159                if {($arrowhigh && $le == $row + 1) || $dir == 0} {
4160                    set d [expr {int(0.5 * $linespc)}]
4161                    set x1 [xc $row $x]
4162                    if {$xc < $x} {
4163                        set x2 [expr {$x1 - $d}]
4164                    } else {
4165                        set x2 [expr {$x1 + $d}]
4166                    }
4167                    set y2 [yc $row]
4168                    set y1 [expr {$y2 + $d}]
4169                    lappend coords $x1 $y1 $x2 $y2
4170                } elseif {$xc < $x - 1} {
4171                    lappend coords [xc $row [expr {$x-1}]] [yc $row]
4172                } elseif {$xc > $x + 1} {
4173                    lappend coords [xc $row [expr {$x+1}]] [yc $row]
4174                }
4175                set x $xc
4176            }
4177            lappend coords [xc $row $x] [yc $row]
4178        } else {
4179            set xn [xc $row $xp]
4180            set yn [yc $row]
4181            lappend coords $xn $yn
4182        }
4183        if {!$joinhigh} {
4184            assigncolor $id
4185            set t [$canv create line $coords -width [linewidth $id] \
4186                       -fill $colormap($id) -tags lines.$id -arrow $arrow]
4187            $canv lower $t
4188            bindline $t $id
4189            set lines [linsert $lines $i [list $row $le $t]]
4190        } else {
4191            $canv coords $ith $coords
4192            if {$arrow ne $ah} {
4193                $canv itemconf $ith -arrow $arrow
4194            }
4195            lset lines $i 0 $row
4196        }
4197    } else {
4198        set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
4199        set ndir [expr {$xo - $xp}]
4200        set clow [$canv coords $itl]
4201        if {$dir == $ndir} {
4202            set clow [lrange $clow 2 end]
4203        }
4204        set coords [concat $coords $clow]
4205        if {!$joinhigh} {
4206            lset lines [expr {$i-1}] 1 $le
4207        } else {
4208            # coalesce two pieces
4209            $canv delete $ith
4210            set b [lindex $lines [expr {$i-1}] 0]
4211            set e [lindex $lines $i 1]
4212            set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
4213        }
4214        $canv coords $itl $coords
4215        if {$arrow ne $al} {
4216            $canv itemconf $itl -arrow $arrow
4217        }
4218    }
4219
4220    set linesegs($id) $lines
4221    return $le
4222}
4223
4224proc drawparentlinks {id row} {
4225    global rowidlist canv colormap curview parentlist
4226    global idpos linespc
4227
4228    set rowids [lindex $rowidlist $row]
4229    set col [lsearch -exact $rowids $id]
4230    if {$col < 0} return
4231    set olds [lindex $parentlist $row]
4232    set row2 [expr {$row + 1}]
4233    set x [xc $row $col]
4234    set y [yc $row]
4235    set y2 [yc $row2]
4236    set d [expr {int(0.5 * $linespc)}]
4237    set ymid [expr {$y + $d}]
4238    set ids [lindex $rowidlist $row2]
4239    # rmx = right-most X coord used
4240    set rmx 0
4241    foreach p $olds {
4242        set i [lsearch -exact $ids $p]
4243        if {$i < 0} {
4244            puts "oops, parent $p of $id not in list"
4245            continue
4246        }
4247        set x2 [xc $row2 $i]
4248        if {$x2 > $rmx} {
4249            set rmx $x2
4250        }
4251        set j [lsearch -exact $rowids $p]
4252        if {$j < 0} {
4253            # drawlineseg will do this one for us
4254            continue
4255        }
4256        assigncolor $p
4257        # should handle duplicated parents here...
4258        set coords [list $x $y]
4259        if {$i != $col} {
4260            # if attaching to a vertical segment, draw a smaller
4261            # slant for visual distinctness
4262            if {$i == $j} {
4263                if {$i < $col} {
4264                    lappend coords [expr {$x2 + $d}] $y $x2 $ymid
4265                } else {
4266                    lappend coords [expr {$x2 - $d}] $y $x2 $ymid
4267                }
4268            } elseif {$i < $col && $i < $j} {
4269                # segment slants towards us already
4270                lappend coords [xc $row $j] $y
4271            } else {
4272                if {$i < $col - 1} {
4273                    lappend coords [expr {$x2 + $linespc}] $y
4274                } elseif {$i > $col + 1} {
4275                    lappend coords [expr {$x2 - $linespc}] $y
4276                }
4277                lappend coords $x2 $y2
4278            }
4279        } else {
4280            lappend coords $x2 $y2
4281        }
4282        set t [$canv create line $coords -width [linewidth $p] \
4283                   -fill $colormap($p) -tags lines.$p]
4284        $canv lower $t
4285        bindline $t $p
4286    }
4287    if {$rmx > [lindex $idpos($id) 1]} {
4288        lset idpos($id) 1 $rmx
4289        redrawtags $id
4290    }
4291}
4292
4293proc drawlines {id} {
4294    global canv
4295
4296    $canv itemconf lines.$id -width [linewidth $id]
4297}
4298
4299proc drawcmittext {id row col} {
4300    global linespc canv canv2 canv3 fgcolor curview
4301    global cmitlisted commitinfo rowidlist parentlist
4302    global rowtextx idpos idtags idheads idotherrefs
4303    global linehtag linentag linedtag selectedline
4304    global canvxmax boldrows boldnamerows fgcolor nullid nullid2
4305
4306    # listed is 0 for boundary, 1 for normal, 2 for left, 3 for right
4307    set listed $cmitlisted($curview,$id)
4308    if {$id eq $nullid} {
4309        set ofill red
4310    } elseif {$id eq $nullid2} {
4311        set ofill green
4312    } else {
4313        set ofill [expr {$listed != 0? "blue": "white"}]
4314    }
4315    set x [xc $row $col]
4316    set y [yc $row]
4317    set orad [expr {$linespc / 3}]
4318    if {$listed <= 1} {
4319        set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
4320                   [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
4321                   -fill $ofill -outline $fgcolor -width 1 -tags circle]
4322    } elseif {$listed == 2} {
4323        # triangle pointing left for left-side commits
4324        set t [$canv create polygon \
4325                   [expr {$x - $orad}] $y \
4326                   [expr {$x + $orad - 1}] [expr {$y - $orad}] \
4327                   [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
4328                   -fill $ofill -outline $fgcolor -width 1 -tags circle]
4329    } else {
4330        # triangle pointing right for right-side commits
4331        set t [$canv create polygon \
4332                   [expr {$x + $orad - 1}] $y \
4333                   [expr {$x - $orad}] [expr {$y - $orad}] \
4334                   [expr {$x - $orad}] [expr {$y + $orad - 1}] \
4335                   -fill $ofill -outline $fgcolor -width 1 -tags circle]
4336    }
4337    $canv raise $t
4338    $canv bind $t <1> {selcanvline {} %x %y}
4339    set rmx [llength [lindex $rowidlist $row]]
4340    set olds [lindex $parentlist $row]
4341    if {$olds ne {}} {
4342        set nextids [lindex $rowidlist [expr {$row + 1}]]
4343        foreach p $olds {
4344            set i [lsearch -exact $nextids $p]
4345            if {$i > $rmx} {
4346                set rmx $i
4347            }
4348        }
4349    }
4350    set xt [xc $row $rmx]
4351    set rowtextx($row) $xt
4352    set idpos($id) [list $x $xt $y]
4353    if {[info exists idtags($id)] || [info exists idheads($id)]
4354        || [info exists idotherrefs($id)]} {
4355        set xt [drawtags $id $x $xt $y]
4356    }
4357    set headline [lindex $commitinfo($id) 0]
4358    set name [lindex $commitinfo($id) 1]
4359    set date [lindex $commitinfo($id) 2]
4360    set date [formatdate $date]
4361    set font mainfont
4362    set nfont mainfont
4363    set isbold [ishighlighted $row]
4364    if {$isbold > 0} {
4365        lappend boldrows $row
4366        set font mainfontbold
4367        if {$isbold > 1} {
4368            lappend boldnamerows $row
4369            set nfont mainfontbold
4370        }
4371    }
4372    set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
4373                            -text $headline -font $font -tags text]
4374    $canv bind $linehtag($row) <Button-3> "rowmenu %X %Y $id"
4375    set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
4376                            -text $name -font $nfont -tags text]
4377    set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
4378                            -text $date -font mainfont -tags text]
4379    if {[info exists selectedline] && $selectedline == $row} {
4380        make_secsel $row
4381    }
4382    set xr [expr {$xt + [font measure $font $headline]}]
4383    if {$xr > $canvxmax} {
4384        set canvxmax $xr
4385        setcanvscroll
4386    }
4387}
4388
4389proc drawcmitrow {row} {
4390    global displayorder rowidlist nrows_drawn
4391    global iddrawn markingmatches
4392    global commitinfo numcommits
4393    global filehighlight fhighlights findpattern nhighlights
4394    global hlview vhighlights
4395    global highlight_related rhighlights
4396
4397    if {$row >= $numcommits} return
4398
4399    set id [lindex $displayorder $row]
4400    if {[info exists hlview] && ![info exists vhighlights($row)]} {
4401        askvhighlight $row $id
4402    }
4403    if {[info exists filehighlight] && ![info exists fhighlights($row)]} {
4404        askfilehighlight $row $id
4405    }
4406    if {$findpattern ne {} && ![info exists nhighlights($row)]} {
4407        askfindhighlight $row $id
4408    }
4409    if {$highlight_related ne "None" && ![info exists rhighlights($row)]} {
4410        askrelhighlight $row $id
4411    }
4412    if {![info exists iddrawn($id)]} {
4413        set col [lsearch -exact [lindex $rowidlist $row] $id]
4414        if {$col < 0} {
4415            puts "oops, row $row id $id not in list"
4416            return
4417        }
4418        if {![info exists commitinfo($id)]} {
4419            getcommit $id
4420        }
4421        assigncolor $id
4422        drawcmittext $id $row $col
4423        set iddrawn($id) 1
4424        incr nrows_drawn
4425    }
4426    if {$markingmatches} {
4427        markrowmatches $row $id
4428    }
4429}
4430
4431proc drawcommits {row {endrow {}}} {
4432    global numcommits iddrawn displayorder curview need_redisplay
4433    global parentlist rowidlist rowfinal uparrowlen downarrowlen nrows_drawn
4434
4435    if {$row < 0} {
4436        set row 0
4437    }
4438    if {$endrow eq {}} {
4439        set endrow $row
4440    }
4441    if {$endrow >= $numcommits} {
4442        set endrow [expr {$numcommits - 1}]
4443    }
4444
4445    set rl1 [expr {$row - $downarrowlen - 3}]
4446    if {$rl1 < 0} {
4447        set rl1 0
4448    }
4449    set ro1 [expr {$row - 3}]
4450    if {$ro1 < 0} {
4451        set ro1 0
4452    }
4453    set r2 [expr {$endrow + $uparrowlen + 3}]
4454    if {$r2 > $numcommits} {
4455        set r2 $numcommits
4456    }
4457    for {set r $rl1} {$r < $r2} {incr r} {
4458        if {[lindex $rowidlist $r] ne {} && [lindex $rowfinal $r]} {
4459            if {$rl1 < $r} {
4460                layoutrows $rl1 $r
4461            }
4462            set rl1 [expr {$r + 1}]
4463        }
4464    }
4465    if {$rl1 < $r} {
4466        layoutrows $rl1 $r
4467    }
4468    optimize_rows $ro1 0 $r2
4469    if {$need_redisplay || $nrows_drawn > 2000} {
4470        clear_display
4471        drawvisible
4472    }
4473
4474    # make the lines join to already-drawn rows either side
4475    set r [expr {$row - 1}]
4476    if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
4477        set r $row
4478    }
4479    set er [expr {$endrow + 1}]
4480    if {$er >= $numcommits ||
4481        ![info exists iddrawn([lindex $displayorder $er])]} {
4482        set er $endrow
4483    }
4484    for {} {$r <= $er} {incr r} {
4485        set id [lindex $displayorder $r]
4486        set wasdrawn [info exists iddrawn($id)]
4487        drawcmitrow $r
4488        if {$r == $er} break
4489        set nextid [lindex $displayorder [expr {$r + 1}]]
4490        if {$wasdrawn && [info exists iddrawn($nextid)]} continue
4491        drawparentlinks $id $r
4492
4493        set rowids [lindex $rowidlist $r]
4494        foreach lid $rowids {
4495            if {$lid eq {}} continue
4496            if {[info exists lineend($lid)] && $lineend($lid) > $r} continue
4497            if {$lid eq $id} {
4498                # see if this is the first child of any of its parents
4499                foreach p [lindex $parentlist $r] {
4500                    if {[lsearch -exact $rowids $p] < 0} {
4501                        # make this line extend up to the child
4502                        set lineend($p) [drawlineseg $p $r $er 0]
4503                    }
4504                }
4505            } else {
4506                set lineend($lid) [drawlineseg $lid $r $er 1]
4507            }
4508        }
4509    }
4510}
4511
4512proc undolayout {row} {
4513    global uparrowlen mingaplen downarrowlen
4514    global rowidlist rowisopt rowfinal need_redisplay
4515
4516    set r [expr {$row - ($uparrowlen + $mingaplen + $downarrowlen)}]
4517    if {$r < 0} {
4518        set r 0
4519    }
4520    if {[llength $rowidlist] > $r} {
4521        incr r -1
4522        set rowidlist [lrange $rowidlist 0 $r]
4523        set rowfinal [lrange $rowfinal 0 $r]
4524        set rowisopt [lrange $rowisopt 0 $r]
4525        set need_redisplay 1
4526        run drawvisible
4527    }
4528}
4529
4530proc drawfrac {f0 f1} {
4531    global canv linespc
4532
4533    set ymax [lindex [$canv cget -scrollregion] 3]
4534    if {$ymax eq {} || $ymax == 0} return
4535    set y0 [expr {int($f0 * $ymax)}]
4536    set row [expr {int(($y0 - 3) / $linespc) - 1}]
4537    set y1 [expr {int($f1 * $ymax)}]
4538    set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
4539    drawcommits $row $endrow
4540}
4541
4542proc drawvisible {} {
4543    global canv
4544    eval drawfrac [$canv yview]
4545}
4546
4547proc clear_display {} {
4548    global iddrawn linesegs need_redisplay nrows_drawn
4549    global vhighlights fhighlights nhighlights rhighlights
4550
4551    allcanvs delete all
4552    catch {unset iddrawn}
4553    catch {unset linesegs}
4554    catch {unset vhighlights}
4555    catch {unset fhighlights}
4556    catch {unset nhighlights}
4557    catch {unset rhighlights}
4558    set need_redisplay 0
4559    set nrows_drawn 0
4560}
4561
4562proc findcrossings {id} {
4563    global rowidlist parentlist numcommits displayorder
4564
4565    set cross {}
4566    set ccross {}
4567    foreach {s e} [rowranges $id] {
4568        if {$e >= $numcommits} {
4569            set e [expr {$numcommits - 1}]
4570        }
4571        if {$e <= $s} continue
4572        for {set row $e} {[incr row -1] >= $s} {} {
4573            set x [lsearch -exact [lindex $rowidlist $row] $id]
4574            if {$x < 0} break
4575            set olds [lindex $parentlist $row]
4576            set kid [lindex $displayorder $row]
4577            set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
4578            if {$kidx < 0} continue
4579            set nextrow [lindex $rowidlist [expr {$row + 1}]]
4580            foreach p $olds {
4581                set px [lsearch -exact $nextrow $p]
4582                if {$px < 0} continue
4583                if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
4584                    if {[lsearch -exact $ccross $p] >= 0} continue
4585                    if {$x == $px + ($kidx < $px? -1: 1)} {
4586                        lappend ccross $p
4587                    } elseif {[lsearch -exact $cross $p] < 0} {
4588                        lappend cross $p
4589                    }
4590                }
4591            }
4592        }
4593    }
4594    return [concat $ccross {{}} $cross]
4595}
4596
4597proc assigncolor {id} {
4598    global colormap colors nextcolor
4599    global parents children children curview
4600
4601    if {[info exists colormap($id)]} return
4602    set ncolors [llength $colors]
4603    if {[info exists children($curview,$id)]} {
4604        set kids $children($curview,$id)
4605    } else {
4606        set kids {}
4607    }
4608    if {[llength $kids] == 1} {
4609        set child [lindex $kids 0]
4610        if {[info exists colormap($child)]
4611            && [llength $parents($curview,$child)] == 1} {
4612            set colormap($id) $colormap($child)
4613            return
4614        }
4615    }
4616    set badcolors {}
4617    set origbad {}
4618    foreach x [findcrossings $id] {
4619        if {$x eq {}} {
4620            # delimiter between corner crossings and other crossings
4621            if {[llength $badcolors] >= $ncolors - 1} break
4622            set origbad $badcolors
4623        }
4624        if {[info exists colormap($x)]
4625            && [lsearch -exact $badcolors $colormap($x)] < 0} {
4626            lappend badcolors $colormap($x)
4627        }
4628    }
4629    if {[llength $badcolors] >= $ncolors} {
4630        set badcolors $origbad
4631    }
4632    set origbad $badcolors
4633    if {[llength $badcolors] < $ncolors - 1} {
4634        foreach child $kids {
4635            if {[info exists colormap($child)]
4636                && [lsearch -exact $badcolors $colormap($child)] < 0} {
4637                lappend badcolors $colormap($child)
4638            }
4639            foreach p $parents($curview,$child) {
4640                if {[info exists colormap($p)]
4641                    && [lsearch -exact $badcolors $colormap($p)] < 0} {
4642                    lappend badcolors $colormap($p)
4643                }
4644            }
4645        }
4646        if {[llength $badcolors] >= $ncolors} {
4647            set badcolors $origbad
4648        }
4649    }
4650    for {set i 0} {$i <= $ncolors} {incr i} {
4651        set c [lindex $colors $nextcolor]
4652        if {[incr nextcolor] >= $ncolors} {
4653            set nextcolor 0
4654        }
4655        if {[lsearch -exact $badcolors $c]} break
4656    }
4657    set colormap($id) $c
4658}
4659
4660proc bindline {t id} {
4661    global canv
4662
4663    $canv bind $t <Enter> "lineenter %x %y $id"
4664    $canv bind $t <Motion> "linemotion %x %y $id"
4665    $canv bind $t <Leave> "lineleave $id"
4666    $canv bind $t <Button-1> "lineclick %x %y $id 1"
4667}
4668
4669proc drawtags {id x xt y1} {
4670    global idtags idheads idotherrefs mainhead
4671    global linespc lthickness
4672    global canv rowtextx curview fgcolor bgcolor
4673
4674    set marks {}
4675    set ntags 0
4676    set nheads 0
4677    if {[info exists idtags($id)]} {
4678        set marks $idtags($id)
4679        set ntags [llength $marks]
4680    }
4681    if {[info exists idheads($id)]} {
4682        set marks [concat $marks $idheads($id)]
4683        set nheads [llength $idheads($id)]
4684    }
4685    if {[info exists idotherrefs($id)]} {
4686        set marks [concat $marks $idotherrefs($id)]
4687    }
4688    if {$marks eq {}} {
4689        return $xt
4690    }
4691
4692    set delta [expr {int(0.5 * ($linespc - $lthickness))}]
4693    set yt [expr {$y1 - 0.5 * $linespc}]
4694    set yb [expr {$yt + $linespc - 1}]
4695    set xvals {}
4696    set wvals {}
4697    set i -1
4698    foreach tag $marks {
4699        incr i
4700        if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
4701            set wid [font measure mainfontbold $tag]
4702        } else {
4703            set wid [font measure mainfont $tag]
4704        }
4705        lappend xvals $xt
4706        lappend wvals $wid
4707        set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
4708    }
4709    set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
4710               -width $lthickness -fill black -tags tag.$id]
4711    $canv lower $t
4712    foreach tag $marks x $xvals wid $wvals {
4713        set xl [expr {$x + $delta}]
4714        set xr [expr {$x + $delta + $wid + $lthickness}]
4715        set font mainfont
4716        if {[incr ntags -1] >= 0} {
4717            # draw a tag
4718            set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
4719                       $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
4720                       -width 1 -outline black -fill yellow -tags tag.$id]
4721            $canv bind $t <1> [list showtag $tag 1]
4722            set rowtextx([rowofcommit $id]) [expr {$xr + $linespc}]
4723        } else {
4724            # draw a head or other ref
4725            if {[incr nheads -1] >= 0} {
4726                set col green
4727                if {$tag eq $mainhead} {
4728                    set font mainfontbold
4729                }
4730            } else {
4731                set col "#ddddff"
4732            }
4733            set xl [expr {$xl - $delta/2}]
4734            $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
4735                -width 1 -outline black -fill $col -tags tag.$id
4736            if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
4737                set rwid [font measure mainfont $remoteprefix]
4738                set xi [expr {$x + 1}]
4739                set yti [expr {$yt + 1}]
4740                set xri [expr {$x + $rwid}]
4741                $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
4742                        -width 0 -fill "#ffddaa" -tags tag.$id
4743            }
4744        }
4745        set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
4746                   -font $font -tags [list tag.$id text]]
4747        if {$ntags >= 0} {
4748            $canv bind $t <1> [list showtag $tag 1]
4749        } elseif {$nheads >= 0} {
4750            $canv bind $t <Button-3> [list headmenu %X %Y $id $tag]
4751        }
4752    }
4753    return $xt
4754}
4755
4756proc xcoord {i level ln} {
4757    global canvx0 xspc1 xspc2
4758
4759    set x [expr {$canvx0 + $i * $xspc1($ln)}]
4760    if {$i > 0 && $i == $level} {
4761        set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
4762    } elseif {$i > $level} {
4763        set x [expr {$x + $xspc2 - $xspc1($ln)}]
4764    }
4765    return $x
4766}
4767
4768proc show_status {msg} {
4769    global canv fgcolor
4770
4771    clear_display
4772    $canv create text 3 3 -anchor nw -text $msg -font mainfont \
4773        -tags text -fill $fgcolor
4774}
4775
4776# Don't change the text pane cursor if it is currently the hand cursor,
4777# showing that we are over a sha1 ID link.
4778proc settextcursor {c} {
4779    global ctext curtextcursor
4780
4781    if {[$ctext cget -cursor] == $curtextcursor} {
4782        $ctext config -cursor $c
4783    }
4784    set curtextcursor $c
4785}
4786
4787proc nowbusy {what {name {}}} {
4788    global isbusy busyname statusw
4789
4790    if {[array names isbusy] eq {}} {
4791        . config -cursor watch
4792        settextcursor watch
4793    }
4794    set isbusy($what) 1
4795    set busyname($what) $name
4796    if {$name ne {}} {
4797        $statusw conf -text $name
4798    }
4799}
4800
4801proc notbusy {what} {
4802    global isbusy maincursor textcursor busyname statusw
4803
4804    catch {
4805        unset isbusy($what)
4806        if {$busyname($what) ne {} &&
4807            [$statusw cget -text] eq $busyname($what)} {
4808            $statusw conf -text {}
4809        }
4810    }
4811    if {[array names isbusy] eq {}} {
4812        . config -cursor $maincursor
4813        settextcursor $textcursor
4814    }
4815}
4816
4817proc findmatches {f} {
4818    global findtype findstring
4819    if {$findtype == "Regexp"} {
4820        set matches [regexp -indices -all -inline $findstring $f]
4821    } else {
4822        set fs $findstring
4823        if {$findtype == "IgnCase"} {
4824            set f [string tolower $f]
4825            set fs [string tolower $fs]
4826        }
4827        set matches {}
4828        set i 0
4829        set l [string length $fs]
4830        while {[set j [string first $fs $f $i]] >= 0} {
4831            lappend matches [list $j [expr {$j+$l-1}]]
4832            set i [expr {$j + $l}]
4833        }
4834    }
4835    return $matches
4836}
4837
4838proc dofind {{dirn 1} {wrap 1}} {
4839    global findstring findstartline findcurline selectedline numcommits
4840    global gdttype filehighlight fh_serial find_dirn findallowwrap
4841
4842    if {[info exists find_dirn]} {
4843        if {$find_dirn == $dirn} return
4844        stopfinding
4845    }
4846    focus .
4847    if {$findstring eq {} || $numcommits == 0} return
4848    if {![info exists selectedline]} {
4849        set findstartline [lindex [visiblerows] [expr {$dirn < 0}]]
4850    } else {
4851        set findstartline $selectedline
4852    }
4853    set findcurline $findstartline
4854    nowbusy finding "Searching"
4855    if {$gdttype ne "containing:" && ![info exists filehighlight]} {
4856        after cancel do_file_hl $fh_serial
4857        do_file_hl $fh_serial
4858    }
4859    set find_dirn $dirn
4860    set findallowwrap $wrap
4861    run findmore
4862}
4863
4864proc stopfinding {} {
4865    global find_dirn findcurline fprogcoord
4866
4867    if {[info exists find_dirn]} {
4868        unset find_dirn
4869        unset findcurline
4870        notbusy finding
4871        set fprogcoord 0
4872        adjustprogress
4873    }
4874}
4875
4876proc findmore {} {
4877    global commitdata commitinfo numcommits findpattern findloc
4878    global findstartline findcurline findallowwrap
4879    global find_dirn gdttype fhighlights fprogcoord
4880    global curview varcorder vrownum varccommits
4881
4882    if {![info exists find_dirn]} {
4883        return 0
4884    }
4885    set fldtypes {Headline Author Date Committer CDate Comments}
4886    set l $findcurline
4887    set moretodo 0
4888    if {$find_dirn > 0} {
4889        incr l
4890        if {$l >= $numcommits} {
4891            set l 0
4892        }
4893        if {$l <= $findstartline} {
4894            set lim [expr {$findstartline + 1}]
4895        } else {
4896            set lim $numcommits
4897            set moretodo $findallowwrap
4898        }
4899    } else {
4900        if {$l == 0} {
4901            set l $numcommits
4902        }
4903        incr l -1
4904        if {$l >= $findstartline} {
4905            set lim [expr {$findstartline - 1}]
4906        } else {
4907            set lim -1
4908            set moretodo $findallowwrap
4909        }
4910    }
4911    set n [expr {($lim - $l) * $find_dirn}]
4912    if {$n > 500} {
4913        set n 500
4914        set moretodo 1
4915    }
4916    set found 0
4917    set domore 1
4918    set ai [bsearch $vrownum($curview) $l]
4919    set a [lindex $varcorder($curview) $ai]
4920    set arow [lindex $vrownum($curview) $ai]
4921    set ids [lindex $varccommits($curview,$a)]
4922    set arowend [expr {$arow + [llength $ids]}]
4923    if {$gdttype eq "containing:"} {
4924        for {} {$n > 0} {incr n -1; incr l $find_dirn} {
4925            if {$l < $arow || $l >= $arowend} {
4926                incr ai $find_dirn
4927                set a [lindex $varcorder($curview) $ai]
4928                set arow [lindex $vrownum($curview) $ai]
4929                set ids [lindex $varccommits($curview,$a)]
4930                set arowend [expr {$arow + [llength $ids]}]
4931            }
4932            set id [lindex $ids [expr {$l - $arow}]]
4933            # shouldn't happen unless git log doesn't give all the commits...
4934            if {![info exists commitdata($id)] ||
4935                ![doesmatch $commitdata($id)]} {
4936                continue
4937            }
4938            if {![info exists commitinfo($id)]} {
4939                getcommit $id
4940            }
4941            set info $commitinfo($id)
4942            foreach f $info ty $fldtypes {
4943                if {($findloc eq "All fields" || $findloc eq $ty) &&
4944                    [doesmatch $f]} {
4945                    set found 1
4946                    break
4947                }
4948            }
4949            if {$found} break
4950        }
4951    } else {
4952        for {} {$n > 0} {incr n -1; incr l $find_dirn} {
4953            if {$l < $arow || $l >= $arowend} {
4954                incr ai $find_dirn
4955                set a [lindex $varcorder($curview) $ai]
4956                set arow [lindex $vrownum($curview) $ai]
4957                set ids [lindex $varccommits($curview,$a)]
4958                set arowend [expr {$arow + [llength $ids]}]
4959            }
4960            set id [lindex $ids [expr {$l - $arow}]]
4961            if {![info exists fhighlights($l)]} {
4962                askfilehighlight $l $id
4963                if {$domore} {
4964                    set domore 0
4965                    set findcurline [expr {$l - $find_dirn}]
4966                }
4967            } elseif {$fhighlights($l)} {
4968                set found $domore
4969                break
4970            }
4971        }
4972    }
4973    if {$found || ($domore && !$moretodo)} {
4974        unset findcurline
4975        unset find_dirn
4976        notbusy finding
4977        set fprogcoord 0
4978        adjustprogress
4979        if {$found} {
4980            findselectline $l
4981        } else {
4982            bell
4983        }
4984        return 0
4985    }
4986    if {!$domore} {
4987        flushhighlights
4988    } else {
4989        set findcurline [expr {$l - $find_dirn}]
4990    }
4991    set n [expr {($findcurline - $findstartline) * $find_dirn - 1}]
4992    if {$n < 0} {
4993        incr n $numcommits
4994    }
4995    set fprogcoord [expr {$n * 1.0 / $numcommits}]
4996    adjustprogress
4997    return $domore
4998}
4999
5000proc findselectline {l} {
5001    global findloc commentend ctext findcurline markingmatches gdttype
5002
5003    set markingmatches 1
5004    set findcurline $l
5005    selectline $l 1
5006    if {$findloc == "All fields" || $findloc == "Comments"} {
5007        # highlight the matches in the comments
5008        set f [$ctext get 1.0 $commentend]
5009        set matches [findmatches $f]
5010        foreach match $matches {
5011            set start [lindex $match 0]
5012            set end [expr {[lindex $match 1] + 1}]
5013            $ctext tag add found "1.0 + $start c" "1.0 + $end c"
5014        }
5015    }
5016    drawvisible
5017}
5018
5019# mark the bits of a headline or author that match a find string
5020proc markmatches {canv l str tag matches font row} {
5021    global selectedline
5022
5023    set bbox [$canv bbox $tag]
5024    set x0 [lindex $bbox 0]
5025    set y0 [lindex $bbox 1]
5026    set y1 [lindex $bbox 3]
5027    foreach match $matches {
5028        set start [lindex $match 0]
5029        set end [lindex $match 1]
5030        if {$start > $end} continue
5031        set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
5032        set xlen [font measure $font [string range $str 0 [expr {$end}]]]
5033        set t [$canv create rect [expr {$x0+$xoff}] $y0 \
5034                   [expr {$x0+$xlen+2}] $y1 \
5035                   -outline {} -tags [list match$l matches] -fill yellow]
5036        $canv lower $t
5037        if {[info exists selectedline] && $row == $selectedline} {
5038            $canv raise $t secsel
5039        }
5040    }
5041}
5042
5043proc unmarkmatches {} {
5044    global markingmatches
5045
5046    allcanvs delete matches
5047    set markingmatches 0
5048    stopfinding
5049}
5050
5051proc selcanvline {w x y} {
5052    global canv canvy0 ctext linespc
5053    global rowtextx
5054    set ymax [lindex [$canv cget -scrollregion] 3]
5055    if {$ymax == {}} return
5056    set yfrac [lindex [$canv yview] 0]
5057    set y [expr {$y + $yfrac * $ymax}]
5058    set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
5059    if {$l < 0} {
5060        set l 0
5061    }
5062    if {$w eq $canv} {
5063        if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
5064    }
5065    unmarkmatches
5066    selectline $l 1
5067}
5068
5069proc commit_descriptor {p} {
5070    global commitinfo
5071    if {![info exists commitinfo($p)]} {
5072        getcommit $p
5073    }
5074    set l "..."
5075    if {[llength $commitinfo($p)] > 1} {
5076        set l [lindex $commitinfo($p) 0]
5077    }
5078    return "$p ($l)\n"
5079}
5080
5081# append some text to the ctext widget, and make any SHA1 ID
5082# that we know about be a clickable link.
5083proc appendwithlinks {text tags} {
5084    global ctext linknum curview pendinglinks
5085
5086    set start [$ctext index "end - 1c"]
5087    $ctext insert end $text $tags
5088    set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
5089    foreach l $links {
5090        set s [lindex $l 0]
5091        set e [lindex $l 1]
5092        set linkid [string range $text $s $e]
5093        incr e
5094        $ctext tag delete link$linknum
5095        $ctext tag add link$linknum "$start + $s c" "$start + $e c"
5096        setlink $linkid link$linknum
5097        incr linknum
5098    }
5099}
5100
5101proc setlink {id lk} {
5102    global curview ctext pendinglinks commitinterest
5103
5104    if {[commitinview $id $curview]} {
5105        $ctext tag conf $lk -foreground blue -underline 1
5106        $ctext tag bind $lk <1> [list selectline [rowofcommit $id] 1]
5107        $ctext tag bind $lk <Enter> {linkcursor %W 1}
5108        $ctext tag bind $lk <Leave> {linkcursor %W -1}
5109    } else {
5110        lappend pendinglinks($id) $lk
5111        lappend commitinterest($id) {makelink %I}
5112    }
5113}
5114
5115proc makelink {id} {
5116    global pendinglinks
5117
5118    if {![info exists pendinglinks($id)]} return
5119    foreach lk $pendinglinks($id) {
5120        setlink $id $lk
5121    }
5122    unset pendinglinks($id)
5123}
5124
5125proc linkcursor {w inc} {
5126    global linkentercount curtextcursor
5127
5128    if {[incr linkentercount $inc] > 0} {
5129        $w configure -cursor hand2
5130    } else {
5131        $w configure -cursor $curtextcursor
5132        if {$linkentercount < 0} {
5133            set linkentercount 0
5134        }
5135    }
5136}
5137
5138proc viewnextline {dir} {
5139    global canv linespc
5140
5141    $canv delete hover
5142    set ymax [lindex [$canv cget -scrollregion] 3]
5143    set wnow [$canv yview]
5144    set wtop [expr {[lindex $wnow 0] * $ymax}]
5145    set newtop [expr {$wtop + $dir * $linespc}]
5146    if {$newtop < 0} {
5147        set newtop 0
5148    } elseif {$newtop > $ymax} {
5149        set newtop $ymax
5150    }
5151    allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
5152}
5153
5154# add a list of tag or branch names at position pos
5155# returns the number of names inserted
5156proc appendrefs {pos ids var} {
5157    global ctext linknum curview $var maxrefs
5158
5159    if {[catch {$ctext index $pos}]} {
5160        return 0
5161    }
5162    $ctext conf -state normal
5163    $ctext delete $pos "$pos lineend"
5164    set tags {}
5165    foreach id $ids {
5166        foreach tag [set $var\($id\)] {
5167            lappend tags [list $tag $id]
5168        }
5169    }
5170    if {[llength $tags] > $maxrefs} {
5171        $ctext insert $pos "many ([llength $tags])"
5172    } else {
5173        set tags [lsort -index 0 -decreasing $tags]
5174        set sep {}
5175        foreach ti $tags {
5176            set id [lindex $ti 1]
5177            set lk link$linknum
5178            incr linknum
5179            $ctext tag delete $lk
5180            $ctext insert $pos $sep
5181            $ctext insert $pos [lindex $ti 0] $lk
5182            setlink $id $lk
5183            set sep ", "
5184        }
5185    }
5186    $ctext conf -state disabled
5187    return [llength $tags]
5188}
5189
5190# called when we have finished computing the nearby tags
5191proc dispneartags {delay} {
5192    global selectedline currentid showneartags tagphase
5193
5194    if {![info exists selectedline] || !$showneartags} return
5195    after cancel dispnexttag
5196    if {$delay} {
5197        after 200 dispnexttag
5198        set tagphase -1
5199    } else {
5200        after idle dispnexttag
5201        set tagphase 0
5202    }
5203}
5204
5205proc dispnexttag {} {
5206    global selectedline currentid showneartags tagphase ctext
5207
5208    if {![info exists selectedline] || !$showneartags} return
5209    switch -- $tagphase {
5210        0 {
5211            set dtags [desctags $currentid]
5212            if {$dtags ne {}} {
5213                appendrefs precedes $dtags idtags
5214            }
5215        }
5216        1 {
5217            set atags [anctags $currentid]
5218            if {$atags ne {}} {
5219                appendrefs follows $atags idtags
5220            }
5221        }
5222        2 {
5223            set dheads [descheads $currentid]
5224            if {$dheads ne {}} {
5225                if {[appendrefs branch $dheads idheads] > 1
5226                    && [$ctext get "branch -3c"] eq "h"} {
5227                    # turn "Branch" into "Branches"
5228                    $ctext conf -state normal
5229                    $ctext insert "branch -2c" "es"
5230                    $ctext conf -state disabled
5231                }
5232            }
5233        }
5234    }
5235    if {[incr tagphase] <= 2} {
5236        after idle dispnexttag
5237    }
5238}
5239
5240proc make_secsel {l} {
5241    global linehtag linentag linedtag canv canv2 canv3
5242
5243    if {![info exists linehtag($l)]} return
5244    $canv delete secsel
5245    set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
5246               -tags secsel -fill [$canv cget -selectbackground]]
5247    $canv lower $t
5248    $canv2 delete secsel
5249    set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
5250               -tags secsel -fill [$canv2 cget -selectbackground]]
5251    $canv2 lower $t
5252    $canv3 delete secsel
5253    set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
5254               -tags secsel -fill [$canv3 cget -selectbackground]]
5255    $canv3 lower $t
5256}
5257
5258proc selectline {l isnew} {
5259    global canv ctext commitinfo selectedline
5260    global canvy0 linespc parents children curview
5261    global currentid sha1entry
5262    global commentend idtags linknum
5263    global mergemax numcommits pending_select
5264    global cmitmode showneartags allcommits
5265
5266    catch {unset pending_select}
5267    $canv delete hover
5268    normalline
5269    unsel_reflist
5270    stopfinding
5271    if {$l < 0 || $l >= $numcommits} return
5272    set y [expr {$canvy0 + $l * $linespc}]
5273    set ymax [lindex [$canv cget -scrollregion] 3]
5274    set ytop [expr {$y - $linespc - 1}]
5275    set ybot [expr {$y + $linespc + 1}]
5276    set wnow [$canv yview]
5277    set wtop [expr {[lindex $wnow 0] * $ymax}]
5278    set wbot [expr {[lindex $wnow 1] * $ymax}]
5279    set wh [expr {$wbot - $wtop}]
5280    set newtop $wtop
5281    if {$ytop < $wtop} {
5282        if {$ybot < $wtop} {
5283            set newtop [expr {$y - $wh / 2.0}]
5284        } else {
5285            set newtop $ytop
5286            if {$newtop > $wtop - $linespc} {
5287                set newtop [expr {$wtop - $linespc}]
5288            }
5289        }
5290    } elseif {$ybot > $wbot} {
5291        if {$ytop > $wbot} {
5292            set newtop [expr {$y - $wh / 2.0}]
5293        } else {
5294            set newtop [expr {$ybot - $wh}]
5295            if {$newtop < $wtop + $linespc} {
5296                set newtop [expr {$wtop + $linespc}]
5297            }
5298        }
5299    }
5300    if {$newtop != $wtop} {
5301        if {$newtop < 0} {
5302            set newtop 0
5303        }
5304        allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
5305        drawvisible
5306    }
5307
5308    make_secsel $l
5309
5310    if {$isnew} {
5311        addtohistory [list selectline $l 0]
5312    }
5313
5314    set selectedline $l
5315
5316    set id [commitonrow $l]
5317    set currentid $id
5318    $sha1entry delete 0 end
5319    $sha1entry insert 0 $id
5320    $sha1entry selection from 0
5321    $sha1entry selection to end
5322    rhighlight_sel $id
5323
5324    $ctext conf -state normal
5325    clear_ctext
5326    set linknum 0
5327    set info $commitinfo($id)
5328    set date [formatdate [lindex $info 2]]
5329    $ctext insert end "Author: [lindex $info 1]  $date\n"
5330    set date [formatdate [lindex $info 4]]
5331    $ctext insert end "Committer: [lindex $info 3]  $date\n"
5332    if {[info exists idtags($id)]} {
5333        $ctext insert end "Tags:"
5334        foreach tag $idtags($id) {
5335            $ctext insert end " $tag"
5336        }
5337        $ctext insert end "\n"
5338    }
5339
5340    set headers {}
5341    set olds $parents($curview,$id)
5342    if {[llength $olds] > 1} {
5343        set np 0
5344        foreach p $olds {
5345            if {$np >= $mergemax} {
5346                set tag mmax
5347            } else {
5348                set tag m$np
5349            }
5350            $ctext insert end "Parent: " $tag
5351            appendwithlinks [commit_descriptor $p] {}
5352            incr np
5353        }
5354    } else {
5355        foreach p $olds {
5356            append headers "Parent: [commit_descriptor $p]"
5357        }
5358    }
5359
5360    foreach c $children($curview,$id) {
5361        append headers "Child:  [commit_descriptor $c]"
5362    }
5363
5364    # make anything that looks like a SHA1 ID be a clickable link
5365    appendwithlinks $headers {}
5366    if {$showneartags} {
5367        if {![info exists allcommits]} {
5368            getallcommits
5369        }
5370        $ctext insert end "Branch: "
5371        $ctext mark set branch "end -1c"
5372        $ctext mark gravity branch left
5373        $ctext insert end "\nFollows: "
5374        $ctext mark set follows "end -1c"
5375        $ctext mark gravity follows left
5376        $ctext insert end "\nPrecedes: "
5377        $ctext mark set precedes "end -1c"
5378        $ctext mark gravity precedes left
5379        $ctext insert end "\n"
5380        dispneartags 1
5381    }
5382    $ctext insert end "\n"
5383    set comment [lindex $info 5]
5384    if {[string first "\r" $comment] >= 0} {
5385        set comment [string map {"\r" "\n    "} $comment]
5386    }
5387    appendwithlinks $comment {comment}
5388
5389    $ctext tag remove found 1.0 end
5390    $ctext conf -state disabled
5391    set commentend [$ctext index "end - 1c"]
5392
5393    init_flist "Comments"
5394    if {$cmitmode eq "tree"} {
5395        gettree $id
5396    } elseif {[llength $olds] <= 1} {
5397        startdiff $id
5398    } else {
5399        mergediff $id
5400    }
5401}
5402
5403proc selfirstline {} {
5404    unmarkmatches
5405    selectline 0 1
5406}
5407
5408proc sellastline {} {
5409    global numcommits
5410    unmarkmatches
5411    set l [expr {$numcommits - 1}]
5412    selectline $l 1
5413}
5414
5415proc selnextline {dir} {
5416    global selectedline
5417    focus .
5418    if {![info exists selectedline]} return
5419    set l [expr {$selectedline + $dir}]
5420    unmarkmatches
5421    selectline $l 1
5422}
5423
5424proc selnextpage {dir} {
5425    global canv linespc selectedline numcommits
5426
5427    set lpp [expr {([winfo height $canv] - 2) / $linespc}]
5428    if {$lpp < 1} {
5429        set lpp 1
5430    }
5431    allcanvs yview scroll [expr {$dir * $lpp}] units
5432    drawvisible
5433    if {![info exists selectedline]} return
5434    set l [expr {$selectedline + $dir * $lpp}]
5435    if {$l < 0} {
5436        set l 0
5437    } elseif {$l >= $numcommits} {
5438        set l [expr $numcommits - 1]
5439    }
5440    unmarkmatches
5441    selectline $l 1
5442}
5443
5444proc unselectline {} {
5445    global selectedline currentid
5446
5447    catch {unset selectedline}
5448    catch {unset currentid}
5449    allcanvs delete secsel
5450    rhighlight_none
5451}
5452
5453proc reselectline {} {
5454    global selectedline
5455
5456    if {[info exists selectedline]} {
5457        selectline $selectedline 0
5458    }
5459}
5460
5461proc addtohistory {cmd} {
5462    global history historyindex curview
5463
5464    set elt [list $curview $cmd]
5465    if {$historyindex > 0
5466        && [lindex $history [expr {$historyindex - 1}]] == $elt} {
5467        return
5468    }
5469
5470    if {$historyindex < [llength $history]} {
5471        set history [lreplace $history $historyindex end $elt]
5472    } else {
5473        lappend history $elt
5474    }
5475    incr historyindex
5476    if {$historyindex > 1} {
5477        .tf.bar.leftbut conf -state normal
5478    } else {
5479        .tf.bar.leftbut conf -state disabled
5480    }
5481    .tf.bar.rightbut conf -state disabled
5482}
5483
5484proc godo {elt} {
5485    global curview
5486
5487    set view [lindex $elt 0]
5488    set cmd [lindex $elt 1]
5489    if {$curview != $view} {
5490        showview $view
5491    }
5492    eval $cmd
5493}
5494
5495proc goback {} {
5496    global history historyindex
5497    focus .
5498
5499    if {$historyindex > 1} {
5500        incr historyindex -1
5501        godo [lindex $history [expr {$historyindex - 1}]]
5502        .tf.bar.rightbut conf -state normal
5503    }
5504    if {$historyindex <= 1} {
5505        .tf.bar.leftbut conf -state disabled
5506    }
5507}
5508
5509proc goforw {} {
5510    global history historyindex
5511    focus .
5512
5513    if {$historyindex < [llength $history]} {
5514        set cmd [lindex $history $historyindex]
5515        incr historyindex
5516        godo $cmd
5517        .tf.bar.leftbut conf -state normal
5518    }
5519    if {$historyindex >= [llength $history]} {
5520        .tf.bar.rightbut conf -state disabled
5521    }
5522}
5523
5524proc gettree {id} {
5525    global treefilelist treeidlist diffids diffmergeid treepending
5526    global nullid nullid2
5527
5528    set diffids $id
5529    catch {unset diffmergeid}
5530    if {![info exists treefilelist($id)]} {
5531        if {![info exists treepending]} {
5532            if {$id eq $nullid} {
5533                set cmd [list | git ls-files]
5534            } elseif {$id eq $nullid2} {
5535                set cmd [list | git ls-files --stage -t]
5536            } else {
5537                set cmd [list | git ls-tree -r $id]
5538            }
5539            if {[catch {set gtf [open $cmd r]}]} {
5540                return
5541            }
5542            set treepending $id
5543            set treefilelist($id) {}
5544            set treeidlist($id) {}
5545            fconfigure $gtf -blocking 0
5546            filerun $gtf [list gettreeline $gtf $id]
5547        }
5548    } else {
5549        setfilelist $id
5550    }
5551}
5552
5553proc gettreeline {gtf id} {
5554    global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
5555
5556    set nl 0
5557    while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
5558        if {$diffids eq $nullid} {
5559            set fname $line
5560        } else {
5561            if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
5562            set i [string first "\t" $line]
5563            if {$i < 0} continue
5564            set sha1 [lindex $line 2]
5565            set fname [string range $line [expr {$i+1}] end]
5566            if {[string index $fname 0] eq "\""} {
5567                set fname [lindex $fname 0]
5568            }
5569            lappend treeidlist($id) $sha1
5570        }
5571        lappend treefilelist($id) $fname
5572    }
5573    if {![eof $gtf]} {
5574        return [expr {$nl >= 1000? 2: 1}]
5575    }
5576    close $gtf
5577    unset treepending
5578    if {$cmitmode ne "tree"} {
5579        if {![info exists diffmergeid]} {
5580            gettreediffs $diffids
5581        }
5582    } elseif {$id ne $diffids} {
5583        gettree $diffids
5584    } else {
5585        setfilelist $id
5586    }
5587    return 0
5588}
5589
5590proc showfile {f} {
5591    global treefilelist treeidlist diffids nullid nullid2
5592    global ctext commentend
5593
5594    set i [lsearch -exact $treefilelist($diffids) $f]
5595    if {$i < 0} {
5596        puts "oops, $f not in list for id $diffids"
5597        return
5598    }
5599    if {$diffids eq $nullid} {
5600        if {[catch {set bf [open $f r]} err]} {
5601            puts "oops, can't read $f: $err"
5602            return
5603        }
5604    } else {
5605        set blob [lindex $treeidlist($diffids) $i]
5606        if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
5607            puts "oops, error reading blob $blob: $err"
5608            return
5609        }
5610    }
5611    fconfigure $bf -blocking 0
5612    filerun $bf [list getblobline $bf $diffids]
5613    $ctext config -state normal
5614    clear_ctext $commentend
5615    $ctext insert end "\n"
5616    $ctext insert end "$f\n" filesep
5617    $ctext config -state disabled
5618    $ctext yview $commentend
5619    settabs 0
5620}
5621
5622proc getblobline {bf id} {
5623    global diffids cmitmode ctext
5624
5625    if {$id ne $diffids || $cmitmode ne "tree"} {
5626        catch {close $bf}
5627        return 0
5628    }
5629    $ctext config -state normal
5630    set nl 0
5631    while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
5632        $ctext insert end "$line\n"
5633    }
5634    if {[eof $bf]} {
5635        # delete last newline
5636        $ctext delete "end - 2c" "end - 1c"
5637        close $bf
5638        return 0
5639    }
5640    $ctext config -state disabled
5641    return [expr {$nl >= 1000? 2: 1}]
5642}
5643
5644proc mergediff {id} {
5645    global diffmergeid mdifffd
5646    global diffids
5647    global parents
5648    global limitdiffs viewfiles curview
5649
5650    set diffmergeid $id
5651    set diffids $id
5652    # this doesn't seem to actually affect anything...
5653    set cmd [concat | git diff-tree --no-commit-id --cc $id]
5654    if {$limitdiffs && $viewfiles($curview) ne {}} {
5655        set cmd [concat $cmd -- $viewfiles($curview)]
5656    }
5657    if {[catch {set mdf [open $cmd r]} err]} {
5658        error_popup "Error getting merge diffs: $err"
5659        return
5660    }
5661    fconfigure $mdf -blocking 0
5662    set mdifffd($id) $mdf
5663    set np [llength $parents($curview,$id)]
5664    settabs $np
5665    filerun $mdf [list getmergediffline $mdf $id $np]
5666}
5667
5668proc getmergediffline {mdf id np} {
5669    global diffmergeid ctext cflist mergemax
5670    global difffilestart mdifffd
5671
5672    $ctext conf -state normal
5673    set nr 0
5674    while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
5675        if {![info exists diffmergeid] || $id != $diffmergeid
5676            || $mdf != $mdifffd($id)} {
5677            close $mdf
5678            return 0
5679        }
5680        if {[regexp {^diff --cc (.*)} $line match fname]} {
5681            # start of a new file
5682            $ctext insert end "\n"
5683            set here [$ctext index "end - 1c"]
5684            lappend difffilestart $here
5685            add_flist [list $fname]
5686            set l [expr {(78 - [string length $fname]) / 2}]
5687            set pad [string range "----------------------------------------" 1 $l]
5688            $ctext insert end "$pad $fname $pad\n" filesep
5689        } elseif {[regexp {^@@} $line]} {
5690            $ctext insert end "$line\n" hunksep
5691        } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
5692            # do nothing
5693        } else {
5694            # parse the prefix - one ' ', '-' or '+' for each parent
5695            set spaces {}
5696            set minuses {}
5697            set pluses {}
5698            set isbad 0
5699            for {set j 0} {$j < $np} {incr j} {
5700                set c [string range $line $j $j]
5701                if {$c == " "} {
5702                    lappend spaces $j
5703                } elseif {$c == "-"} {
5704                    lappend minuses $j
5705                } elseif {$c == "+"} {
5706                    lappend pluses $j
5707                } else {
5708                    set isbad 1
5709                    break
5710                }
5711            }
5712            set tags {}
5713            set num {}
5714            if {!$isbad && $minuses ne {} && $pluses eq {}} {
5715                # line doesn't appear in result, parents in $minuses have the line
5716                set num [lindex $minuses 0]
5717            } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
5718                # line appears in result, parents in $pluses don't have the line
5719                lappend tags mresult
5720                set num [lindex $spaces 0]
5721            }
5722            if {$num ne {}} {
5723                if {$num >= $mergemax} {
5724                    set num "max"
5725                }
5726                lappend tags m$num
5727            }
5728            $ctext insert end "$line\n" $tags
5729        }
5730    }
5731    $ctext conf -state disabled
5732    if {[eof $mdf]} {
5733        close $mdf
5734        return 0
5735    }
5736    return [expr {$nr >= 1000? 2: 1}]
5737}
5738
5739proc startdiff {ids} {
5740    global treediffs diffids treepending diffmergeid nullid nullid2
5741
5742    settabs 1
5743    set diffids $ids
5744    catch {unset diffmergeid}
5745    if {![info exists treediffs($ids)] ||
5746        [lsearch -exact $ids $nullid] >= 0 ||
5747        [lsearch -exact $ids $nullid2] >= 0} {
5748        if {![info exists treepending]} {
5749            gettreediffs $ids
5750        }
5751    } else {
5752        addtocflist $ids
5753    }
5754}
5755
5756proc path_filter {filter name} {
5757    foreach p $filter {
5758        set l [string length $p]
5759        if {[string index $p end] eq "/"} {
5760            if {[string compare -length $l $p $name] == 0} {
5761                return 1
5762            }
5763        } else {
5764            if {[string compare -length $l $p $name] == 0 &&
5765                ([string length $name] == $l ||
5766                 [string index $name $l] eq "/")} {
5767                return 1
5768            }
5769        }
5770    }
5771    return 0
5772}
5773
5774proc addtocflist {ids} {
5775    global treediffs
5776
5777    add_flist $treediffs($ids)
5778    getblobdiffs $ids
5779}
5780
5781proc diffcmd {ids flags} {
5782    global nullid nullid2
5783
5784    set i [lsearch -exact $ids $nullid]
5785    set j [lsearch -exact $ids $nullid2]
5786    if {$i >= 0} {
5787        if {[llength $ids] > 1 && $j < 0} {
5788            # comparing working directory with some specific revision
5789            set cmd [concat | git diff-index $flags]
5790            if {$i == 0} {
5791                lappend cmd -R [lindex $ids 1]
5792            } else {
5793                lappend cmd [lindex $ids 0]
5794            }
5795        } else {
5796            # comparing working directory with index
5797            set cmd [concat | git diff-files $flags]
5798            if {$j == 1} {
5799                lappend cmd -R
5800            }
5801        }
5802    } elseif {$j >= 0} {
5803        set cmd [concat | git diff-index --cached $flags]
5804        if {[llength $ids] > 1} {
5805            # comparing index with specific revision
5806            if {$i == 0} {
5807                lappend cmd -R [lindex $ids 1]
5808            } else {
5809                lappend cmd [lindex $ids 0]
5810            }
5811        } else {
5812            # comparing index with HEAD
5813            lappend cmd HEAD
5814        }
5815    } else {
5816        set cmd [concat | git diff-tree -r $flags $ids]
5817    }
5818    return $cmd
5819}
5820
5821proc gettreediffs {ids} {
5822    global treediff treepending
5823
5824    set treepending $ids
5825    set treediff {}
5826    if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
5827    fconfigure $gdtf -blocking 0
5828    filerun $gdtf [list gettreediffline $gdtf $ids]
5829}
5830
5831proc gettreediffline {gdtf ids} {
5832    global treediff treediffs treepending diffids diffmergeid
5833    global cmitmode viewfiles curview limitdiffs
5834
5835    set nr 0
5836    while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} {
5837        set i [string first "\t" $line]
5838        if {$i >= 0} {
5839            set file [string range $line [expr {$i+1}] end]
5840            if {[string index $file 0] eq "\""} {
5841                set file [lindex $file 0]
5842            }
5843            lappend treediff $file
5844        }
5845    }
5846    if {![eof $gdtf]} {
5847        return [expr {$nr >= 1000? 2: 1}]
5848    }
5849    close $gdtf
5850    if {$limitdiffs && $viewfiles($curview) ne {}} {
5851        set flist {}
5852        foreach f $treediff {
5853            if {[path_filter $viewfiles($curview) $f]} {
5854                lappend flist $f
5855            }
5856        }
5857        set treediffs($ids) $flist
5858    } else {
5859        set treediffs($ids) $treediff
5860    }
5861    unset treepending
5862    if {$cmitmode eq "tree"} {
5863        gettree $diffids
5864    } elseif {$ids != $diffids} {
5865        if {![info exists diffmergeid]} {
5866            gettreediffs $diffids
5867        }
5868    } else {
5869        addtocflist $ids
5870    }
5871    return 0
5872}
5873
5874# empty string or positive integer
5875proc diffcontextvalidate {v} {
5876    return [regexp {^(|[1-9][0-9]*)$} $v]
5877}
5878
5879proc diffcontextchange {n1 n2 op} {
5880    global diffcontextstring diffcontext
5881
5882    if {[string is integer -strict $diffcontextstring]} {
5883        if {$diffcontextstring > 0} {
5884            set diffcontext $diffcontextstring
5885            reselectline
5886        }
5887    }
5888}
5889
5890proc getblobdiffs {ids} {
5891    global blobdifffd diffids env
5892    global diffinhdr treediffs
5893    global diffcontext
5894    global limitdiffs viewfiles curview
5895
5896    set cmd [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"]
5897    if {$limitdiffs && $viewfiles($curview) ne {}} {
5898        set cmd [concat $cmd -- $viewfiles($curview)]
5899    }
5900    if {[catch {set bdf [open $cmd r]} err]} {
5901        puts "error getting diffs: $err"
5902        return
5903    }
5904    set diffinhdr 0
5905    fconfigure $bdf -blocking 0
5906    set blobdifffd($ids) $bdf
5907    filerun $bdf [list getblobdiffline $bdf $diffids]
5908}
5909
5910proc setinlist {var i val} {
5911    global $var
5912
5913    while {[llength [set $var]] < $i} {
5914        lappend $var {}
5915    }
5916    if {[llength [set $var]] == $i} {
5917        lappend $var $val
5918    } else {
5919        lset $var $i $val
5920    }
5921}
5922
5923proc makediffhdr {fname ids} {
5924    global ctext curdiffstart treediffs
5925
5926    set i [lsearch -exact $treediffs($ids) $fname]
5927    if {$i >= 0} {
5928        setinlist difffilestart $i $curdiffstart
5929    }
5930    set l [expr {(78 - [string length $fname]) / 2}]
5931    set pad [string range "----------------------------------------" 1 $l]
5932    $ctext insert $curdiffstart "$pad $fname $pad" filesep
5933}
5934
5935proc getblobdiffline {bdf ids} {
5936    global diffids blobdifffd ctext curdiffstart
5937    global diffnexthead diffnextnote difffilestart
5938    global diffinhdr treediffs
5939
5940    set nr 0
5941    $ctext conf -state normal
5942    while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
5943        if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
5944            close $bdf
5945            return 0
5946        }
5947        if {![string compare -length 11 "diff --git " $line]} {
5948            # trim off "diff --git "
5949            set line [string range $line 11 end]
5950            set diffinhdr 1
5951            # start of a new file
5952            $ctext insert end "\n"
5953            set curdiffstart [$ctext index "end - 1c"]
5954            $ctext insert end "\n" filesep
5955            # If the name hasn't changed the length will be odd,
5956            # the middle char will be a space, and the two bits either
5957            # side will be a/name and b/name, or "a/name" and "b/name".
5958            # If the name has changed we'll get "rename from" and
5959            # "rename to" or "copy from" and "copy to" lines following this,
5960            # and we'll use them to get the filenames.
5961            # This complexity is necessary because spaces in the filename(s)
5962            # don't get escaped.
5963            set l [string length $line]
5964            set i [expr {$l / 2}]
5965            if {!(($l & 1) && [string index $line $i] eq " " &&
5966                  [string range $line 2 [expr {$i - 1}]] eq \
5967                      [string range $line [expr {$i + 3}] end])} {
5968                continue
5969            }
5970            # unescape if quoted and chop off the a/ from the front
5971            if {[string index $line 0] eq "\""} {
5972                set fname [string range [lindex $line 0] 2 end]
5973            } else {
5974                set fname [string range $line 2 [expr {$i - 1}]]
5975            }
5976            makediffhdr $fname $ids
5977
5978        } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \
5979                       $line match f1l f1c f2l f2c rest]} {
5980            $ctext insert end "$line\n" hunksep
5981            set diffinhdr 0
5982
5983        } elseif {$diffinhdr} {
5984            if {![string compare -length 12 "rename from " $line]} {
5985                set fname [string range $line [expr 6 + [string first " from " $line] ] end]
5986                if {[string index $fname 0] eq "\""} {
5987                    set fname [lindex $fname 0]
5988                }
5989                set i [lsearch -exact $treediffs($ids) $fname]
5990                if {$i >= 0} {
5991                    setinlist difffilestart $i $curdiffstart
5992                }
5993            } elseif {![string compare -length 10 $line "rename to "] ||
5994                      ![string compare -length 8 $line "copy to "]} {
5995                set fname [string range $line [expr 4 + [string first " to " $line] ] end]
5996                if {[string index $fname 0] eq "\""} {
5997                    set fname [lindex $fname 0]
5998                }
5999                makediffhdr $fname $ids
6000            } elseif {[string compare -length 3 $line "---"] == 0} {
6001                # do nothing
6002                continue
6003            } elseif {[string compare -length 3 $line "+++"] == 0} {
6004                set diffinhdr 0
6005                continue
6006            }
6007            $ctext insert end "$line\n" filesep
6008
6009        } else {
6010            set x [string range $line 0 0]
6011            if {$x == "-" || $x == "+"} {
6012                set tag [expr {$x == "+"}]
6013                $ctext insert end "$line\n" d$tag
6014            } elseif {$x == " "} {
6015                $ctext insert end "$line\n"
6016            } else {
6017                # "\ No newline at end of file",
6018                # or something else we don't recognize
6019                $ctext insert end "$line\n" hunksep
6020            }
6021        }
6022    }
6023    $ctext conf -state disabled
6024    if {[eof $bdf]} {
6025        close $bdf
6026        return 0
6027    }
6028    return [expr {$nr >= 1000? 2: 1}]
6029}
6030
6031proc changediffdisp {} {
6032    global ctext diffelide
6033
6034    $ctext tag conf d0 -elide [lindex $diffelide 0]
6035    $ctext tag conf d1 -elide [lindex $diffelide 1]
6036}
6037
6038proc prevfile {} {
6039    global difffilestart ctext
6040    set prev [lindex $difffilestart 0]
6041    set here [$ctext index @0,0]
6042    foreach loc $difffilestart {
6043        if {[$ctext compare $loc >= $here]} {
6044            $ctext yview $prev
6045            return
6046        }
6047        set prev $loc
6048    }
6049    $ctext yview $prev
6050}
6051
6052proc nextfile {} {
6053    global difffilestart ctext
6054    set here [$ctext index @0,0]
6055    foreach loc $difffilestart {
6056        if {[$ctext compare $loc > $here]} {
6057            $ctext yview $loc
6058            return
6059        }
6060    }
6061}
6062
6063proc clear_ctext {{first 1.0}} {
6064    global ctext smarktop smarkbot
6065    global pendinglinks
6066
6067    set l [lindex [split $first .] 0]
6068    if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
6069        set smarktop $l
6070    }
6071    if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
6072        set smarkbot $l
6073    }
6074    $ctext delete $first end
6075    if {$first eq "1.0"} {
6076        catch {unset pendinglinks}
6077    }
6078}
6079
6080proc settabs {{firstab {}}} {
6081    global firsttabstop tabstop ctext have_tk85
6082
6083    if {$firstab ne {} && $have_tk85} {
6084        set firsttabstop $firstab
6085    }
6086    set w [font measure textfont "0"]
6087    if {$firsttabstop != 0} {
6088        $ctext conf -tabs [list [expr {($firsttabstop + $tabstop) * $w}] \
6089                               [expr {($firsttabstop + 2 * $tabstop) * $w}]]
6090    } elseif {$have_tk85 || $tabstop != 8} {
6091        $ctext conf -tabs [expr {$tabstop * $w}]
6092    } else {
6093        $ctext conf -tabs {}
6094    }
6095}
6096
6097proc incrsearch {name ix op} {
6098    global ctext searchstring searchdirn
6099
6100    $ctext tag remove found 1.0 end
6101    if {[catch {$ctext index anchor}]} {
6102        # no anchor set, use start of selection, or of visible area
6103        set sel [$ctext tag ranges sel]
6104        if {$sel ne {}} {
6105            $ctext mark set anchor [lindex $sel 0]
6106        } elseif {$searchdirn eq "-forwards"} {
6107            $ctext mark set anchor @0,0
6108        } else {
6109            $ctext mark set anchor @0,[winfo height $ctext]
6110        }
6111    }
6112    if {$searchstring ne {}} {
6113        set here [$ctext search $searchdirn -- $searchstring anchor]
6114        if {$here ne {}} {
6115            $ctext see $here
6116        }
6117        searchmarkvisible 1
6118    }
6119}
6120
6121proc dosearch {} {
6122    global sstring ctext searchstring searchdirn
6123
6124    focus $sstring
6125    $sstring icursor end
6126    set searchdirn -forwards
6127    if {$searchstring ne {}} {
6128        set sel [$ctext tag ranges sel]
6129        if {$sel ne {}} {
6130            set start "[lindex $sel 0] + 1c"
6131        } elseif {[catch {set start [$ctext index anchor]}]} {
6132            set start "@0,0"
6133        }
6134        set match [$ctext search -count mlen -- $searchstring $start]
6135        $ctext tag remove sel 1.0 end
6136        if {$match eq {}} {
6137            bell
6138            return
6139        }
6140        $ctext see $match
6141        set mend "$match + $mlen c"
6142        $ctext tag add sel $match $mend
6143        $ctext mark unset anchor
6144    }
6145}
6146
6147proc dosearchback {} {
6148    global sstring ctext searchstring searchdirn
6149
6150    focus $sstring
6151    $sstring icursor end
6152    set searchdirn -backwards
6153    if {$searchstring ne {}} {
6154        set sel [$ctext tag ranges sel]
6155        if {$sel ne {}} {
6156            set start [lindex $sel 0]
6157        } elseif {[catch {set start [$ctext index anchor]}]} {
6158            set start @0,[winfo height $ctext]
6159        }
6160        set match [$ctext search -backwards -count ml -- $searchstring $start]
6161        $ctext tag remove sel 1.0 end
6162        if {$match eq {}} {
6163            bell
6164            return
6165        }
6166        $ctext see $match
6167        set mend "$match + $ml c"
6168        $ctext tag add sel $match $mend
6169        $ctext mark unset anchor
6170    }
6171}
6172
6173proc searchmark {first last} {
6174    global ctext searchstring
6175
6176    set mend $first.0
6177    while {1} {
6178        set match [$ctext search -count mlen -- $searchstring $mend $last.end]
6179        if {$match eq {}} break
6180        set mend "$match + $mlen c"
6181        $ctext tag add found $match $mend
6182    }
6183}
6184
6185proc searchmarkvisible {doall} {
6186    global ctext smarktop smarkbot
6187
6188    set topline [lindex [split [$ctext index @0,0] .] 0]
6189    set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
6190    if {$doall || $botline < $smarktop || $topline > $smarkbot} {
6191        # no overlap with previous
6192        searchmark $topline $botline
6193        set smarktop $topline
6194        set smarkbot $botline
6195    } else {
6196        if {$topline < $smarktop} {
6197            searchmark $topline [expr {$smarktop-1}]
6198            set smarktop $topline
6199        }
6200        if {$botline > $smarkbot} {
6201            searchmark [expr {$smarkbot+1}] $botline
6202            set smarkbot $botline
6203        }
6204    }
6205}
6206
6207proc scrolltext {f0 f1} {
6208    global searchstring
6209
6210    .bleft.sb set $f0 $f1
6211    if {$searchstring ne {}} {
6212        searchmarkvisible 0
6213    }
6214}
6215
6216proc setcoords {} {
6217    global linespc charspc canvx0 canvy0
6218    global xspc1 xspc2 lthickness
6219
6220    set linespc [font metrics mainfont -linespace]
6221    set charspc [font measure mainfont "m"]
6222    set canvy0 [expr {int(3 + 0.5 * $linespc)}]
6223    set canvx0 [expr {int(3 + 0.5 * $linespc)}]
6224    set lthickness [expr {int($linespc / 9) + 1}]
6225    set xspc1(0) $linespc
6226    set xspc2 $linespc
6227}
6228
6229proc redisplay {} {
6230    global canv
6231    global selectedline
6232
6233    set ymax [lindex [$canv cget -scrollregion] 3]
6234    if {$ymax eq {} || $ymax == 0} return
6235    set span [$canv yview]
6236    clear_display
6237    setcanvscroll
6238    allcanvs yview moveto [lindex $span 0]
6239    drawvisible
6240    if {[info exists selectedline]} {
6241        selectline $selectedline 0
6242        allcanvs yview moveto [lindex $span 0]
6243    }
6244}
6245
6246proc parsefont {f n} {
6247    global fontattr
6248
6249    set fontattr($f,family) [lindex $n 0]
6250    set s [lindex $n 1]
6251    if {$s eq {} || $s == 0} {
6252        set s 10
6253    } elseif {$s < 0} {
6254        set s [expr {int(-$s / [winfo fpixels . 1p] + 0.5)}]
6255    }
6256    set fontattr($f,size) $s
6257    set fontattr($f,weight) normal
6258    set fontattr($f,slant) roman
6259    foreach style [lrange $n 2 end] {
6260        switch -- $style {
6261            "normal" -
6262            "bold"   {set fontattr($f,weight) $style}
6263            "roman" -
6264            "italic" {set fontattr($f,slant) $style}
6265        }
6266    }
6267}
6268
6269proc fontflags {f {isbold 0}} {
6270    global fontattr
6271
6272    return [list -family $fontattr($f,family) -size $fontattr($f,size) \
6273                -weight [expr {$isbold? "bold": $fontattr($f,weight)}] \
6274                -slant $fontattr($f,slant)]
6275}
6276
6277proc fontname {f} {
6278    global fontattr
6279
6280    set n [list $fontattr($f,family) $fontattr($f,size)]
6281    if {$fontattr($f,weight) eq "bold"} {
6282        lappend n "bold"
6283    }
6284    if {$fontattr($f,slant) eq "italic"} {
6285        lappend n "italic"
6286    }
6287    return $n
6288}
6289
6290proc incrfont {inc} {
6291    global mainfont textfont ctext canv cflist showrefstop
6292    global stopped entries fontattr
6293
6294    unmarkmatches
6295    set s $fontattr(mainfont,size)
6296    incr s $inc
6297    if {$s < 1} {
6298        set s 1
6299    }
6300    set fontattr(mainfont,size) $s
6301    font config mainfont -size $s
6302    font config mainfontbold -size $s
6303    set mainfont [fontname mainfont]
6304    set s $fontattr(textfont,size)
6305    incr s $inc
6306    if {$s < 1} {
6307        set s 1
6308    }
6309    set fontattr(textfont,size) $s
6310    font config textfont -size $s
6311    font config textfontbold -size $s
6312    set textfont [fontname textfont]
6313    setcoords
6314    settabs
6315    redisplay
6316}
6317
6318proc clearsha1 {} {
6319    global sha1entry sha1string
6320    if {[string length $sha1string] == 40} {
6321        $sha1entry delete 0 end
6322    }
6323}
6324
6325proc sha1change {n1 n2 op} {
6326    global sha1string currentid sha1but
6327    if {$sha1string == {}
6328        || ([info exists currentid] && $sha1string == $currentid)} {
6329        set state disabled
6330    } else {
6331        set state normal
6332    }
6333    if {[$sha1but cget -state] == $state} return
6334    if {$state == "normal"} {
6335        $sha1but conf -state normal -relief raised -text "Goto: "
6336    } else {
6337        $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
6338    }
6339}
6340
6341proc gotocommit {} {
6342    global sha1string tagids headids curview varcid
6343
6344    if {$sha1string == {}
6345        || ([info exists currentid] && $sha1string == $currentid)} return
6346    if {[info exists tagids($sha1string)]} {
6347        set id $tagids($sha1string)
6348    } elseif {[info exists headids($sha1string)]} {
6349        set id $headids($sha1string)
6350    } else {
6351        set id [string tolower $sha1string]
6352        if {[regexp {^[0-9a-f]{4,39}$} $id]} {
6353            set matches [array names varcid "$curview,$id*"]
6354            if {$matches ne {}} {
6355                if {[llength $matches] > 1} {
6356                    error_popup "Short SHA1 id $id is ambiguous"
6357                    return
6358                }
6359                set id [lindex [split [lindex $matches 0] ","] 1]
6360            }
6361        }
6362    }
6363    if {[commitinview $id $curview]} {
6364        selectline [rowofcommit $id] 1
6365        return
6366    }
6367    if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
6368        set type "SHA1 id"
6369    } else {
6370        set type "Tag/Head"
6371    }
6372    error_popup "$type $sha1string is not known"
6373}
6374
6375proc lineenter {x y id} {
6376    global hoverx hovery hoverid hovertimer
6377    global commitinfo canv
6378
6379    if {![info exists commitinfo($id)] && ![getcommit $id]} return
6380    set hoverx $x
6381    set hovery $y
6382    set hoverid $id
6383    if {[info exists hovertimer]} {
6384        after cancel $hovertimer
6385    }
6386    set hovertimer [after 500 linehover]
6387    $canv delete hover
6388}
6389
6390proc linemotion {x y id} {
6391    global hoverx hovery hoverid hovertimer
6392
6393    if {[info exists hoverid] && $id == $hoverid} {
6394        set hoverx $x
6395        set hovery $y
6396        if {[info exists hovertimer]} {
6397            after cancel $hovertimer
6398        }
6399        set hovertimer [after 500 linehover]
6400    }
6401}
6402
6403proc lineleave {id} {
6404    global hoverid hovertimer canv
6405
6406    if {[info exists hoverid] && $id == $hoverid} {
6407        $canv delete hover
6408        if {[info exists hovertimer]} {
6409            after cancel $hovertimer
6410            unset hovertimer
6411        }
6412        unset hoverid
6413    }
6414}
6415
6416proc linehover {} {
6417    global hoverx hovery hoverid hovertimer
6418    global canv linespc lthickness
6419    global commitinfo
6420
6421    set text [lindex $commitinfo($hoverid) 0]
6422    set ymax [lindex [$canv cget -scrollregion] 3]
6423    if {$ymax == {}} return
6424    set yfrac [lindex [$canv yview] 0]
6425    set x [expr {$hoverx + 2 * $linespc}]
6426    set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
6427    set x0 [expr {$x - 2 * $lthickness}]
6428    set y0 [expr {$y - 2 * $lthickness}]
6429    set x1 [expr {$x + [font measure mainfont $text] + 2 * $lthickness}]
6430    set y1 [expr {$y + $linespc + 2 * $lthickness}]
6431    set t [$canv create rectangle $x0 $y0 $x1 $y1 \
6432               -fill \#ffff80 -outline black -width 1 -tags hover]
6433    $canv raise $t
6434    set t [$canv create text $x $y -anchor nw -text $text -tags hover \
6435               -font mainfont]
6436    $canv raise $t
6437}
6438
6439proc clickisonarrow {id y} {
6440    global lthickness
6441
6442    set ranges [rowranges $id]
6443    set thresh [expr {2 * $lthickness + 6}]
6444    set n [expr {[llength $ranges] - 1}]
6445    for {set i 1} {$i < $n} {incr i} {
6446        set row [lindex $ranges $i]
6447        if {abs([yc $row] - $y) < $thresh} {
6448            return $i
6449        }
6450    }
6451    return {}
6452}
6453
6454proc arrowjump {id n y} {
6455    global canv
6456
6457    # 1 <-> 2, 3 <-> 4, etc...
6458    set n [expr {(($n - 1) ^ 1) + 1}]
6459    set row [lindex [rowranges $id] $n]
6460    set yt [yc $row]
6461    set ymax [lindex [$canv cget -scrollregion] 3]
6462    if {$ymax eq {} || $ymax <= 0} return
6463    set view [$canv yview]
6464    set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
6465    set yfrac [expr {$yt / $ymax - $yspan / 2}]
6466    if {$yfrac < 0} {
6467        set yfrac 0
6468    }
6469    allcanvs yview moveto $yfrac
6470}
6471
6472proc lineclick {x y id isnew} {
6473    global ctext commitinfo children canv thickerline curview
6474
6475    if {![info exists commitinfo($id)] && ![getcommit $id]} return
6476    unmarkmatches
6477    unselectline
6478    normalline
6479    $canv delete hover
6480    # draw this line thicker than normal
6481    set thickerline $id
6482    drawlines $id
6483    if {$isnew} {
6484        set ymax [lindex [$canv cget -scrollregion] 3]
6485        if {$ymax eq {}} return
6486        set yfrac [lindex [$canv yview] 0]
6487        set y [expr {$y + $yfrac * $ymax}]
6488    }
6489    set dirn [clickisonarrow $id $y]
6490    if {$dirn ne {}} {
6491        arrowjump $id $dirn $y
6492        return
6493    }
6494
6495    if {$isnew} {
6496        addtohistory [list lineclick $x $y $id 0]
6497    }
6498    # fill the details pane with info about this line
6499    $ctext conf -state normal
6500    clear_ctext
6501    settabs 0
6502    $ctext insert end "Parent:\t"
6503    $ctext insert end $id link0
6504    setlink $id link0
6505    set info $commitinfo($id)
6506    $ctext insert end "\n\t[lindex $info 0]\n"
6507    $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
6508    set date [formatdate [lindex $info 2]]
6509    $ctext insert end "\tDate:\t$date\n"
6510    set kids $children($curview,$id)
6511    if {$kids ne {}} {
6512        $ctext insert end "\nChildren:"
6513        set i 0
6514        foreach child $kids {
6515            incr i
6516            if {![info exists commitinfo($child)] && ![getcommit $child]} continue
6517            set info $commitinfo($child)
6518            $ctext insert end "\n\t"
6519            $ctext insert end $child link$i
6520            setlink $child link$i
6521            $ctext insert end "\n\t[lindex $info 0]"
6522            $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
6523            set date [formatdate [lindex $info 2]]
6524            $ctext insert end "\n\tDate:\t$date\n"
6525        }
6526    }
6527    $ctext conf -state disabled
6528    init_flist {}
6529}
6530
6531proc normalline {} {
6532    global thickerline
6533    if {[info exists thickerline]} {
6534        set id $thickerline
6535        unset thickerline
6536        drawlines $id
6537    }
6538}
6539
6540proc selbyid {id} {
6541    global curview
6542    if {[commitinview $id $curview]} {
6543        selectline [rowofcommit $id] 1
6544    }
6545}
6546
6547proc mstime {} {
6548    global startmstime
6549    if {![info exists startmstime]} {
6550        set startmstime [clock clicks -milliseconds]
6551    }
6552    return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
6553}
6554
6555proc rowmenu {x y id} {
6556    global rowctxmenu selectedline rowmenuid curview
6557    global nullid nullid2 fakerowmenu mainhead
6558
6559    stopfinding
6560    set rowmenuid $id
6561    if {![info exists selectedline]
6562        || [rowofcommit $id] eq $selectedline} {
6563        set state disabled
6564    } else {
6565        set state normal
6566    }
6567    if {$id ne $nullid && $id ne $nullid2} {
6568        set menu $rowctxmenu
6569        $menu entryconfigure 7 -label "Reset $mainhead branch to here"
6570    } else {
6571        set menu $fakerowmenu
6572    }
6573    $menu entryconfigure "Diff this*" -state $state
6574    $menu entryconfigure "Diff selected*" -state $state
6575    $menu entryconfigure "Make patch" -state $state
6576    tk_popup $menu $x $y
6577}
6578
6579proc diffvssel {dirn} {
6580    global rowmenuid selectedline
6581
6582    if {![info exists selectedline]} return
6583    if {$dirn} {
6584        set oldid [commitonrow $selectedline]
6585        set newid $rowmenuid
6586    } else {
6587        set oldid $rowmenuid
6588        set newid [commitonrow $selectedline]
6589    }
6590    addtohistory [list doseldiff $oldid $newid]
6591    doseldiff $oldid $newid
6592}
6593
6594proc doseldiff {oldid newid} {
6595    global ctext
6596    global commitinfo
6597
6598    $ctext conf -state normal
6599    clear_ctext
6600    init_flist "Top"
6601    $ctext insert end "From "
6602    $ctext insert end $oldid link0
6603    setlink $oldid link0
6604    $ctext insert end "\n     "
6605    $ctext insert end [lindex $commitinfo($oldid) 0]
6606    $ctext insert end "\n\nTo   "
6607    $ctext insert end $newid link1
6608    setlink $newid link1
6609    $ctext insert end "\n     "
6610    $ctext insert end [lindex $commitinfo($newid) 0]
6611    $ctext insert end "\n"
6612    $ctext conf -state disabled
6613    $ctext tag remove found 1.0 end
6614    startdiff [list $oldid $newid]
6615}
6616
6617proc mkpatch {} {
6618    global rowmenuid currentid commitinfo patchtop patchnum
6619
6620    if {![info exists currentid]} return
6621    set oldid $currentid
6622    set oldhead [lindex $commitinfo($oldid) 0]
6623    set newid $rowmenuid
6624    set newhead [lindex $commitinfo($newid) 0]
6625    set top .patch
6626    set patchtop $top
6627    catch {destroy $top}
6628    toplevel $top
6629    label $top.title -text "Generate patch"
6630    grid $top.title - -pady 10
6631    label $top.from -text "From:"
6632    entry $top.fromsha1 -width 40 -relief flat
6633    $top.fromsha1 insert 0 $oldid
6634    $top.fromsha1 conf -state readonly
6635    grid $top.from $top.fromsha1 -sticky w
6636    entry $top.fromhead -width 60 -relief flat
6637    $top.fromhead insert 0 $oldhead
6638    $top.fromhead conf -state readonly
6639    grid x $top.fromhead -sticky w
6640    label $top.to -text "To:"
6641    entry $top.tosha1 -width 40 -relief flat
6642    $top.tosha1 insert 0 $newid
6643    $top.tosha1 conf -state readonly
6644    grid $top.to $top.tosha1 -sticky w
6645    entry $top.tohead -width 60 -relief flat
6646    $top.tohead insert 0 $newhead
6647    $top.tohead conf -state readonly
6648    grid x $top.tohead -sticky w
6649    button $top.rev -text "Reverse" -command mkpatchrev -padx 5
6650    grid $top.rev x -pady 10
6651    label $top.flab -text "Output file:"
6652    entry $top.fname -width 60
6653    $top.fname insert 0 [file normalize "patch$patchnum.patch"]
6654    incr patchnum
6655    grid $top.flab $top.fname -sticky w
6656    frame $top.buts
6657    button $top.buts.gen -text "Generate" -command mkpatchgo
6658    button $top.buts.can -text "Cancel" -command mkpatchcan
6659    grid $top.buts.gen $top.buts.can
6660    grid columnconfigure $top.buts 0 -weight 1 -uniform a
6661    grid columnconfigure $top.buts 1 -weight 1 -uniform a
6662    grid $top.buts - -pady 10 -sticky ew
6663    focus $top.fname
6664}
6665
6666proc mkpatchrev {} {
6667    global patchtop
6668
6669    set oldid [$patchtop.fromsha1 get]
6670    set oldhead [$patchtop.fromhead get]
6671    set newid [$patchtop.tosha1 get]
6672    set newhead [$patchtop.tohead get]
6673    foreach e [list fromsha1 fromhead tosha1 tohead] \
6674            v [list $newid $newhead $oldid $oldhead] {
6675        $patchtop.$e conf -state normal
6676        $patchtop.$e delete 0 end
6677        $patchtop.$e insert 0 $v
6678        $patchtop.$e conf -state readonly
6679    }
6680}
6681
6682proc mkpatchgo {} {
6683    global patchtop nullid nullid2
6684
6685    set oldid [$patchtop.fromsha1 get]
6686    set newid [$patchtop.tosha1 get]
6687    set fname [$patchtop.fname get]
6688    set cmd [diffcmd [list $oldid $newid] -p]
6689    # trim off the initial "|"
6690    set cmd [lrange $cmd 1 end]
6691    lappend cmd >$fname &
6692    if {[catch {eval exec $cmd} err]} {
6693        error_popup "Error creating patch: $err"
6694    }
6695    catch {destroy $patchtop}
6696    unset patchtop
6697}
6698
6699proc mkpatchcan {} {
6700    global patchtop
6701
6702    catch {destroy $patchtop}
6703    unset patchtop
6704}
6705
6706proc mktag {} {
6707    global rowmenuid mktagtop commitinfo
6708
6709    set top .maketag
6710    set mktagtop $top
6711    catch {destroy $top}
6712    toplevel $top
6713    label $top.title -text "Create tag"
6714    grid $top.title - -pady 10
6715    label $top.id -text "ID:"
6716    entry $top.sha1 -width 40 -relief flat
6717    $top.sha1 insert 0 $rowmenuid
6718    $top.sha1 conf -state readonly
6719    grid $top.id $top.sha1 -sticky w
6720    entry $top.head -width 60 -relief flat
6721    $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
6722    $top.head conf -state readonly
6723    grid x $top.head -sticky w
6724    label $top.tlab -text "Tag name:"
6725    entry $top.tag -width 60
6726    grid $top.tlab $top.tag -sticky w
6727    frame $top.buts
6728    button $top.buts.gen -text "Create" -command mktaggo
6729    button $top.buts.can -text "Cancel" -command mktagcan
6730    grid $top.buts.gen $top.buts.can
6731    grid columnconfigure $top.buts 0 -weight 1 -uniform a
6732    grid columnconfigure $top.buts 1 -weight 1 -uniform a
6733    grid $top.buts - -pady 10 -sticky ew
6734    focus $top.tag
6735}
6736
6737proc domktag {} {
6738    global mktagtop env tagids idtags
6739
6740    set id [$mktagtop.sha1 get]
6741    set tag [$mktagtop.tag get]
6742    if {$tag == {}} {
6743        error_popup "No tag name specified"
6744        return
6745    }
6746    if {[info exists tagids($tag)]} {
6747        error_popup "Tag \"$tag\" already exists"
6748        return
6749    }
6750    if {[catch {
6751        set dir [gitdir]
6752        set fname [file join $dir "refs/tags" $tag]
6753        set f [open $fname w]
6754        puts $f $id
6755        close $f
6756    } err]} {
6757        error_popup "Error creating tag: $err"
6758        return
6759    }
6760
6761    set tagids($tag) $id
6762    lappend idtags($id) $tag
6763    redrawtags $id
6764    addedtag $id
6765    dispneartags 0
6766    run refill_reflist
6767}
6768
6769proc redrawtags {id} {
6770    global canv linehtag idpos selectedline curview
6771    global canvxmax iddrawn
6772
6773    if {![commitinview $id $curview]} return
6774    if {![info exists iddrawn($id)]} return
6775    drawcommits [rowofcommit $id]
6776    $canv delete tag.$id
6777    set xt [eval drawtags $id $idpos($id)]
6778    $canv coords $linehtag([rowofcommit $id]) $xt [lindex $idpos($id) 2]
6779    set text [$canv itemcget $linehtag([rowofcommit $id]) -text]
6780    set xr [expr {$xt + [font measure mainfont $text]}]
6781    if {$xr > $canvxmax} {
6782        set canvxmax $xr
6783        setcanvscroll
6784    }
6785    if {[info exists selectedline]
6786        && $selectedline == [rowofcommit $id]} {
6787        selectline $selectedline 0
6788    }
6789}
6790
6791proc mktagcan {} {
6792    global mktagtop
6793
6794    catch {destroy $mktagtop}
6795    unset mktagtop
6796}
6797
6798proc mktaggo {} {
6799    domktag
6800    mktagcan
6801}
6802
6803proc writecommit {} {
6804    global rowmenuid wrcomtop commitinfo wrcomcmd
6805
6806    set top .writecommit
6807    set wrcomtop $top
6808    catch {destroy $top}
6809    toplevel $top
6810    label $top.title -text "Write commit to file"
6811    grid $top.title - -pady 10
6812    label $top.id -text "ID:"
6813    entry $top.sha1 -width 40 -relief flat
6814    $top.sha1 insert 0 $rowmenuid
6815    $top.sha1 conf -state readonly
6816    grid $top.id $top.sha1 -sticky w
6817    entry $top.head -width 60 -relief flat
6818    $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
6819    $top.head conf -state readonly
6820    grid x $top.head -sticky w
6821    label $top.clab -text "Command:"
6822    entry $top.cmd -width 60 -textvariable wrcomcmd
6823    grid $top.clab $top.cmd -sticky w -pady 10
6824    label $top.flab -text "Output file:"
6825    entry $top.fname -width 60
6826    $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
6827    grid $top.flab $top.fname -sticky w
6828    frame $top.buts
6829    button $top.buts.gen -text "Write" -command wrcomgo
6830    button $top.buts.can -text "Cancel" -command wrcomcan
6831    grid $top.buts.gen $top.buts.can
6832    grid columnconfigure $top.buts 0 -weight 1 -uniform a
6833    grid columnconfigure $top.buts 1 -weight 1 -uniform a
6834    grid $top.buts - -pady 10 -sticky ew
6835    focus $top.fname
6836}
6837
6838proc wrcomgo {} {
6839    global wrcomtop
6840
6841    set id [$wrcomtop.sha1 get]
6842    set cmd "echo $id | [$wrcomtop.cmd get]"
6843    set fname [$wrcomtop.fname get]
6844    if {[catch {exec sh -c $cmd >$fname &} err]} {
6845        error_popup "Error writing commit: $err"
6846    }
6847    catch {destroy $wrcomtop}
6848    unset wrcomtop
6849}
6850
6851proc wrcomcan {} {
6852    global wrcomtop
6853
6854    catch {destroy $wrcomtop}
6855    unset wrcomtop
6856}
6857
6858proc mkbranch {} {
6859    global rowmenuid mkbrtop
6860
6861    set top .makebranch
6862    catch {destroy $top}
6863    toplevel $top
6864    label $top.title -text "Create new branch"
6865    grid $top.title - -pady 10
6866    label $top.id -text "ID:"
6867    entry $top.sha1 -width 40 -relief flat
6868    $top.sha1 insert 0 $rowmenuid
6869    $top.sha1 conf -state readonly
6870    grid $top.id $top.sha1 -sticky w
6871    label $top.nlab -text "Name:"
6872    entry $top.name -width 40
6873    grid $top.nlab $top.name -sticky w
6874    frame $top.buts
6875    button $top.buts.go -text "Create" -command [list mkbrgo $top]
6876    button $top.buts.can -text "Cancel" -command "catch {destroy $top}"
6877    grid $top.buts.go $top.buts.can
6878    grid columnconfigure $top.buts 0 -weight 1 -uniform a
6879    grid columnconfigure $top.buts 1 -weight 1 -uniform a
6880    grid $top.buts - -pady 10 -sticky ew
6881    focus $top.name
6882}
6883
6884proc mkbrgo {top} {
6885    global headids idheads
6886
6887    set name [$top.name get]
6888    set id [$top.sha1 get]
6889    if {$name eq {}} {
6890        error_popup "Please specify a name for the new branch"
6891        return
6892    }
6893    catch {destroy $top}
6894    nowbusy newbranch
6895    update
6896    if {[catch {
6897        exec git branch $name $id
6898    } err]} {
6899        notbusy newbranch
6900        error_popup $err
6901    } else {
6902        set headids($name) $id
6903        lappend idheads($id) $name
6904        addedhead $id $name
6905        notbusy newbranch
6906        redrawtags $id
6907        dispneartags 0
6908        run refill_reflist
6909    }
6910}
6911
6912proc cherrypick {} {
6913    global rowmenuid curview
6914    global mainhead
6915
6916    set oldhead [exec git rev-parse HEAD]
6917    set dheads [descheads $rowmenuid]
6918    if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
6919        set ok [confirm_popup "Commit [string range $rowmenuid 0 7] is already\
6920                        included in branch $mainhead -- really re-apply it?"]
6921        if {!$ok} return
6922    }
6923    nowbusy cherrypick "Cherry-picking"
6924    update
6925    # Unfortunately git-cherry-pick writes stuff to stderr even when
6926    # no error occurs, and exec takes that as an indication of error...
6927    if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
6928        notbusy cherrypick
6929        error_popup $err
6930        return
6931    }
6932    set newhead [exec git rev-parse HEAD]
6933    if {$newhead eq $oldhead} {
6934        notbusy cherrypick
6935        error_popup "No changes committed"
6936        return
6937    }
6938    addnewchild $newhead $oldhead
6939    if {[commitinview $oldhead $curview]} {
6940        insertrow $newhead $oldhead $curview
6941        if {$mainhead ne {}} {
6942            movehead $newhead $mainhead
6943            movedhead $newhead $mainhead
6944        }
6945        redrawtags $oldhead
6946        redrawtags $newhead
6947    }
6948    notbusy cherrypick
6949}
6950
6951proc resethead {} {
6952    global mainheadid mainhead rowmenuid confirm_ok resettype
6953
6954    set confirm_ok 0
6955    set w ".confirmreset"
6956    toplevel $w
6957    wm transient $w .
6958    wm title $w "Confirm reset"
6959    message $w.m -text \
6960        "Reset branch $mainhead to [string range $rowmenuid 0 7]?" \
6961        -justify center -aspect 1000
6962    pack $w.m -side top -fill x -padx 20 -pady 20
6963    frame $w.f -relief sunken -border 2
6964    message $w.f.rt -text "Reset type:" -aspect 1000
6965    grid $w.f.rt -sticky w
6966    set resettype mixed
6967    radiobutton $w.f.soft -value soft -variable resettype -justify left \
6968        -text "Soft: Leave working tree and index untouched"
6969    grid $w.f.soft -sticky w
6970    radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
6971        -text "Mixed: Leave working tree untouched, reset index"
6972    grid $w.f.mixed -sticky w
6973    radiobutton $w.f.hard -value hard -variable resettype -justify left \
6974        -text "Hard: Reset working tree and index\n(discard ALL local changes)"
6975    grid $w.f.hard -sticky w
6976    pack $w.f -side top -fill x
6977    button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
6978    pack $w.ok -side left -fill x -padx 20 -pady 20
6979    button $w.cancel -text Cancel -command "destroy $w"
6980    pack $w.cancel -side right -fill x -padx 20 -pady 20
6981    bind $w <Visibility> "grab $w; focus $w"
6982    tkwait window $w
6983    if {!$confirm_ok} return
6984    if {[catch {set fd [open \
6985            [list | sh -c "git reset --$resettype $rowmenuid 2>&1"] r]} err]} {
6986        error_popup $err
6987    } else {
6988        dohidelocalchanges
6989        filerun $fd [list readresetstat $fd]
6990        nowbusy reset "Resetting"
6991    }
6992}
6993
6994proc readresetstat {fd} {
6995    global mainhead mainheadid showlocalchanges rprogcoord
6996
6997    if {[gets $fd line] >= 0} {
6998        if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
6999            set rprogcoord [expr {1.0 * $m / $n}]
7000            adjustprogress
7001        }
7002        return 1
7003    }
7004    set rprogcoord 0
7005    adjustprogress
7006    notbusy reset
7007    if {[catch {close $fd} err]} {
7008        error_popup $err
7009    }
7010    set oldhead $mainheadid
7011    set newhead [exec git rev-parse HEAD]
7012    if {$newhead ne $oldhead} {
7013        movehead $newhead $mainhead
7014        movedhead $newhead $mainhead
7015        set mainheadid $newhead
7016        redrawtags $oldhead
7017        redrawtags $newhead
7018    }
7019    if {$showlocalchanges} {
7020        doshowlocalchanges
7021    }
7022    return 0
7023}
7024
7025# context menu for a head
7026proc headmenu {x y id head} {
7027    global headmenuid headmenuhead headctxmenu mainhead
7028
7029    stopfinding
7030    set headmenuid $id
7031    set headmenuhead $head
7032    set state normal
7033    if {$head eq $mainhead} {
7034        set state disabled
7035    }
7036    $headctxmenu entryconfigure 0 -state $state
7037    $headctxmenu entryconfigure 1 -state $state
7038    tk_popup $headctxmenu $x $y
7039}
7040
7041proc cobranch {} {
7042    global headmenuid headmenuhead mainhead headids
7043    global showlocalchanges mainheadid
7044
7045    # check the tree is clean first??
7046    set oldmainhead $mainhead
7047    nowbusy checkout "Checking out"
7048    update
7049    dohidelocalchanges
7050    if {[catch {
7051        exec git checkout -q $headmenuhead
7052    } err]} {
7053        notbusy checkout
7054        error_popup $err
7055    } else {
7056        notbusy checkout
7057        set mainhead $headmenuhead
7058        set mainheadid $headmenuid
7059        if {[info exists headids($oldmainhead)]} {
7060            redrawtags $headids($oldmainhead)
7061        }
7062        redrawtags $headmenuid
7063    }
7064    if {$showlocalchanges} {
7065        dodiffindex
7066    }
7067}
7068
7069proc rmbranch {} {
7070    global headmenuid headmenuhead mainhead
7071    global idheads
7072
7073    set head $headmenuhead
7074    set id $headmenuid
7075    # this check shouldn't be needed any more...
7076    if {$head eq $mainhead} {
7077        error_popup "Cannot delete the currently checked-out branch"
7078        return
7079    }
7080    set dheads [descheads $id]
7081    if {[llength $dheads] == 1 && $idheads($dheads) eq $head} {
7082        # the stuff on this branch isn't on any other branch
7083        if {![confirm_popup "The commits on branch $head aren't on any other\
7084                        branch.\nReally delete branch $head?"]} return
7085    }
7086    nowbusy rmbranch
7087    update
7088    if {[catch {exec git branch -D $head} err]} {
7089        notbusy rmbranch
7090        error_popup $err
7091        return
7092    }
7093    removehead $id $head
7094    removedhead $id $head
7095    redrawtags $id
7096    notbusy rmbranch
7097    dispneartags 0
7098    run refill_reflist
7099}
7100
7101# Display a list of tags and heads
7102proc showrefs {} {
7103    global showrefstop bgcolor fgcolor selectbgcolor
7104    global bglist fglist reflistfilter reflist maincursor
7105
7106    set top .showrefs
7107    set showrefstop $top
7108    if {[winfo exists $top]} {
7109        raise $top
7110        refill_reflist
7111        return
7112    }
7113    toplevel $top
7114    wm title $top "Tags and heads: [file tail [pwd]]"
7115    text $top.list -background $bgcolor -foreground $fgcolor \
7116        -selectbackground $selectbgcolor -font mainfont \
7117        -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
7118        -width 30 -height 20 -cursor $maincursor \
7119        -spacing1 1 -spacing3 1 -state disabled
7120    $top.list tag configure highlight -background $selectbgcolor
7121    lappend bglist $top.list
7122    lappend fglist $top.list
7123    scrollbar $top.ysb -command "$top.list yview" -orient vertical
7124    scrollbar $top.xsb -command "$top.list xview" -orient horizontal
7125    grid $top.list $top.ysb -sticky nsew
7126    grid $top.xsb x -sticky ew
7127    frame $top.f
7128    label $top.f.l -text "Filter: " -font uifont
7129    entry $top.f.e -width 20 -textvariable reflistfilter -font uifont
7130    set reflistfilter "*"
7131    trace add variable reflistfilter write reflistfilter_change
7132    pack $top.f.e -side right -fill x -expand 1
7133    pack $top.f.l -side left
7134    grid $top.f - -sticky ew -pady 2
7135    button $top.close -command [list destroy $top] -text "Close" \
7136        -font uifont
7137    grid $top.close -
7138    grid columnconfigure $top 0 -weight 1
7139    grid rowconfigure $top 0 -weight 1
7140    bind $top.list <1> {break}
7141    bind $top.list <B1-Motion> {break}
7142    bind $top.list <ButtonRelease-1> {sel_reflist %W %x %y; break}
7143    set reflist {}
7144    refill_reflist
7145}
7146
7147proc sel_reflist {w x y} {
7148    global showrefstop reflist headids tagids otherrefids
7149
7150    if {![winfo exists $showrefstop]} return
7151    set l [lindex [split [$w index "@$x,$y"] "."] 0]
7152    set ref [lindex $reflist [expr {$l-1}]]
7153    set n [lindex $ref 0]
7154    switch -- [lindex $ref 1] {
7155        "H" {selbyid $headids($n)}
7156        "T" {selbyid $tagids($n)}
7157        "o" {selbyid $otherrefids($n)}
7158    }
7159    $showrefstop.list tag add highlight $l.0 "$l.0 lineend"
7160}
7161
7162proc unsel_reflist {} {
7163    global showrefstop
7164
7165    if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
7166    $showrefstop.list tag remove highlight 0.0 end
7167}
7168
7169proc reflistfilter_change {n1 n2 op} {
7170    global reflistfilter
7171
7172    after cancel refill_reflist
7173    after 200 refill_reflist
7174}
7175
7176proc refill_reflist {} {
7177    global reflist reflistfilter showrefstop headids tagids otherrefids
7178    global curview commitinterest
7179
7180    if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
7181    set refs {}
7182    foreach n [array names headids] {
7183        if {[string match $reflistfilter $n]} {
7184            if {[commitinview $headids($n) $curview]} {
7185                lappend refs [list $n H]
7186            } else {
7187                set commitinterest($headids($n)) {run refill_reflist}
7188            }
7189        }
7190    }
7191    foreach n [array names tagids] {
7192        if {[string match $reflistfilter $n]} {
7193            if {[commitinview $tagids($n) $curview]} {
7194                lappend refs [list $n T]
7195            } else {
7196                set commitinterest($tagids($n)) {run refill_reflist}
7197            }
7198        }
7199    }
7200    foreach n [array names otherrefids] {
7201        if {[string match $reflistfilter $n]} {
7202            if {[commitinview $otherrefids($n) $curview]} {
7203                lappend refs [list $n o]
7204            } else {
7205                set commitinterest($otherrefids($n)) {run refill_reflist}
7206            }
7207        }
7208    }
7209    set refs [lsort -index 0 $refs]
7210    if {$refs eq $reflist} return
7211
7212    # Update the contents of $showrefstop.list according to the
7213    # differences between $reflist (old) and $refs (new)
7214    $showrefstop.list conf -state normal
7215    $showrefstop.list insert end "\n"
7216    set i 0
7217    set j 0
7218    while {$i < [llength $reflist] || $j < [llength $refs]} {
7219        if {$i < [llength $reflist]} {
7220            if {$j < [llength $refs]} {
7221                set cmp [string compare [lindex $reflist $i 0] \
7222                             [lindex $refs $j 0]]
7223                if {$cmp == 0} {
7224                    set cmp [string compare [lindex $reflist $i 1] \
7225                                 [lindex $refs $j 1]]
7226                }
7227            } else {
7228                set cmp -1
7229            }
7230        } else {
7231            set cmp 1
7232        }
7233        switch -- $cmp {
7234            -1 {
7235                $showrefstop.list delete "[expr {$j+1}].0" "[expr {$j+2}].0"
7236                incr i
7237            }
7238            0 {
7239                incr i
7240                incr j
7241            }
7242            1 {
7243                set l [expr {$j + 1}]
7244                $showrefstop.list image create $l.0 -align baseline \
7245                    -image reficon-[lindex $refs $j 1] -padx 2
7246                $showrefstop.list insert $l.1 "[lindex $refs $j 0]\n"
7247                incr j
7248            }
7249        }
7250    }
7251    set reflist $refs
7252    # delete last newline
7253    $showrefstop.list delete end-2c end-1c
7254    $showrefstop.list conf -state disabled
7255}
7256
7257# Stuff for finding nearby tags
7258proc getallcommits {} {
7259    global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate
7260    global idheads idtags idotherrefs allparents tagobjid
7261
7262    if {![info exists allcommits]} {
7263        set nextarc 0
7264        set allcommits 0
7265        set seeds {}
7266        set allcwait 0
7267        set cachedarcs 0
7268        set allccache [file join [gitdir] "gitk.cache"]
7269        if {![catch {
7270            set f [open $allccache r]
7271            set allcwait 1
7272            getcache $f
7273        }]} return
7274    }
7275
7276    if {$allcwait} {
7277        return
7278    }
7279    set cmd [list | git rev-list --parents]
7280    set allcupdate [expr {$seeds ne {}}]
7281    if {!$allcupdate} {
7282        set ids "--all"
7283    } else {
7284        set refs [concat [array names idheads] [array names idtags] \
7285                      [array names idotherrefs]]
7286        set ids {}
7287        set tagobjs {}
7288        foreach name [array names tagobjid] {
7289            lappend tagobjs $tagobjid($name)
7290        }
7291        foreach id [lsort -unique $refs] {
7292            if {![info exists allparents($id)] &&
7293                [lsearch -exact $tagobjs $id] < 0} {
7294                lappend ids $id
7295            }
7296        }
7297        if {$ids ne {}} {
7298            foreach id $seeds {
7299                lappend ids "^$id"
7300            }
7301        }
7302    }
7303    if {$ids ne {}} {
7304        set fd [open [concat $cmd $ids] r]
7305        fconfigure $fd -blocking 0
7306        incr allcommits
7307        nowbusy allcommits
7308        filerun $fd [list getallclines $fd]
7309    } else {
7310        dispneartags 0
7311    }
7312}
7313
7314# Since most commits have 1 parent and 1 child, we group strings of
7315# such commits into "arcs" joining branch/merge points (BMPs), which
7316# are commits that either don't have 1 parent or don't have 1 child.
7317#
7318# arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
7319# arcout(id) - outgoing arcs for BMP
7320# arcids(a) - list of IDs on arc including end but not start
7321# arcstart(a) - BMP ID at start of arc
7322# arcend(a) - BMP ID at end of arc
7323# growing(a) - arc a is still growing
7324# arctags(a) - IDs out of arcids (excluding end) that have tags
7325# archeads(a) - IDs out of arcids (excluding end) that have heads
7326# The start of an arc is at the descendent end, so "incoming" means
7327# coming from descendents, and "outgoing" means going towards ancestors.
7328
7329proc getallclines {fd} {
7330    global allparents allchildren idtags idheads nextarc
7331    global arcnos arcids arctags arcout arcend arcstart archeads growing
7332    global seeds allcommits cachedarcs allcupdate
7333    
7334    set nid 0
7335    while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
7336        set id [lindex $line 0]
7337        if {[info exists allparents($id)]} {
7338            # seen it already
7339            continue
7340        }
7341        set cachedarcs 0
7342        set olds [lrange $line 1 end]
7343        set allparents($id) $olds
7344        if {![info exists allchildren($id)]} {
7345            set allchildren($id) {}
7346            set arcnos($id) {}
7347            lappend seeds $id
7348        } else {
7349            set a $arcnos($id)
7350            if {[llength $olds] == 1 && [llength $a] == 1} {
7351                lappend arcids($a) $id
7352                if {[info exists idtags($id)]} {
7353                    lappend arctags($a) $id
7354                }
7355                if {[info exists idheads($id)]} {
7356                    lappend archeads($a) $id
7357                }
7358                if {[info exists allparents($olds)]} {
7359                    # seen parent already
7360                    if {![info exists arcout($olds)]} {
7361                        splitarc $olds
7362                    }
7363                    lappend arcids($a) $olds
7364                    set arcend($a) $olds
7365                    unset growing($a)
7366                }
7367                lappend allchildren($olds) $id
7368                lappend arcnos($olds) $a
7369                continue
7370            }
7371        }
7372        foreach a $arcnos($id) {
7373            lappend arcids($a) $id
7374            set arcend($a) $id
7375            unset growing($a)
7376        }
7377
7378        set ao {}
7379        foreach p $olds {
7380            lappend allchildren($p) $id
7381            set a [incr nextarc]
7382            set arcstart($a) $id
7383            set archeads($a) {}
7384            set arctags($a) {}
7385            set archeads($a) {}
7386            set arcids($a) {}
7387            lappend ao $a
7388            set growing($a) 1
7389            if {[info exists allparents($p)]} {
7390                # seen it already, may need to make a new branch
7391                if {![info exists arcout($p)]} {
7392                    splitarc $p
7393                }
7394                lappend arcids($a) $p
7395                set arcend($a) $p
7396                unset growing($a)
7397            }
7398            lappend arcnos($p) $a
7399        }
7400        set arcout($id) $ao
7401    }
7402    if {$nid > 0} {
7403        global cached_dheads cached_dtags cached_atags
7404        catch {unset cached_dheads}
7405        catch {unset cached_dtags}
7406        catch {unset cached_atags}
7407    }
7408    if {![eof $fd]} {
7409        return [expr {$nid >= 1000? 2: 1}]
7410    }
7411    set cacheok 1
7412    if {[catch {
7413        fconfigure $fd -blocking 1
7414        close $fd
7415    } err]} {
7416        # got an error reading the list of commits
7417        # if we were updating, try rereading the whole thing again
7418        if {$allcupdate} {
7419            incr allcommits -1
7420            dropcache $err
7421            return
7422        }
7423        error_popup "Error reading commit topology information;\
7424                branch and preceding/following tag information\
7425                will be incomplete.\n($err)"
7426        set cacheok 0
7427    }
7428    if {[incr allcommits -1] == 0} {
7429        notbusy allcommits
7430        if {$cacheok} {
7431            run savecache
7432        }
7433    }
7434    dispneartags 0
7435    return 0
7436}
7437
7438proc recalcarc {a} {
7439    global arctags archeads arcids idtags idheads
7440
7441    set at {}
7442    set ah {}
7443    foreach id [lrange $arcids($a) 0 end-1] {
7444        if {[info exists idtags($id)]} {
7445            lappend at $id
7446        }
7447        if {[info exists idheads($id)]} {
7448            lappend ah $id
7449        }
7450    }
7451    set arctags($a) $at
7452    set archeads($a) $ah
7453}
7454
7455proc splitarc {p} {
7456    global arcnos arcids nextarc arctags archeads idtags idheads
7457    global arcstart arcend arcout allparents growing
7458
7459    set a $arcnos($p)
7460    if {[llength $a] != 1} {
7461        puts "oops splitarc called but [llength $a] arcs already"
7462        return
7463    }
7464    set a [lindex $a 0]
7465    set i [lsearch -exact $arcids($a) $p]
7466    if {$i < 0} {
7467        puts "oops splitarc $p not in arc $a"
7468        return
7469    }
7470    set na [incr nextarc]
7471    if {[info exists arcend($a)]} {
7472        set arcend($na) $arcend($a)
7473    } else {
7474        set l [lindex $allparents([lindex $arcids($a) end]) 0]
7475        set j [lsearch -exact $arcnos($l) $a]
7476        set arcnos($l) [lreplace $arcnos($l) $j $j $na]
7477    }
7478    set tail [lrange $arcids($a) [expr {$i+1}] end]
7479    set arcids($a) [lrange $arcids($a) 0 $i]
7480    set arcend($a) $p
7481    set arcstart($na) $p
7482    set arcout($p) $na
7483    set arcids($na) $tail
7484    if {[info exists growing($a)]} {
7485        set growing($na) 1
7486        unset growing($a)
7487    }
7488
7489    foreach id $tail {
7490        if {[llength $arcnos($id)] == 1} {
7491            set arcnos($id) $na
7492        } else {
7493            set j [lsearch -exact $arcnos($id) $a]
7494            set arcnos($id) [lreplace $arcnos($id) $j $j $na]
7495        }
7496    }
7497
7498    # reconstruct tags and heads lists
7499    if {$arctags($a) ne {} || $archeads($a) ne {}} {
7500        recalcarc $a
7501        recalcarc $na
7502    } else {
7503        set arctags($na) {}
7504        set archeads($na) {}
7505    }
7506}
7507
7508# Update things for a new commit added that is a child of one
7509# existing commit.  Used when cherry-picking.
7510proc addnewchild {id p} {
7511    global allparents allchildren idtags nextarc
7512    global arcnos arcids arctags arcout arcend arcstart archeads growing
7513    global seeds allcommits
7514
7515    if {![info exists allcommits] || ![info exists arcnos($p)]} return
7516    set allparents($id) [list $p]
7517    set allchildren($id) {}
7518    set arcnos($id) {}
7519    lappend seeds $id
7520    lappend allchildren($p) $id
7521    set a [incr nextarc]
7522    set arcstart($a) $id
7523    set archeads($a) {}
7524    set arctags($a) {}
7525    set arcids($a) [list $p]
7526    set arcend($a) $p
7527    if {![info exists arcout($p)]} {
7528        splitarc $p
7529    }
7530    lappend arcnos($p) $a
7531    set arcout($id) [list $a]
7532}
7533
7534# This implements a cache for the topology information.
7535# The cache saves, for each arc, the start and end of the arc,
7536# the ids on the arc, and the outgoing arcs from the end.
7537proc readcache {f} {
7538    global arcnos arcids arcout arcstart arcend arctags archeads nextarc
7539    global idtags idheads allparents cachedarcs possible_seeds seeds growing
7540    global allcwait
7541
7542    set a $nextarc
7543    set lim $cachedarcs
7544    if {$lim - $a > 500} {
7545        set lim [expr {$a + 500}]
7546    }
7547    if {[catch {
7548        if {$a == $lim} {
7549            # finish reading the cache and setting up arctags, etc.
7550            set line [gets $f]
7551            if {$line ne "1"} {error "bad final version"}
7552            close $f
7553            foreach id [array names idtags] {
7554                if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
7555                    [llength $allparents($id)] == 1} {
7556                    set a [lindex $arcnos($id) 0]
7557                    if {$arctags($a) eq {}} {
7558                        recalcarc $a
7559                    }
7560                }
7561            }
7562            foreach id [array names idheads] {
7563                if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
7564                    [llength $allparents($id)] == 1} {
7565                    set a [lindex $arcnos($id) 0]
7566                    if {$archeads($a) eq {}} {
7567                        recalcarc $a
7568                    }
7569                }
7570            }
7571            foreach id [lsort -unique $possible_seeds] {
7572                if {$arcnos($id) eq {}} {
7573                    lappend seeds $id
7574                }
7575            }
7576            set allcwait 0
7577        } else {
7578            while {[incr a] <= $lim} {
7579                set line [gets $f]
7580                if {[llength $line] != 3} {error "bad line"}
7581                set s [lindex $line 0]
7582                set arcstart($a) $s
7583                lappend arcout($s) $a
7584                if {![info exists arcnos($s)]} {
7585                    lappend possible_seeds $s
7586                    set arcnos($s) {}
7587                }
7588                set e [lindex $line 1]
7589                if {$e eq {}} {
7590                    set growing($a) 1
7591                } else {
7592                    set arcend($a) $e
7593                    if {![info exists arcout($e)]} {
7594                        set arcout($e) {}
7595                    }
7596                }
7597                set arcids($a) [lindex $line 2]
7598                foreach id $arcids($a) {
7599                    lappend allparents($s) $id
7600                    set s $id
7601                    lappend arcnos($id) $a
7602                }
7603                if {![info exists allparents($s)]} {
7604                    set allparents($s) {}
7605                }
7606                set arctags($a) {}
7607                set archeads($a) {}
7608            }
7609            set nextarc [expr {$a - 1}]
7610        }
7611    } err]} {
7612        dropcache $err
7613        return 0
7614    }
7615    if {!$allcwait} {
7616        getallcommits
7617    }
7618    return $allcwait
7619}
7620
7621proc getcache {f} {
7622    global nextarc cachedarcs possible_seeds
7623
7624    if {[catch {
7625        set line [gets $f]
7626        if {[llength $line] != 2 || [lindex $line 0] ne "1"} {error "bad version"}
7627        # make sure it's an integer
7628        set cachedarcs [expr {int([lindex $line 1])}]
7629        if {$cachedarcs < 0} {error "bad number of arcs"}
7630        set nextarc 0
7631        set possible_seeds {}
7632        run readcache $f
7633    } err]} {
7634        dropcache $err
7635    }
7636    return 0
7637}
7638
7639proc dropcache {err} {
7640    global allcwait nextarc cachedarcs seeds
7641
7642    #puts "dropping cache ($err)"
7643    foreach v {arcnos arcout arcids arcstart arcend growing \
7644                   arctags archeads allparents allchildren} {
7645        global $v
7646        catch {unset $v}
7647    }
7648    set allcwait 0
7649    set nextarc 0
7650    set cachedarcs 0
7651    set seeds {}
7652    getallcommits
7653}
7654
7655proc writecache {f} {
7656    global cachearc cachedarcs allccache
7657    global arcstart arcend arcnos arcids arcout
7658
7659    set a $cachearc
7660    set lim $cachedarcs
7661    if {$lim - $a > 1000} {
7662        set lim [expr {$a + 1000}]
7663    }
7664    if {[catch {
7665        while {[incr a] <= $lim} {
7666            if {[info exists arcend($a)]} {
7667                puts $f [list $arcstart($a) $arcend($a) $arcids($a)]
7668            } else {
7669                puts $f [list $arcstart($a) {} $arcids($a)]
7670            }
7671        }
7672    } err]} {
7673        catch {close $f}
7674        catch {file delete $allccache}
7675        #puts "writing cache failed ($err)"
7676        return 0
7677    }
7678    set cachearc [expr {$a - 1}]
7679    if {$a > $cachedarcs} {
7680        puts $f "1"
7681        close $f
7682        return 0
7683    }
7684    return 1
7685}
7686
7687proc savecache {} {
7688    global nextarc cachedarcs cachearc allccache
7689
7690    if {$nextarc == $cachedarcs} return
7691    set cachearc 0
7692    set cachedarcs $nextarc
7693    catch {
7694        set f [open $allccache w]
7695        puts $f [list 1 $cachedarcs]
7696        run writecache $f
7697    }
7698}
7699
7700# Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
7701# or 0 if neither is true.
7702proc anc_or_desc {a b} {
7703    global arcout arcstart arcend arcnos cached_isanc
7704
7705    if {$arcnos($a) eq $arcnos($b)} {
7706        # Both are on the same arc(s); either both are the same BMP,
7707        # or if one is not a BMP, the other is also not a BMP or is
7708        # the BMP at end of the arc (and it only has 1 incoming arc).
7709        # Or both can be BMPs with no incoming arcs.
7710        if {$a eq $b || $arcnos($a) eq {}} {
7711            return 0
7712        }
7713        # assert {[llength $arcnos($a)] == 1}
7714        set arc [lindex $arcnos($a) 0]
7715        set i [lsearch -exact $arcids($arc) $a]
7716        set j [lsearch -exact $arcids($arc) $b]
7717        if {$i < 0 || $i > $j} {
7718            return 1
7719        } else {
7720            return -1
7721        }
7722    }
7723
7724    if {![info exists arcout($a)]} {
7725        set arc [lindex $arcnos($a) 0]
7726        if {[info exists arcend($arc)]} {
7727            set aend $arcend($arc)
7728        } else {
7729            set aend {}
7730        }
7731        set a $arcstart($arc)
7732    } else {
7733        set aend $a
7734    }
7735    if {![info exists arcout($b)]} {
7736        set arc [lindex $arcnos($b) 0]
7737        if {[info exists arcend($arc)]} {
7738            set bend $arcend($arc)
7739        } else {
7740            set bend {}
7741        }
7742        set b $arcstart($arc)
7743    } else {
7744        set bend $b
7745    }
7746    if {$a eq $bend} {
7747        return 1
7748    }
7749    if {$b eq $aend} {
7750        return -1
7751    }
7752    if {[info exists cached_isanc($a,$bend)]} {
7753        if {$cached_isanc($a,$bend)} {
7754            return 1
7755        }
7756    }
7757    if {[info exists cached_isanc($b,$aend)]} {
7758        if {$cached_isanc($b,$aend)} {
7759            return -1
7760        }
7761        if {[info exists cached_isanc($a,$bend)]} {
7762            return 0
7763        }
7764    }
7765
7766    set todo [list $a $b]
7767    set anc($a) a
7768    set anc($b) b
7769    for {set i 0} {$i < [llength $todo]} {incr i} {
7770        set x [lindex $todo $i]
7771        if {$anc($x) eq {}} {
7772            continue
7773        }
7774        foreach arc $arcnos($x) {
7775            set xd $arcstart($arc)
7776            if {$xd eq $bend} {
7777                set cached_isanc($a,$bend) 1
7778                set cached_isanc($b,$aend) 0
7779                return 1
7780            } elseif {$xd eq $aend} {
7781                set cached_isanc($b,$aend) 1
7782                set cached_isanc($a,$bend) 0
7783                return -1
7784            }
7785            if {![info exists anc($xd)]} {
7786                set anc($xd) $anc($x)
7787                lappend todo $xd
7788            } elseif {$anc($xd) ne $anc($x)} {
7789                set anc($xd) {}
7790            }
7791        }
7792    }
7793    set cached_isanc($a,$bend) 0
7794    set cached_isanc($b,$aend) 0
7795    return 0
7796}
7797
7798# This identifies whether $desc has an ancestor that is
7799# a growing tip of the graph and which is not an ancestor of $anc
7800# and returns 0 if so and 1 if not.
7801# If we subsequently discover a tag on such a growing tip, and that
7802# turns out to be a descendent of $anc (which it could, since we
7803# don't necessarily see children before parents), then $desc
7804# isn't a good choice to display as a descendent tag of
7805# $anc (since it is the descendent of another tag which is
7806# a descendent of $anc).  Similarly, $anc isn't a good choice to
7807# display as a ancestor tag of $desc.
7808#
7809proc is_certain {desc anc} {
7810    global arcnos arcout arcstart arcend growing problems
7811
7812    set certain {}
7813    if {[llength $arcnos($anc)] == 1} {
7814        # tags on the same arc are certain
7815        if {$arcnos($desc) eq $arcnos($anc)} {
7816            return 1
7817        }
7818        if {![info exists arcout($anc)]} {
7819            # if $anc is partway along an arc, use the start of the arc instead
7820            set a [lindex $arcnos($anc) 0]
7821            set anc $arcstart($a)
7822        }
7823    }
7824    if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
7825        set x $desc
7826    } else {
7827        set a [lindex $arcnos($desc) 0]
7828        set x $arcend($a)
7829    }
7830    if {$x == $anc} {
7831        return 1
7832    }
7833    set anclist [list $x]
7834    set dl($x) 1
7835    set nnh 1
7836    set ngrowanc 0
7837    for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
7838        set x [lindex $anclist $i]
7839        if {$dl($x)} {
7840            incr nnh -1
7841        }
7842        set done($x) 1
7843        foreach a $arcout($x) {
7844            if {[info exists growing($a)]} {
7845                if {![info exists growanc($x)] && $dl($x)} {
7846                    set growanc($x) 1
7847                    incr ngrowanc
7848                }
7849            } else {
7850                set y $arcend($a)
7851                if {[info exists dl($y)]} {
7852                    if {$dl($y)} {
7853                        if {!$dl($x)} {
7854                            set dl($y) 0
7855                            if {![info exists done($y)]} {
7856                                incr nnh -1
7857                            }
7858                            if {[info exists growanc($x)]} {
7859                                incr ngrowanc -1
7860                            }
7861                            set xl [list $y]
7862                            for {set k 0} {$k < [llength $xl]} {incr k} {
7863                                set z [lindex $xl $k]
7864                                foreach c $arcout($z) {
7865                                    if {[info exists arcend($c)]} {
7866                                        set v $arcend($c)
7867                                        if {[info exists dl($v)] && $dl($v)} {
7868                                            set dl($v) 0
7869                                            if {![info exists done($v)]} {
7870                                                incr nnh -1
7871                                            }
7872                                            if {[info exists growanc($v)]} {
7873                                                incr ngrowanc -1
7874                                            }
7875                                            lappend xl $v
7876                                        }
7877                                    }
7878                                }
7879                            }
7880                        }
7881                    }
7882                } elseif {$y eq $anc || !$dl($x)} {
7883                    set dl($y) 0
7884                    lappend anclist $y
7885                } else {
7886                    set dl($y) 1
7887                    lappend anclist $y
7888                    incr nnh
7889                }
7890            }
7891        }
7892    }
7893    foreach x [array names growanc] {
7894        if {$dl($x)} {
7895            return 0
7896        }
7897        return 0
7898    }
7899    return 1
7900}
7901
7902proc validate_arctags {a} {
7903    global arctags idtags
7904
7905    set i -1
7906    set na $arctags($a)
7907    foreach id $arctags($a) {
7908        incr i
7909        if {![info exists idtags($id)]} {
7910            set na [lreplace $na $i $i]
7911            incr i -1
7912        }
7913    }
7914    set arctags($a) $na
7915}
7916
7917proc validate_archeads {a} {
7918    global archeads idheads
7919
7920    set i -1
7921    set na $archeads($a)
7922    foreach id $archeads($a) {
7923        incr i
7924        if {![info exists idheads($id)]} {
7925            set na [lreplace $na $i $i]
7926            incr i -1
7927        }
7928    }
7929    set archeads($a) $na
7930}
7931
7932# Return the list of IDs that have tags that are descendents of id,
7933# ignoring IDs that are descendents of IDs already reported.
7934proc desctags {id} {
7935    global arcnos arcstart arcids arctags idtags allparents
7936    global growing cached_dtags
7937
7938    if {![info exists allparents($id)]} {
7939        return {}
7940    }
7941    set t1 [clock clicks -milliseconds]
7942    set argid $id
7943    if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
7944        # part-way along an arc; check that arc first
7945        set a [lindex $arcnos($id) 0]
7946        if {$arctags($a) ne {}} {
7947            validate_arctags $a
7948            set i [lsearch -exact $arcids($a) $id]
7949            set tid {}
7950            foreach t $arctags($a) {
7951                set j [lsearch -exact $arcids($a) $t]
7952                if {$j >= $i} break
7953                set tid $t
7954            }
7955            if {$tid ne {}} {
7956                return $tid
7957            }
7958        }
7959        set id $arcstart($a)
7960        if {[info exists idtags($id)]} {
7961            return $id
7962        }
7963    }
7964    if {[info exists cached_dtags($id)]} {
7965        return $cached_dtags($id)
7966    }
7967
7968    set origid $id
7969    set todo [list $id]
7970    set queued($id) 1
7971    set nc 1
7972    for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
7973        set id [lindex $todo $i]
7974        set done($id) 1
7975        set ta [info exists hastaggedancestor($id)]
7976        if {!$ta} {
7977            incr nc -1
7978        }
7979        # ignore tags on starting node
7980        if {!$ta && $i > 0} {
7981            if {[info exists idtags($id)]} {
7982                set tagloc($id) $id
7983                set ta 1
7984            } elseif {[info exists cached_dtags($id)]} {
7985                set tagloc($id) $cached_dtags($id)
7986                set ta 1
7987            }
7988        }
7989        foreach a $arcnos($id) {
7990            set d $arcstart($a)
7991            if {!$ta && $arctags($a) ne {}} {
7992                validate_arctags $a
7993                if {$arctags($a) ne {}} {
7994                    lappend tagloc($id) [lindex $arctags($a) end]
7995                }
7996            }
7997            if {$ta || $arctags($a) ne {}} {
7998                set tomark [list $d]
7999                for {set j 0} {$j < [llength $tomark]} {incr j} {
8000                    set dd [lindex $tomark $j]
8001                    if {![info exists hastaggedancestor($dd)]} {
8002                        if {[info exists done($dd)]} {
8003                            foreach b $arcnos($dd) {
8004                                lappend tomark $arcstart($b)
8005                            }
8006                            if {[info exists tagloc($dd)]} {
8007                                unset tagloc($dd)
8008                            }
8009                        } elseif {[info exists queued($dd)]} {
8010                            incr nc -1
8011                        }
8012                        set hastaggedancestor($dd) 1
8013                    }
8014                }
8015            }
8016            if {![info exists queued($d)]} {
8017                lappend todo $d
8018                set queued($d) 1
8019                if {![info exists hastaggedancestor($d)]} {
8020                    incr nc
8021                }
8022            }
8023        }
8024    }
8025    set tags {}
8026    foreach id [array names tagloc] {
8027        if {![info exists hastaggedancestor($id)]} {
8028            foreach t $tagloc($id) {
8029                if {[lsearch -exact $tags $t] < 0} {
8030                    lappend tags $t
8031                }
8032            }
8033        }
8034    }
8035    set t2 [clock clicks -milliseconds]
8036    set loopix $i
8037
8038    # remove tags that are descendents of other tags
8039    for {set i 0} {$i < [llength $tags]} {incr i} {
8040        set a [lindex $tags $i]
8041        for {set j 0} {$j < $i} {incr j} {
8042            set b [lindex $tags $j]
8043            set r [anc_or_desc $a $b]
8044            if {$r == 1} {
8045                set tags [lreplace $tags $j $j]
8046                incr j -1
8047                incr i -1
8048            } elseif {$r == -1} {
8049                set tags [lreplace $tags $i $i]
8050                incr i -1
8051                break
8052            }
8053        }
8054    }
8055
8056    if {[array names growing] ne {}} {
8057        # graph isn't finished, need to check if any tag could get
8058        # eclipsed by another tag coming later.  Simply ignore any
8059        # tags that could later get eclipsed.
8060        set ctags {}
8061        foreach t $tags {
8062            if {[is_certain $t $origid]} {
8063                lappend ctags $t
8064            }
8065        }
8066        if {$tags eq $ctags} {
8067            set cached_dtags($origid) $tags
8068        } else {
8069            set tags $ctags
8070        }
8071    } else {
8072        set cached_dtags($origid) $tags
8073    }
8074    set t3 [clock clicks -milliseconds]
8075    if {0 && $t3 - $t1 >= 100} {
8076        puts "iterating descendents ($loopix/[llength $todo] nodes) took\
8077            [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
8078    }
8079    return $tags
8080}
8081
8082proc anctags {id} {
8083    global arcnos arcids arcout arcend arctags idtags allparents
8084    global growing cached_atags
8085
8086    if {![info exists allparents($id)]} {
8087        return {}
8088    }
8089    set t1 [clock clicks -milliseconds]
8090    set argid $id
8091    if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
8092        # part-way along an arc; check that arc first
8093        set a [lindex $arcnos($id) 0]
8094        if {$arctags($a) ne {}} {
8095            validate_arctags $a
8096            set i [lsearch -exact $arcids($a) $id]
8097            foreach t $arctags($a) {
8098                set j [lsearch -exact $arcids($a) $t]
8099                if {$j > $i} {
8100                    return $t
8101                }
8102            }
8103        }
8104        if {![info exists arcend($a)]} {
8105            return {}
8106        }
8107        set id $arcend($a)
8108        if {[info exists idtags($id)]} {
8109            return $id
8110        }
8111    }
8112    if {[info exists cached_atags($id)]} {
8113        return $cached_atags($id)
8114    }
8115
8116    set origid $id
8117    set todo [list $id]
8118    set queued($id) 1
8119    set taglist {}
8120    set nc 1
8121    for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
8122        set id [lindex $todo $i]
8123        set done($id) 1
8124        set td [info exists hastaggeddescendent($id)]
8125        if {!$td} {
8126            incr nc -1
8127        }
8128        # ignore tags on starting node
8129        if {!$td && $i > 0} {
8130            if {[info exists idtags($id)]} {
8131                set tagloc($id) $id
8132                set td 1
8133            } elseif {[info exists cached_atags($id)]} {
8134                set tagloc($id) $cached_atags($id)
8135                set td 1
8136            }
8137        }
8138        foreach a $arcout($id) {
8139            if {!$td && $arctags($a) ne {}} {
8140                validate_arctags $a
8141                if {$arctags($a) ne {}} {
8142                    lappend tagloc($id) [lindex $arctags($a) 0]
8143                }
8144            }
8145            if {![info exists arcend($a)]} continue
8146            set d $arcend($a)
8147            if {$td || $arctags($a) ne {}} {
8148                set tomark [list $d]
8149                for {set j 0} {$j < [llength $tomark]} {incr j} {
8150                    set dd [lindex $tomark $j]
8151                    if {![info exists hastaggeddescendent($dd)]} {
8152                        if {[info exists done($dd)]} {
8153                            foreach b $arcout($dd) {
8154                                if {[info exists arcend($b)]} {
8155                                    lappend tomark $arcend($b)
8156                                }
8157                            }
8158                            if {[info exists tagloc($dd)]} {
8159                                unset tagloc($dd)
8160                            }
8161                        } elseif {[info exists queued($dd)]} {
8162                            incr nc -1
8163                        }
8164                        set hastaggeddescendent($dd) 1
8165                    }
8166                }
8167            }
8168            if {![info exists queued($d)]} {
8169                lappend todo $d
8170                set queued($d) 1
8171                if {![info exists hastaggeddescendent($d)]} {
8172                    incr nc
8173                }
8174            }
8175        }
8176    }
8177    set t2 [clock clicks -milliseconds]
8178    set loopix $i
8179    set tags {}
8180    foreach id [array names tagloc] {
8181        if {![info exists hastaggeddescendent($id)]} {
8182            foreach t $tagloc($id) {
8183                if {[lsearch -exact $tags $t] < 0} {
8184                    lappend tags $t
8185                }
8186            }
8187        }
8188    }
8189
8190    # remove tags that are ancestors of other tags
8191    for {set i 0} {$i < [llength $tags]} {incr i} {
8192        set a [lindex $tags $i]
8193        for {set j 0} {$j < $i} {incr j} {
8194            set b [lindex $tags $j]
8195            set r [anc_or_desc $a $b]
8196            if {$r == -1} {
8197                set tags [lreplace $tags $j $j]
8198                incr j -1
8199                incr i -1
8200            } elseif {$r == 1} {
8201                set tags [lreplace $tags $i $i]
8202                incr i -1
8203                break
8204            }
8205        }
8206    }
8207
8208    if {[array names growing] ne {}} {
8209        # graph isn't finished, need to check if any tag could get
8210        # eclipsed by another tag coming later.  Simply ignore any
8211        # tags that could later get eclipsed.
8212        set ctags {}
8213        foreach t $tags {
8214            if {[is_certain $origid $t]} {
8215                lappend ctags $t
8216            }
8217        }
8218        if {$tags eq $ctags} {
8219            set cached_atags($origid) $tags
8220        } else {
8221            set tags $ctags
8222        }
8223    } else {
8224        set cached_atags($origid) $tags
8225    }
8226    set t3 [clock clicks -milliseconds]
8227    if {0 && $t3 - $t1 >= 100} {
8228        puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
8229            [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
8230    }
8231    return $tags
8232}
8233
8234# Return the list of IDs that have heads that are descendents of id,
8235# including id itself if it has a head.
8236proc descheads {id} {
8237    global arcnos arcstart arcids archeads idheads cached_dheads
8238    global allparents
8239
8240    if {![info exists allparents($id)]} {
8241        return {}
8242    }
8243    set aret {}
8244    if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
8245        # part-way along an arc; check it first
8246        set a [lindex $arcnos($id) 0]
8247        if {$archeads($a) ne {}} {
8248            validate_archeads $a
8249            set i [lsearch -exact $arcids($a) $id]
8250            foreach t $archeads($a) {
8251                set j [lsearch -exact $arcids($a) $t]
8252                if {$j > $i} break
8253                lappend aret $t
8254            }
8255        }
8256        set id $arcstart($a)
8257    }
8258    set origid $id
8259    set todo [list $id]
8260    set seen($id) 1
8261    set ret {}
8262    for {set i 0} {$i < [llength $todo]} {incr i} {
8263        set id [lindex $todo $i]
8264        if {[info exists cached_dheads($id)]} {
8265            set ret [concat $ret $cached_dheads($id)]
8266        } else {
8267            if {[info exists idheads($id)]} {
8268                lappend ret $id
8269            }
8270            foreach a $arcnos($id) {
8271                if {$archeads($a) ne {}} {
8272                    validate_archeads $a
8273                    if {$archeads($a) ne {}} {
8274                        set ret [concat $ret $archeads($a)]
8275                    }
8276                }
8277                set d $arcstart($a)
8278                if {![info exists seen($d)]} {
8279                    lappend todo $d
8280                    set seen($d) 1
8281                }
8282            }
8283        }
8284    }
8285    set ret [lsort -unique $ret]
8286    set cached_dheads($origid) $ret
8287    return [concat $ret $aret]
8288}
8289
8290proc addedtag {id} {
8291    global arcnos arcout cached_dtags cached_atags
8292
8293    if {![info exists arcnos($id)]} return
8294    if {![info exists arcout($id)]} {
8295        recalcarc [lindex $arcnos($id) 0]
8296    }
8297    catch {unset cached_dtags}
8298    catch {unset cached_atags}
8299}
8300
8301proc addedhead {hid head} {
8302    global arcnos arcout cached_dheads
8303
8304    if {![info exists arcnos($hid)]} return
8305    if {![info exists arcout($hid)]} {
8306        recalcarc [lindex $arcnos($hid) 0]
8307    }
8308    catch {unset cached_dheads}
8309}
8310
8311proc removedhead {hid head} {
8312    global cached_dheads
8313
8314    catch {unset cached_dheads}
8315}
8316
8317proc movedhead {hid head} {
8318    global arcnos arcout cached_dheads
8319
8320    if {![info exists arcnos($hid)]} return
8321    if {![info exists arcout($hid)]} {
8322        recalcarc [lindex $arcnos($hid) 0]
8323    }
8324    catch {unset cached_dheads}
8325}
8326
8327proc changedrefs {} {
8328    global cached_dheads cached_dtags cached_atags
8329    global arctags archeads arcnos arcout idheads idtags
8330
8331    foreach id [concat [array names idheads] [array names idtags]] {
8332        if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
8333            set a [lindex $arcnos($id) 0]
8334            if {![info exists donearc($a)]} {
8335                recalcarc $a
8336                set donearc($a) 1
8337            }
8338        }
8339    }
8340    catch {unset cached_dtags}
8341    catch {unset cached_atags}
8342    catch {unset cached_dheads}
8343}
8344
8345proc rereadrefs {} {
8346    global idtags idheads idotherrefs mainhead
8347
8348    set refids [concat [array names idtags] \
8349                    [array names idheads] [array names idotherrefs]]
8350    foreach id $refids {
8351        if {![info exists ref($id)]} {
8352            set ref($id) [listrefs $id]
8353        }
8354    }
8355    set oldmainhead $mainhead
8356    readrefs
8357    changedrefs
8358    set refids [lsort -unique [concat $refids [array names idtags] \
8359                        [array names idheads] [array names idotherrefs]]]
8360    foreach id $refids {
8361        set v [listrefs $id]
8362        if {![info exists ref($id)] || $ref($id) != $v ||
8363            ($id eq $oldmainhead && $id ne $mainhead) ||
8364            ($id eq $mainhead && $id ne $oldmainhead)} {
8365            redrawtags $id
8366        }
8367    }
8368    run refill_reflist
8369}
8370
8371proc listrefs {id} {
8372    global idtags idheads idotherrefs
8373
8374    set x {}
8375    if {[info exists idtags($id)]} {
8376        set x $idtags($id)
8377    }
8378    set y {}
8379    if {[info exists idheads($id)]} {
8380        set y $idheads($id)
8381    }
8382    set z {}
8383    if {[info exists idotherrefs($id)]} {
8384        set z $idotherrefs($id)
8385    }
8386    return [list $x $y $z]
8387}
8388
8389proc showtag {tag isnew} {
8390    global ctext tagcontents tagids linknum tagobjid
8391
8392    if {$isnew} {
8393        addtohistory [list showtag $tag 0]
8394    }
8395    $ctext conf -state normal
8396    clear_ctext
8397    settabs 0
8398    set linknum 0
8399    if {![info exists tagcontents($tag)]} {
8400        catch {
8401            set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
8402        }
8403    }
8404    if {[info exists tagcontents($tag)]} {
8405        set text $tagcontents($tag)
8406    } else {
8407        set text "Tag: $tag\nId:  $tagids($tag)"
8408    }
8409    appendwithlinks $text {}
8410    $ctext conf -state disabled
8411    init_flist {}
8412}
8413
8414proc doquit {} {
8415    global stopped
8416    set stopped 100
8417    savestuff .
8418    destroy .
8419}
8420
8421proc mkfontdisp {font top which} {
8422    global fontattr fontpref $font
8423
8424    set fontpref($font) [set $font]
8425    button $top.${font}but -text $which -font optionfont \
8426        -command [list choosefont $font $which]
8427    label $top.$font -relief flat -font $font \
8428        -text $fontattr($font,family) -justify left
8429    grid x $top.${font}but $top.$font -sticky w
8430}
8431
8432proc choosefont {font which} {
8433    global fontparam fontlist fonttop fontattr
8434
8435    set fontparam(which) $which
8436    set fontparam(font) $font
8437    set fontparam(family) [font actual $font -family]
8438    set fontparam(size) $fontattr($font,size)
8439    set fontparam(weight) $fontattr($font,weight)
8440    set fontparam(slant) $fontattr($font,slant)
8441    set top .gitkfont
8442    set fonttop $top
8443    if {![winfo exists $top]} {
8444        font create sample
8445        eval font config sample [font actual $font]
8446        toplevel $top
8447        wm title $top "Gitk font chooser"
8448        label $top.l -textvariable fontparam(which) -font uifont
8449        pack $top.l -side top
8450        set fontlist [lsort [font families]]
8451        frame $top.f
8452        listbox $top.f.fam -listvariable fontlist \
8453            -yscrollcommand [list $top.f.sb set]
8454        bind $top.f.fam <<ListboxSelect>> selfontfam
8455        scrollbar $top.f.sb -command [list $top.f.fam yview]
8456        pack $top.f.sb -side right -fill y
8457        pack $top.f.fam -side left -fill both -expand 1
8458        pack $top.f -side top -fill both -expand 1
8459        frame $top.g
8460        spinbox $top.g.size -from 4 -to 40 -width 4 \
8461            -textvariable fontparam(size) \
8462            -validatecommand {string is integer -strict %s}
8463        checkbutton $top.g.bold -padx 5 \
8464            -font {{Times New Roman} 12 bold} -text "B" -indicatoron 0 \
8465            -variable fontparam(weight) -onvalue bold -offvalue normal
8466        checkbutton $top.g.ital -padx 5 \
8467            -font {{Times New Roman} 12 italic} -text "I" -indicatoron 0  \
8468            -variable fontparam(slant) -onvalue italic -offvalue roman
8469        pack $top.g.size $top.g.bold $top.g.ital -side left
8470        pack $top.g -side top
8471        canvas $top.c -width 150 -height 50 -border 2 -relief sunk \
8472            -background white
8473        $top.c create text 100 25 -anchor center -text $which -font sample \
8474            -fill black -tags text
8475        bind $top.c <Configure> [list centertext $top.c]
8476        pack $top.c -side top -fill x
8477        frame $top.buts
8478        button $top.buts.ok -text "OK" -command fontok -default active \
8479            -font uifont
8480        button $top.buts.can -text "Cancel" -command fontcan -default normal \
8481            -font uifont
8482        grid $top.buts.ok $top.buts.can
8483        grid columnconfigure $top.buts 0 -weight 1 -uniform a
8484        grid columnconfigure $top.buts 1 -weight 1 -uniform a
8485        pack $top.buts -side bottom -fill x
8486        trace add variable fontparam write chg_fontparam
8487    } else {
8488        raise $top
8489        $top.c itemconf text -text $which
8490    }
8491    set i [lsearch -exact $fontlist $fontparam(family)]
8492    if {$i >= 0} {
8493        $top.f.fam selection set $i
8494        $top.f.fam see $i
8495    }
8496}
8497
8498proc centertext {w} {
8499    $w coords text [expr {[winfo width $w] / 2}] [expr {[winfo height $w] / 2}]
8500}
8501
8502proc fontok {} {
8503    global fontparam fontpref prefstop
8504
8505    set f $fontparam(font)
8506    set fontpref($f) [list $fontparam(family) $fontparam(size)]
8507    if {$fontparam(weight) eq "bold"} {
8508        lappend fontpref($f) "bold"
8509    }
8510    if {$fontparam(slant) eq "italic"} {
8511        lappend fontpref($f) "italic"
8512    }
8513    set w $prefstop.$f
8514    $w conf -text $fontparam(family) -font $fontpref($f)
8515        
8516    fontcan
8517}
8518
8519proc fontcan {} {
8520    global fonttop fontparam
8521
8522    if {[info exists fonttop]} {
8523        catch {destroy $fonttop}
8524        catch {font delete sample}
8525        unset fonttop
8526        unset fontparam
8527    }
8528}
8529
8530proc selfontfam {} {
8531    global fonttop fontparam
8532
8533    set i [$fonttop.f.fam curselection]
8534    if {$i ne {}} {
8535        set fontparam(family) [$fonttop.f.fam get $i]
8536    }
8537}
8538
8539proc chg_fontparam {v sub op} {
8540    global fontparam
8541
8542    font config sample -$sub $fontparam($sub)
8543}
8544
8545proc doprefs {} {
8546    global maxwidth maxgraphpct
8547    global oldprefs prefstop showneartags showlocalchanges
8548    global bgcolor fgcolor ctext diffcolors selectbgcolor
8549    global uifont tabstop limitdiffs
8550
8551    set top .gitkprefs
8552    set prefstop $top
8553    if {[winfo exists $top]} {
8554        raise $top
8555        return
8556    }
8557    foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
8558                   limitdiffs tabstop} {
8559        set oldprefs($v) [set $v]
8560    }
8561    toplevel $top
8562    wm title $top "Gitk preferences"
8563    label $top.ldisp -text "Commit list display options"
8564    $top.ldisp configure -font uifont
8565    grid $top.ldisp - -sticky w -pady 10
8566    label $top.spacer -text " "
8567    label $top.maxwidthl -text "Maximum graph width (lines)" \
8568        -font optionfont
8569    spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
8570    grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
8571    label $top.maxpctl -text "Maximum graph width (% of pane)" \
8572        -font optionfont
8573    spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
8574    grid x $top.maxpctl $top.maxpct -sticky w
8575    frame $top.showlocal
8576    label $top.showlocal.l -text "Show local changes" -font optionfont
8577    checkbutton $top.showlocal.b -variable showlocalchanges
8578    pack $top.showlocal.b $top.showlocal.l -side left
8579    grid x $top.showlocal -sticky w
8580
8581    label $top.ddisp -text "Diff display options"
8582    $top.ddisp configure -font uifont
8583    grid $top.ddisp - -sticky w -pady 10
8584    label $top.tabstopl -text "Tab spacing" -font optionfont
8585    spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
8586    grid x $top.tabstopl $top.tabstop -sticky w
8587    frame $top.ntag
8588    label $top.ntag.l -text "Display nearby tags" -font optionfont
8589    checkbutton $top.ntag.b -variable showneartags
8590    pack $top.ntag.b $top.ntag.l -side left
8591    grid x $top.ntag -sticky w
8592    frame $top.ldiff
8593    label $top.ldiff.l -text "Limit diffs to listed paths" -font optionfont
8594    checkbutton $top.ldiff.b -variable limitdiffs
8595    pack $top.ldiff.b $top.ldiff.l -side left
8596    grid x $top.ldiff -sticky w
8597
8598    label $top.cdisp -text "Colors: press to choose"
8599    $top.cdisp configure -font uifont
8600    grid $top.cdisp - -sticky w -pady 10
8601    label $top.bg -padx 40 -relief sunk -background $bgcolor
8602    button $top.bgbut -text "Background" -font optionfont \
8603        -command [list choosecolor bgcolor 0 $top.bg background setbg]
8604    grid x $top.bgbut $top.bg -sticky w
8605    label $top.fg -padx 40 -relief sunk -background $fgcolor
8606    button $top.fgbut -text "Foreground" -font optionfont \
8607        -command [list choosecolor fgcolor 0 $top.fg foreground setfg]
8608    grid x $top.fgbut $top.fg -sticky w
8609    label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
8610    button $top.diffoldbut -text "Diff: old lines" -font optionfont \
8611        -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
8612                      [list $ctext tag conf d0 -foreground]]
8613    grid x $top.diffoldbut $top.diffold -sticky w
8614    label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
8615    button $top.diffnewbut -text "Diff: new lines" -font optionfont \
8616        -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
8617                      [list $ctext tag conf d1 -foreground]]
8618    grid x $top.diffnewbut $top.diffnew -sticky w
8619    label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
8620    button $top.hunksepbut -text "Diff: hunk header" -font optionfont \
8621        -command [list choosecolor diffcolors 2 $top.hunksep \
8622                      "diff hunk header" \
8623                      [list $ctext tag conf hunksep -foreground]]
8624    grid x $top.hunksepbut $top.hunksep -sticky w
8625    label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
8626    button $top.selbgbut -text "Select bg" -font optionfont \
8627        -command [list choosecolor selectbgcolor 0 $top.selbgsep background setselbg]
8628    grid x $top.selbgbut $top.selbgsep -sticky w
8629
8630    label $top.cfont -text "Fonts: press to choose"
8631    $top.cfont configure -font uifont
8632    grid $top.cfont - -sticky w -pady 10
8633    mkfontdisp mainfont $top "Main font"
8634    mkfontdisp textfont $top "Diff display font"
8635    mkfontdisp uifont $top "User interface font"
8636
8637    frame $top.buts
8638    button $top.buts.ok -text "OK" -command prefsok -default active
8639    $top.buts.ok configure -font uifont
8640    button $top.buts.can -text "Cancel" -command prefscan -default normal
8641    $top.buts.can configure -font uifont
8642    grid $top.buts.ok $top.buts.can
8643    grid columnconfigure $top.buts 0 -weight 1 -uniform a
8644    grid columnconfigure $top.buts 1 -weight 1 -uniform a
8645    grid $top.buts - - -pady 10 -sticky ew
8646    bind $top <Visibility> "focus $top.buts.ok"
8647}
8648
8649proc choosecolor {v vi w x cmd} {
8650    global $v
8651
8652    set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
8653               -title "Gitk: choose color for $x"]
8654    if {$c eq {}} return
8655    $w conf -background $c
8656    lset $v $vi $c
8657    eval $cmd $c
8658}
8659
8660proc setselbg {c} {
8661    global bglist cflist
8662    foreach w $bglist {
8663        $w configure -selectbackground $c
8664    }
8665    $cflist tag configure highlight \
8666        -background [$cflist cget -selectbackground]
8667    allcanvs itemconf secsel -fill $c
8668}
8669
8670proc setbg {c} {
8671    global bglist
8672
8673    foreach w $bglist {
8674        $w conf -background $c
8675    }
8676}
8677
8678proc setfg {c} {
8679    global fglist canv
8680
8681    foreach w $fglist {
8682        $w conf -foreground $c
8683    }
8684    allcanvs itemconf text -fill $c
8685    $canv itemconf circle -outline $c
8686}
8687
8688proc prefscan {} {
8689    global oldprefs prefstop
8690
8691    foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
8692                   limitdiffs tabstop} {
8693        global $v
8694        set $v $oldprefs($v)
8695    }
8696    catch {destroy $prefstop}
8697    unset prefstop
8698    fontcan
8699}
8700
8701proc prefsok {} {
8702    global maxwidth maxgraphpct
8703    global oldprefs prefstop showneartags showlocalchanges
8704    global fontpref mainfont textfont uifont
8705    global limitdiffs treediffs
8706
8707    catch {destroy $prefstop}
8708    unset prefstop
8709    fontcan
8710    set fontchanged 0
8711    if {$mainfont ne $fontpref(mainfont)} {
8712        set mainfont $fontpref(mainfont)
8713        parsefont mainfont $mainfont
8714        eval font configure mainfont [fontflags mainfont]
8715        eval font configure mainfontbold [fontflags mainfont 1]
8716        setcoords
8717        set fontchanged 1
8718    }
8719    if {$textfont ne $fontpref(textfont)} {
8720        set textfont $fontpref(textfont)
8721        parsefont textfont $textfont
8722        eval font configure textfont [fontflags textfont]
8723        eval font configure textfontbold [fontflags textfont 1]
8724    }
8725    if {$uifont ne $fontpref(uifont)} {
8726        set uifont $fontpref(uifont)
8727        parsefont uifont $uifont
8728        eval font configure uifont [fontflags uifont]
8729    }
8730    settabs
8731    if {$showlocalchanges != $oldprefs(showlocalchanges)} {
8732        if {$showlocalchanges} {
8733            doshowlocalchanges
8734        } else {
8735            dohidelocalchanges
8736        }
8737    }
8738    if {$limitdiffs != $oldprefs(limitdiffs)} {
8739        # treediffs elements are limited by path
8740        catch {unset treediffs}
8741    }
8742    if {$fontchanged || $maxwidth != $oldprefs(maxwidth)
8743        || $maxgraphpct != $oldprefs(maxgraphpct)} {
8744        redisplay
8745    } elseif {$showneartags != $oldprefs(showneartags) ||
8746          $limitdiffs != $oldprefs(limitdiffs)} {
8747        reselectline
8748    }
8749}
8750
8751proc formatdate {d} {
8752    global datetimeformat
8753    if {$d ne {}} {
8754        set d [clock format $d -format $datetimeformat]
8755    }
8756    return $d
8757}
8758
8759# This list of encoding names and aliases is distilled from
8760# http://www.iana.org/assignments/character-sets.
8761# Not all of them are supported by Tcl.
8762set encoding_aliases {
8763    { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
8764      ISO646-US US-ASCII us IBM367 cp367 csASCII }
8765    { ISO-10646-UTF-1 csISO10646UTF1 }
8766    { ISO_646.basic:1983 ref csISO646basic1983 }
8767    { INVARIANT csINVARIANT }
8768    { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
8769    { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
8770    { NATS-SEFI iso-ir-8-1 csNATSSEFI }
8771    { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
8772    { NATS-DANO iso-ir-9-1 csNATSDANO }
8773    { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
8774    { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
8775    { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
8776    { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
8777    { ISO-2022-KR csISO2022KR }
8778    { EUC-KR csEUCKR }
8779    { ISO-2022-JP csISO2022JP }
8780    { ISO-2022-JP-2 csISO2022JP2 }
8781    { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
8782      csISO13JISC6220jp }
8783    { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
8784    { IT iso-ir-15 ISO646-IT csISO15Italian }
8785    { PT iso-ir-16 ISO646-PT csISO16Portuguese }
8786    { ES iso-ir-17 ISO646-ES csISO17Spanish }
8787    { greek7-old iso-ir-18 csISO18Greek7Old }
8788    { latin-greek iso-ir-19 csISO19LatinGreek }
8789    { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
8790    { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
8791    { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
8792    { ISO_5427 iso-ir-37 csISO5427Cyrillic }
8793    { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
8794    { BS_viewdata iso-ir-47 csISO47BSViewdata }
8795    { INIS iso-ir-49 csISO49INIS }
8796    { INIS-8 iso-ir-50 csISO50INIS8 }
8797    { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
8798    { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
8799    { ISO_5428:1980 iso-ir-55 csISO5428Greek }
8800    { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
8801    { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
8802    { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
8803      csISO60Norwegian1 }
8804    { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
8805    { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
8806    { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
8807    { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
8808    { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
8809    { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
8810    { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
8811    { greek7 iso-ir-88 csISO88Greek7 }
8812    { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
8813    { iso-ir-90 csISO90 }
8814    { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
8815    { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
8816      csISO92JISC62991984b }
8817    { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
8818    { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
8819    { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
8820      csISO95JIS62291984handadd }
8821    { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
8822    { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
8823    { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
8824    { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
8825      CP819 csISOLatin1 }
8826    { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
8827    { T.61-7bit iso-ir-102 csISO102T617bit }
8828    { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
8829    { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
8830    { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
8831    { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
8832    { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
8833    { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
8834    { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
8835    { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
8836      arabic csISOLatinArabic }
8837    { ISO_8859-6-E csISO88596E ISO-8859-6-E }
8838    { ISO_8859-6-I csISO88596I ISO-8859-6-I }
8839    { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
8840      greek greek8 csISOLatinGreek }
8841    { T.101-G2 iso-ir-128 csISO128T101G2 }
8842    { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
8843      csISOLatinHebrew }
8844    { ISO_8859-8-E csISO88598E ISO-8859-8-E }
8845    { ISO_8859-8-I csISO88598I ISO-8859-8-I }
8846    { CSN_369103 iso-ir-139 csISO139CSN369103 }
8847    { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
8848    { ISO_6937-2-add iso-ir-142 csISOTextComm }
8849    { IEC_P27-1 iso-ir-143 csISO143IECP271 }
8850    { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
8851      csISOLatinCyrillic }
8852    { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
8853    { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
8854    { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
8855    { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
8856    { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
8857    { ISO_6937-2-25 iso-ir-152 csISO6937Add }
8858    { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
8859    { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
8860    { ISO_10367-box iso-ir-155 csISO10367Box }
8861    { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
8862    { latin-lap lap iso-ir-158 csISO158Lap }
8863    { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
8864    { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
8865    { us-dk csUSDK }
8866    { dk-us csDKUS }
8867    { JIS_X0201 X0201 csHalfWidthKatakana }
8868    { KSC5636 ISO646-KR csKSC5636 }
8869    { ISO-10646-UCS-2 csUnicode }
8870    { ISO-10646-UCS-4 csUCS4 }
8871    { DEC-MCS dec csDECMCS }
8872    { hp-roman8 roman8 r8 csHPRoman8 }
8873    { macintosh mac csMacintosh }
8874    { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
8875      csIBM037 }
8876    { IBM038 EBCDIC-INT cp038 csIBM038 }
8877    { IBM273 CP273 csIBM273 }
8878    { IBM274 EBCDIC-BE CP274 csIBM274 }
8879    { IBM275 EBCDIC-BR cp275 csIBM275 }
8880    { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
8881    { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
8882    { IBM280 CP280 ebcdic-cp-it csIBM280 }
8883    { IBM281 EBCDIC-JP-E cp281 csIBM281 }
8884    { IBM284 CP284 ebcdic-cp-es csIBM284 }
8885    { IBM285 CP285 ebcdic-cp-gb csIBM285 }
8886    { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
8887    { IBM297 cp297 ebcdic-cp-fr csIBM297 }
8888    { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
8889    { IBM423 cp423 ebcdic-cp-gr csIBM423 }
8890    { IBM424 cp424 ebcdic-cp-he csIBM424 }
8891    { IBM437 cp437 437 csPC8CodePage437 }
8892    { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
8893    { IBM775 cp775 csPC775Baltic }
8894    { IBM850 cp850 850 csPC850Multilingual }
8895    { IBM851 cp851 851 csIBM851 }
8896    { IBM852 cp852 852 csPCp852 }
8897    { IBM855 cp855 855 csIBM855 }
8898    { IBM857 cp857 857 csIBM857 }
8899    { IBM860 cp860 860 csIBM860 }
8900    { IBM861 cp861 861 cp-is csIBM861 }
8901    { IBM862 cp862 862 csPC862LatinHebrew }
8902    { IBM863 cp863 863 csIBM863 }
8903    { IBM864 cp864 csIBM864 }
8904    { IBM865 cp865 865 csIBM865 }
8905    { IBM866 cp866 866 csIBM866 }
8906    { IBM868 CP868 cp-ar csIBM868 }
8907    { IBM869 cp869 869 cp-gr csIBM869 }
8908    { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
8909    { IBM871 CP871 ebcdic-cp-is csIBM871 }
8910    { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
8911    { IBM891 cp891 csIBM891 }
8912    { IBM903 cp903 csIBM903 }
8913    { IBM904 cp904 904 csIBBM904 }
8914    { IBM905 CP905 ebcdic-cp-tr csIBM905 }
8915    { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
8916    { IBM1026 CP1026 csIBM1026 }
8917    { EBCDIC-AT-DE csIBMEBCDICATDE }
8918    { EBCDIC-AT-DE-A csEBCDICATDEA }
8919    { EBCDIC-CA-FR csEBCDICCAFR }
8920    { EBCDIC-DK-NO csEBCDICDKNO }
8921    { EBCDIC-DK-NO-A csEBCDICDKNOA }
8922    { EBCDIC-FI-SE csEBCDICFISE }
8923    { EBCDIC-FI-SE-A csEBCDICFISEA }
8924    { EBCDIC-FR csEBCDICFR }
8925    { EBCDIC-IT csEBCDICIT }
8926    { EBCDIC-PT csEBCDICPT }
8927    { EBCDIC-ES csEBCDICES }
8928    { EBCDIC-ES-A csEBCDICESA }
8929    { EBCDIC-ES-S csEBCDICESS }
8930    { EBCDIC-UK csEBCDICUK }
8931    { EBCDIC-US csEBCDICUS }
8932    { UNKNOWN-8BIT csUnknown8BiT }
8933    { MNEMONIC csMnemonic }
8934    { MNEM csMnem }
8935    { VISCII csVISCII }
8936    { VIQR csVIQR }
8937    { KOI8-R csKOI8R }
8938    { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
8939    { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
8940    { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
8941    { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
8942    { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
8943    { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
8944    { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
8945    { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
8946    { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
8947    { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
8948    { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
8949    { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
8950    { IBM1047 IBM-1047 }
8951    { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
8952    { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
8953    { UNICODE-1-1 csUnicode11 }
8954    { CESU-8 csCESU-8 }
8955    { BOCU-1 csBOCU-1 }
8956    { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
8957    { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
8958      l8 }
8959    { ISO-8859-15 ISO_8859-15 Latin-9 }
8960    { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
8961    { GBK CP936 MS936 windows-936 }
8962    { JIS_Encoding csJISEncoding }
8963    { Shift_JIS MS_Kanji csShiftJIS }
8964    { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
8965      EUC-JP }
8966    { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
8967    { ISO-10646-UCS-Basic csUnicodeASCII }
8968    { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
8969    { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
8970    { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
8971    { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
8972    { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
8973    { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
8974    { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
8975    { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
8976    { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
8977    { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
8978    { Adobe-Standard-Encoding csAdobeStandardEncoding }
8979    { Ventura-US csVenturaUS }
8980    { Ventura-International csVenturaInternational }
8981    { PC8-Danish-Norwegian csPC8DanishNorwegian }
8982    { PC8-Turkish csPC8Turkish }
8983    { IBM-Symbols csIBMSymbols }
8984    { IBM-Thai csIBMThai }
8985    { HP-Legal csHPLegal }
8986    { HP-Pi-font csHPPiFont }
8987    { HP-Math8 csHPMath8 }
8988    { Adobe-Symbol-Encoding csHPPSMath }
8989    { HP-DeskTop csHPDesktop }
8990    { Ventura-Math csVenturaMath }
8991    { Microsoft-Publishing csMicrosoftPublishing }
8992    { Windows-31J csWindows31J }
8993    { GB2312 csGB2312 }
8994    { Big5 csBig5 }
8995}
8996
8997proc tcl_encoding {enc} {
8998    global encoding_aliases
8999    set names [encoding names]
9000    set lcnames [string tolower $names]
9001    set enc [string tolower $enc]
9002    set i [lsearch -exact $lcnames $enc]
9003    if {$i < 0} {
9004        # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
9005        if {[regsub {^iso[-_]} $enc iso encx]} {
9006            set i [lsearch -exact $lcnames $encx]
9007        }
9008    }
9009    if {$i < 0} {
9010        foreach l $encoding_aliases {
9011            set ll [string tolower $l]
9012            if {[lsearch -exact $ll $enc] < 0} continue
9013            # look through the aliases for one that tcl knows about
9014            foreach e $ll {
9015                set i [lsearch -exact $lcnames $e]
9016                if {$i < 0} {
9017                    if {[regsub {^iso[-_]} $e iso ex]} {
9018                        set i [lsearch -exact $lcnames $ex]
9019                    }
9020                }
9021                if {$i >= 0} break
9022            }
9023            break
9024        }
9025    }
9026    if {$i >= 0} {
9027        return [lindex $names $i]
9028    }
9029    return {}
9030}
9031
9032# First check that Tcl/Tk is recent enough
9033if {[catch {package require Tk 8.4} err]} {
9034    show_error {} . "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
9035                     Gitk requires at least Tcl/Tk 8.4."
9036    exit 1
9037}
9038
9039# defaults...
9040set datemode 0
9041set wrcomcmd "git diff-tree --stdin -p --pretty"
9042
9043set gitencoding {}
9044catch {
9045    set gitencoding [exec git config --get i18n.commitencoding]
9046}
9047if {$gitencoding == ""} {
9048    set gitencoding "utf-8"
9049}
9050set tclencoding [tcl_encoding $gitencoding]
9051if {$tclencoding == {}} {
9052    puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
9053}
9054
9055set mainfont {Helvetica 9}
9056set textfont {Courier 9}
9057set uifont {Helvetica 9 bold}
9058set tabstop 8
9059set findmergefiles 0
9060set maxgraphpct 50
9061set maxwidth 16
9062set revlistorder 0
9063set fastdate 0
9064set uparrowlen 5
9065set downarrowlen 5
9066set mingaplen 100
9067set cmitmode "patch"
9068set wrapcomment "none"
9069set showneartags 1
9070set maxrefs 20
9071set maxlinelen 200
9072set showlocalchanges 1
9073set limitdiffs 1
9074set datetimeformat "%Y-%m-%d %H:%M:%S"
9075
9076set colors {green red blue magenta darkgrey brown orange}
9077set bgcolor white
9078set fgcolor black
9079set diffcolors {red "#00a000" blue}
9080set diffcontext 3
9081set selectbgcolor gray85
9082
9083catch {source ~/.gitk}
9084
9085font create optionfont -family sans-serif -size -12
9086
9087parsefont mainfont $mainfont
9088eval font create mainfont [fontflags mainfont]
9089eval font create mainfontbold [fontflags mainfont 1]
9090
9091parsefont textfont $textfont
9092eval font create textfont [fontflags textfont]
9093eval font create textfontbold [fontflags textfont 1]
9094
9095parsefont uifont $uifont
9096eval font create uifont [fontflags uifont]
9097
9098# check that we can find a .git directory somewhere...
9099if {[catch {set gitdir [gitdir]}]} {
9100    show_error {} . "Cannot find a git repository here."
9101    exit 1
9102}
9103if {![file isdirectory $gitdir]} {
9104    show_error {} . "Cannot find the git directory \"$gitdir\"."
9105    exit 1
9106}
9107
9108set mergeonly 0
9109set revtreeargs {}
9110set cmdline_files {}
9111set i 0
9112foreach arg $argv {
9113    switch -- $arg {
9114        "" { }
9115        "-d" { set datemode 1 }
9116        "--merge" {
9117            set mergeonly 1
9118            lappend revtreeargs $arg
9119        }
9120        "--" {
9121            set cmdline_files [lrange $argv [expr {$i + 1}] end]
9122            break
9123        }
9124        default {
9125            lappend revtreeargs $arg
9126        }
9127    }
9128    incr i
9129}
9130
9131if {$i >= [llength $argv] && $revtreeargs ne {}} {
9132    # no -- on command line, but some arguments (other than -d)
9133    if {[catch {
9134        set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
9135        set cmdline_files [split $f "\n"]
9136        set n [llength $cmdline_files]
9137        set revtreeargs [lrange $revtreeargs 0 end-$n]
9138        # Unfortunately git rev-parse doesn't produce an error when
9139        # something is both a revision and a filename.  To be consistent
9140        # with git log and git rev-list, check revtreeargs for filenames.
9141        foreach arg $revtreeargs {
9142            if {[file exists $arg]} {
9143                show_error {} . "Ambiguous argument '$arg': both revision\
9144                                 and filename"
9145                exit 1
9146            }
9147        }
9148    } err]} {
9149        # unfortunately we get both stdout and stderr in $err,
9150        # so look for "fatal:".
9151        set i [string first "fatal:" $err]
9152        if {$i > 0} {
9153            set err [string range $err [expr {$i + 6}] end]
9154        }
9155        show_error {} . "Bad arguments to gitk:\n$err"
9156        exit 1
9157    }
9158}
9159
9160if {$mergeonly} {
9161    # find the list of unmerged files
9162    set mlist {}
9163    set nr_unmerged 0
9164    if {[catch {
9165        set fd [open "| git ls-files -u" r]
9166    } err]} {
9167        show_error {} . "Couldn't get list of unmerged files: $err"
9168        exit 1
9169    }
9170    while {[gets $fd line] >= 0} {
9171        set i [string first "\t" $line]
9172        if {$i < 0} continue
9173        set fname [string range $line [expr {$i+1}] end]
9174        if {[lsearch -exact $mlist $fname] >= 0} continue
9175        incr nr_unmerged
9176        if {$cmdline_files eq {} || [path_filter $cmdline_files $fname]} {
9177            lappend mlist $fname
9178        }
9179    }
9180    catch {close $fd}
9181    if {$mlist eq {}} {
9182        if {$nr_unmerged == 0} {
9183            show_error {} . "No files selected: --merge specified but\
9184                             no files are unmerged."
9185        } else {
9186            show_error {} . "No files selected: --merge specified but\
9187                             no unmerged files are within file limit."
9188        }
9189        exit 1
9190    }
9191    set cmdline_files $mlist
9192}
9193
9194set nullid "0000000000000000000000000000000000000000"
9195set nullid2 "0000000000000000000000000000000000000001"
9196
9197set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
9198
9199set runq {}
9200set history {}
9201set historyindex 0
9202set fh_serial 0
9203set nhl_names {}
9204set highlight_paths {}
9205set findpattern {}
9206set searchdirn -forwards
9207set boldrows {}
9208set boldnamerows {}
9209set diffelide {0 0}
9210set markingmatches 0
9211set linkentercount 0
9212set need_redisplay 0
9213set nrows_drawn 0
9214set firsttabstop 0
9215
9216set nextviewnum 1
9217set curview 0
9218set selectedview 0
9219set selectedhlview None
9220set highlight_related None
9221set highlight_files {}
9222set viewfiles(0) {}
9223set viewperm(0) 0
9224set viewargs(0) {}
9225
9226set loginstance 0
9227set cmdlineok 0
9228set stopped 0
9229set stuffsaved 0
9230set patchnum 0
9231set lserial 0
9232setcoords
9233makewindow
9234# wait for the window to become visible
9235tkwait visibility .
9236wm title . "[file tail $argv0]: [file tail [pwd]]"
9237readrefs
9238
9239if {$cmdline_files ne {} || $revtreeargs ne {}} {
9240    # create a view for the files/dirs specified on the command line
9241    set curview 1
9242    set selectedview 1
9243    set nextviewnum 2
9244    set viewname(1) "Command line"
9245    set viewfiles(1) $cmdline_files
9246    set viewargs(1) $revtreeargs
9247    set viewperm(1) 0
9248    addviewmenu 1
9249    .bar.view entryconf Edit* -state normal
9250    .bar.view entryconf Delete* -state normal
9251}
9252
9253if {[info exists permviews]} {
9254    foreach v $permviews {
9255        set n $nextviewnum
9256        incr nextviewnum
9257        set viewname($n) [lindex $v 0]
9258        set viewfiles($n) [lindex $v 1]
9259        set viewargs($n) [lindex $v 2]
9260        set viewperm($n) 1
9261        addviewmenu $n
9262    }
9263}
9264getcommits