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