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