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