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