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