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