gitkon commit gitk: Synchronize highlighting in file view when scrolling diff (b967135)
   1#!/bin/sh
   2# Tcl ignores the next line -*- tcl -*- \
   3exec wish "$0" -- "$@"
   4
   5# Copyright © 2005-2011 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
  10package require Tk
  11
  12proc hasworktree {} {
  13    return [expr {[exec git rev-parse --is-bare-repository] == "false" &&
  14                  [exec git rev-parse --is-inside-git-dir] == "false"}]
  15}
  16
  17proc reponame {} {
  18    global gitdir
  19    set n [file normalize $gitdir]
  20    if {[string match "*/.git" $n]} {
  21        set n [string range $n 0 end-5]
  22    }
  23    return [file tail $n]
  24}
  25
  26proc gitworktree {} {
  27    variable _gitworktree
  28    if {[info exists _gitworktree]} {
  29        return $_gitworktree
  30    }
  31    # v1.7.0 introduced --show-toplevel to return the canonical work-tree
  32    if {[catch {set _gitworktree [exec git rev-parse --show-toplevel]}]} {
  33        # try to set work tree from environment, core.worktree or use
  34        # cdup to obtain a relative path to the top of the worktree. If
  35        # run from the top, the ./ prefix ensures normalize expands pwd.
  36        if {[catch { set _gitworktree $env(GIT_WORK_TREE) }]} {
  37            catch {set _gitworktree [exec git config --get core.worktree]}
  38            if {$_gitworktree eq ""} {
  39                set _gitworktree [file normalize ./[exec git rev-parse --show-cdup]]
  40            }
  41        }
  42    }
  43    return $_gitworktree
  44}
  45
  46# A simple scheduler for compute-intensive stuff.
  47# The aim is to make sure that event handlers for GUI actions can
  48# run at least every 50-100 ms.  Unfortunately fileevent handlers are
  49# run before X event handlers, so reading from a fast source can
  50# make the GUI completely unresponsive.
  51proc run args {
  52    global isonrunq runq currunq
  53
  54    set script $args
  55    if {[info exists isonrunq($script)]} return
  56    if {$runq eq {} && ![info exists currunq]} {
  57        after idle dorunq
  58    }
  59    lappend runq [list {} $script]
  60    set isonrunq($script) 1
  61}
  62
  63proc filerun {fd script} {
  64    fileevent $fd readable [list filereadable $fd $script]
  65}
  66
  67proc filereadable {fd script} {
  68    global runq currunq
  69
  70    fileevent $fd readable {}
  71    if {$runq eq {} && ![info exists currunq]} {
  72        after idle dorunq
  73    }
  74    lappend runq [list $fd $script]
  75}
  76
  77proc nukefile {fd} {
  78    global runq
  79
  80    for {set i 0} {$i < [llength $runq]} {} {
  81        if {[lindex $runq $i 0] eq $fd} {
  82            set runq [lreplace $runq $i $i]
  83        } else {
  84            incr i
  85        }
  86    }
  87}
  88
  89proc dorunq {} {
  90    global isonrunq runq currunq
  91
  92    set tstart [clock clicks -milliseconds]
  93    set t0 $tstart
  94    while {[llength $runq] > 0} {
  95        set fd [lindex $runq 0 0]
  96        set script [lindex $runq 0 1]
  97        set currunq [lindex $runq 0]
  98        set runq [lrange $runq 1 end]
  99        set repeat [eval $script]
 100        unset currunq
 101        set t1 [clock clicks -milliseconds]
 102        set t [expr {$t1 - $t0}]
 103        if {$repeat ne {} && $repeat} {
 104            if {$fd eq {} || $repeat == 2} {
 105                # script returns 1 if it wants to be readded
 106                # file readers return 2 if they could do more straight away
 107                lappend runq [list $fd $script]
 108            } else {
 109                fileevent $fd readable [list filereadable $fd $script]
 110            }
 111        } elseif {$fd eq {}} {
 112            unset isonrunq($script)
 113        }
 114        set t0 $t1
 115        if {$t1 - $tstart >= 80} break
 116    }
 117    if {$runq ne {}} {
 118        after idle dorunq
 119    }
 120}
 121
 122proc reg_instance {fd} {
 123    global commfd leftover loginstance
 124
 125    set i [incr loginstance]
 126    set commfd($i) $fd
 127    set leftover($i) {}
 128    return $i
 129}
 130
 131proc unmerged_files {files} {
 132    global nr_unmerged
 133
 134    # find the list of unmerged files
 135    set mlist {}
 136    set nr_unmerged 0
 137    if {[catch {
 138        set fd [open "| git ls-files -u" r]
 139    } err]} {
 140        show_error {} . "[mc "Couldn't get list of unmerged files:"] $err"
 141        exit 1
 142    }
 143    while {[gets $fd line] >= 0} {
 144        set i [string first "\t" $line]
 145        if {$i < 0} continue
 146        set fname [string range $line [expr {$i+1}] end]
 147        if {[lsearch -exact $mlist $fname] >= 0} continue
 148        incr nr_unmerged
 149        if {$files eq {} || [path_filter $files $fname]} {
 150            lappend mlist $fname
 151        }
 152    }
 153    catch {close $fd}
 154    return $mlist
 155}
 156
 157proc parseviewargs {n arglist} {
 158    global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs env
 159    global worddiff git_version
 160
 161    set vdatemode($n) 0
 162    set vmergeonly($n) 0
 163    set glflags {}
 164    set diffargs {}
 165    set nextisval 0
 166    set revargs {}
 167    set origargs $arglist
 168    set allknown 1
 169    set filtered 0
 170    set i -1
 171    foreach arg $arglist {
 172        incr i
 173        if {$nextisval} {
 174            lappend glflags $arg
 175            set nextisval 0
 176            continue
 177        }
 178        switch -glob -- $arg {
 179            "-d" -
 180            "--date-order" {
 181                set vdatemode($n) 1
 182                # remove from origargs in case we hit an unknown option
 183                set origargs [lreplace $origargs $i $i]
 184                incr i -1
 185            }
 186            "-[puabwcrRBMC]" -
 187            "--no-renames" - "--full-index" - "--binary" - "--abbrev=*" -
 188            "--find-copies-harder" - "-l*" - "--ext-diff" - "--no-ext-diff" -
 189            "--src-prefix=*" - "--dst-prefix=*" - "--no-prefix" -
 190            "-O*" - "--text" - "--full-diff" - "--ignore-space-at-eol" -
 191            "--ignore-space-change" - "-U*" - "--unified=*" {
 192                # These request or affect diff output, which we don't want.
 193                # Some could be used to set our defaults for diff display.
 194                lappend diffargs $arg
 195            }
 196            "--raw" - "--patch-with-raw" - "--patch-with-stat" -
 197            "--name-only" - "--name-status" - "--color" -
 198            "--log-size" - "--pretty=*" - "--decorate" - "--abbrev-commit" -
 199            "--cc" - "-z" - "--header" - "--parents" - "--boundary" -
 200            "--no-color" - "-g" - "--walk-reflogs" - "--no-walk" -
 201            "--timestamp" - "relative-date" - "--date=*" - "--stdin" -
 202            "--objects" - "--objects-edge" - "--reverse" {
 203                # These cause our parsing of git log's output to fail, or else
 204                # they're options we want to set ourselves, so ignore them.
 205            }
 206            "--color-words*" - "--word-diff=color" {
 207                # These trigger a word diff in the console interface,
 208                # so help the user by enabling our own support
 209                if {[package vcompare $git_version "1.7.2"] >= 0} {
 210                    set worddiff [mc "Color words"]
 211                }
 212            }
 213            "--word-diff*" {
 214                if {[package vcompare $git_version "1.7.2"] >= 0} {
 215                    set worddiff [mc "Markup words"]
 216                }
 217            }
 218            "--stat=*" - "--numstat" - "--shortstat" - "--summary" -
 219            "--check" - "--exit-code" - "--quiet" - "--topo-order" -
 220            "--full-history" - "--dense" - "--sparse" -
 221            "--follow" - "--left-right" - "--encoding=*" {
 222                # These are harmless, and some are even useful
 223                lappend glflags $arg
 224            }
 225            "--diff-filter=*" - "--no-merges" - "--unpacked" -
 226            "--max-count=*" - "--skip=*" - "--since=*" - "--after=*" -
 227            "--until=*" - "--before=*" - "--max-age=*" - "--min-age=*" -
 228            "--author=*" - "--committer=*" - "--grep=*" - "-[iE]" -
 229            "--remove-empty" - "--first-parent" - "--cherry-pick" -
 230            "-S*" - "--pickaxe-all" - "--pickaxe-regex" -
 231            "--simplify-by-decoration" {
 232                # These mean that we get a subset of the commits
 233                set filtered 1
 234                lappend glflags $arg
 235            }
 236            "-n" {
 237                # This appears to be the only one that has a value as a
 238                # separate word following it
 239                set filtered 1
 240                set nextisval 1
 241                lappend glflags $arg
 242            }
 243            "--not" - "--all" {
 244                lappend revargs $arg
 245            }
 246            "--merge" {
 247                set vmergeonly($n) 1
 248                # git rev-parse doesn't understand --merge
 249                lappend revargs --gitk-symmetric-diff-marker MERGE_HEAD...HEAD
 250            }
 251            "--no-replace-objects" {
 252                set env(GIT_NO_REPLACE_OBJECTS) "1"
 253            }
 254            "-*" {
 255                # Other flag arguments including -<n>
 256                if {[string is digit -strict [string range $arg 1 end]]} {
 257                    set filtered 1
 258                } else {
 259                    # a flag argument that we don't recognize;
 260                    # that means we can't optimize
 261                    set allknown 0
 262                }
 263                lappend glflags $arg
 264            }
 265            default {
 266                # Non-flag arguments specify commits or ranges of commits
 267                if {[string match "*...*" $arg]} {
 268                    lappend revargs --gitk-symmetric-diff-marker
 269                }
 270                lappend revargs $arg
 271            }
 272        }
 273    }
 274    set vdflags($n) $diffargs
 275    set vflags($n) $glflags
 276    set vrevs($n) $revargs
 277    set vfiltered($n) $filtered
 278    set vorigargs($n) $origargs
 279    return $allknown
 280}
 281
 282proc parseviewrevs {view revs} {
 283    global vposids vnegids
 284
 285    if {$revs eq {}} {
 286        set revs HEAD
 287    }
 288    if {[catch {set ids [eval exec git rev-parse $revs]} err]} {
 289        # we get stdout followed by stderr in $err
 290        # for an unknown rev, git rev-parse echoes it and then errors out
 291        set errlines [split $err "\n"]
 292        set badrev {}
 293        for {set l 0} {$l < [llength $errlines]} {incr l} {
 294            set line [lindex $errlines $l]
 295            if {!([string length $line] == 40 && [string is xdigit $line])} {
 296                if {[string match "fatal:*" $line]} {
 297                    if {[string match "fatal: ambiguous argument*" $line]
 298                        && $badrev ne {}} {
 299                        if {[llength $badrev] == 1} {
 300                            set err "unknown revision $badrev"
 301                        } else {
 302                            set err "unknown revisions: [join $badrev ", "]"
 303                        }
 304                    } else {
 305                        set err [join [lrange $errlines $l end] "\n"]
 306                    }
 307                    break
 308                }
 309                lappend badrev $line
 310            }
 311        }
 312        error_popup "[mc "Error parsing revisions:"] $err"
 313        return {}
 314    }
 315    set ret {}
 316    set pos {}
 317    set neg {}
 318    set sdm 0
 319    foreach id [split $ids "\n"] {
 320        if {$id eq "--gitk-symmetric-diff-marker"} {
 321            set sdm 4
 322        } elseif {[string match "^*" $id]} {
 323            if {$sdm != 1} {
 324                lappend ret $id
 325                if {$sdm == 3} {
 326                    set sdm 0
 327                }
 328            }
 329            lappend neg [string range $id 1 end]
 330        } else {
 331            if {$sdm != 2} {
 332                lappend ret $id
 333            } else {
 334                lset ret end $id...[lindex $ret end]
 335            }
 336            lappend pos $id
 337        }
 338        incr sdm -1
 339    }
 340    set vposids($view) $pos
 341    set vnegids($view) $neg
 342    return $ret
 343}
 344
 345# Start off a git log process and arrange to read its output
 346proc start_rev_list {view} {
 347    global startmsecs commitidx viewcomplete curview
 348    global tclencoding
 349    global viewargs viewargscmd viewfiles vfilelimit
 350    global showlocalchanges
 351    global viewactive viewinstances vmergeonly
 352    global mainheadid viewmainheadid viewmainheadid_orig
 353    global vcanopt vflags vrevs vorigargs
 354    global show_notes
 355
 356    set startmsecs [clock clicks -milliseconds]
 357    set commitidx($view) 0
 358    # these are set this way for the error exits
 359    set viewcomplete($view) 1
 360    set viewactive($view) 0
 361    varcinit $view
 362
 363    set args $viewargs($view)
 364    if {$viewargscmd($view) ne {}} {
 365        if {[catch {
 366            set str [exec sh -c $viewargscmd($view)]
 367        } err]} {
 368            error_popup "[mc "Error executing --argscmd command:"] $err"
 369            return 0
 370        }
 371        set args [concat $args [split $str "\n"]]
 372    }
 373    set vcanopt($view) [parseviewargs $view $args]
 374
 375    set files $viewfiles($view)
 376    if {$vmergeonly($view)} {
 377        set files [unmerged_files $files]
 378        if {$files eq {}} {
 379            global nr_unmerged
 380            if {$nr_unmerged == 0} {
 381                error_popup [mc "No files selected: --merge specified but\
 382                             no files are unmerged."]
 383            } else {
 384                error_popup [mc "No files selected: --merge specified but\
 385                             no unmerged files are within file limit."]
 386            }
 387            return 0
 388        }
 389    }
 390    set vfilelimit($view) $files
 391
 392    if {$vcanopt($view)} {
 393        set revs [parseviewrevs $view $vrevs($view)]
 394        if {$revs eq {}} {
 395            return 0
 396        }
 397        set args [concat $vflags($view) $revs]
 398    } else {
 399        set args $vorigargs($view)
 400    }
 401
 402    if {[catch {
 403        set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \
 404                        --parents --boundary $args "--" $files] r]
 405    } err]} {
 406        error_popup "[mc "Error executing git log:"] $err"
 407        return 0
 408    }
 409    set i [reg_instance $fd]
 410    set viewinstances($view) [list $i]
 411    set viewmainheadid($view) $mainheadid
 412    set viewmainheadid_orig($view) $mainheadid
 413    if {$files ne {} && $mainheadid ne {}} {
 414        get_viewmainhead $view
 415    }
 416    if {$showlocalchanges && $viewmainheadid($view) ne {}} {
 417        interestedin $viewmainheadid($view) dodiffindex
 418    }
 419    fconfigure $fd -blocking 0 -translation lf -eofchar {}
 420    if {$tclencoding != {}} {
 421        fconfigure $fd -encoding $tclencoding
 422    }
 423    filerun $fd [list getcommitlines $fd $i $view 0]
 424    nowbusy $view [mc "Reading"]
 425    set viewcomplete($view) 0
 426    set viewactive($view) 1
 427    return 1
 428}
 429
 430proc stop_instance {inst} {
 431    global commfd leftover
 432
 433    set fd $commfd($inst)
 434    catch {
 435        set pid [pid $fd]
 436
 437        if {$::tcl_platform(platform) eq {windows}} {
 438            exec kill -f $pid
 439        } else {
 440            exec kill $pid
 441        }
 442    }
 443    catch {close $fd}
 444    nukefile $fd
 445    unset commfd($inst)
 446    unset leftover($inst)
 447}
 448
 449proc stop_backends {} {
 450    global commfd
 451
 452    foreach inst [array names commfd] {
 453        stop_instance $inst
 454    }
 455}
 456
 457proc stop_rev_list {view} {
 458    global viewinstances
 459
 460    foreach inst $viewinstances($view) {
 461        stop_instance $inst
 462    }
 463    set viewinstances($view) {}
 464}
 465
 466proc reset_pending_select {selid} {
 467    global pending_select mainheadid selectheadid
 468
 469    if {$selid ne {}} {
 470        set pending_select $selid
 471    } elseif {$selectheadid ne {}} {
 472        set pending_select $selectheadid
 473    } else {
 474        set pending_select $mainheadid
 475    }
 476}
 477
 478proc getcommits {selid} {
 479    global canv curview need_redisplay viewactive
 480
 481    initlayout
 482    if {[start_rev_list $curview]} {
 483        reset_pending_select $selid
 484        show_status [mc "Reading commits..."]
 485        set need_redisplay 1
 486    } else {
 487        show_status [mc "No commits selected"]
 488    }
 489}
 490
 491proc updatecommits {} {
 492    global curview vcanopt vorigargs vfilelimit viewinstances
 493    global viewactive viewcomplete tclencoding
 494    global startmsecs showneartags showlocalchanges
 495    global mainheadid viewmainheadid viewmainheadid_orig pending_select
 496    global hasworktree
 497    global varcid vposids vnegids vflags vrevs
 498    global show_notes
 499
 500    set hasworktree [hasworktree]
 501    rereadrefs
 502    set view $curview
 503    if {$mainheadid ne $viewmainheadid_orig($view)} {
 504        if {$showlocalchanges} {
 505            dohidelocalchanges
 506        }
 507        set viewmainheadid($view) $mainheadid
 508        set viewmainheadid_orig($view) $mainheadid
 509        if {$vfilelimit($view) ne {}} {
 510            get_viewmainhead $view
 511        }
 512    }
 513    if {$showlocalchanges} {
 514        doshowlocalchanges
 515    }
 516    if {$vcanopt($view)} {
 517        set oldpos $vposids($view)
 518        set oldneg $vnegids($view)
 519        set revs [parseviewrevs $view $vrevs($view)]
 520        if {$revs eq {}} {
 521            return
 522        }
 523        # note: getting the delta when negative refs change is hard,
 524        # and could require multiple git log invocations, so in that
 525        # case we ask git log for all the commits (not just the delta)
 526        if {$oldneg eq $vnegids($view)} {
 527            set newrevs {}
 528            set npos 0
 529            # take out positive refs that we asked for before or
 530            # that we have already seen
 531            foreach rev $revs {
 532                if {[string length $rev] == 40} {
 533                    if {[lsearch -exact $oldpos $rev] < 0
 534                        && ![info exists varcid($view,$rev)]} {
 535                        lappend newrevs $rev
 536                        incr npos
 537                    }
 538                } else {
 539                    lappend $newrevs $rev
 540                }
 541            }
 542            if {$npos == 0} return
 543            set revs $newrevs
 544            set vposids($view) [lsort -unique [concat $oldpos $vposids($view)]]
 545        }
 546        set args [concat $vflags($view) $revs --not $oldpos]
 547    } else {
 548        set args $vorigargs($view)
 549    }
 550    if {[catch {
 551        set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \
 552                        --parents --boundary $args "--" $vfilelimit($view)] r]
 553    } err]} {
 554        error_popup "[mc "Error executing git log:"] $err"
 555        return
 556    }
 557    if {$viewactive($view) == 0} {
 558        set startmsecs [clock clicks -milliseconds]
 559    }
 560    set i [reg_instance $fd]
 561    lappend viewinstances($view) $i
 562    fconfigure $fd -blocking 0 -translation lf -eofchar {}
 563    if {$tclencoding != {}} {
 564        fconfigure $fd -encoding $tclencoding
 565    }
 566    filerun $fd [list getcommitlines $fd $i $view 1]
 567    incr viewactive($view)
 568    set viewcomplete($view) 0
 569    reset_pending_select {}
 570    nowbusy $view [mc "Reading"]
 571    if {$showneartags} {
 572        getallcommits
 573    }
 574}
 575
 576proc reloadcommits {} {
 577    global curview viewcomplete selectedline currentid thickerline
 578    global showneartags treediffs commitinterest cached_commitrow
 579    global targetid
 580
 581    set selid {}
 582    if {$selectedline ne {}} {
 583        set selid $currentid
 584    }
 585
 586    if {!$viewcomplete($curview)} {
 587        stop_rev_list $curview
 588    }
 589    resetvarcs $curview
 590    set selectedline {}
 591    catch {unset currentid}
 592    catch {unset thickerline}
 593    catch {unset treediffs}
 594    readrefs
 595    changedrefs
 596    if {$showneartags} {
 597        getallcommits
 598    }
 599    clear_display
 600    catch {unset commitinterest}
 601    catch {unset cached_commitrow}
 602    catch {unset targetid}
 603    setcanvscroll
 604    getcommits $selid
 605    return 0
 606}
 607
 608# This makes a string representation of a positive integer which
 609# sorts as a string in numerical order
 610proc strrep {n} {
 611    if {$n < 16} {
 612        return [format "%x" $n]
 613    } elseif {$n < 256} {
 614        return [format "x%.2x" $n]
 615    } elseif {$n < 65536} {
 616        return [format "y%.4x" $n]
 617    }
 618    return [format "z%.8x" $n]
 619}
 620
 621# Procedures used in reordering commits from git log (without
 622# --topo-order) into the order for display.
 623
 624proc varcinit {view} {
 625    global varcstart vupptr vdownptr vleftptr vbackptr varctok varcrow
 626    global vtokmod varcmod vrowmod varcix vlastins
 627
 628    set varcstart($view) {{}}
 629    set vupptr($view) {0}
 630    set vdownptr($view) {0}
 631    set vleftptr($view) {0}
 632    set vbackptr($view) {0}
 633    set varctok($view) {{}}
 634    set varcrow($view) {{}}
 635    set vtokmod($view) {}
 636    set varcmod($view) 0
 637    set vrowmod($view) 0
 638    set varcix($view) {{}}
 639    set vlastins($view) {0}
 640}
 641
 642proc resetvarcs {view} {
 643    global varcid varccommits parents children vseedcount ordertok
 644    global vshortids
 645
 646    foreach vid [array names varcid $view,*] {
 647        unset varcid($vid)
 648        unset children($vid)
 649        unset parents($vid)
 650    }
 651    foreach vid [array names vshortids $view,*] {
 652        unset vshortids($vid)
 653    }
 654    # some commits might have children but haven't been seen yet
 655    foreach vid [array names children $view,*] {
 656        unset children($vid)
 657    }
 658    foreach va [array names varccommits $view,*] {
 659        unset varccommits($va)
 660    }
 661    foreach vd [array names vseedcount $view,*] {
 662        unset vseedcount($vd)
 663    }
 664    catch {unset ordertok}
 665}
 666
 667# returns a list of the commits with no children
 668proc seeds {v} {
 669    global vdownptr vleftptr varcstart
 670
 671    set ret {}
 672    set a [lindex $vdownptr($v) 0]
 673    while {$a != 0} {
 674        lappend ret [lindex $varcstart($v) $a]
 675        set a [lindex $vleftptr($v) $a]
 676    }
 677    return $ret
 678}
 679
 680proc newvarc {view id} {
 681    global varcid varctok parents children vdatemode
 682    global vupptr vdownptr vleftptr vbackptr varcrow varcix varcstart
 683    global commitdata commitinfo vseedcount varccommits vlastins
 684
 685    set a [llength $varctok($view)]
 686    set vid $view,$id
 687    if {[llength $children($vid)] == 0 || $vdatemode($view)} {
 688        if {![info exists commitinfo($id)]} {
 689            parsecommit $id $commitdata($id) 1
 690        }
 691        set cdate [lindex [lindex $commitinfo($id) 4] 0]
 692        if {![string is integer -strict $cdate]} {
 693            set cdate 0
 694        }
 695        if {![info exists vseedcount($view,$cdate)]} {
 696            set vseedcount($view,$cdate) -1
 697        }
 698        set c [incr vseedcount($view,$cdate)]
 699        set cdate [expr {$cdate ^ 0xffffffff}]
 700        set tok "s[strrep $cdate][strrep $c]"
 701    } else {
 702        set tok {}
 703    }
 704    set ka 0
 705    if {[llength $children($vid)] > 0} {
 706        set kid [lindex $children($vid) end]
 707        set k $varcid($view,$kid)
 708        if {[string compare [lindex $varctok($view) $k] $tok] > 0} {
 709            set ki $kid
 710            set ka $k
 711            set tok [lindex $varctok($view) $k]
 712        }
 713    }
 714    if {$ka != 0} {
 715        set i [lsearch -exact $parents($view,$ki) $id]
 716        set j [expr {[llength $parents($view,$ki)] - 1 - $i}]
 717        append tok [strrep $j]
 718    }
 719    set c [lindex $vlastins($view) $ka]
 720    if {$c == 0 || [string compare $tok [lindex $varctok($view) $c]] < 0} {
 721        set c $ka
 722        set b [lindex $vdownptr($view) $ka]
 723    } else {
 724        set b [lindex $vleftptr($view) $c]
 725    }
 726    while {$b != 0 && [string compare $tok [lindex $varctok($view) $b]] >= 0} {
 727        set c $b
 728        set b [lindex $vleftptr($view) $c]
 729    }
 730    if {$c == $ka} {
 731        lset vdownptr($view) $ka $a
 732        lappend vbackptr($view) 0
 733    } else {
 734        lset vleftptr($view) $c $a
 735        lappend vbackptr($view) $c
 736    }
 737    lset vlastins($view) $ka $a
 738    lappend vupptr($view) $ka
 739    lappend vleftptr($view) $b
 740    if {$b != 0} {
 741        lset vbackptr($view) $b $a
 742    }
 743    lappend varctok($view) $tok
 744    lappend varcstart($view) $id
 745    lappend vdownptr($view) 0
 746    lappend varcrow($view) {}
 747    lappend varcix($view) {}
 748    set varccommits($view,$a) {}
 749    lappend vlastins($view) 0
 750    return $a
 751}
 752
 753proc splitvarc {p v} {
 754    global varcid varcstart varccommits varctok vtokmod
 755    global vupptr vdownptr vleftptr vbackptr varcix varcrow vlastins
 756
 757    set oa $varcid($v,$p)
 758    set otok [lindex $varctok($v) $oa]
 759    set ac $varccommits($v,$oa)
 760    set i [lsearch -exact $varccommits($v,$oa) $p]
 761    if {$i <= 0} return
 762    set na [llength $varctok($v)]
 763    # "%" sorts before "0"...
 764    set tok "$otok%[strrep $i]"
 765    lappend varctok($v) $tok
 766    lappend varcrow($v) {}
 767    lappend varcix($v) {}
 768    set varccommits($v,$oa) [lrange $ac 0 [expr {$i - 1}]]
 769    set varccommits($v,$na) [lrange $ac $i end]
 770    lappend varcstart($v) $p
 771    foreach id $varccommits($v,$na) {
 772        set varcid($v,$id) $na
 773    }
 774    lappend vdownptr($v) [lindex $vdownptr($v) $oa]
 775    lappend vlastins($v) [lindex $vlastins($v) $oa]
 776    lset vdownptr($v) $oa $na
 777    lset vlastins($v) $oa 0
 778    lappend vupptr($v) $oa
 779    lappend vleftptr($v) 0
 780    lappend vbackptr($v) 0
 781    for {set b [lindex $vdownptr($v) $na]} {$b != 0} {set b [lindex $vleftptr($v) $b]} {
 782        lset vupptr($v) $b $na
 783    }
 784    if {[string compare $otok $vtokmod($v)] <= 0} {
 785        modify_arc $v $oa
 786    }
 787}
 788
 789proc renumbervarc {a v} {
 790    global parents children varctok varcstart varccommits
 791    global vupptr vdownptr vleftptr vbackptr vlastins varcid vtokmod vdatemode
 792
 793    set t1 [clock clicks -milliseconds]
 794    set todo {}
 795    set isrelated($a) 1
 796    set kidchanged($a) 1
 797    set ntot 0
 798    while {$a != 0} {
 799        if {[info exists isrelated($a)]} {
 800            lappend todo $a
 801            set id [lindex $varccommits($v,$a) end]
 802            foreach p $parents($v,$id) {
 803                if {[info exists varcid($v,$p)]} {
 804                    set isrelated($varcid($v,$p)) 1
 805                }
 806            }
 807        }
 808        incr ntot
 809        set b [lindex $vdownptr($v) $a]
 810        if {$b == 0} {
 811            while {$a != 0} {
 812                set b [lindex $vleftptr($v) $a]
 813                if {$b != 0} break
 814                set a [lindex $vupptr($v) $a]
 815            }
 816        }
 817        set a $b
 818    }
 819    foreach a $todo {
 820        if {![info exists kidchanged($a)]} continue
 821        set id [lindex $varcstart($v) $a]
 822        if {[llength $children($v,$id)] > 1} {
 823            set children($v,$id) [lsort -command [list vtokcmp $v] \
 824                                      $children($v,$id)]
 825        }
 826        set oldtok [lindex $varctok($v) $a]
 827        if {!$vdatemode($v)} {
 828            set tok {}
 829        } else {
 830            set tok $oldtok
 831        }
 832        set ka 0
 833        set kid [last_real_child $v,$id]
 834        if {$kid ne {}} {
 835            set k $varcid($v,$kid)
 836            if {[string compare [lindex $varctok($v) $k] $tok] > 0} {
 837                set ki $kid
 838                set ka $k
 839                set tok [lindex $varctok($v) $k]
 840            }
 841        }
 842        if {$ka != 0} {
 843            set i [lsearch -exact $parents($v,$ki) $id]
 844            set j [expr {[llength $parents($v,$ki)] - 1 - $i}]
 845            append tok [strrep $j]
 846        }
 847        if {$tok eq $oldtok} {
 848            continue
 849        }
 850        set id [lindex $varccommits($v,$a) end]
 851        foreach p $parents($v,$id) {
 852            if {[info exists varcid($v,$p)]} {
 853                set kidchanged($varcid($v,$p)) 1
 854            } else {
 855                set sortkids($p) 1
 856            }
 857        }
 858        lset varctok($v) $a $tok
 859        set b [lindex $vupptr($v) $a]
 860        if {$b != $ka} {
 861            if {[string compare [lindex $varctok($v) $ka] $vtokmod($v)] < 0} {
 862                modify_arc $v $ka
 863            }
 864            if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
 865                modify_arc $v $b
 866            }
 867            set c [lindex $vbackptr($v) $a]
 868            set d [lindex $vleftptr($v) $a]
 869            if {$c == 0} {
 870                lset vdownptr($v) $b $d
 871            } else {
 872                lset vleftptr($v) $c $d
 873            }
 874            if {$d != 0} {
 875                lset vbackptr($v) $d $c
 876            }
 877            if {[lindex $vlastins($v) $b] == $a} {
 878                lset vlastins($v) $b $c
 879            }
 880            lset vupptr($v) $a $ka
 881            set c [lindex $vlastins($v) $ka]
 882            if {$c == 0 || \
 883                    [string compare $tok [lindex $varctok($v) $c]] < 0} {
 884                set c $ka
 885                set b [lindex $vdownptr($v) $ka]
 886            } else {
 887                set b [lindex $vleftptr($v) $c]
 888            }
 889            while {$b != 0 && \
 890                      [string compare $tok [lindex $varctok($v) $b]] >= 0} {
 891                set c $b
 892                set b [lindex $vleftptr($v) $c]
 893            }
 894            if {$c == $ka} {
 895                lset vdownptr($v) $ka $a
 896                lset vbackptr($v) $a 0
 897            } else {
 898                lset vleftptr($v) $c $a
 899                lset vbackptr($v) $a $c
 900            }
 901            lset vleftptr($v) $a $b
 902            if {$b != 0} {
 903                lset vbackptr($v) $b $a
 904            }
 905            lset vlastins($v) $ka $a
 906        }
 907    }
 908    foreach id [array names sortkids] {
 909        if {[llength $children($v,$id)] > 1} {
 910            set children($v,$id) [lsort -command [list vtokcmp $v] \
 911                                      $children($v,$id)]
 912        }
 913    }
 914    set t2 [clock clicks -milliseconds]
 915    #puts "renumbervarc did [llength $todo] of $ntot arcs in [expr {$t2-$t1}]ms"
 916}
 917
 918# Fix up the graph after we have found out that in view $v,
 919# $p (a commit that we have already seen) is actually the parent
 920# of the last commit in arc $a.
 921proc fix_reversal {p a v} {
 922    global varcid varcstart varctok vupptr
 923
 924    set pa $varcid($v,$p)
 925    if {$p ne [lindex $varcstart($v) $pa]} {
 926        splitvarc $p $v
 927        set pa $varcid($v,$p)
 928    }
 929    # seeds always need to be renumbered
 930    if {[lindex $vupptr($v) $pa] == 0 ||
 931        [string compare [lindex $varctok($v) $a] \
 932             [lindex $varctok($v) $pa]] > 0} {
 933        renumbervarc $pa $v
 934    }
 935}
 936
 937proc insertrow {id p v} {
 938    global cmitlisted children parents varcid varctok vtokmod
 939    global varccommits ordertok commitidx numcommits curview
 940    global targetid targetrow vshortids
 941
 942    readcommit $id
 943    set vid $v,$id
 944    set cmitlisted($vid) 1
 945    set children($vid) {}
 946    set parents($vid) [list $p]
 947    set a [newvarc $v $id]
 948    set varcid($vid) $a
 949    lappend vshortids($v,[string range $id 0 3]) $id
 950    if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] < 0} {
 951        modify_arc $v $a
 952    }
 953    lappend varccommits($v,$a) $id
 954    set vp $v,$p
 955    if {[llength [lappend children($vp) $id]] > 1} {
 956        set children($vp) [lsort -command [list vtokcmp $v] $children($vp)]
 957        catch {unset ordertok}
 958    }
 959    fix_reversal $p $a $v
 960    incr commitidx($v)
 961    if {$v == $curview} {
 962        set numcommits $commitidx($v)
 963        setcanvscroll
 964        if {[info exists targetid]} {
 965            if {![comes_before $targetid $p]} {
 966                incr targetrow
 967            }
 968        }
 969    }
 970}
 971
 972proc insertfakerow {id p} {
 973    global varcid varccommits parents children cmitlisted
 974    global commitidx varctok vtokmod targetid targetrow curview numcommits
 975
 976    set v $curview
 977    set a $varcid($v,$p)
 978    set i [lsearch -exact $varccommits($v,$a) $p]
 979    if {$i < 0} {
 980        puts "oops: insertfakerow can't find [shortids $p] on arc $a"
 981        return
 982    }
 983    set children($v,$id) {}
 984    set parents($v,$id) [list $p]
 985    set varcid($v,$id) $a
 986    lappend children($v,$p) $id
 987    set cmitlisted($v,$id) 1
 988    set numcommits [incr commitidx($v)]
 989    # note we deliberately don't update varcstart($v) even if $i == 0
 990    set varccommits($v,$a) [linsert $varccommits($v,$a) $i $id]
 991    modify_arc $v $a $i
 992    if {[info exists targetid]} {
 993        if {![comes_before $targetid $p]} {
 994            incr targetrow
 995        }
 996    }
 997    setcanvscroll
 998    drawvisible
 999}
1000
1001proc removefakerow {id} {
1002    global varcid varccommits parents children commitidx
1003    global varctok vtokmod cmitlisted currentid selectedline
1004    global targetid curview numcommits
1005
1006    set v $curview
1007    if {[llength $parents($v,$id)] != 1} {
1008        puts "oops: removefakerow [shortids $id] has [llength $parents($v,$id)] parents"
1009        return
1010    }
1011    set p [lindex $parents($v,$id) 0]
1012    set a $varcid($v,$id)
1013    set i [lsearch -exact $varccommits($v,$a) $id]
1014    if {$i < 0} {
1015        puts "oops: removefakerow can't find [shortids $id] on arc $a"
1016        return
1017    }
1018    unset varcid($v,$id)
1019    set varccommits($v,$a) [lreplace $varccommits($v,$a) $i $i]
1020    unset parents($v,$id)
1021    unset children($v,$id)
1022    unset cmitlisted($v,$id)
1023    set numcommits [incr commitidx($v) -1]
1024    set j [lsearch -exact $children($v,$p) $id]
1025    if {$j >= 0} {
1026        set children($v,$p) [lreplace $children($v,$p) $j $j]
1027    }
1028    modify_arc $v $a $i
1029    if {[info exist currentid] && $id eq $currentid} {
1030        unset currentid
1031        set selectedline {}
1032    }
1033    if {[info exists targetid] && $targetid eq $id} {
1034        set targetid $p
1035    }
1036    setcanvscroll
1037    drawvisible
1038}
1039
1040proc real_children {vp} {
1041    global children nullid nullid2
1042
1043    set kids {}
1044    foreach id $children($vp) {
1045        if {$id ne $nullid && $id ne $nullid2} {
1046            lappend kids $id
1047        }
1048    }
1049    return $kids
1050}
1051
1052proc first_real_child {vp} {
1053    global children nullid nullid2
1054
1055    foreach id $children($vp) {
1056        if {$id ne $nullid && $id ne $nullid2} {
1057            return $id
1058        }
1059    }
1060    return {}
1061}
1062
1063proc last_real_child {vp} {
1064    global children nullid nullid2
1065
1066    set kids $children($vp)
1067    for {set i [llength $kids]} {[incr i -1] >= 0} {} {
1068        set id [lindex $kids $i]
1069        if {$id ne $nullid && $id ne $nullid2} {
1070            return $id
1071        }
1072    }
1073    return {}
1074}
1075
1076proc vtokcmp {v a b} {
1077    global varctok varcid
1078
1079    return [string compare [lindex $varctok($v) $varcid($v,$a)] \
1080                [lindex $varctok($v) $varcid($v,$b)]]
1081}
1082
1083# This assumes that if lim is not given, the caller has checked that
1084# arc a's token is less than $vtokmod($v)
1085proc modify_arc {v a {lim {}}} {
1086    global varctok vtokmod varcmod varcrow vupptr curview vrowmod varccommits
1087
1088    if {$lim ne {}} {
1089        set c [string compare [lindex $varctok($v) $a] $vtokmod($v)]
1090        if {$c > 0} return
1091        if {$c == 0} {
1092            set r [lindex $varcrow($v) $a]
1093            if {$r ne {} && $vrowmod($v) <= $r + $lim} return
1094        }
1095    }
1096    set vtokmod($v) [lindex $varctok($v) $a]
1097    set varcmod($v) $a
1098    if {$v == $curview} {
1099        while {$a != 0 && [lindex $varcrow($v) $a] eq {}} {
1100            set a [lindex $vupptr($v) $a]
1101            set lim {}
1102        }
1103        set r 0
1104        if {$a != 0} {
1105            if {$lim eq {}} {
1106                set lim [llength $varccommits($v,$a)]
1107            }
1108            set r [expr {[lindex $varcrow($v) $a] + $lim}]
1109        }
1110        set vrowmod($v) $r
1111        undolayout $r
1112    }
1113}
1114
1115proc update_arcrows {v} {
1116    global vtokmod varcmod vrowmod varcrow commitidx currentid selectedline
1117    global varcid vrownum varcorder varcix varccommits
1118    global vupptr vdownptr vleftptr varctok
1119    global displayorder parentlist curview cached_commitrow
1120
1121    if {$vrowmod($v) == $commitidx($v)} return
1122    if {$v == $curview} {
1123        if {[llength $displayorder] > $vrowmod($v)} {
1124            set displayorder [lrange $displayorder 0 [expr {$vrowmod($v) - 1}]]
1125            set parentlist [lrange $parentlist 0 [expr {$vrowmod($v) - 1}]]
1126        }
1127        catch {unset cached_commitrow}
1128    }
1129    set narctot [expr {[llength $varctok($v)] - 1}]
1130    set a $varcmod($v)
1131    while {$a != 0 && [lindex $varcix($v) $a] eq {}} {
1132        # go up the tree until we find something that has a row number,
1133        # or we get to a seed
1134        set a [lindex $vupptr($v) $a]
1135    }
1136    if {$a == 0} {
1137        set a [lindex $vdownptr($v) 0]
1138        if {$a == 0} return
1139        set vrownum($v) {0}
1140        set varcorder($v) [list $a]
1141        lset varcix($v) $a 0
1142        lset varcrow($v) $a 0
1143        set arcn 0
1144        set row 0
1145    } else {
1146        set arcn [lindex $varcix($v) $a]
1147        if {[llength $vrownum($v)] > $arcn + 1} {
1148            set vrownum($v) [lrange $vrownum($v) 0 $arcn]
1149            set varcorder($v) [lrange $varcorder($v) 0 $arcn]
1150        }
1151        set row [lindex $varcrow($v) $a]
1152    }
1153    while {1} {
1154        set p $a
1155        incr row [llength $varccommits($v,$a)]
1156        # go down if possible
1157        set b [lindex $vdownptr($v) $a]
1158        if {$b == 0} {
1159            # if not, go left, or go up until we can go left
1160            while {$a != 0} {
1161                set b [lindex $vleftptr($v) $a]
1162                if {$b != 0} break
1163                set a [lindex $vupptr($v) $a]
1164            }
1165            if {$a == 0} break
1166        }
1167        set a $b
1168        incr arcn
1169        lappend vrownum($v) $row
1170        lappend varcorder($v) $a
1171        lset varcix($v) $a $arcn
1172        lset varcrow($v) $a $row
1173    }
1174    set vtokmod($v) [lindex $varctok($v) $p]
1175    set varcmod($v) $p
1176    set vrowmod($v) $row
1177    if {[info exists currentid]} {
1178        set selectedline [rowofcommit $currentid]
1179    }
1180}
1181
1182# Test whether view $v contains commit $id
1183proc commitinview {id v} {
1184    global varcid
1185
1186    return [info exists varcid($v,$id)]
1187}
1188
1189# Return the row number for commit $id in the current view
1190proc rowofcommit {id} {
1191    global varcid varccommits varcrow curview cached_commitrow
1192    global varctok vtokmod
1193
1194    set v $curview
1195    if {![info exists varcid($v,$id)]} {
1196        puts "oops rowofcommit no arc for [shortids $id]"
1197        return {}
1198    }
1199    set a $varcid($v,$id)
1200    if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] >= 0} {
1201        update_arcrows $v
1202    }
1203    if {[info exists cached_commitrow($id)]} {
1204        return $cached_commitrow($id)
1205    }
1206    set i [lsearch -exact $varccommits($v,$a) $id]
1207    if {$i < 0} {
1208        puts "oops didn't find commit [shortids $id] in arc $a"
1209        return {}
1210    }
1211    incr i [lindex $varcrow($v) $a]
1212    set cached_commitrow($id) $i
1213    return $i
1214}
1215
1216# Returns 1 if a is on an earlier row than b, otherwise 0
1217proc comes_before {a b} {
1218    global varcid varctok curview
1219
1220    set v $curview
1221    if {$a eq $b || ![info exists varcid($v,$a)] || \
1222            ![info exists varcid($v,$b)]} {
1223        return 0
1224    }
1225    if {$varcid($v,$a) != $varcid($v,$b)} {
1226        return [expr {[string compare [lindex $varctok($v) $varcid($v,$a)] \
1227                           [lindex $varctok($v) $varcid($v,$b)]] < 0}]
1228    }
1229    return [expr {[rowofcommit $a] < [rowofcommit $b]}]
1230}
1231
1232proc bsearch {l elt} {
1233    if {[llength $l] == 0 || $elt <= [lindex $l 0]} {
1234        return 0
1235    }
1236    set lo 0
1237    set hi [llength $l]
1238    while {$hi - $lo > 1} {
1239        set mid [expr {int(($lo + $hi) / 2)}]
1240        set t [lindex $l $mid]
1241        if {$elt < $t} {
1242            set hi $mid
1243        } elseif {$elt > $t} {
1244            set lo $mid
1245        } else {
1246            return $mid
1247        }
1248    }
1249    return $lo
1250}
1251
1252# Make sure rows $start..$end-1 are valid in displayorder and parentlist
1253proc make_disporder {start end} {
1254    global vrownum curview commitidx displayorder parentlist
1255    global varccommits varcorder parents vrowmod varcrow
1256    global d_valid_start d_valid_end
1257
1258    if {$end > $vrowmod($curview)} {
1259        update_arcrows $curview
1260    }
1261    set ai [bsearch $vrownum($curview) $start]
1262    set start [lindex $vrownum($curview) $ai]
1263    set narc [llength $vrownum($curview)]
1264    for {set r $start} {$ai < $narc && $r < $end} {incr ai} {
1265        set a [lindex $varcorder($curview) $ai]
1266        set l [llength $displayorder]
1267        set al [llength $varccommits($curview,$a)]
1268        if {$l < $r + $al} {
1269            if {$l < $r} {
1270                set pad [ntimes [expr {$r - $l}] {}]
1271                set displayorder [concat $displayorder $pad]
1272                set parentlist [concat $parentlist $pad]
1273            } elseif {$l > $r} {
1274                set displayorder [lrange $displayorder 0 [expr {$r - 1}]]
1275                set parentlist [lrange $parentlist 0 [expr {$r - 1}]]
1276            }
1277            foreach id $varccommits($curview,$a) {
1278                lappend displayorder $id
1279                lappend parentlist $parents($curview,$id)
1280            }
1281        } elseif {[lindex $displayorder [expr {$r + $al - 1}]] eq {}} {
1282            set i $r
1283            foreach id $varccommits($curview,$a) {
1284                lset displayorder $i $id
1285                lset parentlist $i $parents($curview,$id)
1286                incr i
1287            }
1288        }
1289        incr r $al
1290    }
1291}
1292
1293proc commitonrow {row} {
1294    global displayorder
1295
1296    set id [lindex $displayorder $row]
1297    if {$id eq {}} {
1298        make_disporder $row [expr {$row + 1}]
1299        set id [lindex $displayorder $row]
1300    }
1301    return $id
1302}
1303
1304proc closevarcs {v} {
1305    global varctok varccommits varcid parents children
1306    global cmitlisted commitidx vtokmod
1307
1308    set missing_parents 0
1309    set scripts {}
1310    set narcs [llength $varctok($v)]
1311    for {set a 1} {$a < $narcs} {incr a} {
1312        set id [lindex $varccommits($v,$a) end]
1313        foreach p $parents($v,$id) {
1314            if {[info exists varcid($v,$p)]} continue
1315            # add p as a new commit
1316            incr missing_parents
1317            set cmitlisted($v,$p) 0
1318            set parents($v,$p) {}
1319            if {[llength $children($v,$p)] == 1 &&
1320                [llength $parents($v,$id)] == 1} {
1321                set b $a
1322            } else {
1323                set b [newvarc $v $p]
1324            }
1325            set varcid($v,$p) $b
1326            if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
1327                modify_arc $v $b
1328            }
1329            lappend varccommits($v,$b) $p
1330            incr commitidx($v)
1331            set scripts [check_interest $p $scripts]
1332        }
1333    }
1334    if {$missing_parents > 0} {
1335        foreach s $scripts {
1336            eval $s
1337        }
1338    }
1339}
1340
1341# Use $rwid as a substitute for $id, i.e. reparent $id's children to $rwid
1342# Assumes we already have an arc for $rwid.
1343proc rewrite_commit {v id rwid} {
1344    global children parents varcid varctok vtokmod varccommits
1345
1346    foreach ch $children($v,$id) {
1347        # make $rwid be $ch's parent in place of $id
1348        set i [lsearch -exact $parents($v,$ch) $id]
1349        if {$i < 0} {
1350            puts "oops rewrite_commit didn't find $id in parent list for $ch"
1351        }
1352        set parents($v,$ch) [lreplace $parents($v,$ch) $i $i $rwid]
1353        # add $ch to $rwid's children and sort the list if necessary
1354        if {[llength [lappend children($v,$rwid) $ch]] > 1} {
1355            set children($v,$rwid) [lsort -command [list vtokcmp $v] \
1356                                        $children($v,$rwid)]
1357        }
1358        # fix the graph after joining $id to $rwid
1359        set a $varcid($v,$ch)
1360        fix_reversal $rwid $a $v
1361        # parentlist is wrong for the last element of arc $a
1362        # even if displayorder is right, hence the 3rd arg here
1363        modify_arc $v $a [expr {[llength $varccommits($v,$a)] - 1}]
1364    }
1365}
1366
1367# Mechanism for registering a command to be executed when we come
1368# across a particular commit.  To handle the case when only the
1369# prefix of the commit is known, the commitinterest array is now
1370# indexed by the first 4 characters of the ID.  Each element is a
1371# list of id, cmd pairs.
1372proc interestedin {id cmd} {
1373    global commitinterest
1374
1375    lappend commitinterest([string range $id 0 3]) $id $cmd
1376}
1377
1378proc check_interest {id scripts} {
1379    global commitinterest
1380
1381    set prefix [string range $id 0 3]
1382    if {[info exists commitinterest($prefix)]} {
1383        set newlist {}
1384        foreach {i script} $commitinterest($prefix) {
1385            if {[string match "$i*" $id]} {
1386                lappend scripts [string map [list "%I" $id "%P" $i] $script]
1387            } else {
1388                lappend newlist $i $script
1389            }
1390        }
1391        if {$newlist ne {}} {
1392            set commitinterest($prefix) $newlist
1393        } else {
1394            unset commitinterest($prefix)
1395        }
1396    }
1397    return $scripts
1398}
1399
1400proc getcommitlines {fd inst view updating}  {
1401    global cmitlisted leftover
1402    global commitidx commitdata vdatemode
1403    global parents children curview hlview
1404    global idpending ordertok
1405    global varccommits varcid varctok vtokmod vfilelimit vshortids
1406
1407    set stuff [read $fd 500000]
1408    # git log doesn't terminate the last commit with a null...
1409    if {$stuff == {} && $leftover($inst) ne {} && [eof $fd]} {
1410        set stuff "\0"
1411    }
1412    if {$stuff == {}} {
1413        if {![eof $fd]} {
1414            return 1
1415        }
1416        global commfd viewcomplete viewactive viewname
1417        global viewinstances
1418        unset commfd($inst)
1419        set i [lsearch -exact $viewinstances($view) $inst]
1420        if {$i >= 0} {
1421            set viewinstances($view) [lreplace $viewinstances($view) $i $i]
1422        }
1423        # set it blocking so we wait for the process to terminate
1424        fconfigure $fd -blocking 1
1425        if {[catch {close $fd} err]} {
1426            set fv {}
1427            if {$view != $curview} {
1428                set fv " for the \"$viewname($view)\" view"
1429            }
1430            if {[string range $err 0 4] == "usage"} {
1431                set err "Gitk: error reading commits$fv:\
1432                        bad arguments to git log."
1433                if {$viewname($view) eq "Command line"} {
1434                    append err \
1435                        "  (Note: arguments to gitk are passed to git log\
1436                         to allow selection of commits to be displayed.)"
1437                }
1438            } else {
1439                set err "Error reading commits$fv: $err"
1440            }
1441            error_popup $err
1442        }
1443        if {[incr viewactive($view) -1] <= 0} {
1444            set viewcomplete($view) 1
1445            # Check if we have seen any ids listed as parents that haven't
1446            # appeared in the list
1447            closevarcs $view
1448            notbusy $view
1449        }
1450        if {$view == $curview} {
1451            run chewcommits
1452        }
1453        return 0
1454    }
1455    set start 0
1456    set gotsome 0
1457    set scripts {}
1458    while 1 {
1459        set i [string first "\0" $stuff $start]
1460        if {$i < 0} {
1461            append leftover($inst) [string range $stuff $start end]
1462            break
1463        }
1464        if {$start == 0} {
1465            set cmit $leftover($inst)
1466            append cmit [string range $stuff 0 [expr {$i - 1}]]
1467            set leftover($inst) {}
1468        } else {
1469            set cmit [string range $stuff $start [expr {$i - 1}]]
1470        }
1471        set start [expr {$i + 1}]
1472        set j [string first "\n" $cmit]
1473        set ok 0
1474        set listed 1
1475        if {$j >= 0 && [string match "commit *" $cmit]} {
1476            set ids [string range $cmit 7 [expr {$j - 1}]]
1477            if {[string match {[-^<>]*} $ids]} {
1478                switch -- [string index $ids 0] {
1479                    "-" {set listed 0}
1480                    "^" {set listed 2}
1481                    "<" {set listed 3}
1482                    ">" {set listed 4}
1483                }
1484                set ids [string range $ids 1 end]
1485            }
1486            set ok 1
1487            foreach id $ids {
1488                if {[string length $id] != 40} {
1489                    set ok 0
1490                    break
1491                }
1492            }
1493        }
1494        if {!$ok} {
1495            set shortcmit $cmit
1496            if {[string length $shortcmit] > 80} {
1497                set shortcmit "[string range $shortcmit 0 80]..."
1498            }
1499            error_popup "[mc "Can't parse git log output:"] {$shortcmit}"
1500            exit 1
1501        }
1502        set id [lindex $ids 0]
1503        set vid $view,$id
1504
1505        lappend vshortids($view,[string range $id 0 3]) $id
1506
1507        if {!$listed && $updating && ![info exists varcid($vid)] &&
1508            $vfilelimit($view) ne {}} {
1509            # git log doesn't rewrite parents for unlisted commits
1510            # when doing path limiting, so work around that here
1511            # by working out the rewritten parent with git rev-list
1512            # and if we already know about it, using the rewritten
1513            # parent as a substitute parent for $id's children.
1514            if {![catch {
1515                set rwid [exec git rev-list --first-parent --max-count=1 \
1516                              $id -- $vfilelimit($view)]
1517            }]} {
1518                if {$rwid ne {} && [info exists varcid($view,$rwid)]} {
1519                    # use $rwid in place of $id
1520                    rewrite_commit $view $id $rwid
1521                    continue
1522                }
1523            }
1524        }
1525
1526        set a 0
1527        if {[info exists varcid($vid)]} {
1528            if {$cmitlisted($vid) || !$listed} continue
1529            set a $varcid($vid)
1530        }
1531        if {$listed} {
1532            set olds [lrange $ids 1 end]
1533        } else {
1534            set olds {}
1535        }
1536        set commitdata($id) [string range $cmit [expr {$j + 1}] end]
1537        set cmitlisted($vid) $listed
1538        set parents($vid) $olds
1539        if {![info exists children($vid)]} {
1540            set children($vid) {}
1541        } elseif {$a == 0 && [llength $children($vid)] == 1} {
1542            set k [lindex $children($vid) 0]
1543            if {[llength $parents($view,$k)] == 1 &&
1544                (!$vdatemode($view) ||
1545                 $varcid($view,$k) == [llength $varctok($view)] - 1)} {
1546                set a $varcid($view,$k)
1547            }
1548        }
1549        if {$a == 0} {
1550            # new arc
1551            set a [newvarc $view $id]
1552        }
1553        if {[string compare [lindex $varctok($view) $a] $vtokmod($view)] < 0} {
1554            modify_arc $view $a
1555        }
1556        if {![info exists varcid($vid)]} {
1557            set varcid($vid) $a
1558            lappend varccommits($view,$a) $id
1559            incr commitidx($view)
1560        }
1561
1562        set i 0
1563        foreach p $olds {
1564            if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
1565                set vp $view,$p
1566                if {[llength [lappend children($vp) $id]] > 1 &&
1567                    [vtokcmp $view [lindex $children($vp) end-1] $id] > 0} {
1568                    set children($vp) [lsort -command [list vtokcmp $view] \
1569                                           $children($vp)]
1570                    catch {unset ordertok}
1571                }
1572                if {[info exists varcid($view,$p)]} {
1573                    fix_reversal $p $a $view
1574                }
1575            }
1576            incr i
1577        }
1578
1579        set scripts [check_interest $id $scripts]
1580        set gotsome 1
1581    }
1582    if {$gotsome} {
1583        global numcommits hlview
1584
1585        if {$view == $curview} {
1586            set numcommits $commitidx($view)
1587            run chewcommits
1588        }
1589        if {[info exists hlview] && $view == $hlview} {
1590            # we never actually get here...
1591            run vhighlightmore
1592        }
1593        foreach s $scripts {
1594            eval $s
1595        }
1596    }
1597    return 2
1598}
1599
1600proc chewcommits {} {
1601    global curview hlview viewcomplete
1602    global pending_select
1603
1604    layoutmore
1605    if {$viewcomplete($curview)} {
1606        global commitidx varctok
1607        global numcommits startmsecs
1608
1609        if {[info exists pending_select]} {
1610            update
1611            reset_pending_select {}
1612
1613            if {[commitinview $pending_select $curview]} {
1614                selectline [rowofcommit $pending_select] 1
1615            } else {
1616                set row [first_real_row]
1617                selectline $row 1
1618            }
1619        }
1620        if {$commitidx($curview) > 0} {
1621            #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
1622            #puts "overall $ms ms for $numcommits commits"
1623            #puts "[llength $varctok($view)] arcs, $commitidx($view) commits"
1624        } else {
1625            show_status [mc "No commits selected"]
1626        }
1627        notbusy layout
1628    }
1629    return 0
1630}
1631
1632proc do_readcommit {id} {
1633    global tclencoding
1634
1635    # Invoke git-log to handle automatic encoding conversion
1636    set fd [open [concat | git log --no-color --pretty=raw -1 $id] r]
1637    # Read the results using i18n.logoutputencoding
1638    fconfigure $fd -translation lf -eofchar {}
1639    if {$tclencoding != {}} {
1640        fconfigure $fd -encoding $tclencoding
1641    }
1642    set contents [read $fd]
1643    close $fd
1644    # Remove the heading line
1645    regsub {^commit [0-9a-f]+\n} $contents {} contents
1646
1647    return $contents
1648}
1649
1650proc readcommit {id} {
1651    if {[catch {set contents [do_readcommit $id]}]} return
1652    parsecommit $id $contents 1
1653}
1654
1655proc parsecommit {id contents listed} {
1656    global commitinfo
1657
1658    set inhdr 1
1659    set comment {}
1660    set headline {}
1661    set auname {}
1662    set audate {}
1663    set comname {}
1664    set comdate {}
1665    set hdrend [string first "\n\n" $contents]
1666    if {$hdrend < 0} {
1667        # should never happen...
1668        set hdrend [string length $contents]
1669    }
1670    set header [string range $contents 0 [expr {$hdrend - 1}]]
1671    set comment [string range $contents [expr {$hdrend + 2}] end]
1672    foreach line [split $header "\n"] {
1673        set line [split $line " "]
1674        set tag [lindex $line 0]
1675        if {$tag == "author"} {
1676            set audate [lrange $line end-1 end]
1677            set auname [join [lrange $line 1 end-2] " "]
1678        } elseif {$tag == "committer"} {
1679            set comdate [lrange $line end-1 end]
1680            set comname [join [lrange $line 1 end-2] " "]
1681        }
1682    }
1683    set headline {}
1684    # take the first non-blank line of the comment as the headline
1685    set headline [string trimleft $comment]
1686    set i [string first "\n" $headline]
1687    if {$i >= 0} {
1688        set headline [string range $headline 0 $i]
1689    }
1690    set headline [string trimright $headline]
1691    set i [string first "\r" $headline]
1692    if {$i >= 0} {
1693        set headline [string trimright [string range $headline 0 $i]]
1694    }
1695    if {!$listed} {
1696        # git log indents the comment by 4 spaces;
1697        # if we got this via git cat-file, add the indentation
1698        set newcomment {}
1699        foreach line [split $comment "\n"] {
1700            append newcomment "    "
1701            append newcomment $line
1702            append newcomment "\n"
1703        }
1704        set comment $newcomment
1705    }
1706    set hasnote [string first "\nNotes:\n" $contents]
1707    set commitinfo($id) [list $headline $auname $audate \
1708                             $comname $comdate $comment $hasnote]
1709}
1710
1711proc getcommit {id} {
1712    global commitdata commitinfo
1713
1714    if {[info exists commitdata($id)]} {
1715        parsecommit $id $commitdata($id) 1
1716    } else {
1717        readcommit $id
1718        if {![info exists commitinfo($id)]} {
1719            set commitinfo($id) [list [mc "No commit information available"]]
1720        }
1721    }
1722    return 1
1723}
1724
1725# Expand an abbreviated commit ID to a list of full 40-char IDs that match
1726# and are present in the current view.
1727# This is fairly slow...
1728proc longid {prefix} {
1729    global varcid curview vshortids
1730
1731    set ids {}
1732    if {[string length $prefix] >= 4} {
1733        set vshortid $curview,[string range $prefix 0 3]
1734        if {[info exists vshortids($vshortid)]} {
1735            foreach id $vshortids($vshortid) {
1736                if {[string match "$prefix*" $id]} {
1737                    if {[lsearch -exact $ids $id] < 0} {
1738                        lappend ids $id
1739                        if {[llength $ids] >= 2} break
1740                    }
1741                }
1742            }
1743        }
1744    } else {
1745        foreach match [array names varcid "$curview,$prefix*"] {
1746            lappend ids [lindex [split $match ","] 1]
1747            if {[llength $ids] >= 2} break
1748        }
1749    }
1750    return $ids
1751}
1752
1753proc readrefs {} {
1754    global tagids idtags headids idheads tagobjid
1755    global otherrefids idotherrefs mainhead mainheadid
1756    global selecthead selectheadid
1757    global hideremotes
1758
1759    foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
1760        catch {unset $v}
1761    }
1762    set refd [open [list | git show-ref -d] r]
1763    while {[gets $refd line] >= 0} {
1764        if {[string index $line 40] ne " "} continue
1765        set id [string range $line 0 39]
1766        set ref [string range $line 41 end]
1767        if {![string match "refs/*" $ref]} continue
1768        set name [string range $ref 5 end]
1769        if {[string match "remotes/*" $name]} {
1770            if {![string match "*/HEAD" $name] && !$hideremotes} {
1771                set headids($name) $id
1772                lappend idheads($id) $name
1773            }
1774        } elseif {[string match "heads/*" $name]} {
1775            set name [string range $name 6 end]
1776            set headids($name) $id
1777            lappend idheads($id) $name
1778        } elseif {[string match "tags/*" $name]} {
1779            # this lets refs/tags/foo^{} overwrite refs/tags/foo,
1780            # which is what we want since the former is the commit ID
1781            set name [string range $name 5 end]
1782            if {[string match "*^{}" $name]} {
1783                set name [string range $name 0 end-3]
1784            } else {
1785                set tagobjid($name) $id
1786            }
1787            set tagids($name) $id
1788            lappend idtags($id) $name
1789        } else {
1790            set otherrefids($name) $id
1791            lappend idotherrefs($id) $name
1792        }
1793    }
1794    catch {close $refd}
1795    set mainhead {}
1796    set mainheadid {}
1797    catch {
1798        set mainheadid [exec git rev-parse HEAD]
1799        set thehead [exec git symbolic-ref HEAD]
1800        if {[string match "refs/heads/*" $thehead]} {
1801            set mainhead [string range $thehead 11 end]
1802        }
1803    }
1804    set selectheadid {}
1805    if {$selecthead ne {}} {
1806        catch {
1807            set selectheadid [exec git rev-parse --verify $selecthead]
1808        }
1809    }
1810}
1811
1812# skip over fake commits
1813proc first_real_row {} {
1814    global nullid nullid2 numcommits
1815
1816    for {set row 0} {$row < $numcommits} {incr row} {
1817        set id [commitonrow $row]
1818        if {$id ne $nullid && $id ne $nullid2} {
1819            break
1820        }
1821    }
1822    return $row
1823}
1824
1825# update things for a head moved to a child of its previous location
1826proc movehead {id name} {
1827    global headids idheads
1828
1829    removehead $headids($name) $name
1830    set headids($name) $id
1831    lappend idheads($id) $name
1832}
1833
1834# update things when a head has been removed
1835proc removehead {id name} {
1836    global headids idheads
1837
1838    if {$idheads($id) eq $name} {
1839        unset idheads($id)
1840    } else {
1841        set i [lsearch -exact $idheads($id) $name]
1842        if {$i >= 0} {
1843            set idheads($id) [lreplace $idheads($id) $i $i]
1844        }
1845    }
1846    unset headids($name)
1847}
1848
1849proc ttk_toplevel {w args} {
1850    global use_ttk
1851    eval [linsert $args 0 ::toplevel $w]
1852    if {$use_ttk} {
1853        place [ttk::frame $w._toplevel_background] -x 0 -y 0 -relwidth 1 -relheight 1
1854    }
1855    return $w
1856}
1857
1858proc make_transient {window origin} {
1859    global have_tk85
1860
1861    # In MacOS Tk 8.4 transient appears to work by setting
1862    # overrideredirect, which is utterly useless, since the
1863    # windows get no border, and are not even kept above
1864    # the parent.
1865    if {!$have_tk85 && [tk windowingsystem] eq {aqua}} return
1866
1867    wm transient $window $origin
1868
1869    # Windows fails to place transient windows normally, so
1870    # schedule a callback to center them on the parent.
1871    if {[tk windowingsystem] eq {win32}} {
1872        after idle [list tk::PlaceWindow $window widget $origin]
1873    }
1874}
1875
1876proc show_error {w top msg {mc mc}} {
1877    global NS
1878    if {![info exists NS]} {set NS ""}
1879    if {[wm state $top] eq "withdrawn"} { wm deiconify $top }
1880    message $w.m -text $msg -justify center -aspect 400
1881    pack $w.m -side top -fill x -padx 20 -pady 20
1882    ${NS}::button $w.ok -default active -text [$mc OK] -command "destroy $top"
1883    pack $w.ok -side bottom -fill x
1884    bind $top <Visibility> "grab $top; focus $top"
1885    bind $top <Key-Return> "destroy $top"
1886    bind $top <Key-space>  "destroy $top"
1887    bind $top <Key-Escape> "destroy $top"
1888    tkwait window $top
1889}
1890
1891proc error_popup {msg {owner .}} {
1892    if {[tk windowingsystem] eq "win32"} {
1893        tk_messageBox -icon error -type ok -title [wm title .] \
1894            -parent $owner -message $msg
1895    } else {
1896        set w .error
1897        ttk_toplevel $w
1898        make_transient $w $owner
1899        show_error $w $w $msg
1900    }
1901}
1902
1903proc confirm_popup {msg {owner .}} {
1904    global confirm_ok NS
1905    set confirm_ok 0
1906    set w .confirm
1907    ttk_toplevel $w
1908    make_transient $w $owner
1909    message $w.m -text $msg -justify center -aspect 400
1910    pack $w.m -side top -fill x -padx 20 -pady 20
1911    ${NS}::button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
1912    pack $w.ok -side left -fill x
1913    ${NS}::button $w.cancel -text [mc Cancel] -command "destroy $w"
1914    pack $w.cancel -side right -fill x
1915    bind $w <Visibility> "grab $w; focus $w"
1916    bind $w <Key-Return> "set confirm_ok 1; destroy $w"
1917    bind $w <Key-space>  "set confirm_ok 1; destroy $w"
1918    bind $w <Key-Escape> "destroy $w"
1919    tk::PlaceWindow $w widget $owner
1920    tkwait window $w
1921    return $confirm_ok
1922}
1923
1924proc setoptions {} {
1925    if {[tk windowingsystem] ne "win32"} {
1926        option add *Panedwindow.showHandle 1 startupFile
1927        option add *Panedwindow.sashRelief raised startupFile
1928        if {[tk windowingsystem] ne "aqua"} {
1929            option add *Menu.font uifont startupFile
1930        }
1931    } else {
1932        option add *Menu.TearOff 0 startupFile
1933    }
1934    option add *Button.font uifont startupFile
1935    option add *Checkbutton.font uifont startupFile
1936    option add *Radiobutton.font uifont startupFile
1937    option add *Menubutton.font uifont startupFile
1938    option add *Label.font uifont startupFile
1939    option add *Message.font uifont startupFile
1940    option add *Entry.font textfont startupFile
1941    option add *Text.font textfont startupFile
1942    option add *Labelframe.font uifont startupFile
1943    option add *Spinbox.font textfont startupFile
1944    option add *Listbox.font mainfont startupFile
1945}
1946
1947# Make a menu and submenus.
1948# m is the window name for the menu, items is the list of menu items to add.
1949# Each item is a list {mc label type description options...}
1950# mc is ignored; it's so we can put mc there to alert xgettext
1951# label is the string that appears in the menu
1952# type is cascade, command or radiobutton (should add checkbutton)
1953# description depends on type; it's the sublist for cascade, the
1954# command to invoke for command, or {variable value} for radiobutton
1955proc makemenu {m items} {
1956    menu $m
1957    if {[tk windowingsystem] eq {aqua}} {
1958        set Meta1 Cmd
1959    } else {
1960        set Meta1 Ctrl
1961    }
1962    foreach i $items {
1963        set name [mc [lindex $i 1]]
1964        set type [lindex $i 2]
1965        set thing [lindex $i 3]
1966        set params [list $type]
1967        if {$name ne {}} {
1968            set u [string first "&" [string map {&& x} $name]]
1969            lappend params -label [string map {&& & & {}} $name]
1970            if {$u >= 0} {
1971                lappend params -underline $u
1972            }
1973        }
1974        switch -- $type {
1975            "cascade" {
1976                set submenu [string tolower [string map {& ""} [lindex $i 1]]]
1977                lappend params -menu $m.$submenu
1978            }
1979            "command" {
1980                lappend params -command $thing
1981            }
1982            "radiobutton" {
1983                lappend params -variable [lindex $thing 0] \
1984                    -value [lindex $thing 1]
1985            }
1986        }
1987        set tail [lrange $i 4 end]
1988        regsub -all {\yMeta1\y} $tail $Meta1 tail
1989        eval $m add $params $tail
1990        if {$type eq "cascade"} {
1991            makemenu $m.$submenu $thing
1992        }
1993    }
1994}
1995
1996# translate string and remove ampersands
1997proc mca {str} {
1998    return [string map {&& & & {}} [mc $str]]
1999}
2000
2001proc makedroplist {w varname args} {
2002    global use_ttk
2003    if {$use_ttk} {
2004        set width 0
2005        foreach label $args {
2006            set cx [string length $label]
2007            if {$cx > $width} {set width $cx}
2008        }
2009        set gm [ttk::combobox $w -width $width -state readonly\
2010                    -textvariable $varname -values $args]
2011    } else {
2012        set gm [eval [linsert $args 0 tk_optionMenu $w $varname]]
2013    }
2014    return $gm
2015}
2016
2017proc makewindow {} {
2018    global canv canv2 canv3 linespc charspc ctext cflist cscroll
2019    global tabstop
2020    global findtype findtypemenu findloc findstring fstring geometry
2021    global entries sha1entry sha1string sha1but
2022    global diffcontextstring diffcontext
2023    global ignorespace
2024    global maincursor textcursor curtextcursor
2025    global rowctxmenu fakerowmenu mergemax wrapcomment
2026    global highlight_files gdttype
2027    global searchstring sstring
2028    global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
2029    global headctxmenu progresscanv progressitem progresscoords statusw
2030    global fprogitem fprogcoord lastprogupdate progupdatepending
2031    global rprogitem rprogcoord rownumsel numcommits
2032    global have_tk85 use_ttk NS
2033    global git_version
2034    global worddiff
2035
2036    # The "mc" arguments here are purely so that xgettext
2037    # sees the following string as needing to be translated
2038    set file {
2039        mc "File" cascade {
2040            {mc "Update" command updatecommits -accelerator F5}
2041            {mc "Reload" command reloadcommits -accelerator Shift-F5}
2042            {mc "Reread references" command rereadrefs}
2043            {mc "List references" command showrefs -accelerator F2}
2044            {xx "" separator}
2045            {mc "Start git gui" command {exec git gui &}}
2046            {xx "" separator}
2047            {mc "Quit" command doquit -accelerator Meta1-Q}
2048        }}
2049    set edit {
2050        mc "Edit" cascade {
2051            {mc "Preferences" command doprefs}
2052        }}
2053    set view {
2054        mc "View" cascade {
2055            {mc "New view..." command {newview 0} -accelerator Shift-F4}
2056            {mc "Edit view..." command editview -state disabled -accelerator F4}
2057            {mc "Delete view" command delview -state disabled}
2058            {xx "" separator}
2059            {mc "All files" radiobutton {selectedview 0} -command {showview 0}}
2060        }}
2061    if {[tk windowingsystem] ne "aqua"} {
2062        set help {
2063        mc "Help" cascade {
2064            {mc "About gitk" command about}
2065            {mc "Key bindings" command keys}
2066        }}
2067        set bar [list $file $edit $view $help]
2068    } else {
2069        proc ::tk::mac::ShowPreferences {} {doprefs}
2070        proc ::tk::mac::Quit {} {doquit}
2071        lset file end [lreplace [lindex $file end] end-1 end]
2072        set apple {
2073        xx "Apple" cascade {
2074            {mc "About gitk" command about}
2075            {xx "" separator}
2076        }}
2077        set help {
2078        mc "Help" cascade {
2079            {mc "Key bindings" command keys}
2080        }}
2081        set bar [list $apple $file $view $help]
2082    }
2083    makemenu .bar $bar
2084    . configure -menu .bar
2085
2086    if {$use_ttk} {
2087        # cover the non-themed toplevel with a themed frame.
2088        place [ttk::frame ._main_background] -x 0 -y 0 -relwidth 1 -relheight 1
2089    }
2090
2091    # the gui has upper and lower half, parts of a paned window.
2092    ${NS}::panedwindow .ctop -orient vertical
2093
2094    # possibly use assumed geometry
2095    if {![info exists geometry(pwsash0)]} {
2096        set geometry(topheight) [expr {15 * $linespc}]
2097        set geometry(topwidth) [expr {80 * $charspc}]
2098        set geometry(botheight) [expr {15 * $linespc}]
2099        set geometry(botwidth) [expr {50 * $charspc}]
2100        set geometry(pwsash0) [list [expr {40 * $charspc}] 2]
2101        set geometry(pwsash1) [list [expr {60 * $charspc}] 2]
2102    }
2103
2104    # the upper half will have a paned window, a scroll bar to the right, and some stuff below
2105    ${NS}::frame .tf -height $geometry(topheight) -width $geometry(topwidth)
2106    ${NS}::frame .tf.histframe
2107    ${NS}::panedwindow .tf.histframe.pwclist -orient horizontal
2108    if {!$use_ttk} {
2109        .tf.histframe.pwclist configure -sashpad 0 -handlesize 4
2110    }
2111
2112    # create three canvases
2113    set cscroll .tf.histframe.csb
2114    set canv .tf.histframe.pwclist.canv
2115    canvas $canv \
2116        -selectbackground $selectbgcolor \
2117        -background $bgcolor -bd 0 \
2118        -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
2119    .tf.histframe.pwclist add $canv
2120    set canv2 .tf.histframe.pwclist.canv2
2121    canvas $canv2 \
2122        -selectbackground $selectbgcolor \
2123        -background $bgcolor -bd 0 -yscrollincr $linespc
2124    .tf.histframe.pwclist add $canv2
2125    set canv3 .tf.histframe.pwclist.canv3
2126    canvas $canv3 \
2127        -selectbackground $selectbgcolor \
2128        -background $bgcolor -bd 0 -yscrollincr $linespc
2129    .tf.histframe.pwclist add $canv3
2130    if {$use_ttk} {
2131        bind .tf.histframe.pwclist <Map> {
2132            bind %W <Map> {}
2133            .tf.histframe.pwclist sashpos 1 [lindex $::geometry(pwsash1) 0]
2134            .tf.histframe.pwclist sashpos 0 [lindex $::geometry(pwsash0) 0]
2135        }
2136    } else {
2137        eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
2138        eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
2139    }
2140
2141    # a scroll bar to rule them
2142    ${NS}::scrollbar $cscroll -command {allcanvs yview}
2143    if {!$use_ttk} {$cscroll configure -highlightthickness 0}
2144    pack $cscroll -side right -fill y
2145    bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
2146    lappend bglist $canv $canv2 $canv3
2147    pack .tf.histframe.pwclist -fill both -expand 1 -side left
2148
2149    # we have two button bars at bottom of top frame. Bar 1
2150    ${NS}::frame .tf.bar
2151    ${NS}::frame .tf.lbar -height 15
2152
2153    set sha1entry .tf.bar.sha1
2154    set entries $sha1entry
2155    set sha1but .tf.bar.sha1label
2156    button $sha1but -text "[mc "SHA1 ID:"] " -state disabled -relief flat \
2157        -command gotocommit -width 8
2158    $sha1but conf -disabledforeground [$sha1but cget -foreground]
2159    pack .tf.bar.sha1label -side left
2160    ${NS}::entry $sha1entry -width 40 -font textfont -textvariable sha1string
2161    trace add variable sha1string write sha1change
2162    pack $sha1entry -side left -pady 2
2163
2164    image create bitmap bm-left -data {
2165        #define left_width 16
2166        #define left_height 16
2167        static unsigned char left_bits[] = {
2168        0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
2169        0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
2170        0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
2171    }
2172    image create bitmap bm-right -data {
2173        #define right_width 16
2174        #define right_height 16
2175        static unsigned char right_bits[] = {
2176        0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
2177        0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
2178        0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
2179    }
2180    ${NS}::button .tf.bar.leftbut -image bm-left -command goback \
2181        -state disabled -width 26
2182    pack .tf.bar.leftbut -side left -fill y
2183    ${NS}::button .tf.bar.rightbut -image bm-right -command goforw \
2184        -state disabled -width 26
2185    pack .tf.bar.rightbut -side left -fill y
2186
2187    ${NS}::label .tf.bar.rowlabel -text [mc "Row"]
2188    set rownumsel {}
2189    ${NS}::label .tf.bar.rownum -width 7 -textvariable rownumsel \
2190        -relief sunken -anchor e
2191    ${NS}::label .tf.bar.rowlabel2 -text "/"
2192    ${NS}::label .tf.bar.numcommits -width 7 -textvariable numcommits \
2193        -relief sunken -anchor e
2194    pack .tf.bar.rowlabel .tf.bar.rownum .tf.bar.rowlabel2 .tf.bar.numcommits \
2195        -side left
2196    if {!$use_ttk} {
2197        foreach w {rownum numcommits} {.tf.bar.$w configure -font textfont}
2198    }
2199    global selectedline
2200    trace add variable selectedline write selectedline_change
2201
2202    # Status label and progress bar
2203    set statusw .tf.bar.status
2204    ${NS}::label $statusw -width 15 -relief sunken
2205    pack $statusw -side left -padx 5
2206    if {$use_ttk} {
2207        set progresscanv [ttk::progressbar .tf.bar.progress]
2208    } else {
2209        set h [expr {[font metrics uifont -linespace] + 2}]
2210        set progresscanv .tf.bar.progress
2211        canvas $progresscanv -relief sunken -height $h -borderwidth 2
2212        set progressitem [$progresscanv create rect -1 0 0 $h -fill green]
2213        set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow]
2214        set rprogitem [$progresscanv create rect -1 0 0 $h -fill red]
2215    }
2216    pack $progresscanv -side right -expand 1 -fill x -padx {0 2}
2217    set progresscoords {0 0}
2218    set fprogcoord 0
2219    set rprogcoord 0
2220    bind $progresscanv <Configure> adjustprogress
2221    set lastprogupdate [clock clicks -milliseconds]
2222    set progupdatepending 0
2223
2224    # build up the bottom bar of upper window
2225    ${NS}::label .tf.lbar.flabel -text "[mc "Find"] "
2226    ${NS}::button .tf.lbar.fnext -text [mc "next"] -command {dofind 1 1}
2227    ${NS}::button .tf.lbar.fprev -text [mc "prev"] -command {dofind -1 1}
2228    ${NS}::label .tf.lbar.flab2 -text " [mc "commit"] "
2229    pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \
2230        -side left -fill y
2231    set gdttype [mc "containing:"]
2232    set gm [makedroplist .tf.lbar.gdttype gdttype \
2233                [mc "containing:"] \
2234                [mc "touching paths:"] \
2235                [mc "adding/removing string:"]]
2236    trace add variable gdttype write gdttype_change
2237    pack .tf.lbar.gdttype -side left -fill y
2238
2239    set findstring {}
2240    set fstring .tf.lbar.findstring
2241    lappend entries $fstring
2242    ${NS}::entry $fstring -width 30 -textvariable findstring
2243    trace add variable findstring write find_change
2244    set findtype [mc "Exact"]
2245    set findtypemenu [makedroplist .tf.lbar.findtype \
2246                          findtype [mc "Exact"] [mc "IgnCase"] [mc "Regexp"]]
2247    trace add variable findtype write findcom_change
2248    set findloc [mc "All fields"]
2249    makedroplist .tf.lbar.findloc findloc [mc "All fields"] [mc "Headline"] \
2250        [mc "Comments"] [mc "Author"] [mc "Committer"]
2251    trace add variable findloc write find_change
2252    pack .tf.lbar.findloc -side right
2253    pack .tf.lbar.findtype -side right
2254    pack $fstring -side left -expand 1 -fill x
2255
2256    # Finish putting the upper half of the viewer together
2257    pack .tf.lbar -in .tf -side bottom -fill x
2258    pack .tf.bar -in .tf -side bottom -fill x
2259    pack .tf.histframe -fill both -side top -expand 1
2260    .ctop add .tf
2261    if {!$use_ttk} {
2262        .ctop paneconfigure .tf -height $geometry(topheight)
2263        .ctop paneconfigure .tf -width $geometry(topwidth)
2264    }
2265
2266    # now build up the bottom
2267    ${NS}::panedwindow .pwbottom -orient horizontal
2268
2269    # lower left, a text box over search bar, scroll bar to the right
2270    # if we know window height, then that will set the lower text height, otherwise
2271    # we set lower text height which will drive window height
2272    if {[info exists geometry(main)]} {
2273        ${NS}::frame .bleft -width $geometry(botwidth)
2274    } else {
2275        ${NS}::frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
2276    }
2277    ${NS}::frame .bleft.top
2278    ${NS}::frame .bleft.mid
2279    ${NS}::frame .bleft.bottom
2280
2281    ${NS}::button .bleft.top.search -text [mc "Search"] -command dosearch
2282    pack .bleft.top.search -side left -padx 5
2283    set sstring .bleft.top.sstring
2284    set searchstring ""
2285    ${NS}::entry $sstring -width 20 -textvariable searchstring
2286    lappend entries $sstring
2287    trace add variable searchstring write incrsearch
2288    pack $sstring -side left -expand 1 -fill x
2289    ${NS}::radiobutton .bleft.mid.diff -text [mc "Diff"] \
2290        -command changediffdisp -variable diffelide -value {0 0}
2291    ${NS}::radiobutton .bleft.mid.old -text [mc "Old version"] \
2292        -command changediffdisp -variable diffelide -value {0 1}
2293    ${NS}::radiobutton .bleft.mid.new -text [mc "New version"] \
2294        -command changediffdisp -variable diffelide -value {1 0}
2295    ${NS}::label .bleft.mid.labeldiffcontext -text "      [mc "Lines of context"]: "
2296    pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
2297    spinbox .bleft.mid.diffcontext -width 5 \
2298        -from 0 -increment 1 -to 10000000 \
2299        -validate all -validatecommand "diffcontextvalidate %P" \
2300        -textvariable diffcontextstring
2301    .bleft.mid.diffcontext set $diffcontext
2302    trace add variable diffcontextstring write diffcontextchange
2303    lappend entries .bleft.mid.diffcontext
2304    pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left
2305    ${NS}::checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
2306        -command changeignorespace -variable ignorespace
2307    pack .bleft.mid.ignspace -side left -padx 5
2308
2309    set worddiff [mc "Line diff"]
2310    if {[package vcompare $git_version "1.7.2"] >= 0} {
2311        makedroplist .bleft.mid.worddiff worddiff [mc "Line diff"] \
2312            [mc "Markup words"] [mc "Color words"]
2313        trace add variable worddiff write changeworddiff
2314        pack .bleft.mid.worddiff -side left -padx 5
2315    }
2316
2317    set ctext .bleft.bottom.ctext
2318    text $ctext -background $bgcolor -foreground $fgcolor \
2319        -state disabled -font textfont \
2320        -yscrollcommand scrolltext -wrap none \
2321        -xscrollcommand ".bleft.bottom.sbhorizontal set"
2322    if {$have_tk85} {
2323        $ctext conf -tabstyle wordprocessor
2324    }
2325    ${NS}::scrollbar .bleft.bottom.sb -command "$ctext yview"
2326    ${NS}::scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h
2327    pack .bleft.top -side top -fill x
2328    pack .bleft.mid -side top -fill x
2329    grid $ctext .bleft.bottom.sb -sticky nsew
2330    grid .bleft.bottom.sbhorizontal -sticky ew
2331    grid columnconfigure .bleft.bottom 0 -weight 1
2332    grid rowconfigure .bleft.bottom 0 -weight 1
2333    grid rowconfigure .bleft.bottom 1 -weight 0
2334    pack .bleft.bottom -side top -fill both -expand 1
2335    lappend bglist $ctext
2336    lappend fglist $ctext
2337
2338    $ctext tag conf comment -wrap $wrapcomment
2339    $ctext tag conf filesep -font textfontbold -back "#aaaaaa"
2340    $ctext tag conf hunksep -fore [lindex $diffcolors 2]
2341    $ctext tag conf d0 -fore [lindex $diffcolors 0]
2342    $ctext tag conf dresult -fore [lindex $diffcolors 1]
2343    $ctext tag conf m0 -fore red
2344    $ctext tag conf m1 -fore blue
2345    $ctext tag conf m2 -fore green
2346    $ctext tag conf m3 -fore purple
2347    $ctext tag conf m4 -fore brown
2348    $ctext tag conf m5 -fore "#009090"
2349    $ctext tag conf m6 -fore magenta
2350    $ctext tag conf m7 -fore "#808000"
2351    $ctext tag conf m8 -fore "#009000"
2352    $ctext tag conf m9 -fore "#ff0080"
2353    $ctext tag conf m10 -fore cyan
2354    $ctext tag conf m11 -fore "#b07070"
2355    $ctext tag conf m12 -fore "#70b0f0"
2356    $ctext tag conf m13 -fore "#70f0b0"
2357    $ctext tag conf m14 -fore "#f0b070"
2358    $ctext tag conf m15 -fore "#ff70b0"
2359    $ctext tag conf mmax -fore darkgrey
2360    set mergemax 16
2361    $ctext tag conf mresult -font textfontbold
2362    $ctext tag conf msep -font textfontbold
2363    $ctext tag conf found -back yellow
2364
2365    .pwbottom add .bleft
2366    if {!$use_ttk} {
2367        .pwbottom paneconfigure .bleft -width $geometry(botwidth)
2368    }
2369
2370    # lower right
2371    ${NS}::frame .bright
2372    ${NS}::frame .bright.mode
2373    ${NS}::radiobutton .bright.mode.patch -text [mc "Patch"] \
2374        -command reselectline -variable cmitmode -value "patch"
2375    ${NS}::radiobutton .bright.mode.tree -text [mc "Tree"] \
2376        -command reselectline -variable cmitmode -value "tree"
2377    grid .bright.mode.patch .bright.mode.tree -sticky ew
2378    pack .bright.mode -side top -fill x
2379    set cflist .bright.cfiles
2380    set indent [font measure mainfont "nn"]
2381    text $cflist \
2382        -selectbackground $selectbgcolor \
2383        -background $bgcolor -foreground $fgcolor \
2384        -font mainfont \
2385        -tabs [list $indent [expr {2 * $indent}]] \
2386        -yscrollcommand ".bright.sb set" \
2387        -cursor [. cget -cursor] \
2388        -spacing1 1 -spacing3 1
2389    lappend bglist $cflist
2390    lappend fglist $cflist
2391    ${NS}::scrollbar .bright.sb -command "$cflist yview"
2392    pack .bright.sb -side right -fill y
2393    pack $cflist -side left -fill both -expand 1
2394    $cflist tag configure highlight \
2395        -background [$cflist cget -selectbackground]
2396    $cflist tag configure bold -font mainfontbold
2397
2398    .pwbottom add .bright
2399    .ctop add .pwbottom
2400
2401    # restore window width & height if known
2402    if {[info exists geometry(main)]} {
2403        if {[scan $geometry(main) "%dx%d" w h] >= 2} {
2404            if {$w > [winfo screenwidth .]} {
2405                set w [winfo screenwidth .]
2406            }
2407            if {$h > [winfo screenheight .]} {
2408                set h [winfo screenheight .]
2409            }
2410            wm geometry . "${w}x$h"
2411        }
2412    }
2413
2414    if {[info exists geometry(state)] && $geometry(state) eq "zoomed"} {
2415        wm state . $geometry(state)
2416    }
2417
2418    if {[tk windowingsystem] eq {aqua}} {
2419        set M1B M1
2420        set ::BM "3"
2421    } else {
2422        set M1B Control
2423        set ::BM "2"
2424    }
2425
2426    if {$use_ttk} {
2427        bind .ctop <Map> {
2428            bind %W <Map> {}
2429            %W sashpos 0 $::geometry(topheight)
2430        }
2431        bind .pwbottom <Map> {
2432            bind %W <Map> {}
2433            %W sashpos 0 $::geometry(botwidth)
2434        }
2435    }
2436
2437    bind .pwbottom <Configure> {resizecdetpanes %W %w}
2438    pack .ctop -fill both -expand 1
2439    bindall <1> {selcanvline %W %x %y}
2440    #bindall <B1-Motion> {selcanvline %W %x %y}
2441    if {[tk windowingsystem] == "win32"} {
2442        bind . <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D }
2443        bind $ctext <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D ; break }
2444    } else {
2445        bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
2446        bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
2447        if {[tk windowingsystem] eq "aqua"} {
2448            bindall <MouseWheel> {
2449                set delta [expr {- (%D)}]
2450                allcanvs yview scroll $delta units
2451            }
2452            bindall <Shift-MouseWheel> {
2453                set delta [expr {- (%D)}]
2454                $canv xview scroll $delta units
2455            }
2456        }
2457    }
2458    bindall <$::BM> "canvscan mark %W %x %y"
2459    bindall <B$::BM-Motion> "canvscan dragto %W %x %y"
2460    bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
2461    bind . <$M1B-Key-w> doquit
2462    bindkey <Home> selfirstline
2463    bindkey <End> sellastline
2464    bind . <Key-Up> "selnextline -1"
2465    bind . <Key-Down> "selnextline 1"
2466    bind . <Shift-Key-Up> "dofind -1 0"
2467    bind . <Shift-Key-Down> "dofind 1 0"
2468    bindkey <Key-Right> "goforw"
2469    bindkey <Key-Left> "goback"
2470    bind . <Key-Prior> "selnextpage -1"
2471    bind . <Key-Next> "selnextpage 1"
2472    bind . <$M1B-Home> "allcanvs yview moveto 0.0"
2473    bind . <$M1B-End> "allcanvs yview moveto 1.0"
2474    bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
2475    bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
2476    bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
2477    bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
2478    bindkey <Key-Delete> "$ctext yview scroll -1 pages"
2479    bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
2480    bindkey <Key-space> "$ctext yview scroll 1 pages"
2481    bindkey p "selnextline -1"
2482    bindkey n "selnextline 1"
2483    bindkey z "goback"
2484    bindkey x "goforw"
2485    bindkey k "selnextline -1"
2486    bindkey j "selnextline 1"
2487    bindkey h "goback"
2488    bindkey l "goforw"
2489    bindkey b prevfile
2490    bindkey d "$ctext yview scroll 18 units"
2491    bindkey u "$ctext yview scroll -18 units"
2492    bindkey / {focus $fstring}
2493    bindkey <Key-KP_Divide> {focus $fstring}
2494    bindkey <Key-Return> {dofind 1 1}
2495    bindkey ? {dofind -1 1}
2496    bindkey f nextfile
2497    bind . <F5> updatecommits
2498    bind . <Shift-F5> reloadcommits
2499    bind . <F2> showrefs
2500    bind . <Shift-F4> {newview 0}
2501    catch { bind . <Shift-Key-XF86_Switch_VT_4> {newview 0} }
2502    bind . <F4> edit_or_newview
2503    bind . <$M1B-q> doquit
2504    bind . <$M1B-f> {dofind 1 1}
2505    bind . <$M1B-g> {dofind 1 0}
2506    bind . <$M1B-r> dosearchback
2507    bind . <$M1B-s> dosearch
2508    bind . <$M1B-equal> {incrfont 1}
2509    bind . <$M1B-plus> {incrfont 1}
2510    bind . <$M1B-KP_Add> {incrfont 1}
2511    bind . <$M1B-minus> {incrfont -1}
2512    bind . <$M1B-KP_Subtract> {incrfont -1}
2513    wm protocol . WM_DELETE_WINDOW doquit
2514    bind . <Destroy> {stop_backends}
2515    bind . <Button-1> "click %W"
2516    bind $fstring <Key-Return> {dofind 1 1}
2517    bind $sha1entry <Key-Return> {gotocommit; break}
2518    bind $sha1entry <<PasteSelection>> clearsha1
2519    bind $cflist <1> {sel_flist %W %x %y; break}
2520    bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
2521    bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
2522    global ctxbut
2523    bind $cflist $ctxbut {pop_flist_menu %W %X %Y %x %y}
2524    bind $ctext $ctxbut {pop_diff_menu %W %X %Y %x %y}
2525    bind $ctext <Button-1> {focus %W}
2526
2527    set maincursor [. cget -cursor]
2528    set textcursor [$ctext cget -cursor]
2529    set curtextcursor $textcursor
2530
2531    set rowctxmenu .rowctxmenu
2532    makemenu $rowctxmenu {
2533        {mc "Diff this -> selected" command {diffvssel 0}}
2534        {mc "Diff selected -> this" command {diffvssel 1}}
2535        {mc "Make patch" command mkpatch}
2536        {mc "Create tag" command mktag}
2537        {mc "Write commit to file" command writecommit}
2538        {mc "Create new branch" command mkbranch}
2539        {mc "Cherry-pick this commit" command cherrypick}
2540        {mc "Reset HEAD branch to here" command resethead}
2541        {mc "Mark this commit" command markhere}
2542        {mc "Return to mark" command gotomark}
2543        {mc "Find descendant of this and mark" command find_common_desc}
2544        {mc "Compare with marked commit" command compare_commits}
2545        {mc "Diff this -> marked commit" command {diffvsmark 0}}
2546        {mc "Diff marked commit -> this" command {diffvsmark 1}}
2547    }
2548    $rowctxmenu configure -tearoff 0
2549
2550    set fakerowmenu .fakerowmenu
2551    makemenu $fakerowmenu {
2552        {mc "Diff this -> selected" command {diffvssel 0}}
2553        {mc "Diff selected -> this" command {diffvssel 1}}
2554        {mc "Make patch" command mkpatch}
2555        {mc "Diff this -> marked commit" command {diffvsmark 0}}
2556        {mc "Diff marked commit -> this" command {diffvsmark 1}}
2557    }
2558    $fakerowmenu configure -tearoff 0
2559
2560    set headctxmenu .headctxmenu
2561    makemenu $headctxmenu {
2562        {mc "Check out this branch" command cobranch}
2563        {mc "Remove this branch" command rmbranch}
2564    }
2565    $headctxmenu configure -tearoff 0
2566
2567    global flist_menu
2568    set flist_menu .flistctxmenu
2569    makemenu $flist_menu {
2570        {mc "Highlight this too" command {flist_hl 0}}
2571        {mc "Highlight this only" command {flist_hl 1}}
2572        {mc "External diff" command {external_diff}}
2573        {mc "Blame parent commit" command {external_blame 1}}
2574    }
2575    $flist_menu configure -tearoff 0
2576
2577    global diff_menu
2578    set diff_menu .diffctxmenu
2579    makemenu $diff_menu {
2580        {mc "Show origin of this line" command show_line_source}
2581        {mc "Run git gui blame on this line" command {external_blame_diff}}
2582    }
2583    $diff_menu configure -tearoff 0
2584}
2585
2586# Windows sends all mouse wheel events to the current focused window, not
2587# the one where the mouse hovers, so bind those events here and redirect
2588# to the correct window
2589proc windows_mousewheel_redirector {W X Y D} {
2590    global canv canv2 canv3
2591    set w [winfo containing -displayof $W $X $Y]
2592    if {$w ne ""} {
2593        set u [expr {$D < 0 ? 5 : -5}]
2594        if {$w == $canv || $w == $canv2 || $w == $canv3} {
2595            allcanvs yview scroll $u units
2596        } else {
2597            catch {
2598                $w yview scroll $u units
2599            }
2600        }
2601    }
2602}
2603
2604# Update row number label when selectedline changes
2605proc selectedline_change {n1 n2 op} {
2606    global selectedline rownumsel
2607
2608    if {$selectedline eq {}} {
2609        set rownumsel {}
2610    } else {
2611        set rownumsel [expr {$selectedline + 1}]
2612    }
2613}
2614
2615# mouse-2 makes all windows scan vertically, but only the one
2616# the cursor is in scans horizontally
2617proc canvscan {op w x y} {
2618    global canv canv2 canv3
2619    foreach c [list $canv $canv2 $canv3] {
2620        if {$c == $w} {
2621            $c scan $op $x $y
2622        } else {
2623            $c scan $op 0 $y
2624        }
2625    }
2626}
2627
2628proc scrollcanv {cscroll f0 f1} {
2629    $cscroll set $f0 $f1
2630    drawvisible
2631    flushhighlights
2632}
2633
2634# when we make a key binding for the toplevel, make sure
2635# it doesn't get triggered when that key is pressed in the
2636# find string entry widget.
2637proc bindkey {ev script} {
2638    global entries
2639    bind . $ev $script
2640    set escript [bind Entry $ev]
2641    if {$escript == {}} {
2642        set escript [bind Entry <Key>]
2643    }
2644    foreach e $entries {
2645        bind $e $ev "$escript; break"
2646    }
2647}
2648
2649# set the focus back to the toplevel for any click outside
2650# the entry widgets
2651proc click {w} {
2652    global ctext entries
2653    foreach e [concat $entries $ctext] {
2654        if {$w == $e} return
2655    }
2656    focus .
2657}
2658
2659# Adjust the progress bar for a change in requested extent or canvas size
2660proc adjustprogress {} {
2661    global progresscanv progressitem progresscoords
2662    global fprogitem fprogcoord lastprogupdate progupdatepending
2663    global rprogitem rprogcoord use_ttk
2664
2665    if {$use_ttk} {
2666        $progresscanv configure -value [expr {int($fprogcoord * 100)}]
2667        return
2668    }
2669
2670    set w [expr {[winfo width $progresscanv] - 4}]
2671    set x0 [expr {$w * [lindex $progresscoords 0]}]
2672    set x1 [expr {$w * [lindex $progresscoords 1]}]
2673    set h [winfo height $progresscanv]
2674    $progresscanv coords $progressitem $x0 0 $x1 $h
2675    $progresscanv coords $fprogitem 0 0 [expr {$w * $fprogcoord}] $h
2676    $progresscanv coords $rprogitem 0 0 [expr {$w * $rprogcoord}] $h
2677    set now [clock clicks -milliseconds]
2678    if {$now >= $lastprogupdate + 100} {
2679        set progupdatepending 0
2680        update
2681    } elseif {!$progupdatepending} {
2682        set progupdatepending 1
2683        after [expr {$lastprogupdate + 100 - $now}] doprogupdate
2684    }
2685}
2686
2687proc doprogupdate {} {
2688    global lastprogupdate progupdatepending
2689
2690    if {$progupdatepending} {
2691        set progupdatepending 0
2692        set lastprogupdate [clock clicks -milliseconds]
2693        update
2694    }
2695}
2696
2697proc savestuff {w} {
2698    global canv canv2 canv3 mainfont textfont uifont tabstop
2699    global stuffsaved findmergefiles maxgraphpct
2700    global maxwidth showneartags showlocalchanges
2701    global viewname viewfiles viewargs viewargscmd viewperm nextviewnum
2702    global cmitmode wrapcomment datetimeformat limitdiffs
2703    global colors uicolor bgcolor fgcolor diffcolors diffcontext selectbgcolor
2704    global autoselect autosellen extdifftool perfile_attrs markbgcolor use_ttk
2705    global hideremotes want_ttk
2706
2707    if {$stuffsaved} return
2708    if {![winfo viewable .]} return
2709    catch {
2710        if {[file exists ~/.gitk-new]} {file delete -force ~/.gitk-new}
2711        set f [open "~/.gitk-new" w]
2712        if {$::tcl_platform(platform) eq {windows}} {
2713            file attributes "~/.gitk-new" -hidden true
2714        }
2715        puts $f [list set mainfont $mainfont]
2716        puts $f [list set textfont $textfont]
2717        puts $f [list set uifont $uifont]
2718        puts $f [list set tabstop $tabstop]
2719        puts $f [list set findmergefiles $findmergefiles]
2720        puts $f [list set maxgraphpct $maxgraphpct]
2721        puts $f [list set maxwidth $maxwidth]
2722        puts $f [list set cmitmode $cmitmode]
2723        puts $f [list set wrapcomment $wrapcomment]
2724        puts $f [list set autoselect $autoselect]
2725        puts $f [list set autosellen $autosellen]
2726        puts $f [list set showneartags $showneartags]
2727        puts $f [list set hideremotes $hideremotes]
2728        puts $f [list set showlocalchanges $showlocalchanges]
2729        puts $f [list set datetimeformat $datetimeformat]
2730        puts $f [list set limitdiffs $limitdiffs]
2731        puts $f [list set uicolor $uicolor]
2732        puts $f [list set want_ttk $want_ttk]
2733        puts $f [list set bgcolor $bgcolor]
2734        puts $f [list set fgcolor $fgcolor]
2735        puts $f [list set colors $colors]
2736        puts $f [list set diffcolors $diffcolors]
2737        puts $f [list set markbgcolor $markbgcolor]
2738        puts $f [list set diffcontext $diffcontext]
2739        puts $f [list set selectbgcolor $selectbgcolor]
2740        puts $f [list set extdifftool $extdifftool]
2741        puts $f [list set perfile_attrs $perfile_attrs]
2742
2743        puts $f "set geometry(main) [wm geometry .]"
2744        puts $f "set geometry(state) [wm state .]"
2745        puts $f "set geometry(topwidth) [winfo width .tf]"
2746        puts $f "set geometry(topheight) [winfo height .tf]"
2747        if {$use_ttk} {
2748            puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sashpos 0] 1\""
2749            puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sashpos 1] 1\""
2750        } else {
2751            puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
2752            puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
2753        }
2754        puts $f "set geometry(botwidth) [winfo width .bleft]"
2755        puts $f "set geometry(botheight) [winfo height .bleft]"
2756
2757        puts -nonewline $f "set permviews {"
2758        for {set v 0} {$v < $nextviewnum} {incr v} {
2759            if {$viewperm($v)} {
2760                puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v) $viewargscmd($v)]}"
2761            }
2762        }
2763        puts $f "}"
2764        close $f
2765        file rename -force "~/.gitk-new" "~/.gitk"
2766    }
2767    set stuffsaved 1
2768}
2769
2770proc resizeclistpanes {win w} {
2771    global oldwidth use_ttk
2772    if {[info exists oldwidth($win)]} {
2773        if {$use_ttk} {
2774            set s0 [$win sashpos 0]
2775            set s1 [$win sashpos 1]
2776        } else {
2777            set s0 [$win sash coord 0]
2778            set s1 [$win sash coord 1]
2779        }
2780        if {$w < 60} {
2781            set sash0 [expr {int($w/2 - 2)}]
2782            set sash1 [expr {int($w*5/6 - 2)}]
2783        } else {
2784            set factor [expr {1.0 * $w / $oldwidth($win)}]
2785            set sash0 [expr {int($factor * [lindex $s0 0])}]
2786            set sash1 [expr {int($factor * [lindex $s1 0])}]
2787            if {$sash0 < 30} {
2788                set sash0 30
2789            }
2790            if {$sash1 < $sash0 + 20} {
2791                set sash1 [expr {$sash0 + 20}]
2792            }
2793            if {$sash1 > $w - 10} {
2794                set sash1 [expr {$w - 10}]
2795                if {$sash0 > $sash1 - 20} {
2796                    set sash0 [expr {$sash1 - 20}]
2797                }
2798            }
2799        }
2800        if {$use_ttk} {
2801            $win sashpos 0 $sash0
2802            $win sashpos 1 $sash1
2803        } else {
2804            $win sash place 0 $sash0 [lindex $s0 1]
2805            $win sash place 1 $sash1 [lindex $s1 1]
2806        }
2807    }
2808    set oldwidth($win) $w
2809}
2810
2811proc resizecdetpanes {win w} {
2812    global oldwidth use_ttk
2813    if {[info exists oldwidth($win)]} {
2814        if {$use_ttk} {
2815            set s0 [$win sashpos 0]
2816        } else {
2817            set s0 [$win sash coord 0]
2818        }
2819        if {$w < 60} {
2820            set sash0 [expr {int($w*3/4 - 2)}]
2821        } else {
2822            set factor [expr {1.0 * $w / $oldwidth($win)}]
2823            set sash0 [expr {int($factor * [lindex $s0 0])}]
2824            if {$sash0 < 45} {
2825                set sash0 45
2826            }
2827            if {$sash0 > $w - 15} {
2828                set sash0 [expr {$w - 15}]
2829            }
2830        }
2831        if {$use_ttk} {
2832            $win sashpos 0 $sash0
2833        } else {
2834            $win sash place 0 $sash0 [lindex $s0 1]
2835        }
2836    }
2837    set oldwidth($win) $w
2838}
2839
2840proc allcanvs args {
2841    global canv canv2 canv3
2842    eval $canv $args
2843    eval $canv2 $args
2844    eval $canv3 $args
2845}
2846
2847proc bindall {event action} {
2848    global canv canv2 canv3
2849    bind $canv $event $action
2850    bind $canv2 $event $action
2851    bind $canv3 $event $action
2852}
2853
2854proc about {} {
2855    global uifont NS
2856    set w .about
2857    if {[winfo exists $w]} {
2858        raise $w
2859        return
2860    }
2861    ttk_toplevel $w
2862    wm title $w [mc "About gitk"]
2863    make_transient $w .
2864    message $w.m -text [mc "
2865Gitk - a commit viewer for git
2866
2867Copyright \u00a9 2005-2011 Paul Mackerras
2868
2869Use and redistribute under the terms of the GNU General Public License"] \
2870            -justify center -aspect 400 -border 2 -bg white -relief groove
2871    pack $w.m -side top -fill x -padx 2 -pady 2
2872    ${NS}::button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2873    pack $w.ok -side bottom
2874    bind $w <Visibility> "focus $w.ok"
2875    bind $w <Key-Escape> "destroy $w"
2876    bind $w <Key-Return> "destroy $w"
2877    tk::PlaceWindow $w widget .
2878}
2879
2880proc keys {} {
2881    global NS
2882    set w .keys
2883    if {[winfo exists $w]} {
2884        raise $w
2885        return
2886    }
2887    if {[tk windowingsystem] eq {aqua}} {
2888        set M1T Cmd
2889    } else {
2890        set M1T Ctrl
2891    }
2892    ttk_toplevel $w
2893    wm title $w [mc "Gitk key bindings"]
2894    make_transient $w .
2895    message $w.m -text "
2896[mc "Gitk key bindings:"]
2897
2898[mc "<%s-Q>             Quit" $M1T]
2899[mc "<%s-W>             Close window" $M1T]
2900[mc "<Home>             Move to first commit"]
2901[mc "<End>              Move to last commit"]
2902[mc "<Up>, p, k Move up one commit"]
2903[mc "<Down>, n, j       Move down one commit"]
2904[mc "<Left>, z, h       Go back in history list"]
2905[mc "<Right>, x, l      Go forward in history list"]
2906[mc "<PageUp>   Move up one page in commit list"]
2907[mc "<PageDown> Move down one page in commit list"]
2908[mc "<%s-Home>  Scroll to top of commit list" $M1T]
2909[mc "<%s-End>   Scroll to bottom of commit list" $M1T]
2910[mc "<%s-Up>    Scroll commit list up one line" $M1T]
2911[mc "<%s-Down>  Scroll commit list down one line" $M1T]
2912[mc "<%s-PageUp>        Scroll commit list up one page" $M1T]
2913[mc "<%s-PageDown>      Scroll commit list down one page" $M1T]
2914[mc "<Shift-Up> Find backwards (upwards, later commits)"]
2915[mc "<Shift-Down>       Find forwards (downwards, earlier commits)"]
2916[mc "<Delete>, b        Scroll diff view up one page"]
2917[mc "<Backspace>        Scroll diff view up one page"]
2918[mc "<Space>            Scroll diff view down one page"]
2919[mc "u          Scroll diff view up 18 lines"]
2920[mc "d          Scroll diff view down 18 lines"]
2921[mc "<%s-F>             Find" $M1T]
2922[mc "<%s-G>             Move to next find hit" $M1T]
2923[mc "<Return>   Move to next find hit"]
2924[mc "/          Focus the search box"]
2925[mc "?          Move to previous find hit"]
2926[mc "f          Scroll diff view to next file"]
2927[mc "<%s-S>             Search for next hit in diff view" $M1T]
2928[mc "<%s-R>             Search for previous hit in diff view" $M1T]
2929[mc "<%s-KP+>   Increase font size" $M1T]
2930[mc "<%s-plus>  Increase font size" $M1T]
2931[mc "<%s-KP->   Decrease font size" $M1T]
2932[mc "<%s-minus> Decrease font size" $M1T]
2933[mc "<F5>               Update"]
2934" \
2935            -justify left -bg white -border 2 -relief groove
2936    pack $w.m -side top -fill both -padx 2 -pady 2
2937    ${NS}::button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2938    bind $w <Key-Escape> [list destroy $w]
2939    pack $w.ok -side bottom
2940    bind $w <Visibility> "focus $w.ok"
2941    bind $w <Key-Escape> "destroy $w"
2942    bind $w <Key-Return> "destroy $w"
2943}
2944
2945# Procedures for manipulating the file list window at the
2946# bottom right of the overall window.
2947
2948proc treeview {w l openlevs} {
2949    global treecontents treediropen treeheight treeparent treeindex
2950
2951    set ix 0
2952    set treeindex() 0
2953    set lev 0
2954    set prefix {}
2955    set prefixend -1
2956    set prefendstack {}
2957    set htstack {}
2958    set ht 0
2959    set treecontents() {}
2960    $w conf -state normal
2961    foreach f $l {
2962        while {[string range $f 0 $prefixend] ne $prefix} {
2963            if {$lev <= $openlevs} {
2964                $w mark set e:$treeindex($prefix) "end -1c"
2965                $w mark gravity e:$treeindex($prefix) left
2966            }
2967            set treeheight($prefix) $ht
2968            incr ht [lindex $htstack end]
2969            set htstack [lreplace $htstack end end]
2970            set prefixend [lindex $prefendstack end]
2971            set prefendstack [lreplace $prefendstack end end]
2972            set prefix [string range $prefix 0 $prefixend]
2973            incr lev -1
2974        }
2975        set tail [string range $f [expr {$prefixend+1}] end]
2976        while {[set slash [string first "/" $tail]] >= 0} {
2977            lappend htstack $ht
2978            set ht 0
2979            lappend prefendstack $prefixend
2980            incr prefixend [expr {$slash + 1}]
2981            set d [string range $tail 0 $slash]
2982            lappend treecontents($prefix) $d
2983            set oldprefix $prefix
2984            append prefix $d
2985            set treecontents($prefix) {}
2986            set treeindex($prefix) [incr ix]
2987            set treeparent($prefix) $oldprefix
2988            set tail [string range $tail [expr {$slash+1}] end]
2989            if {$lev <= $openlevs} {
2990                set ht 1
2991                set treediropen($prefix) [expr {$lev < $openlevs}]
2992                set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
2993                $w mark set d:$ix "end -1c"
2994                $w mark gravity d:$ix left
2995                set str "\n"
2996                for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2997                $w insert end $str
2998                $w image create end -align center -image $bm -padx 1 \
2999                    -name a:$ix
3000                $w insert end $d [highlight_tag $prefix]
3001                $w mark set s:$ix "end -1c"
3002                $w mark gravity s:$ix left
3003            }
3004            incr lev
3005        }
3006        if {$tail ne {}} {
3007            if {$lev <= $openlevs} {
3008                incr ht
3009                set str "\n"
3010                for {set i 0} {$i < $lev} {incr i} {append str "\t"}
3011                $w insert end $str
3012                $w insert end $tail [highlight_tag $f]
3013            }
3014            lappend treecontents($prefix) $tail
3015        }
3016    }
3017    while {$htstack ne {}} {
3018        set treeheight($prefix) $ht
3019        incr ht [lindex $htstack end]
3020        set htstack [lreplace $htstack end end]
3021        set prefixend [lindex $prefendstack end]
3022        set prefendstack [lreplace $prefendstack end end]
3023        set prefix [string range $prefix 0 $prefixend]
3024    }
3025    $w conf -state disabled
3026}
3027
3028proc linetoelt {l} {
3029    global treeheight treecontents
3030
3031    set y 2
3032    set prefix {}
3033    while {1} {
3034        foreach e $treecontents($prefix) {
3035            if {$y == $l} {
3036                return "$prefix$e"
3037            }
3038            set n 1
3039            if {[string index $e end] eq "/"} {
3040                set n $treeheight($prefix$e)
3041                if {$y + $n > $l} {
3042                    append prefix $e
3043                    incr y
3044                    break
3045                }
3046            }
3047            incr y $n
3048        }
3049    }
3050}
3051
3052proc highlight_tree {y prefix} {
3053    global treeheight treecontents cflist
3054
3055    foreach e $treecontents($prefix) {
3056        set path $prefix$e
3057        if {[highlight_tag $path] ne {}} {
3058            $cflist tag add bold $y.0 "$y.0 lineend"
3059        }
3060        incr y
3061        if {[string index $e end] eq "/" && $treeheight($path) > 1} {
3062            set y [highlight_tree $y $path]
3063        }
3064    }
3065    return $y
3066}
3067
3068proc treeclosedir {w dir} {
3069    global treediropen treeheight treeparent treeindex
3070
3071    set ix $treeindex($dir)
3072    $w conf -state normal
3073    $w delete s:$ix e:$ix
3074    set treediropen($dir) 0
3075    $w image configure a:$ix -image tri-rt
3076    $w conf -state disabled
3077    set n [expr {1 - $treeheight($dir)}]
3078    while {$dir ne {}} {
3079        incr treeheight($dir) $n
3080        set dir $treeparent($dir)
3081    }
3082}
3083
3084proc treeopendir {w dir} {
3085    global treediropen treeheight treeparent treecontents treeindex
3086
3087    set ix $treeindex($dir)
3088    $w conf -state normal
3089    $w image configure a:$ix -image tri-dn
3090    $w mark set e:$ix s:$ix
3091    $w mark gravity e:$ix right
3092    set lev 0
3093    set str "\n"
3094    set n [llength $treecontents($dir)]
3095    for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
3096        incr lev
3097        append str "\t"
3098        incr treeheight($x) $n
3099    }
3100    foreach e $treecontents($dir) {
3101        set de $dir$e
3102        if {[string index $e end] eq "/"} {
3103            set iy $treeindex($de)
3104            $w mark set d:$iy e:$ix
3105            $w mark gravity d:$iy left
3106            $w insert e:$ix $str
3107            set treediropen($de) 0
3108            $w image create e:$ix -align center -image tri-rt -padx 1 \
3109                -name a:$iy
3110            $w insert e:$ix $e [highlight_tag $de]
3111            $w mark set s:$iy e:$ix
3112            $w mark gravity s:$iy left
3113            set treeheight($de) 1
3114        } else {
3115            $w insert e:$ix $str
3116            $w insert e:$ix $e [highlight_tag $de]
3117        }
3118    }
3119    $w mark gravity e:$ix right
3120    $w conf -state disabled
3121    set treediropen($dir) 1
3122    set top [lindex [split [$w index @0,0] .] 0]
3123    set ht [$w cget -height]
3124    set l [lindex [split [$w index s:$ix] .] 0]
3125    if {$l < $top} {
3126        $w yview $l.0
3127    } elseif {$l + $n + 1 > $top + $ht} {
3128        set top [expr {$l + $n + 2 - $ht}]
3129        if {$l < $top} {
3130            set top $l
3131        }
3132        $w yview $top.0
3133    }
3134}
3135
3136proc treeclick {w x y} {
3137    global treediropen cmitmode ctext cflist cflist_top
3138
3139    if {$cmitmode ne "tree"} return
3140    if {![info exists cflist_top]} return
3141    set l [lindex [split [$w index "@$x,$y"] "."] 0]
3142    $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
3143    $cflist tag add highlight $l.0 "$l.0 lineend"
3144    set cflist_top $l
3145    if {$l == 1} {
3146        $ctext yview 1.0
3147        return
3148    }
3149    set e [linetoelt $l]
3150    if {[string index $e end] ne "/"} {
3151        showfile $e
3152    } elseif {$treediropen($e)} {
3153        treeclosedir $w $e
3154    } else {
3155        treeopendir $w $e
3156    }
3157}
3158
3159proc setfilelist {id} {
3160    global treefilelist cflist jump_to_here
3161
3162    treeview $cflist $treefilelist($id) 0
3163    if {$jump_to_here ne {}} {
3164        set f [lindex $jump_to_here 0]
3165        if {[lsearch -exact $treefilelist($id) $f] >= 0} {
3166            showfile $f
3167        }
3168    }
3169}
3170
3171image create bitmap tri-rt -background black -foreground blue -data {
3172    #define tri-rt_width 13
3173    #define tri-rt_height 13
3174    static unsigned char tri-rt_bits[] = {
3175       0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
3176       0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
3177       0x00, 0x00};
3178} -maskdata {
3179    #define tri-rt-mask_width 13
3180    #define tri-rt-mask_height 13
3181    static unsigned char tri-rt-mask_bits[] = {
3182       0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
3183       0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
3184       0x08, 0x00};
3185}
3186image create bitmap tri-dn -background black -foreground blue -data {
3187    #define tri-dn_width 13
3188    #define tri-dn_height 13
3189    static unsigned char tri-dn_bits[] = {
3190       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
3191       0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3192       0x00, 0x00};
3193} -maskdata {
3194    #define tri-dn-mask_width 13
3195    #define tri-dn-mask_height 13
3196    static unsigned char tri-dn-mask_bits[] = {
3197       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
3198       0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
3199       0x00, 0x00};
3200}
3201
3202image create bitmap reficon-T -background black -foreground yellow -data {
3203    #define tagicon_width 13
3204    #define tagicon_height 9
3205    static unsigned char tagicon_bits[] = {
3206       0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0xf8, 0x07,
3207       0xfc, 0x07, 0xf8, 0x07, 0xf0, 0x07, 0x00, 0x00, 0x00, 0x00};
3208} -maskdata {
3209    #define tagicon-mask_width 13
3210    #define tagicon-mask_height 9
3211    static unsigned char tagicon-mask_bits[] = {
3212       0x00, 0x00, 0xf0, 0x0f, 0xf8, 0x0f, 0xfc, 0x0f,
3213       0xfe, 0x0f, 0xfc, 0x0f, 0xf8, 0x0f, 0xf0, 0x0f, 0x00, 0x00};
3214}
3215set rectdata {
3216    #define headicon_width 13
3217    #define headicon_height 9
3218    static unsigned char headicon_bits[] = {
3219       0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0xf8, 0x07,
3220       0xf8, 0x07, 0xf8, 0x07, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x00};
3221}
3222set rectmask {
3223    #define headicon-mask_width 13
3224    #define headicon-mask_height 9
3225    static unsigned char headicon-mask_bits[] = {
3226       0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f,
3227       0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00};
3228}
3229image create bitmap reficon-H -background black -foreground green \
3230    -data $rectdata -maskdata $rectmask
3231image create bitmap reficon-o -background black -foreground "#ddddff" \
3232    -data $rectdata -maskdata $rectmask
3233
3234proc init_flist {first} {
3235    global cflist cflist_top difffilestart
3236
3237    $cflist conf -state normal
3238    $cflist delete 0.0 end
3239    if {$first ne {}} {
3240        $cflist insert end $first
3241        set cflist_top 1
3242        $cflist tag add highlight 1.0 "1.0 lineend"
3243    } else {
3244        catch {unset cflist_top}
3245    }
3246    $cflist conf -state disabled
3247    set difffilestart {}
3248}
3249
3250proc highlight_tag {f} {
3251    global highlight_paths
3252
3253    foreach p $highlight_paths {
3254        if {[string match $p $f]} {
3255            return "bold"
3256        }
3257    }
3258    return {}
3259}
3260
3261proc highlight_filelist {} {
3262    global cmitmode cflist
3263
3264    $cflist conf -state normal
3265    if {$cmitmode ne "tree"} {
3266        set end [lindex [split [$cflist index end] .] 0]
3267        for {set l 2} {$l < $end} {incr l} {
3268            set line [$cflist get $l.0 "$l.0 lineend"]
3269            if {[highlight_tag $line] ne {}} {
3270                $cflist tag add bold $l.0 "$l.0 lineend"
3271            }
3272        }
3273    } else {
3274        highlight_tree 2 {}
3275    }
3276    $cflist conf -state disabled
3277}
3278
3279proc unhighlight_filelist {} {
3280    global cflist
3281
3282    $cflist conf -state normal
3283    $cflist tag remove bold 1.0 end
3284    $cflist conf -state disabled
3285}
3286
3287proc add_flist {fl} {
3288    global cflist
3289
3290    $cflist conf -state normal
3291    foreach f $fl {
3292        $cflist insert end "\n"
3293        $cflist insert end $f [highlight_tag $f]
3294    }
3295    $cflist conf -state disabled
3296}
3297
3298proc sel_flist {w x y} {
3299    global ctext difffilestart cflist cflist_top cmitmode
3300
3301    if {$cmitmode eq "tree"} return
3302    if {![info exists cflist_top]} return
3303    set l [lindex [split [$w index "@$x,$y"] "."] 0]
3304    $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
3305    $cflist tag add highlight $l.0 "$l.0 lineend"
3306    set cflist_top $l
3307    if {$l == 1} {
3308        $ctext yview 1.0
3309    } else {
3310        catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
3311    }
3312    suppress_highlighting_file_for_current_scrollpos
3313}
3314
3315proc pop_flist_menu {w X Y x y} {
3316    global ctext cflist cmitmode flist_menu flist_menu_file
3317    global treediffs diffids
3318
3319    stopfinding
3320    set l [lindex [split [$w index "@$x,$y"] "."] 0]
3321    if {$l <= 1} return
3322    if {$cmitmode eq "tree"} {
3323        set e [linetoelt $l]
3324        if {[string index $e end] eq "/"} return
3325    } else {
3326        set e [lindex $treediffs($diffids) [expr {$l-2}]]
3327    }
3328    set flist_menu_file $e
3329    set xdiffstate "normal"
3330    if {$cmitmode eq "tree"} {
3331        set xdiffstate "disabled"
3332    }
3333    # Disable "External diff" item in tree mode
3334    $flist_menu entryconf 2 -state $xdiffstate
3335    tk_popup $flist_menu $X $Y
3336}
3337
3338proc find_ctext_fileinfo {line} {
3339    global ctext_file_names ctext_file_lines
3340
3341    set ok [bsearch $ctext_file_lines $line]
3342    set tline [lindex $ctext_file_lines $ok]
3343
3344    if {$ok >= [llength $ctext_file_lines] || $line < $tline} {
3345        return {}
3346    } else {
3347        return [list [lindex $ctext_file_names $ok] $tline]
3348    }
3349}
3350
3351proc pop_diff_menu {w X Y x y} {
3352    global ctext diff_menu flist_menu_file
3353    global diff_menu_txtpos diff_menu_line
3354    global diff_menu_filebase
3355
3356    set diff_menu_txtpos [split [$w index "@$x,$y"] "."]
3357    set diff_menu_line [lindex $diff_menu_txtpos 0]
3358    # don't pop up the menu on hunk-separator or file-separator lines
3359    if {[lsearch -glob [$ctext tag names $diff_menu_line.0] "*sep"] >= 0} {
3360        return
3361    }
3362    stopfinding
3363    set f [find_ctext_fileinfo $diff_menu_line]
3364    if {$f eq {}} return
3365    set flist_menu_file [lindex $f 0]
3366    set diff_menu_filebase [lindex $f 1]
3367    tk_popup $diff_menu $X $Y
3368}
3369
3370proc flist_hl {only} {
3371    global flist_menu_file findstring gdttype
3372
3373    set x [shellquote $flist_menu_file]
3374    if {$only || $findstring eq {} || $gdttype ne [mc "touching paths:"]} {
3375        set findstring $x
3376    } else {
3377        append findstring " " $x
3378    }
3379    set gdttype [mc "touching paths:"]
3380}
3381
3382proc gitknewtmpdir {} {
3383    global diffnum gitktmpdir gitdir
3384
3385    if {![info exists gitktmpdir]} {
3386        set gitktmpdir [file join $gitdir [format ".gitk-tmp.%s" [pid]]]
3387        if {[catch {file mkdir $gitktmpdir} err]} {
3388            error_popup "[mc "Error creating temporary directory %s:" $gitktmpdir] $err"
3389            unset gitktmpdir
3390            return {}
3391        }
3392        set diffnum 0
3393    }
3394    incr diffnum
3395    set diffdir [file join $gitktmpdir $diffnum]
3396    if {[catch {file mkdir $diffdir} err]} {
3397        error_popup "[mc "Error creating temporary directory %s:" $diffdir] $err"
3398        return {}
3399    }
3400    return $diffdir
3401}
3402
3403proc save_file_from_commit {filename output what} {
3404    global nullfile
3405
3406    if {[catch {exec git show $filename -- > $output} err]} {
3407        if {[string match "fatal: bad revision *" $err]} {
3408            return $nullfile
3409        }
3410        error_popup "[mc "Error getting \"%s\" from %s:" $filename $what] $err"
3411        return {}
3412    }
3413    return $output
3414}
3415
3416proc external_diff_get_one_file {diffid filename diffdir} {
3417    global nullid nullid2 nullfile
3418    global worktree
3419
3420    if {$diffid == $nullid} {
3421        set difffile [file join $worktree $filename]
3422        if {[file exists $difffile]} {
3423            return $difffile
3424        }
3425        return $nullfile
3426    }
3427    if {$diffid == $nullid2} {
3428        set difffile [file join $diffdir "\[index\] [file tail $filename]"]
3429        return [save_file_from_commit :$filename $difffile index]
3430    }
3431    set difffile [file join $diffdir "\[$diffid\] [file tail $filename]"]
3432    return [save_file_from_commit $diffid:$filename $difffile \
3433               "revision $diffid"]
3434}
3435
3436proc external_diff {} {
3437    global nullid nullid2
3438    global flist_menu_file
3439    global diffids
3440    global extdifftool
3441
3442    if {[llength $diffids] == 1} {
3443        # no reference commit given
3444        set diffidto [lindex $diffids 0]
3445        if {$diffidto eq $nullid} {
3446            # diffing working copy with index
3447            set diffidfrom $nullid2
3448        } elseif {$diffidto eq $nullid2} {
3449            # diffing index with HEAD
3450            set diffidfrom "HEAD"
3451        } else {
3452            # use first parent commit
3453            global parentlist selectedline
3454            set diffidfrom [lindex $parentlist $selectedline 0]
3455        }
3456    } else {
3457        set diffidfrom [lindex $diffids 0]
3458        set diffidto [lindex $diffids 1]
3459    }
3460
3461    # make sure that several diffs wont collide
3462    set diffdir [gitknewtmpdir]
3463    if {$diffdir eq {}} return
3464
3465    # gather files to diff
3466    set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]
3467    set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]
3468
3469    if {$difffromfile ne {} && $difftofile ne {}} {
3470        set cmd [list [shellsplit $extdifftool] $difffromfile $difftofile]
3471        if {[catch {set fl [open |$cmd r]} err]} {
3472            file delete -force $diffdir
3473            error_popup "$extdifftool: [mc "command failed:"] $err"
3474        } else {
3475            fconfigure $fl -blocking 0
3476            filerun $fl [list delete_at_eof $fl $diffdir]
3477        }
3478    }
3479}
3480
3481proc find_hunk_blamespec {base line} {
3482    global ctext
3483
3484    # Find and parse the hunk header
3485    set s_lix [$ctext search -backwards -regexp ^@@ "$line.0 lineend" $base.0]
3486    if {$s_lix eq {}} return
3487
3488    set s_line [$ctext get $s_lix "$s_lix + 1 lines"]
3489    if {![regexp {^@@@*(( -\d+(,\d+)?)+) \+(\d+)(,\d+)? @@} $s_line \
3490            s_line old_specs osz osz1 new_line nsz]} {
3491        return
3492    }
3493
3494    # base lines for the parents
3495    set base_lines [list $new_line]
3496    foreach old_spec [lrange [split $old_specs " "] 1 end] {
3497        if {![regexp -- {-(\d+)(,\d+)?} $old_spec \
3498                old_spec old_line osz]} {
3499            return
3500        }
3501        lappend base_lines $old_line
3502    }
3503
3504    # Now scan the lines to determine offset within the hunk
3505    set max_parent [expr {[llength $base_lines]-2}]
3506    set dline 0
3507    set s_lno [lindex [split $s_lix "."] 0]
3508
3509    # Determine if the line is removed
3510    set chunk [$ctext get $line.0 "$line.1 + $max_parent chars"]
3511    if {[string match {[-+ ]*} $chunk]} {
3512        set removed_idx [string first "-" $chunk]
3513        # Choose a parent index
3514        if {$removed_idx >= 0} {
3515            set parent $removed_idx
3516        } else {
3517            set unchanged_idx [string first " " $chunk]
3518            if {$unchanged_idx >= 0} {
3519                set parent $unchanged_idx
3520            } else {
3521                # blame the current commit
3522                set parent -1
3523            }
3524        }
3525        # then count other lines that belong to it
3526        for {set i $line} {[incr i -1] > $s_lno} {} {
3527            set chunk [$ctext get $i.0 "$i.1 + $max_parent chars"]
3528            # Determine if the line is removed
3529            set removed_idx [string first "-" $chunk]
3530            if {$parent >= 0} {
3531                set code [string index $chunk $parent]
3532                if {$code eq "-" || ($removed_idx < 0 && $code ne "+")} {
3533                    incr dline
3534                }
3535            } else {
3536                if {$removed_idx < 0} {
3537                    incr dline
3538                }
3539            }
3540        }
3541        incr parent
3542    } else {
3543        set parent 0
3544    }
3545
3546    incr dline [lindex $base_lines $parent]
3547    return [list $parent $dline]
3548}
3549
3550proc external_blame_diff {} {
3551    global currentid cmitmode
3552    global diff_menu_txtpos diff_menu_line
3553    global diff_menu_filebase flist_menu_file
3554
3555    if {$cmitmode eq "tree"} {
3556        set parent_idx 0
3557        set line [expr {$diff_menu_line - $diff_menu_filebase}]
3558    } else {
3559        set hinfo [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
3560        if {$hinfo ne {}} {
3561            set parent_idx [lindex $hinfo 0]
3562            set line [lindex $hinfo 1]
3563        } else {
3564            set parent_idx 0
3565            set line 0
3566        }
3567    }
3568
3569    external_blame $parent_idx $line
3570}
3571
3572# Find the SHA1 ID of the blob for file $fname in the index
3573# at stage 0 or 2
3574proc index_sha1 {fname} {
3575    set f [open [list | git ls-files -s $fname] r]
3576    while {[gets $f line] >= 0} {
3577        set info [lindex [split $line "\t"] 0]
3578        set stage [lindex $info 2]
3579        if {$stage eq "0" || $stage eq "2"} {
3580            close $f
3581            return [lindex $info 1]
3582        }
3583    }
3584    close $f
3585    return {}
3586}
3587
3588# Turn an absolute path into one relative to the current directory
3589proc make_relative {f} {
3590    if {[file pathtype $f] eq "relative"} {
3591        return $f
3592    }
3593    set elts [file split $f]
3594    set here [file split [pwd]]
3595    set ei 0
3596    set hi 0
3597    set res {}
3598    foreach d $here {
3599        if {$ei < $hi || $ei >= [llength $elts] || [lindex $elts $ei] ne $d} {
3600            lappend res ".."
3601        } else {
3602            incr ei
3603        }
3604        incr hi
3605    }
3606    set elts [concat $res [lrange $elts $ei end]]
3607    return [eval file join $elts]
3608}
3609
3610proc external_blame {parent_idx {line {}}} {
3611    global flist_menu_file cdup
3612    global nullid nullid2
3613    global parentlist selectedline currentid
3614
3615    if {$parent_idx > 0} {
3616        set base_commit [lindex $parentlist $selectedline [expr {$parent_idx-1}]]
3617    } else {
3618        set base_commit $currentid
3619    }
3620
3621    if {$base_commit eq {} || $base_commit eq $nullid || $base_commit eq $nullid2} {
3622        error_popup [mc "No such commit"]
3623        return
3624    }
3625
3626    set cmdline [list git gui blame]
3627    if {$line ne {} && $line > 1} {
3628        lappend cmdline "--line=$line"
3629    }
3630    set f [file join $cdup $flist_menu_file]
3631    # Unfortunately it seems git gui blame doesn't like
3632    # being given an absolute path...
3633    set f [make_relative $f]
3634    lappend cmdline $base_commit $f
3635    if {[catch {eval exec $cmdline &} err]} {
3636        error_popup "[mc "git gui blame: command failed:"] $err"
3637    }
3638}
3639
3640proc show_line_source {} {
3641    global cmitmode currentid parents curview blamestuff blameinst
3642    global diff_menu_line diff_menu_filebase flist_menu_file
3643    global nullid nullid2 gitdir cdup
3644
3645    set from_index {}
3646    if {$cmitmode eq "tree"} {
3647        set id $currentid
3648        set line [expr {$diff_menu_line - $diff_menu_filebase}]
3649    } else {
3650        set h [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
3651        if {$h eq {}} return
3652        set pi [lindex $h 0]
3653        if {$pi == 0} {
3654            mark_ctext_line $diff_menu_line
3655            return
3656        }
3657        incr pi -1
3658        if {$currentid eq $nullid} {
3659            if {$pi > 0} {
3660                # must be a merge in progress...
3661                if {[catch {
3662                    # get the last line from .git/MERGE_HEAD
3663                    set f [open [file join $gitdir MERGE_HEAD] r]
3664                    set id [lindex [split [read $f] "\n"] end-1]
3665                    close $f
3666                } err]} {
3667                    error_popup [mc "Couldn't read merge head: %s" $err]
3668                    return
3669                }
3670            } elseif {$parents($curview,$currentid) eq $nullid2} {
3671                # need to do the blame from the index
3672                if {[catch {
3673                    set from_index [index_sha1 $flist_menu_file]
3674                } err]} {
3675                    error_popup [mc "Error reading index: %s" $err]
3676                    return
3677                }
3678            } else {
3679                set id $parents($curview,$currentid)
3680            }
3681        } else {
3682            set id [lindex $parents($curview,$currentid) $pi]
3683        }
3684        set line [lindex $h 1]
3685    }
3686    set blameargs {}
3687    if {$from_index ne {}} {
3688        lappend blameargs | git cat-file blob $from_index
3689    }
3690    lappend blameargs | git blame -p -L$line,+1
3691    if {$from_index ne {}} {
3692        lappend blameargs --contents -
3693    } else {
3694        lappend blameargs $id
3695    }
3696    lappend blameargs -- [file join $cdup $flist_menu_file]
3697    if {[catch {
3698        set f [open $blameargs r]
3699    } err]} {
3700        error_popup [mc "Couldn't start git blame: %s" $err]
3701        return
3702    }
3703    nowbusy blaming [mc "Searching"]
3704    fconfigure $f -blocking 0
3705    set i [reg_instance $f]
3706    set blamestuff($i) {}
3707    set blameinst $i
3708    filerun $f [list read_line_source $f $i]
3709}
3710
3711proc stopblaming {} {
3712    global blameinst
3713
3714    if {[info exists blameinst]} {
3715        stop_instance $blameinst
3716        unset blameinst
3717        notbusy blaming
3718    }
3719}
3720
3721proc read_line_source {fd inst} {
3722    global blamestuff curview commfd blameinst nullid nullid2
3723
3724    while {[gets $fd line] >= 0} {
3725        lappend blamestuff($inst) $line
3726    }
3727    if {![eof $fd]} {
3728        return 1
3729    }
3730    unset commfd($inst)
3731    unset blameinst
3732    notbusy blaming
3733    fconfigure $fd -blocking 1
3734    if {[catch {close $fd} err]} {
3735        error_popup [mc "Error running git blame: %s" $err]
3736        return 0
3737    }
3738
3739    set fname {}
3740    set line [split [lindex $blamestuff($inst) 0] " "]
3741    set id [lindex $line 0]
3742    set lnum [lindex $line 1]
3743    if {[string length $id] == 40 && [string is xdigit $id] &&
3744        [string is digit -strict $lnum]} {
3745        # look for "filename" line
3746        foreach l $blamestuff($inst) {
3747            if {[string match "filename *" $l]} {
3748                set fname [string range $l 9 end]
3749                break
3750            }
3751        }
3752    }
3753    if {$fname ne {}} {
3754        # all looks good, select it
3755        if {$id eq $nullid} {
3756            # blame uses all-zeroes to mean not committed,
3757            # which would mean a change in the index
3758            set id $nullid2
3759        }
3760        if {[commitinview $id $curview]} {
3761            selectline [rowofcommit $id] 1 [list $fname $lnum]
3762        } else {
3763            error_popup [mc "That line comes from commit %s, \
3764                             which is not in this view" [shortids $id]]
3765        }
3766    } else {
3767        puts "oops couldn't parse git blame output"
3768    }
3769    return 0
3770}
3771
3772# delete $dir when we see eof on $f (presumably because the child has exited)
3773proc delete_at_eof {f dir} {
3774    while {[gets $f line] >= 0} {}
3775    if {[eof $f]} {
3776        if {[catch {close $f} err]} {
3777            error_popup "[mc "External diff viewer failed:"] $err"
3778        }
3779        file delete -force $dir
3780        return 0
3781    }
3782    return 1
3783}
3784
3785# Functions for adding and removing shell-type quoting
3786
3787proc shellquote {str} {
3788    if {![string match "*\['\"\\ \t]*" $str]} {
3789        return $str
3790    }
3791    if {![string match "*\['\"\\]*" $str]} {
3792        return "\"$str\""
3793    }
3794    if {![string match "*'*" $str]} {
3795        return "'$str'"
3796    }
3797    return "\"[string map {\" \\\" \\ \\\\} $str]\""
3798}
3799
3800proc shellarglist {l} {
3801    set str {}
3802    foreach a $l {
3803        if {$str ne {}} {
3804            append str " "
3805        }
3806        append str [shellquote $a]
3807    }
3808    return $str
3809}
3810
3811proc shelldequote {str} {
3812    set ret {}
3813    set used -1
3814    while {1} {
3815        incr used
3816        if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
3817            append ret [string range $str $used end]
3818            set used [string length $str]
3819            break
3820        }
3821        set first [lindex $first 0]
3822        set ch [string index $str $first]
3823        if {$first > $used} {
3824            append ret [string range $str $used [expr {$first - 1}]]
3825            set used $first
3826        }
3827        if {$ch eq " " || $ch eq "\t"} break
3828        incr used
3829        if {$ch eq "'"} {
3830            set first [string first "'" $str $used]
3831            if {$first < 0} {
3832                error "unmatched single-quote"
3833            }
3834            append ret [string range $str $used [expr {$first - 1}]]
3835            set used $first
3836            continue
3837        }
3838        if {$ch eq "\\"} {
3839            if {$used >= [string length $str]} {
3840                error "trailing backslash"
3841            }
3842            append ret [string index $str $used]
3843            continue
3844        }
3845        # here ch == "\""
3846        while {1} {
3847            if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
3848                error "unmatched double-quote"
3849            }
3850            set first [lindex $first 0]
3851            set ch [string index $str $first]
3852            if {$first > $used} {
3853                append ret [string range $str $used [expr {$first - 1}]]
3854                set used $first
3855            }
3856            if {$ch eq "\""} break
3857            incr used
3858            append ret [string index $str $used]
3859            incr used
3860        }
3861    }
3862    return [list $used $ret]
3863}
3864
3865proc shellsplit {str} {
3866    set l {}
3867    while {1} {
3868        set str [string trimleft $str]
3869        if {$str eq {}} break
3870        set dq [shelldequote $str]
3871        set n [lindex $dq 0]
3872        set word [lindex $dq 1]
3873        set str [string range $str $n end]
3874        lappend l $word
3875    }
3876    return $l
3877}
3878
3879# Code to implement multiple views
3880
3881proc newview {ishighlight} {
3882    global nextviewnum newviewname newishighlight
3883    global revtreeargs viewargscmd newviewopts curview
3884
3885    set newishighlight $ishighlight
3886    set top .gitkview
3887    if {[winfo exists $top]} {
3888        raise $top
3889        return
3890    }
3891    decode_view_opts $nextviewnum $revtreeargs
3892    set newviewname($nextviewnum) "[mc "View"] $nextviewnum"
3893    set newviewopts($nextviewnum,perm) 0
3894    set newviewopts($nextviewnum,cmd)  $viewargscmd($curview)
3895    vieweditor $top $nextviewnum [mc "Gitk view definition"]
3896}
3897
3898set known_view_options {
3899    {perm      b    .  {}               {mc "Remember this view"}}
3900    {reflabel  l    +  {}               {mc "References (space separated list):"}}
3901    {refs      t15  .. {}               {mc "Branches & tags:"}}
3902    {allrefs   b    *. "--all"          {mc "All refs"}}
3903    {branches  b    .  "--branches"     {mc "All (local) branches"}}
3904    {tags      b    .  "--tags"         {mc "All tags"}}
3905    {remotes   b    .  "--remotes"      {mc "All remote-tracking branches"}}
3906    {commitlbl l    +  {}               {mc "Commit Info (regular expressions):"}}
3907    {author    t15  .. "--author=*"     {mc "Author:"}}
3908    {committer t15  .  "--committer=*"  {mc "Committer:"}}
3909    {loginfo   t15  .. "--grep=*"       {mc "Commit Message:"}}
3910    {allmatch  b    .. "--all-match"    {mc "Matches all Commit Info criteria"}}
3911    {changes_l l    +  {}               {mc "Changes to Files:"}}
3912    {pickaxe_s r0   .  {}               {mc "Fixed String"}}
3913    {pickaxe_t r1   .  "--pickaxe-regex"  {mc "Regular Expression"}}
3914    {pickaxe   t15  .. "-S*"            {mc "Search string:"}}
3915    {datelabel l    +  {}               {mc "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 15:27:38\"):"}}
3916    {since     t15  ..  {"--since=*" "--after=*"}  {mc "Since:"}}
3917    {until     t15  .   {"--until=*" "--before=*"} {mc "Until:"}}
3918    {limit_lbl l    +  {}               {mc "Limit and/or skip a number of revisions (positive integer):"}}
3919    {limit     t10  *. "--max-count=*"  {mc "Number to show:"}}
3920    {skip      t10  .  "--skip=*"       {mc "Number to skip:"}}
3921    {misc_lbl  l    +  {}               {mc "Miscellaneous options:"}}
3922    {dorder    b    *. {"--date-order" "-d"}      {mc "Strictly sort by date"}}
3923    {lright    b    .  "--left-right"   {mc "Mark branch sides"}}
3924    {first     b    .  "--first-parent" {mc "Limit to first parent"}}
3925    {smplhst   b    .  "--simplify-by-decoration"   {mc "Simple history"}}
3926    {args      t50  *. {}               {mc "Additional arguments to git log:"}}
3927    {allpaths  path +  {}               {mc "Enter files and directories to include, one per line:"}}
3928    {cmd       t50= +  {}               {mc "Command to generate more commits to include:"}}
3929    }
3930
3931# Convert $newviewopts($n, ...) into args for git log.
3932proc encode_view_opts {n} {
3933    global known_view_options newviewopts
3934
3935    set rargs [list]
3936    foreach opt $known_view_options {
3937        set patterns [lindex $opt 3]
3938        if {$patterns eq {}} continue
3939        set pattern [lindex $patterns 0]
3940
3941        if {[lindex $opt 1] eq "b"} {
3942            set val $newviewopts($n,[lindex $opt 0])
3943            if {$val} {
3944                lappend rargs $pattern
3945            }
3946        } elseif {[regexp {^r(\d+)$} [lindex $opt 1] type value]} {
3947            regexp {^(.*_)} [lindex $opt 0] uselessvar button_id
3948            set val $newviewopts($n,$button_id)
3949            if {$val eq $value} {
3950                lappend rargs $pattern
3951            }
3952        } else {
3953            set val $newviewopts($n,[lindex $opt 0])
3954            set val [string trim $val]
3955            if {$val ne {}} {
3956                set pfix [string range $pattern 0 end-1]
3957                lappend rargs $pfix$val
3958            }
3959        }
3960    }
3961    set rargs [concat $rargs [shellsplit $newviewopts($n,refs)]]
3962    return [concat $rargs [shellsplit $newviewopts($n,args)]]
3963}
3964
3965# Fill $newviewopts($n, ...) based on args for git log.
3966proc decode_view_opts {n view_args} {
3967    global known_view_options newviewopts
3968
3969    foreach opt $known_view_options {
3970        set id [lindex $opt 0]
3971        if {[lindex $opt 1] eq "b"} {
3972            # Checkboxes
3973            set val 0
3974        } elseif {[regexp {^r(\d+)$} [lindex $opt 1]]} {
3975            # Radiobuttons
3976            regexp {^(.*_)} $id uselessvar id
3977            set val 0
3978        } else {
3979            # Text fields
3980            set val {}
3981        }
3982        set newviewopts($n,$id) $val
3983    }
3984    set oargs [list]
3985    set refargs [list]
3986    foreach arg $view_args {
3987        if {[regexp -- {^-([0-9]+)$} $arg arg cnt]
3988            && ![info exists found(limit)]} {
3989            set newviewopts($n,limit) $cnt
3990            set found(limit) 1
3991            continue
3992        }
3993        catch { unset val }
3994        foreach opt $known_view_options {
3995            set id [lindex $opt 0]
3996            if {[info exists found($id)]} continue
3997            foreach pattern [lindex $opt 3] {
3998                if {![string match $pattern $arg]} continue
3999                if {[lindex $opt 1] eq "b"} {
4000                    # Check buttons
4001                    set val 1
4002                } elseif {[regexp {^r(\d+)$} [lindex $opt 1] match num]} {
4003                    # Radio buttons
4004                    regexp {^(.*_)} $id uselessvar id
4005                    set val $num
4006                } else {
4007                    # Text input fields
4008                    set size [string length $pattern]
4009                    set val [string range $arg [expr {$size-1}] end]
4010                }
4011                set newviewopts($n,$id) $val
4012                set found($id) 1
4013                break
4014            }
4015            if {[info exists val]} break
4016        }
4017        if {[info exists val]} continue
4018        if {[regexp {^-} $arg]} {
4019            lappend oargs $arg
4020        } else {
4021            lappend refargs $arg
4022        }
4023    }
4024    set newviewopts($n,refs) [shellarglist $refargs]
4025    set newviewopts($n,args) [shellarglist $oargs]
4026}
4027
4028proc edit_or_newview {} {
4029    global curview
4030
4031    if {$curview > 0} {
4032        editview
4033    } else {
4034        newview 0
4035    }
4036}
4037
4038proc editview {} {
4039    global curview
4040    global viewname viewperm newviewname newviewopts
4041    global viewargs viewargscmd
4042
4043    set top .gitkvedit-$curview
4044    if {[winfo exists $top]} {
4045        raise $top
4046        return
4047    }
4048    decode_view_opts $curview $viewargs($curview)
4049    set newviewname($curview)      $viewname($curview)
4050    set newviewopts($curview,perm) $viewperm($curview)
4051    set newviewopts($curview,cmd)  $viewargscmd($curview)
4052    vieweditor $top $curview "[mc "Gitk: edit view"] $viewname($curview)"
4053}
4054
4055proc vieweditor {top n title} {
4056    global newviewname newviewopts viewfiles bgcolor
4057    global known_view_options NS
4058
4059    ttk_toplevel $top
4060    wm title $top [concat $title [mc "-- criteria for selecting revisions"]]
4061    make_transient $top .
4062
4063    # View name
4064    ${NS}::frame $top.nfr
4065    ${NS}::label $top.nl -text [mc "View Name"]
4066    ${NS}::entry $top.name -width 20 -textvariable newviewname($n)
4067    pack $top.nfr -in $top -fill x -pady 5 -padx 3
4068    pack $top.nl -in $top.nfr -side left -padx {0 5}
4069    pack $top.name -in $top.nfr -side left -padx {0 25}
4070
4071    # View options
4072    set cframe $top.nfr
4073    set cexpand 0
4074    set cnt 0
4075    foreach opt $known_view_options {
4076        set id [lindex $opt 0]
4077        set type [lindex $opt 1]
4078        set flags [lindex $opt 2]
4079        set title [eval [lindex $opt 4]]
4080        set lxpad 0
4081
4082        if {$flags eq "+" || $flags eq "*"} {
4083            set cframe $top.fr$cnt
4084            incr cnt
4085            ${NS}::frame $cframe
4086            pack $cframe -in $top -fill x -pady 3 -padx 3
4087            set cexpand [expr {$flags eq "*"}]
4088        } elseif {$flags eq ".." || $flags eq "*."} {
4089            set cframe $top.fr$cnt
4090            incr cnt
4091            ${NS}::frame $cframe
4092            pack $cframe -in $top -fill x -pady 3 -padx [list 15 3]
4093            set cexpand [expr {$flags eq "*."}]
4094        } else {
4095            set lxpad 5
4096        }
4097
4098        if {$type eq "l"} {
4099            ${NS}::label $cframe.l_$id -text $title
4100            pack $cframe.l_$id -in $cframe -side left -pady [list 3 0] -anchor w
4101        } elseif {$type eq "b"} {
4102            ${NS}::checkbutton $cframe.c_$id -text $title -variable newviewopts($n,$id)
4103            pack $cframe.c_$id -in $cframe -side left \
4104                -padx [list $lxpad 0] -expand $cexpand -anchor w
4105        } elseif {[regexp {^r(\d+)$} $type type sz]} {
4106            regexp {^(.*_)} $id uselessvar button_id
4107            ${NS}::radiobutton $cframe.c_$id -text $title -variable newviewopts($n,$button_id) -value $sz
4108            pack $cframe.c_$id -in $cframe -side left \
4109                -padx [list $lxpad 0] -expand $cexpand -anchor w
4110        } elseif {[regexp {^t(\d+)$} $type type sz]} {
4111            ${NS}::label $cframe.l_$id -text $title
4112            ${NS}::entry $cframe.e_$id -width $sz -background $bgcolor \
4113                -textvariable newviewopts($n,$id)
4114            pack $cframe.l_$id -in $cframe -side left -padx [list $lxpad 0]
4115            pack $cframe.e_$id -in $cframe -side left -expand 1 -fill x
4116        } elseif {[regexp {^t(\d+)=$} $type type sz]} {
4117            ${NS}::label $cframe.l_$id -text $title
4118            ${NS}::entry $cframe.e_$id -width $sz -background $bgcolor \
4119                -textvariable newviewopts($n,$id)
4120            pack $cframe.l_$id -in $cframe -side top -pady [list 3 0] -anchor w
4121            pack $cframe.e_$id -in $cframe -side top -fill x
4122        } elseif {$type eq "path"} {
4123            ${NS}::label $top.l -text $title
4124            pack $top.l -in $top -side top -pady [list 3 0] -anchor w -padx 3
4125            text $top.t -width 40 -height 5 -background $bgcolor
4126            if {[info exists viewfiles($n)]} {
4127                foreach f $viewfiles($n) {
4128                    $top.t insert end $f
4129                    $top.t insert end "\n"
4130                }
4131                $top.t delete {end - 1c} end
4132                $top.t mark set insert 0.0
4133            }
4134            pack $top.t -in $top -side top -pady [list 0 5] -fill both -expand 1 -padx 3
4135        }
4136    }
4137
4138    ${NS}::frame $top.buts
4139    ${NS}::button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n]
4140    ${NS}::button $top.buts.apply -text [mc "Apply (F5)"] -command [list newviewok $top $n 1]
4141    ${NS}::button $top.buts.can -text [mc "Cancel"] -command [list destroy $top]
4142    bind $top <Control-Return> [list newviewok $top $n]
4143    bind $top <F5> [list newviewok $top $n 1]
4144    bind $top <Escape> [list destroy $top]
4145    grid $top.buts.ok $top.buts.apply $top.buts.can
4146    grid columnconfigure $top.buts 0 -weight 1 -uniform a
4147    grid columnconfigure $top.buts 1 -weight 1 -uniform a
4148    grid columnconfigure $top.buts 2 -weight 1 -uniform a
4149    pack $top.buts -in $top -side top -fill x
4150    focus $top.t
4151}
4152
4153proc doviewmenu {m first cmd op argv} {
4154    set nmenu [$m index end]
4155    for {set i $first} {$i <= $nmenu} {incr i} {
4156        if {[$m entrycget $i -command] eq $cmd} {
4157            eval $m $op $i $argv
4158            break
4159        }
4160    }
4161}
4162
4163proc allviewmenus {n op args} {
4164    # global viewhlmenu
4165
4166    doviewmenu .bar.view 5 [list showview $n] $op $args
4167    # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
4168}
4169
4170proc newviewok {top n {apply 0}} {
4171    global nextviewnum newviewperm newviewname newishighlight
4172    global viewname viewfiles viewperm selectedview curview
4173    global viewargs viewargscmd newviewopts viewhlmenu
4174
4175    if {[catch {
4176        set newargs [encode_view_opts $n]
4177    } err]} {
4178        error_popup "[mc "Error in commit selection arguments:"] $err" $top
4179        return
4180    }
4181    set files {}
4182    foreach f [split [$top.t get 0.0 end] "\n"] {
4183        set ft [string trim $f]
4184        if {$ft ne {}} {
4185            lappend files $ft
4186        }
4187    }
4188    if {![info exists viewfiles($n)]} {
4189        # creating a new view
4190        incr nextviewnum
4191        set viewname($n) $newviewname($n)
4192        set viewperm($n) $newviewopts($n,perm)
4193        set viewfiles($n) $files
4194        set viewargs($n) $newargs
4195        set viewargscmd($n) $newviewopts($n,cmd)
4196        addviewmenu $n
4197        if {!$newishighlight} {
4198            run showview $n
4199        } else {
4200            run addvhighlight $n
4201        }
4202    } else {
4203        # editing an existing view
4204        set viewperm($n) $newviewopts($n,perm)
4205        if {$newviewname($n) ne $viewname($n)} {
4206            set viewname($n) $newviewname($n)
4207            doviewmenu .bar.view 5 [list showview $n] \
4208                entryconf [list -label $viewname($n)]
4209            # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
4210                # entryconf [list -label $viewname($n) -value $viewname($n)]
4211        }
4212        if {$files ne $viewfiles($n) || $newargs ne $viewargs($n) || \
4213                $newviewopts($n,cmd) ne $viewargscmd($n)} {
4214            set viewfiles($n) $files
4215            set viewargs($n) $newargs
4216            set viewargscmd($n) $newviewopts($n,cmd)
4217            if {$curview == $n} {
4218                run reloadcommits
4219            }
4220        }
4221    }
4222    if {$apply} return
4223    catch {destroy $top}
4224}
4225
4226proc delview {} {
4227    global curview viewperm hlview selectedhlview
4228
4229    if {$curview == 0} return
4230    if {[info exists hlview] && $hlview == $curview} {
4231        set selectedhlview [mc "None"]
4232        unset hlview
4233    }
4234    allviewmenus $curview delete
4235    set viewperm($curview) 0
4236    showview 0
4237}
4238
4239proc addviewmenu {n} {
4240    global viewname viewhlmenu
4241
4242    .bar.view add radiobutton -label $viewname($n) \
4243        -command [list showview $n] -variable selectedview -value $n
4244    #$viewhlmenu add radiobutton -label $viewname($n) \
4245    #   -command [list addvhighlight $n] -variable selectedhlview
4246}
4247
4248proc showview {n} {
4249    global curview cached_commitrow ordertok
4250    global displayorder parentlist rowidlist rowisopt rowfinal
4251    global colormap rowtextx nextcolor canvxmax
4252    global numcommits viewcomplete
4253    global selectedline currentid canv canvy0
4254    global treediffs
4255    global pending_select mainheadid
4256    global commitidx
4257    global selectedview
4258    global hlview selectedhlview commitinterest
4259
4260    if {$n == $curview} return
4261    set selid {}
4262    set ymax [lindex [$canv cget -scrollregion] 3]
4263    set span [$canv yview]
4264    set ytop [expr {[lindex $span 0] * $ymax}]
4265    set ybot [expr {[lindex $span 1] * $ymax}]
4266    set yscreen [expr {($ybot - $ytop) / 2}]
4267    if {$selectedline ne {}} {
4268        set selid $currentid
4269        set y [yc $selectedline]
4270        if {$ytop < $y && $y < $ybot} {
4271            set yscreen [expr {$y - $ytop}]
4272        }
4273    } elseif {[info exists pending_select]} {
4274        set selid $pending_select
4275        unset pending_select
4276    }
4277    unselectline
4278    normalline
4279    catch {unset treediffs}
4280    clear_display
4281    if {[info exists hlview] && $hlview == $n} {
4282        unset hlview
4283        set selectedhlview [mc "None"]
4284    }
4285    catch {unset commitinterest}
4286    catch {unset cached_commitrow}
4287    catch {unset ordertok}
4288
4289    set curview $n
4290    set selectedview $n
4291    .bar.view entryconf [mca "Edit view..."] -state [expr {$n == 0? "disabled": "normal"}]
4292    .bar.view entryconf [mca "Delete view"] -state [expr {$n == 0? "disabled": "normal"}]
4293
4294    run refill_reflist
4295    if {![info exists viewcomplete($n)]} {
4296        getcommits $selid
4297        return
4298    }
4299
4300    set displayorder {}
4301    set parentlist {}
4302    set rowidlist {}
4303    set rowisopt {}
4304    set rowfinal {}
4305    set numcommits $commitidx($n)
4306
4307    catch {unset colormap}
4308    catch {unset rowtextx}
4309    set nextcolor 0
4310    set canvxmax [$canv cget -width]
4311    set curview $n
4312    set row 0
4313    setcanvscroll
4314    set yf 0
4315    set row {}
4316    if {$selid ne {} && [commitinview $selid $n]} {
4317        set row [rowofcommit $selid]
4318        # try to get the selected row in the same position on the screen
4319        set ymax [lindex [$canv cget -scrollregion] 3]
4320        set ytop [expr {[yc $row] - $yscreen}]
4321        if {$ytop < 0} {
4322            set ytop 0
4323        }
4324        set yf [expr {$ytop * 1.0 / $ymax}]
4325    }
4326    allcanvs yview moveto $yf
4327    drawvisible
4328    if {$row ne {}} {
4329        selectline $row 0
4330    } elseif {!$viewcomplete($n)} {
4331        reset_pending_select $selid
4332    } else {
4333        reset_pending_select {}
4334
4335        if {[commitinview $pending_select $curview]} {
4336            selectline [rowofcommit $pending_select] 1
4337        } else {
4338            set row [first_real_row]
4339            if {$row < $numcommits} {
4340                selectline $row 0
4341            }
4342        }
4343    }
4344    if {!$viewcomplete($n)} {
4345        if {$numcommits == 0} {
4346            show_status [mc "Reading commits..."]
4347        }
4348    } elseif {$numcommits == 0} {
4349        show_status [mc "No commits selected"]
4350    }
4351}
4352
4353# Stuff relating to the highlighting facility
4354
4355proc ishighlighted {id} {
4356    global vhighlights fhighlights nhighlights rhighlights
4357
4358    if {[info exists nhighlights($id)] && $nhighlights($id) > 0} {
4359        return $nhighlights($id)
4360    }
4361    if {[info exists vhighlights($id)] && $vhighlights($id) > 0} {
4362        return $vhighlights($id)
4363    }
4364    if {[info exists fhighlights($id)] && $fhighlights($id) > 0} {
4365        return $fhighlights($id)
4366    }
4367    if {[info exists rhighlights($id)] && $rhighlights($id) > 0} {
4368        return $rhighlights($id)
4369    }
4370    return 0
4371}
4372
4373proc bolden {id font} {
4374    global canv linehtag currentid boldids need_redisplay markedid
4375
4376    # need_redisplay = 1 means the display is stale and about to be redrawn
4377    if {$need_redisplay} return
4378    lappend boldids $id
4379    $canv itemconf $linehtag($id) -font $font
4380    if {[info exists currentid] && $id eq $currentid} {
4381        $canv delete secsel
4382        set t [eval $canv create rect [$canv bbox $linehtag($id)] \
4383                   -outline {{}} -tags secsel \
4384                   -fill [$canv cget -selectbackground]]
4385        $canv lower $t
4386    }
4387    if {[info exists markedid] && $id eq $markedid} {
4388        make_idmark $id
4389    }
4390}
4391
4392proc bolden_name {id font} {
4393    global canv2 linentag currentid boldnameids need_redisplay
4394
4395    if {$need_redisplay} return
4396    lappend boldnameids $id
4397    $canv2 itemconf $linentag($id) -font $font
4398    if {[info exists currentid] && $id eq $currentid} {
4399        $canv2 delete secsel
4400        set t [eval $canv2 create rect [$canv2 bbox $linentag($id)] \
4401                   -outline {{}} -tags secsel \
4402                   -fill [$canv2 cget -selectbackground]]
4403        $canv2 lower $t
4404    }
4405}
4406
4407proc unbolden {} {
4408    global boldids
4409
4410    set stillbold {}
4411    foreach id $boldids {
4412        if {![ishighlighted $id]} {
4413            bolden $id mainfont
4414        } else {
4415            lappend stillbold $id
4416        }
4417    }
4418    set boldids $stillbold
4419}
4420
4421proc addvhighlight {n} {
4422    global hlview viewcomplete curview vhl_done commitidx
4423
4424    if {[info exists hlview]} {
4425        delvhighlight
4426    }
4427    set hlview $n
4428    if {$n != $curview && ![info exists viewcomplete($n)]} {
4429        start_rev_list $n
4430    }
4431    set vhl_done $commitidx($hlview)
4432    if {$vhl_done > 0} {
4433        drawvisible
4434    }
4435}
4436
4437proc delvhighlight {} {
4438    global hlview vhighlights
4439
4440    if {![info exists hlview]} return
4441    unset hlview
4442    catch {unset vhighlights}
4443    unbolden
4444}
4445
4446proc vhighlightmore {} {
4447    global hlview vhl_done commitidx vhighlights curview
4448
4449    set max $commitidx($hlview)
4450    set vr [visiblerows]
4451    set r0 [lindex $vr 0]
4452    set r1 [lindex $vr 1]
4453    for {set i $vhl_done} {$i < $max} {incr i} {
4454        set id [commitonrow $i $hlview]
4455        if {[commitinview $id $curview]} {
4456            set row [rowofcommit $id]
4457            if {$r0 <= $row && $row <= $r1} {
4458                if {![highlighted $row]} {
4459                    bolden $id mainfontbold
4460                }
4461                set vhighlights($id) 1
4462            }
4463        }
4464    }
4465    set vhl_done $max
4466    return 0
4467}
4468
4469proc askvhighlight {row id} {
4470    global hlview vhighlights iddrawn
4471
4472    if {[commitinview $id $hlview]} {
4473        if {[info exists iddrawn($id)] && ![ishighlighted $id]} {
4474            bolden $id mainfontbold
4475        }
4476        set vhighlights($id) 1
4477    } else {
4478        set vhighlights($id) 0
4479    }
4480}
4481
4482proc hfiles_change {} {
4483    global highlight_files filehighlight fhighlights fh_serial
4484    global highlight_paths
4485
4486    if {[info exists filehighlight]} {
4487        # delete previous highlights
4488        catch {close $filehighlight}
4489        unset filehighlight
4490        catch {unset fhighlights}
4491        unbolden
4492        unhighlight_filelist
4493    }
4494    set highlight_paths {}
4495    after cancel do_file_hl $fh_serial
4496    incr fh_serial
4497    if {$highlight_files ne {}} {
4498        after 300 do_file_hl $fh_serial
4499    }
4500}
4501
4502proc gdttype_change {name ix op} {
4503    global gdttype highlight_files findstring findpattern
4504
4505    stopfinding
4506    if {$findstring ne {}} {
4507        if {$gdttype eq [mc "containing:"]} {
4508            if {$highlight_files ne {}} {
4509                set highlight_files {}
4510                hfiles_change
4511            }
4512            findcom_change
4513        } else {
4514            if {$findpattern ne {}} {
4515                set findpattern {}
4516                findcom_change
4517            }
4518            set highlight_files $findstring
4519            hfiles_change
4520        }
4521        drawvisible
4522    }
4523    # enable/disable findtype/findloc menus too
4524}
4525
4526proc find_change {name ix op} {
4527    global gdttype findstring highlight_files
4528
4529    stopfinding
4530    if {$gdttype eq [mc "containing:"]} {
4531        findcom_change
4532    } else {
4533        if {$highlight_files ne $findstring} {
4534            set highlight_files $findstring
4535            hfiles_change
4536        }
4537    }
4538    drawvisible
4539}
4540
4541proc findcom_change args {
4542    global nhighlights boldnameids
4543    global findpattern findtype findstring gdttype
4544
4545    stopfinding
4546    # delete previous highlights, if any
4547    foreach id $boldnameids {
4548        bolden_name $id mainfont
4549    }
4550    set boldnameids {}
4551    catch {unset nhighlights}
4552    unbolden
4553    unmarkmatches
4554    if {$gdttype ne [mc "containing:"] || $findstring eq {}} {
4555        set findpattern {}
4556    } elseif {$findtype eq [mc "Regexp"]} {
4557        set findpattern $findstring
4558    } else {
4559        set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
4560                   $findstring]
4561        set findpattern "*$e*"
4562    }
4563}
4564
4565proc makepatterns {l} {
4566    set ret {}
4567    foreach e $l {
4568        set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
4569        if {[string index $ee end] eq "/"} {
4570            lappend ret "$ee*"
4571        } else {
4572            lappend ret $ee
4573            lappend ret "$ee/*"
4574        }
4575    }
4576    return $ret
4577}
4578
4579proc do_file_hl {serial} {
4580    global highlight_files filehighlight highlight_paths gdttype fhl_list
4581    global cdup findtype
4582
4583    if {$gdttype eq [mc "touching paths:"]} {
4584        # If "exact" match then convert backslashes to forward slashes.
4585        # Most useful to support Windows-flavoured file paths.
4586        if {$findtype eq [mc "Exact"]} {
4587            set highlight_files [string map {"\\" "/"} $highlight_files]
4588        }
4589        if {[catch {set paths [shellsplit $highlight_files]}]} return
4590        set highlight_paths [makepatterns $paths]
4591        highlight_filelist
4592        set relative_paths {}
4593        foreach path $paths {
4594            lappend relative_paths [file join $cdup $path]
4595        }
4596        set gdtargs [concat -- $relative_paths]
4597    } elseif {$gdttype eq [mc "adding/removing string:"]} {
4598        set gdtargs [list "-S$highlight_files"]
4599    } else {
4600        # must be "containing:", i.e. we're searching commit info
4601        return
4602    }
4603    set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
4604    set filehighlight [open $cmd r+]
4605    fconfigure $filehighlight -blocking 0
4606    filerun $filehighlight readfhighlight
4607    set fhl_list {}
4608    drawvisible
4609    flushhighlights
4610}
4611
4612proc flushhighlights {} {
4613    global filehighlight fhl_list
4614
4615    if {[info exists filehighlight]} {
4616        lappend fhl_list {}
4617        puts $filehighlight ""
4618        flush $filehighlight
4619    }
4620}
4621
4622proc askfilehighlight {row id} {
4623    global filehighlight fhighlights fhl_list
4624
4625    lappend fhl_list $id
4626    set fhighlights($id) -1
4627    puts $filehighlight $id
4628}
4629
4630proc readfhighlight {} {
4631    global filehighlight fhighlights curview iddrawn
4632    global fhl_list find_dirn
4633
4634    if {![info exists filehighlight]} {
4635        return 0
4636    }
4637    set nr 0
4638    while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
4639        set line [string trim $line]
4640        set i [lsearch -exact $fhl_list $line]
4641        if {$i < 0} continue
4642        for {set j 0} {$j < $i} {incr j} {
4643            set id [lindex $fhl_list $j]
4644            set fhighlights($id) 0
4645        }
4646        set fhl_list [lrange $fhl_list [expr {$i+1}] end]
4647        if {$line eq {}} continue
4648        if {![commitinview $line $curview]} continue
4649        if {[info exists iddrawn($line)] && ![ishighlighted $line]} {
4650            bolden $line mainfontbold
4651        }
4652        set fhighlights($line) 1
4653    }
4654    if {[eof $filehighlight]} {
4655        # strange...
4656        puts "oops, git diff-tree died"
4657        catch {close $filehighlight}
4658        unset filehighlight
4659        return 0
4660    }
4661    if {[info exists find_dirn]} {
4662        run findmore
4663    }
4664    return 1
4665}
4666
4667proc doesmatch {f} {
4668    global findtype findpattern
4669
4670    if {$findtype eq [mc "Regexp"]} {
4671        return [regexp $findpattern $f]
4672    } elseif {$findtype eq [mc "IgnCase"]} {
4673        return [string match -nocase $findpattern $f]
4674    } else {
4675        return [string match $findpattern $f]
4676    }
4677}
4678
4679proc askfindhighlight {row id} {
4680    global nhighlights commitinfo iddrawn
4681    global findloc
4682    global markingmatches
4683
4684    if {![info exists commitinfo($id)]} {
4685        getcommit $id
4686    }
4687    set info $commitinfo($id)
4688    set isbold 0
4689    set fldtypes [list [mc Headline] [mc Author] "" [mc Committer] "" [mc Comments]]
4690    foreach f $info ty $fldtypes {
4691        if {$ty eq ""} continue
4692        if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
4693            [doesmatch $f]} {
4694            if {$ty eq [mc "Author"]} {
4695                set isbold 2
4696                break
4697            }
4698            set isbold 1
4699        }
4700    }
4701    if {$isbold && [info exists iddrawn($id)]} {
4702        if {![ishighlighted $id]} {
4703            bolden $id mainfontbold
4704            if {$isbold > 1} {
4705                bolden_name $id mainfontbold
4706            }
4707        }
4708        if {$markingmatches} {
4709            markrowmatches $row $id
4710        }
4711    }
4712    set nhighlights($id) $isbold
4713}
4714
4715proc markrowmatches {row id} {
4716    global canv canv2 linehtag linentag commitinfo findloc
4717
4718    set headline [lindex $commitinfo($id) 0]
4719    set author [lindex $commitinfo($id) 1]
4720    $canv delete match$row
4721    $canv2 delete match$row
4722    if {$findloc eq [mc "All fields"] || $findloc eq [mc "Headline"]} {
4723        set m [findmatches $headline]
4724        if {$m ne {}} {
4725            markmatches $canv $row $headline $linehtag($id) $m \
4726                [$canv itemcget $linehtag($id) -font] $row
4727        }
4728    }
4729    if {$findloc eq [mc "All fields"] || $findloc eq [mc "Author"]} {
4730        set m [findmatches $author]
4731        if {$m ne {}} {
4732            markmatches $canv2 $row $author $linentag($id) $m \
4733                [$canv2 itemcget $linentag($id) -font] $row
4734        }
4735    }
4736}
4737
4738proc vrel_change {name ix op} {
4739    global highlight_related
4740
4741    rhighlight_none
4742    if {$highlight_related ne [mc "None"]} {
4743        run drawvisible
4744    }
4745}
4746
4747# prepare for testing whether commits are descendents or ancestors of a
4748proc rhighlight_sel {a} {
4749    global descendent desc_todo ancestor anc_todo
4750    global highlight_related
4751
4752    catch {unset descendent}
4753    set desc_todo [list $a]
4754    catch {unset ancestor}
4755    set anc_todo [list $a]
4756    if {$highlight_related ne [mc "None"]} {
4757        rhighlight_none
4758        run drawvisible
4759    }
4760}
4761
4762proc rhighlight_none {} {
4763    global rhighlights
4764
4765    catch {unset rhighlights}
4766    unbolden
4767}
4768
4769proc is_descendent {a} {
4770    global curview children descendent desc_todo
4771
4772    set v $curview
4773    set la [rowofcommit $a]
4774    set todo $desc_todo
4775    set leftover {}
4776    set done 0
4777    for {set i 0} {$i < [llength $todo]} {incr i} {
4778        set do [lindex $todo $i]
4779        if {[rowofcommit $do] < $la} {
4780            lappend leftover $do
4781            continue
4782        }
4783        foreach nk $children($v,$do) {
4784            if {![info exists descendent($nk)]} {
4785                set descendent($nk) 1
4786                lappend todo $nk
4787                if {$nk eq $a} {
4788                    set done 1
4789                }
4790            }
4791        }
4792        if {$done} {
4793            set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
4794            return
4795        }
4796    }
4797    set descendent($a) 0
4798    set desc_todo $leftover
4799}
4800
4801proc is_ancestor {a} {
4802    global curview parents ancestor anc_todo
4803
4804    set v $curview
4805    set la [rowofcommit $a]
4806    set todo $anc_todo
4807    set leftover {}
4808    set done 0
4809    for {set i 0} {$i < [llength $todo]} {incr i} {
4810        set do [lindex $todo $i]
4811        if {![commitinview $do $v] || [rowofcommit $do] > $la} {
4812            lappend leftover $do
4813            continue
4814        }
4815        foreach np $parents($v,$do) {
4816            if {![info exists ancestor($np)]} {
4817                set ancestor($np) 1
4818                lappend todo $np
4819                if {$np eq $a} {
4820                    set done 1
4821                }
4822            }
4823        }
4824        if {$done} {
4825            set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
4826            return
4827        }
4828    }
4829    set ancestor($a) 0
4830    set anc_todo $leftover
4831}
4832
4833proc askrelhighlight {row id} {
4834    global descendent highlight_related iddrawn rhighlights
4835    global selectedline ancestor
4836
4837    if {$selectedline eq {}} return
4838    set isbold 0
4839    if {$highlight_related eq [mc "Descendant"] ||
4840        $highlight_related eq [mc "Not descendant"]} {
4841        if {![info exists descendent($id)]} {
4842            is_descendent $id
4843        }
4844        if {$descendent($id) == ($highlight_related eq [mc "Descendant"])} {
4845            set isbold 1
4846        }
4847    } elseif {$highlight_related eq [mc "Ancestor"] ||
4848              $highlight_related eq [mc "Not ancestor"]} {
4849        if {![info exists ancestor($id)]} {
4850            is_ancestor $id
4851        }
4852        if {$ancestor($id) == ($highlight_related eq [mc "Ancestor"])} {
4853            set isbold 1
4854        }
4855    }
4856    if {[info exists iddrawn($id)]} {
4857        if {$isbold && ![ishighlighted $id]} {
4858            bolden $id mainfontbold
4859        }
4860    }
4861    set rhighlights($id) $isbold
4862}
4863
4864# Graph layout functions
4865
4866proc shortids {ids} {
4867    set res {}
4868    foreach id $ids {
4869        if {[llength $id] > 1} {
4870            lappend res [shortids $id]
4871        } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
4872            lappend res [string range $id 0 7]
4873        } else {
4874            lappend res $id
4875        }
4876    }
4877    return $res
4878}
4879
4880proc ntimes {n o} {
4881    set ret {}
4882    set o [list $o]
4883    for {set mask 1} {$mask <= $n} {incr mask $mask} {
4884        if {($n & $mask) != 0} {
4885            set ret [concat $ret $o]
4886        }
4887        set o [concat $o $o]
4888    }
4889    return $ret
4890}
4891
4892proc ordertoken {id} {
4893    global ordertok curview varcid varcstart varctok curview parents children
4894    global nullid nullid2
4895
4896    if {[info exists ordertok($id)]} {
4897        return $ordertok($id)
4898    }
4899    set origid $id
4900    set todo {}
4901    while {1} {
4902        if {[info exists varcid($curview,$id)]} {
4903            set a $varcid($curview,$id)
4904            set p [lindex $varcstart($curview) $a]
4905        } else {
4906            set p [lindex $children($curview,$id) 0]
4907        }
4908        if {[info exists ordertok($p)]} {
4909            set tok $ordertok($p)
4910            break
4911        }
4912        set id [first_real_child $curview,$p]
4913        if {$id eq {}} {
4914            # it's a root
4915            set tok [lindex $varctok($curview) $varcid($curview,$p)]
4916            break
4917        }
4918        if {[llength $parents($curview,$id)] == 1} {
4919            lappend todo [list $p {}]
4920        } else {
4921            set j [lsearch -exact $parents($curview,$id) $p]
4922            if {$j < 0} {
4923                puts "oops didn't find [shortids $p] in parents of [shortids $id]"
4924            }
4925            lappend todo [list $p [strrep $j]]
4926        }
4927    }
4928    for {set i [llength $todo]} {[incr i -1] >= 0} {} {
4929        set p [lindex $todo $i 0]
4930        append tok [lindex $todo $i 1]
4931        set ordertok($p) $tok
4932    }
4933    set ordertok($origid) $tok
4934    return $tok
4935}
4936
4937# Work out where id should go in idlist so that order-token
4938# values increase from left to right
4939proc idcol {idlist id {i 0}} {
4940    set t [ordertoken $id]
4941    if {$i < 0} {
4942        set i 0
4943    }
4944    if {$i >= [llength $idlist] || $t < [ordertoken [lindex $idlist $i]]} {
4945        if {$i > [llength $idlist]} {
4946            set i [llength $idlist]
4947        }
4948        while {[incr i -1] >= 0 && $t < [ordertoken [lindex $idlist $i]]} {}
4949        incr i
4950    } else {
4951        if {$t > [ordertoken [lindex $idlist $i]]} {
4952            while {[incr i] < [llength $idlist] &&
4953                   $t >= [ordertoken [lindex $idlist $i]]} {}
4954        }
4955    }
4956    return $i
4957}
4958
4959proc initlayout {} {
4960    global rowidlist rowisopt rowfinal displayorder parentlist
4961    global numcommits canvxmax canv
4962    global nextcolor
4963    global colormap rowtextx
4964
4965    set numcommits 0
4966    set displayorder {}
4967    set parentlist {}
4968    set nextcolor 0
4969    set rowidlist {}
4970    set rowisopt {}
4971    set rowfinal {}
4972    set canvxmax [$canv cget -width]
4973    catch {unset colormap}
4974    catch {unset rowtextx}
4975    setcanvscroll
4976}
4977
4978proc setcanvscroll {} {
4979    global canv canv2 canv3 numcommits linespc canvxmax canvy0
4980    global lastscrollset lastscrollrows
4981
4982    set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
4983    $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
4984    $canv2 conf -scrollregion [list 0 0 0 $ymax]
4985    $canv3 conf -scrollregion [list 0 0 0 $ymax]
4986    set lastscrollset [clock clicks -milliseconds]
4987    set lastscrollrows $numcommits
4988}
4989
4990proc visiblerows {} {
4991    global canv numcommits linespc
4992
4993    set ymax [lindex [$canv cget -scrollregion] 3]
4994    if {$ymax eq {} || $ymax == 0} return
4995    set f [$canv yview]
4996    set y0 [expr {int([lindex $f 0] * $ymax)}]
4997    set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
4998    if {$r0 < 0} {
4999        set r0 0
5000    }
5001    set y1 [expr {int([lindex $f 1] * $ymax)}]
5002    set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
5003    if {$r1 >= $numcommits} {
5004        set r1 [expr {$numcommits - 1}]
5005    }
5006    return [list $r0 $r1]
5007}
5008
5009proc layoutmore {} {
5010    global commitidx viewcomplete curview
5011    global numcommits pending_select curview
5012    global lastscrollset lastscrollrows
5013
5014    if {$lastscrollrows < 100 || $viewcomplete($curview) ||
5015        [clock clicks -milliseconds] - $lastscrollset > 500} {
5016        setcanvscroll
5017    }
5018    if {[info exists pending_select] &&
5019        [commitinview $pending_select $curview]} {
5020        update
5021        selectline [rowofcommit $pending_select] 1
5022    }
5023    drawvisible
5024}
5025
5026# With path limiting, we mightn't get the actual HEAD commit,
5027# so ask git rev-list what is the first ancestor of HEAD that
5028# touches a file in the path limit.
5029proc get_viewmainhead {view} {
5030    global viewmainheadid vfilelimit viewinstances mainheadid
5031
5032    catch {
5033        set rfd [open [concat | git rev-list -1 $mainheadid \
5034                           -- $vfilelimit($view)] r]
5035        set j [reg_instance $rfd]
5036        lappend viewinstances($view) $j
5037        fconfigure $rfd -blocking 0
5038        filerun $rfd [list getviewhead $rfd $j $view]
5039        set viewmainheadid($curview) {}
5040    }
5041}
5042
5043# git rev-list should give us just 1 line to use as viewmainheadid($view)
5044proc getviewhead {fd inst view} {
5045    global viewmainheadid commfd curview viewinstances showlocalchanges
5046
5047    set id {}
5048    if {[gets $fd line] < 0} {
5049        if {![eof $fd]} {
5050            return 1
5051        }
5052    } elseif {[string length $line] == 40 && [string is xdigit $line]} {
5053        set id $line
5054    }
5055    set viewmainheadid($view) $id
5056    close $fd
5057    unset commfd($inst)
5058    set i [lsearch -exact $viewinstances($view) $inst]
5059    if {$i >= 0} {
5060        set viewinstances($view) [lreplace $viewinstances($view) $i $i]
5061    }
5062    if {$showlocalchanges && $id ne {} && $view == $curview} {
5063        doshowlocalchanges
5064    }
5065    return 0
5066}
5067
5068proc doshowlocalchanges {} {
5069    global curview viewmainheadid
5070
5071    if {$viewmainheadid($curview) eq {}} return
5072    if {[commitinview $viewmainheadid($curview) $curview]} {
5073        dodiffindex
5074    } else {
5075        interestedin $viewmainheadid($curview) dodiffindex
5076    }
5077}
5078
5079proc dohidelocalchanges {} {
5080    global nullid nullid2 lserial curview
5081
5082    if {[commitinview $nullid $curview]} {
5083        removefakerow $nullid
5084    }
5085    if {[commitinview $nullid2 $curview]} {
5086        removefakerow $nullid2
5087    }
5088    incr lserial
5089}
5090
5091# spawn off a process to do git diff-index --cached HEAD
5092proc dodiffindex {} {
5093    global lserial showlocalchanges vfilelimit curview
5094    global hasworktree
5095
5096    if {!$showlocalchanges || !$hasworktree} return
5097    incr lserial
5098    set cmd "|git diff-index --cached HEAD"
5099    if {$vfilelimit($curview) ne {}} {
5100        set cmd [concat $cmd -- $vfilelimit($curview)]
5101    }
5102    set fd [open $cmd r]
5103    fconfigure $fd -blocking 0
5104    set i [reg_instance $fd]
5105    filerun $fd [list readdiffindex $fd $lserial $i]
5106}
5107
5108proc readdiffindex {fd serial inst} {
5109    global viewmainheadid nullid nullid2 curview commitinfo commitdata lserial
5110    global vfilelimit
5111
5112    set isdiff 1
5113    if {[gets $fd line] < 0} {
5114        if {![eof $fd]} {
5115            return 1
5116        }
5117        set isdiff 0
5118    }
5119    # we only need to see one line and we don't really care what it says...
5120    stop_instance $inst
5121
5122    if {$serial != $lserial} {
5123        return 0
5124    }
5125
5126    # now see if there are any local changes not checked in to the index
5127    set cmd "|git diff-files"
5128    if {$vfilelimit($curview) ne {}} {
5129        set cmd [concat $cmd -- $vfilelimit($curview)]
5130    }
5131    set fd [open $cmd r]
5132    fconfigure $fd -blocking 0
5133    set i [reg_instance $fd]
5134    filerun $fd [list readdifffiles $fd $serial $i]
5135
5136    if {$isdiff && ![commitinview $nullid2 $curview]} {
5137        # add the line for the changes in the index to the graph
5138        set hl [mc "Local changes checked in to index but not committed"]
5139        set commitinfo($nullid2) [list  $hl {} {} {} {} "    $hl\n"]
5140        set commitdata($nullid2) "\n    $hl\n"
5141        if {[commitinview $nullid $curview]} {
5142            removefakerow $nullid
5143        }
5144        insertfakerow $nullid2 $viewmainheadid($curview)
5145    } elseif {!$isdiff && [commitinview $nullid2 $curview]} {
5146        if {[commitinview $nullid $curview]} {
5147            removefakerow $nullid
5148        }
5149        removefakerow $nullid2
5150    }
5151    return 0
5152}
5153
5154proc readdifffiles {fd serial inst} {
5155    global viewmainheadid nullid nullid2 curview
5156    global commitinfo commitdata lserial
5157
5158    set isdiff 1
5159    if {[gets $fd line] < 0} {
5160        if {![eof $fd]} {
5161            return 1
5162        }
5163        set isdiff 0
5164    }
5165    # we only need to see one line and we don't really care what it says...
5166    stop_instance $inst
5167
5168    if {$serial != $lserial} {
5169        return 0
5170    }
5171
5172    if {$isdiff && ![commitinview $nullid $curview]} {
5173        # add the line for the local diff to the graph
5174        set hl [mc "Local uncommitted changes, not checked in to index"]
5175        set commitinfo($nullid) [list  $hl {} {} {} {} "    $hl\n"]
5176        set commitdata($nullid) "\n    $hl\n"
5177        if {[commitinview $nullid2 $curview]} {
5178            set p $nullid2
5179        } else {
5180            set p $viewmainheadid($curview)
5181        }
5182        insertfakerow $nullid $p
5183    } elseif {!$isdiff && [commitinview $nullid $curview]} {
5184        removefakerow $nullid
5185    }
5186    return 0
5187}
5188
5189proc nextuse {id row} {
5190    global curview children
5191
5192    if {[info exists children($curview,$id)]} {
5193        foreach kid $children($curview,$id) {
5194            if {![commitinview $kid $curview]} {
5195                return -1
5196            }
5197            if {[rowofcommit $kid] > $row} {
5198                return [rowofcommit $kid]
5199            }
5200        }
5201    }
5202    if {[commitinview $id $curview]} {
5203        return [rowofcommit $id]
5204    }
5205    return -1
5206}
5207
5208proc prevuse {id row} {
5209    global curview children
5210
5211    set ret -1
5212    if {[info exists children($curview,$id)]} {
5213        foreach kid $children($curview,$id) {
5214            if {![commitinview $kid $curview]} break
5215            if {[rowofcommit $kid] < $row} {
5216                set ret [rowofcommit $kid]
5217            }
5218        }
5219    }
5220    return $ret
5221}
5222
5223proc make_idlist {row} {
5224    global displayorder parentlist uparrowlen downarrowlen mingaplen
5225    global commitidx curview children
5226
5227    set r [expr {$row - $mingaplen - $downarrowlen - 1}]
5228    if {$r < 0} {
5229        set r 0
5230    }
5231    set ra [expr {$row - $downarrowlen}]
5232    if {$ra < 0} {
5233        set ra 0
5234    }
5235    set rb [expr {$row + $uparrowlen}]
5236    if {$rb > $commitidx($curview)} {
5237        set rb $commitidx($curview)
5238    }
5239    make_disporder $r [expr {$rb + 1}]
5240    set ids {}
5241    for {} {$r < $ra} {incr r} {
5242        set nextid [lindex $displayorder [expr {$r + 1}]]
5243        foreach p [lindex $parentlist $r] {
5244            if {$p eq $nextid} continue
5245            set rn [nextuse $p $r]
5246            if {$rn >= $row &&
5247                $rn <= $r + $downarrowlen + $mingaplen + $uparrowlen} {
5248                lappend ids [list [ordertoken $p] $p]
5249            }
5250        }
5251    }
5252    for {} {$r < $row} {incr r} {
5253        set nextid [lindex $displayorder [expr {$r + 1}]]
5254        foreach p [lindex $parentlist $r] {
5255            if {$p eq $nextid} continue
5256            set rn [nextuse $p $r]
5257            if {$rn < 0 || $rn >= $row} {
5258                lappend ids [list [ordertoken $p] $p]
5259            }
5260        }
5261    }
5262    set id [lindex $displayorder $row]
5263    lappend ids [list [ordertoken $id] $id]
5264    while {$r < $rb} {
5265        foreach p [lindex $parentlist $r] {
5266            set firstkid [lindex $children($curview,$p) 0]
5267            if {[rowofcommit $firstkid] < $row} {
5268                lappend ids [list [ordertoken $p] $p]
5269            }
5270        }
5271        incr r
5272        set id [lindex $displayorder $r]
5273        if {$id ne {}} {
5274            set firstkid [lindex $children($curview,$id) 0]
5275            if {$firstkid ne {} && [rowofcommit $firstkid] < $row} {
5276                lappend ids [list [ordertoken $id] $id]
5277            }
5278        }
5279    }
5280    set idlist {}
5281    foreach idx [lsort -unique $ids] {
5282        lappend idlist [lindex $idx 1]
5283    }
5284    return $idlist
5285}
5286
5287proc rowsequal {a b} {
5288    while {[set i [lsearch -exact $a {}]] >= 0} {
5289        set a [lreplace $a $i $i]
5290    }
5291    while {[set i [lsearch -exact $b {}]] >= 0} {
5292        set b [lreplace $b $i $i]
5293    }
5294    return [expr {$a eq $b}]
5295}
5296
5297proc makeupline {id row rend col} {
5298    global rowidlist uparrowlen downarrowlen mingaplen
5299
5300    for {set r $rend} {1} {set r $rstart} {
5301        set rstart [prevuse $id $r]
5302        if {$rstart < 0} return
5303        if {$rstart < $row} break
5304    }
5305    if {$rstart + $uparrowlen + $mingaplen + $downarrowlen < $rend} {
5306        set rstart [expr {$rend - $uparrowlen - 1}]
5307    }
5308    for {set r $rstart} {[incr r] <= $row} {} {
5309        set idlist [lindex $rowidlist $r]
5310        if {$idlist ne {} && [lsearch -exact $idlist $id] < 0} {
5311            set col [idcol $idlist $id $col]
5312            lset rowidlist $r [linsert $idlist $col $id]
5313            changedrow $r
5314        }
5315    }
5316}
5317
5318proc layoutrows {row endrow} {
5319    global rowidlist rowisopt rowfinal displayorder
5320    global uparrowlen downarrowlen maxwidth mingaplen
5321    global children parentlist
5322    global commitidx viewcomplete curview
5323
5324    make_disporder [expr {$row - 1}] [expr {$endrow + $uparrowlen}]
5325    set idlist {}
5326    if {$row > 0} {
5327        set rm1 [expr {$row - 1}]
5328        foreach id [lindex $rowidlist $rm1] {
5329            if {$id ne {}} {
5330                lappend idlist $id
5331            }
5332        }
5333        set final [lindex $rowfinal $rm1]
5334    }
5335    for {} {$row < $endrow} {incr row} {
5336        set rm1 [expr {$row - 1}]
5337        if {$rm1 < 0 || $idlist eq {}} {
5338            set idlist [make_idlist $row]
5339            set final 1
5340        } else {
5341            set id [lindex $displayorder $rm1]
5342            set col [lsearch -exact $idlist $id]
5343            set idlist [lreplace $idlist $col $col]
5344            foreach p [lindex $parentlist $rm1] {
5345                if {[lsearch -exact $idlist $p] < 0} {
5346                    set col [idcol $idlist $p $col]
5347                    set idlist [linsert $idlist $col $p]
5348                    # if not the first child, we have to insert a line going up
5349                    if {$id ne [lindex $children($curview,$p) 0]} {
5350                        makeupline $p $rm1 $row $col
5351                    }
5352                }
5353            }
5354            set id [lindex $displayorder $row]
5355            if {$row > $downarrowlen} {
5356                set termrow [expr {$row - $downarrowlen - 1}]
5357                foreach p [lindex $parentlist $termrow] {
5358                    set i [lsearch -exact $idlist $p]
5359                    if {$i < 0} continue
5360                    set nr [nextuse $p $termrow]
5361                    if {$nr < 0 || $nr >= $row + $mingaplen + $uparrowlen} {
5362                        set idlist [lreplace $idlist $i $i]
5363                    }
5364                }
5365            }
5366            set col [lsearch -exact $idlist $id]
5367            if {$col < 0} {
5368                set col [idcol $idlist $id]
5369                set idlist [linsert $idlist $col $id]
5370                if {$children($curview,$id) ne {}} {
5371                    makeupline $id $rm1 $row $col
5372                }
5373            }
5374            set r [expr {$row + $uparrowlen - 1}]
5375            if {$r < $commitidx($curview)} {
5376                set x $col
5377                foreach p [lindex $parentlist $r] {
5378                    if {[lsearch -exact $idlist $p] >= 0} continue
5379                    set fk [lindex $children($curview,$p) 0]
5380                    if {[rowofcommit $fk] < $row} {
5381                        set x [idcol $idlist $p $x]
5382                        set idlist [linsert $idlist $x $p]
5383                    }
5384                }
5385                if {[incr r] < $commitidx($curview)} {
5386                    set p [lindex $displayorder $r]
5387                    if {[lsearch -exact $idlist $p] < 0} {
5388                        set fk [lindex $children($curview,$p) 0]
5389                        if {$fk ne {} && [rowofcommit $fk] < $row} {
5390                            set x [idcol $idlist $p $x]
5391                            set idlist [linsert $idlist $x $p]
5392                        }
5393                    }
5394                }
5395            }
5396        }
5397        if {$final && !$viewcomplete($curview) &&
5398            $row + $uparrowlen + $mingaplen + $downarrowlen
5399                >= $commitidx($curview)} {
5400            set final 0
5401        }
5402        set l [llength $rowidlist]
5403        if {$row == $l} {
5404            lappend rowidlist $idlist
5405            lappend rowisopt 0
5406            lappend rowfinal $final
5407        } elseif {$row < $l} {
5408            if {![rowsequal $idlist [lindex $rowidlist $row]]} {
5409                lset rowidlist $row $idlist
5410                changedrow $row
5411            }
5412            lset rowfinal $row $final
5413        } else {
5414            set pad [ntimes [expr {$row - $l}] {}]
5415            set rowidlist [concat $rowidlist $pad]
5416            lappend rowidlist $idlist
5417            set rowfinal [concat $rowfinal $pad]
5418            lappend rowfinal $final
5419            set rowisopt [concat $rowisopt [ntimes [expr {$row - $l + 1}] 0]]
5420        }
5421    }
5422    return $row
5423}
5424
5425proc changedrow {row} {
5426    global displayorder iddrawn rowisopt need_redisplay
5427
5428    set l [llength $rowisopt]
5429    if {$row < $l} {
5430        lset rowisopt $row 0
5431        if {$row + 1 < $l} {
5432            lset rowisopt [expr {$row + 1}] 0
5433            if {$row + 2 < $l} {
5434                lset rowisopt [expr {$row + 2}] 0
5435            }
5436        }
5437    }
5438    set id [lindex $displayorder $row]
5439    if {[info exists iddrawn($id)]} {
5440        set need_redisplay 1
5441    }
5442}
5443
5444proc insert_pad {row col npad} {
5445    global rowidlist
5446
5447    set pad [ntimes $npad {}]
5448    set idlist [lindex $rowidlist $row]
5449    set bef [lrange $idlist 0 [expr {$col - 1}]]
5450    set aft [lrange $idlist $col end]
5451    set i [lsearch -exact $aft {}]
5452    if {$i > 0} {
5453        set aft [lreplace $aft $i $i]
5454    }
5455    lset rowidlist $row [concat $bef $pad $aft]
5456    changedrow $row
5457}
5458
5459proc optimize_rows {row col endrow} {
5460    global rowidlist rowisopt displayorder curview children
5461
5462    if {$row < 1} {
5463        set row 1
5464    }
5465    for {} {$row < $endrow} {incr row; set col 0} {
5466        if {[lindex $rowisopt $row]} continue
5467        set haspad 0
5468        set y0 [expr {$row - 1}]
5469        set ym [expr {$row - 2}]
5470        set idlist [lindex $rowidlist $row]
5471        set previdlist [lindex $rowidlist $y0]
5472        if {$idlist eq {} || $previdlist eq {}} continue
5473        if {$ym >= 0} {
5474            set pprevidlist [lindex $rowidlist $ym]
5475            if {$pprevidlist eq {}} continue
5476        } else {
5477            set pprevidlist {}
5478        }
5479        set x0 -1
5480        set xm -1
5481        for {} {$col < [llength $idlist]} {incr col} {
5482            set id [lindex $idlist $col]
5483            if {[lindex $previdlist $col] eq $id} continue
5484            if {$id eq {}} {
5485                set haspad 1
5486                continue
5487            }
5488            set x0 [lsearch -exact $previdlist $id]
5489            if {$x0 < 0} continue
5490            set z [expr {$x0 - $col}]
5491            set isarrow 0
5492            set z0 {}
5493            if {$ym >= 0} {
5494                set xm [lsearch -exact $pprevidlist $id]
5495                if {$xm >= 0} {
5496                    set z0 [expr {$xm - $x0}]
5497                }
5498            }
5499            if {$z0 eq {}} {
5500                # if row y0 is the first child of $id then it's not an arrow
5501                if {[lindex $children($curview,$id) 0] ne
5502                    [lindex $displayorder $y0]} {
5503                    set isarrow 1
5504                }
5505            }
5506            if {!$isarrow && $id ne [lindex $displayorder $row] &&
5507                [lsearch -exact [lindex $rowidlist [expr {$row+1}]] $id] < 0} {
5508                set isarrow 1
5509            }
5510            # Looking at lines from this row to the previous row,
5511            # make them go straight up if they end in an arrow on
5512            # the previous row; otherwise make them go straight up
5513            # or at 45 degrees.
5514            if {$z < -1 || ($z < 0 && $isarrow)} {
5515                # Line currently goes left too much;
5516                # insert pads in the previous row, then optimize it
5517                set npad [expr {-1 - $z + $isarrow}]
5518                insert_pad $y0 $x0 $npad
5519                if {$y0 > 0} {
5520                    optimize_rows $y0 $x0 $row
5521                }
5522                set previdlist [lindex $rowidlist $y0]
5523                set x0 [lsearch -exact $previdlist $id]
5524                set z [expr {$x0 - $col}]
5525                if {$z0 ne {}} {
5526                    set pprevidlist [lindex $rowidlist $ym]
5527                    set xm [lsearch -exact $pprevidlist $id]
5528                    set z0 [expr {$xm - $x0}]
5529                }
5530            } elseif {$z > 1 || ($z > 0 && $isarrow)} {
5531                # Line currently goes right too much;
5532                # insert pads in this line
5533                set npad [expr {$z - 1 + $isarrow}]
5534                insert_pad $row $col $npad
5535                set idlist [lindex $rowidlist $row]
5536                incr col $npad
5537                set z [expr {$x0 - $col}]
5538                set haspad 1
5539            }
5540            if {$z0 eq {} && !$isarrow && $ym >= 0} {
5541                # this line links to its first child on row $row-2
5542                set id [lindex $displayorder $ym]
5543                set xc [lsearch -exact $pprevidlist $id]
5544                if {$xc >= 0} {
5545                    set z0 [expr {$xc - $x0}]
5546                }
5547            }
5548            # avoid lines jigging left then immediately right
5549            if {$z0 ne {} && $z < 0 && $z0 > 0} {
5550                insert_pad $y0 $x0 1
5551                incr x0
5552                optimize_rows $y0 $x0 $row
5553                set previdlist [lindex $rowidlist $y0]
5554            }
5555        }
5556        if {!$haspad} {
5557            # Find the first column that doesn't have a line going right
5558            for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
5559                set id [lindex $idlist $col]
5560                if {$id eq {}} break
5561                set x0 [lsearch -exact $previdlist $id]
5562                if {$x0 < 0} {
5563                    # check if this is the link to the first child
5564                    set kid [lindex $displayorder $y0]
5565                    if {[lindex $children($curview,$id) 0] eq $kid} {
5566                        # it is, work out offset to child
5567                        set x0 [lsearch -exact $previdlist $kid]
5568                    }
5569                }
5570                if {$x0 <= $col} break
5571            }
5572            # Insert a pad at that column as long as it has a line and
5573            # isn't the last column
5574            if {$x0 >= 0 && [incr col] < [llength $idlist]} {
5575                set idlist [linsert $idlist $col {}]
5576                lset rowidlist $row $idlist
5577                changedrow $row
5578            }
5579        }
5580    }
5581}
5582
5583proc xc {row col} {
5584    global canvx0 linespc
5585    return [expr {$canvx0 + $col * $linespc}]
5586}
5587
5588proc yc {row} {
5589    global canvy0 linespc
5590    return [expr {$canvy0 + $row * $linespc}]
5591}
5592
5593proc linewidth {id} {
5594    global thickerline lthickness
5595
5596    set wid $lthickness
5597    if {[info exists thickerline] && $id eq $thickerline} {
5598        set wid [expr {2 * $lthickness}]
5599    }
5600    return $wid
5601}
5602
5603proc rowranges {id} {
5604    global curview children uparrowlen downarrowlen
5605    global rowidlist
5606
5607    set kids $children($curview,$id)
5608    if {$kids eq {}} {
5609        return {}
5610    }
5611    set ret {}
5612    lappend kids $id
5613    foreach child $kids {
5614        if {![commitinview $child $curview]} break
5615        set row [rowofcommit $child]
5616        if {![info exists prev]} {
5617            lappend ret [expr {$row + 1}]
5618        } else {
5619            if {$row <= $prevrow} {
5620                puts "oops children of [shortids $id] out of order [shortids $child] $row <= [shortids $prev] $prevrow"
5621            }
5622            # see if the line extends the whole way from prevrow to row
5623            if {$row > $prevrow + $uparrowlen + $downarrowlen &&
5624                [lsearch -exact [lindex $rowidlist \
5625                            [expr {int(($row + $prevrow) / 2)}]] $id] < 0} {
5626                # it doesn't, see where it ends
5627                set r [expr {$prevrow + $downarrowlen}]
5628                if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
5629                    while {[incr r -1] > $prevrow &&
5630                           [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
5631                } else {
5632                    while {[incr r] <= $row &&
5633                           [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
5634                    incr r -1
5635                }
5636                lappend ret $r
5637                # see where it starts up again
5638                set r [expr {$row - $uparrowlen}]
5639                if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
5640                    while {[incr r] < $row &&
5641                           [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
5642                } else {
5643                    while {[incr r -1] >= $prevrow &&
5644                           [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
5645                    incr r
5646                }
5647                lappend ret $r
5648            }
5649        }
5650        if {$child eq $id} {
5651            lappend ret $row
5652        }
5653        set prev $child
5654        set prevrow $row
5655    }
5656    return $ret
5657}
5658
5659proc drawlineseg {id row endrow arrowlow} {
5660    global rowidlist displayorder iddrawn linesegs
5661    global canv colormap linespc curview maxlinelen parentlist
5662
5663    set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
5664    set le [expr {$row + 1}]
5665    set arrowhigh 1
5666    while {1} {
5667        set c [lsearch -exact [lindex $rowidlist $le] $id]
5668        if {$c < 0} {
5669            incr le -1
5670            break
5671        }
5672        lappend cols $c
5673        set x [lindex $displayorder $le]
5674        if {$x eq $id} {
5675            set arrowhigh 0
5676            break
5677        }
5678        if {[info exists iddrawn($x)] || $le == $endrow} {
5679            set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
5680            if {$c >= 0} {
5681                lappend cols $c
5682                set arrowhigh 0
5683            }
5684            break
5685        }
5686        incr le
5687    }
5688    if {$le <= $row} {
5689        return $row
5690    }
5691
5692    set lines {}
5693    set i 0
5694    set joinhigh 0
5695    if {[info exists linesegs($id)]} {
5696        set lines $linesegs($id)
5697        foreach li $lines {
5698            set r0 [lindex $li 0]
5699            if {$r0 > $row} {
5700                if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
5701                    set joinhigh 1
5702                }
5703                break
5704            }
5705            incr i
5706        }
5707    }
5708    set joinlow 0
5709    if {$i > 0} {
5710        set li [lindex $lines [expr {$i-1}]]
5711        set r1 [lindex $li 1]
5712        if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
5713            set joinlow 1
5714        }
5715    }
5716
5717    set x [lindex $cols [expr {$le - $row}]]
5718    set xp [lindex $cols [expr {$le - 1 - $row}]]
5719    set dir [expr {$xp - $x}]
5720    if {$joinhigh} {
5721        set ith [lindex $lines $i 2]
5722        set coords [$canv coords $ith]
5723        set ah [$canv itemcget $ith -arrow]
5724        set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
5725        set x2 [lindex $cols [expr {$le + 1 - $row}]]
5726        if {$x2 ne {} && $x - $x2 == $dir} {
5727            set coords [lrange $coords 0 end-2]
5728        }
5729    } else {
5730        set coords [list [xc $le $x] [yc $le]]
5731    }
5732    if {$joinlow} {
5733        set itl [lindex $lines [expr {$i-1}] 2]
5734        set al [$canv itemcget $itl -arrow]
5735        set arrowlow [expr {$al eq "last" || $al eq "both"}]
5736    } elseif {$arrowlow} {
5737        if {[lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0 ||
5738            [lsearch -exact [lindex $parentlist [expr {$row-1}]] $id] >= 0} {
5739            set arrowlow 0
5740        }
5741    }
5742    set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
5743    for {set y $le} {[incr y -1] > $row} {} {
5744        set x $xp
5745        set xp [lindex $cols [expr {$y - 1 - $row}]]
5746        set ndir [expr {$xp - $x}]
5747        if {$dir != $ndir || $xp < 0} {
5748            lappend coords [xc $y $x] [yc $y]
5749        }
5750        set dir $ndir
5751    }
5752    if {!$joinlow} {
5753        if {$xp < 0} {
5754            # join parent line to first child
5755            set ch [lindex $displayorder $row]
5756            set xc [lsearch -exact [lindex $rowidlist $row] $ch]
5757            if {$xc < 0} {
5758                puts "oops: drawlineseg: child $ch not on row $row"
5759            } elseif {$xc != $x} {
5760                if {($arrowhigh && $le == $row + 1) || $dir == 0} {
5761                    set d [expr {int(0.5 * $linespc)}]
5762                    set x1 [xc $row $x]
5763                    if {$xc < $x} {
5764                        set x2 [expr {$x1 - $d}]
5765                    } else {
5766                        set x2 [expr {$x1 + $d}]
5767                    }
5768                    set y2 [yc $row]
5769                    set y1 [expr {$y2 + $d}]
5770                    lappend coords $x1 $y1 $x2 $y2
5771                } elseif {$xc < $x - 1} {
5772                    lappend coords [xc $row [expr {$x-1}]] [yc $row]
5773                } elseif {$xc > $x + 1} {
5774                    lappend coords [xc $row [expr {$x+1}]] [yc $row]
5775                }
5776                set x $xc
5777            }
5778            lappend coords [xc $row $x] [yc $row]
5779        } else {
5780            set xn [xc $row $xp]
5781            set yn [yc $row]
5782            lappend coords $xn $yn
5783        }
5784        if {!$joinhigh} {
5785            assigncolor $id
5786            set t [$canv create line $coords -width [linewidth $id] \
5787                       -fill $colormap($id) -tags lines.$id -arrow $arrow]
5788            $canv lower $t
5789            bindline $t $id
5790            set lines [linsert $lines $i [list $row $le $t]]
5791        } else {
5792            $canv coords $ith $coords
5793            if {$arrow ne $ah} {
5794                $canv itemconf $ith -arrow $arrow
5795            }
5796            lset lines $i 0 $row
5797        }
5798    } else {
5799        set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
5800        set ndir [expr {$xo - $xp}]
5801        set clow [$canv coords $itl]
5802        if {$dir == $ndir} {
5803            set clow [lrange $clow 2 end]
5804        }
5805        set coords [concat $coords $clow]
5806        if {!$joinhigh} {
5807            lset lines [expr {$i-1}] 1 $le
5808        } else {
5809            # coalesce two pieces
5810            $canv delete $ith
5811            set b [lindex $lines [expr {$i-1}] 0]
5812            set e [lindex $lines $i 1]
5813            set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
5814        }
5815        $canv coords $itl $coords
5816        if {$arrow ne $al} {
5817            $canv itemconf $itl -arrow $arrow
5818        }
5819    }
5820
5821    set linesegs($id) $lines
5822    return $le
5823}
5824
5825proc drawparentlinks {id row} {
5826    global rowidlist canv colormap curview parentlist
5827    global idpos linespc
5828
5829    set rowids [lindex $rowidlist $row]
5830    set col [lsearch -exact $rowids $id]
5831    if {$col < 0} return
5832    set olds [lindex $parentlist $row]
5833    set row2 [expr {$row + 1}]
5834    set x [xc $row $col]
5835    set y [yc $row]
5836    set y2 [yc $row2]
5837    set d [expr {int(0.5 * $linespc)}]
5838    set ymid [expr {$y + $d}]
5839    set ids [lindex $rowidlist $row2]
5840    # rmx = right-most X coord used
5841    set rmx 0
5842    foreach p $olds {
5843        set i [lsearch -exact $ids $p]
5844        if {$i < 0} {
5845            puts "oops, parent $p of $id not in list"
5846            continue
5847        }
5848        set x2 [xc $row2 $i]
5849        if {$x2 > $rmx} {
5850            set rmx $x2
5851        }
5852        set j [lsearch -exact $rowids $p]
5853        if {$j < 0} {
5854            # drawlineseg will do this one for us
5855            continue
5856        }
5857        assigncolor $p
5858        # should handle duplicated parents here...
5859        set coords [list $x $y]
5860        if {$i != $col} {
5861            # if attaching to a vertical segment, draw a smaller
5862            # slant for visual distinctness
5863            if {$i == $j} {
5864                if {$i < $col} {
5865                    lappend coords [expr {$x2 + $d}] $y $x2 $ymid
5866                } else {
5867                    lappend coords [expr {$x2 - $d}] $y $x2 $ymid
5868                }
5869            } elseif {$i < $col && $i < $j} {
5870                # segment slants towards us already
5871                lappend coords [xc $row $j] $y
5872            } else {
5873                if {$i < $col - 1} {
5874                    lappend coords [expr {$x2 + $linespc}] $y
5875                } elseif {$i > $col + 1} {
5876                    lappend coords [expr {$x2 - $linespc}] $y
5877                }
5878                lappend coords $x2 $y2
5879            }
5880        } else {
5881            lappend coords $x2 $y2
5882        }
5883        set t [$canv create line $coords -width [linewidth $p] \
5884                   -fill $colormap($p) -tags lines.$p]
5885        $canv lower $t
5886        bindline $t $p
5887    }
5888    if {$rmx > [lindex $idpos($id) 1]} {
5889        lset idpos($id) 1 $rmx
5890        redrawtags $id
5891    }
5892}
5893
5894proc drawlines {id} {
5895    global canv
5896
5897    $canv itemconf lines.$id -width [linewidth $id]
5898}
5899
5900proc drawcmittext {id row col} {
5901    global linespc canv canv2 canv3 fgcolor curview
5902    global cmitlisted commitinfo rowidlist parentlist
5903    global rowtextx idpos idtags idheads idotherrefs
5904    global linehtag linentag linedtag selectedline
5905    global canvxmax boldids boldnameids fgcolor markedid
5906    global mainheadid nullid nullid2 circleitem circlecolors ctxbut
5907
5908    # listed is 0 for boundary, 1 for normal, 2 for negative, 3 for left, 4 for right
5909    set listed $cmitlisted($curview,$id)
5910    if {$id eq $nullid} {
5911        set ofill red
5912    } elseif {$id eq $nullid2} {
5913        set ofill green
5914    } elseif {$id eq $mainheadid} {
5915        set ofill yellow
5916    } else {
5917        set ofill [lindex $circlecolors $listed]
5918    }
5919    set x [xc $row $col]
5920    set y [yc $row]
5921    set orad [expr {$linespc / 3}]
5922    if {$listed <= 2} {
5923        set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
5924                   [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
5925                   -fill $ofill -outline $fgcolor -width 1 -tags circle]
5926    } elseif {$listed == 3} {
5927        # triangle pointing left for left-side commits
5928        set t [$canv create polygon \
5929                   [expr {$x - $orad}] $y \
5930                   [expr {$x + $orad - 1}] [expr {$y - $orad}] \
5931                   [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
5932                   -fill $ofill -outline $fgcolor -width 1 -tags circle]
5933    } else {
5934        # triangle pointing right for right-side commits
5935        set t [$canv create polygon \
5936                   [expr {$x + $orad - 1}] $y \
5937                   [expr {$x - $orad}] [expr {$y - $orad}] \
5938                   [expr {$x - $orad}] [expr {$y + $orad - 1}] \
5939                   -fill $ofill -outline $fgcolor -width 1 -tags circle]
5940    }
5941    set circleitem($row) $t
5942    $canv raise $t
5943    $canv bind $t <1> {selcanvline {} %x %y}
5944    set rmx [llength [lindex $rowidlist $row]]
5945    set olds [lindex $parentlist $row]
5946    if {$olds ne {}} {
5947        set nextids [lindex $rowidlist [expr {$row + 1}]]
5948        foreach p $olds {
5949            set i [lsearch -exact $nextids $p]
5950            if {$i > $rmx} {
5951                set rmx $i
5952            }
5953        }
5954    }
5955    set xt [xc $row $rmx]
5956    set rowtextx($row) $xt
5957    set idpos($id) [list $x $xt $y]
5958    if {[info exists idtags($id)] || [info exists idheads($id)]
5959        || [info exists idotherrefs($id)]} {
5960        set xt [drawtags $id $x $xt $y]
5961    }
5962    if {[lindex $commitinfo($id) 6] > 0} {
5963        set xt [drawnotesign $xt $y]
5964    }
5965    set headline [lindex $commitinfo($id) 0]
5966    set name [lindex $commitinfo($id) 1]
5967    set date [lindex $commitinfo($id) 2]
5968    set date [formatdate $date]
5969    set font mainfont
5970    set nfont mainfont
5971    set isbold [ishighlighted $id]
5972    if {$isbold > 0} {
5973        lappend boldids $id
5974        set font mainfontbold
5975        if {$isbold > 1} {
5976            lappend boldnameids $id
5977            set nfont mainfontbold
5978        }
5979    }
5980    set linehtag($id) [$canv create text $xt $y -anchor w -fill $fgcolor \
5981                           -text $headline -font $font -tags text]
5982    $canv bind $linehtag($id) $ctxbut "rowmenu %X %Y $id"
5983    set linentag($id) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
5984                           -text $name -font $nfont -tags text]
5985    set linedtag($id) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
5986                           -text $date -font mainfont -tags text]
5987    if {$selectedline == $row} {
5988        make_secsel $id
5989    }
5990    if {[info exists markedid] && $markedid eq $id} {
5991        make_idmark $id
5992    }
5993    set xr [expr {$xt + [font measure $font $headline]}]
5994    if {$xr > $canvxmax} {
5995        set canvxmax $xr
5996        setcanvscroll
5997    }
5998}
5999
6000proc drawcmitrow {row} {
6001    global displayorder rowidlist nrows_drawn
6002    global iddrawn markingmatches
6003    global commitinfo numcommits
6004    global filehighlight fhighlights findpattern nhighlights
6005    global hlview vhighlights
6006    global highlight_related rhighlights
6007
6008    if {$row >= $numcommits} return
6009
6010    set id [lindex $displayorder $row]
6011    if {[info exists hlview] && ![info exists vhighlights($id)]} {
6012        askvhighlight $row $id
6013    }
6014    if {[info exists filehighlight] && ![info exists fhighlights($id)]} {
6015        askfilehighlight $row $id
6016    }
6017    if {$findpattern ne {} && ![info exists nhighlights($id)]} {
6018        askfindhighlight $row $id
6019    }
6020    if {$highlight_related ne [mc "None"] && ![info exists rhighlights($id)]} {
6021        askrelhighlight $row $id
6022    }
6023    if {![info exists iddrawn($id)]} {
6024        set col [lsearch -exact [lindex $rowidlist $row] $id]
6025        if {$col < 0} {
6026            puts "oops, row $row id $id not in list"
6027            return
6028        }
6029        if {![info exists commitinfo($id)]} {
6030            getcommit $id
6031        }
6032        assigncolor $id
6033        drawcmittext $id $row $col
6034        set iddrawn($id) 1
6035        incr nrows_drawn
6036    }
6037    if {$markingmatches} {
6038        markrowmatches $row $id
6039    }
6040}
6041
6042proc drawcommits {row {endrow {}}} {
6043    global numcommits iddrawn displayorder curview need_redisplay
6044    global parentlist rowidlist rowfinal uparrowlen downarrowlen nrows_drawn
6045
6046    if {$row < 0} {
6047        set row 0
6048    }
6049    if {$endrow eq {}} {
6050        set endrow $row
6051    }
6052    if {$endrow >= $numcommits} {
6053        set endrow [expr {$numcommits - 1}]
6054    }
6055
6056    set rl1 [expr {$row - $downarrowlen - 3}]
6057    if {$rl1 < 0} {
6058        set rl1 0
6059    }
6060    set ro1 [expr {$row - 3}]
6061    if {$ro1 < 0} {
6062        set ro1 0
6063    }
6064    set r2 [expr {$endrow + $uparrowlen + 3}]
6065    if {$r2 > $numcommits} {
6066        set r2 $numcommits
6067    }
6068    for {set r $rl1} {$r < $r2} {incr r} {
6069        if {[lindex $rowidlist $r] ne {} && [lindex $rowfinal $r]} {
6070            if {$rl1 < $r} {
6071                layoutrows $rl1 $r
6072            }
6073            set rl1 [expr {$r + 1}]
6074        }
6075    }
6076    if {$rl1 < $r} {
6077        layoutrows $rl1 $r
6078    }
6079    optimize_rows $ro1 0 $r2
6080    if {$need_redisplay || $nrows_drawn > 2000} {
6081        clear_display
6082    }
6083
6084    # make the lines join to already-drawn rows either side
6085    set r [expr {$row - 1}]
6086    if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
6087        set r $row
6088    }
6089    set er [expr {$endrow + 1}]
6090    if {$er >= $numcommits ||
6091        ![info exists iddrawn([lindex $displayorder $er])]} {
6092        set er $endrow
6093    }
6094    for {} {$r <= $er} {incr r} {
6095        set id [lindex $displayorder $r]
6096        set wasdrawn [info exists iddrawn($id)]
6097        drawcmitrow $r
6098        if {$r == $er} break
6099        set nextid [lindex $displayorder [expr {$r + 1}]]
6100        if {$wasdrawn && [info exists iddrawn($nextid)]} continue
6101        drawparentlinks $id $r
6102
6103        set rowids [lindex $rowidlist $r]
6104        foreach lid $rowids {
6105            if {$lid eq {}} continue
6106            if {[info exists lineend($lid)] && $lineend($lid) > $r} continue
6107            if {$lid eq $id} {
6108                # see if this is the first child of any of its parents
6109                foreach p [lindex $parentlist $r] {
6110                    if {[lsearch -exact $rowids $p] < 0} {
6111                        # make this line extend up to the child
6112                        set lineend($p) [drawlineseg $p $r $er 0]
6113                    }
6114                }
6115            } else {
6116                set lineend($lid) [drawlineseg $lid $r $er 1]
6117            }
6118        }
6119    }
6120}
6121
6122proc undolayout {row} {
6123    global uparrowlen mingaplen downarrowlen
6124    global rowidlist rowisopt rowfinal need_redisplay
6125
6126    set r [expr {$row - ($uparrowlen + $mingaplen + $downarrowlen)}]
6127    if {$r < 0} {
6128        set r 0
6129    }
6130    if {[llength $rowidlist] > $r} {
6131        incr r -1
6132        set rowidlist [lrange $rowidlist 0 $r]
6133        set rowfinal [lrange $rowfinal 0 $r]
6134        set rowisopt [lrange $rowisopt 0 $r]
6135        set need_redisplay 1
6136        run drawvisible
6137    }
6138}
6139
6140proc drawvisible {} {
6141    global canv linespc curview vrowmod selectedline targetrow targetid
6142    global need_redisplay cscroll numcommits
6143
6144    set fs [$canv yview]
6145    set ymax [lindex [$canv cget -scrollregion] 3]
6146    if {$ymax eq {} || $ymax == 0 || $numcommits == 0} return
6147    set f0 [lindex $fs 0]
6148    set f1 [lindex $fs 1]
6149    set y0 [expr {int($f0 * $ymax)}]
6150    set y1 [expr {int($f1 * $ymax)}]
6151
6152    if {[info exists targetid]} {
6153        if {[commitinview $targetid $curview]} {
6154            set r [rowofcommit $targetid]
6155            if {$r != $targetrow} {
6156                # Fix up the scrollregion and change the scrolling position
6157                # now that our target row has moved.
6158                set diff [expr {($r - $targetrow) * $linespc}]
6159                set targetrow $r
6160                setcanvscroll
6161                set ymax [lindex [$canv cget -scrollregion] 3]
6162                incr y0 $diff
6163                incr y1 $diff
6164                set f0 [expr {$y0 / $ymax}]
6165                set f1 [expr {$y1 / $ymax}]
6166                allcanvs yview moveto $f0
6167                $cscroll set $f0 $f1
6168                set need_redisplay 1
6169            }
6170        } else {
6171            unset targetid
6172        }
6173    }
6174
6175    set row [expr {int(($y0 - 3) / $linespc) - 1}]
6176    set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
6177    if {$endrow >= $vrowmod($curview)} {
6178        update_arcrows $curview
6179    }
6180    if {$selectedline ne {} &&
6181        $row <= $selectedline && $selectedline <= $endrow} {
6182        set targetrow $selectedline
6183    } elseif {[info exists targetid]} {
6184        set targetrow [expr {int(($row + $endrow) / 2)}]
6185    }
6186    if {[info exists targetrow]} {
6187        if {$targetrow >= $numcommits} {
6188            set targetrow [expr {$numcommits - 1}]
6189        }
6190        set targetid [commitonrow $targetrow]
6191    }
6192    drawcommits $row $endrow
6193}
6194
6195proc clear_display {} {
6196    global iddrawn linesegs need_redisplay nrows_drawn
6197    global vhighlights fhighlights nhighlights rhighlights
6198    global linehtag linentag linedtag boldids boldnameids
6199
6200    allcanvs delete all
6201    catch {unset iddrawn}
6202    catch {unset linesegs}
6203    catch {unset linehtag}
6204    catch {unset linentag}
6205    catch {unset linedtag}
6206    set boldids {}
6207    set boldnameids {}
6208    catch {unset vhighlights}
6209    catch {unset fhighlights}
6210    catch {unset nhighlights}
6211    catch {unset rhighlights}
6212    set need_redisplay 0
6213    set nrows_drawn 0
6214}
6215
6216proc findcrossings {id} {
6217    global rowidlist parentlist numcommits displayorder
6218
6219    set cross {}
6220    set ccross {}
6221    foreach {s e} [rowranges $id] {
6222        if {$e >= $numcommits} {
6223            set e [expr {$numcommits - 1}]
6224        }
6225        if {$e <= $s} continue
6226        for {set row $e} {[incr row -1] >= $s} {} {
6227            set x [lsearch -exact [lindex $rowidlist $row] $id]
6228            if {$x < 0} break
6229            set olds [lindex $parentlist $row]
6230            set kid [lindex $displayorder $row]
6231            set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
6232            if {$kidx < 0} continue
6233            set nextrow [lindex $rowidlist [expr {$row + 1}]]
6234            foreach p $olds {
6235                set px [lsearch -exact $nextrow $p]
6236                if {$px < 0} continue
6237                if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
6238                    if {[lsearch -exact $ccross $p] >= 0} continue
6239                    if {$x == $px + ($kidx < $px? -1: 1)} {
6240                        lappend ccross $p
6241                    } elseif {[lsearch -exact $cross $p] < 0} {
6242                        lappend cross $p
6243                    }
6244                }
6245            }
6246        }
6247    }
6248    return [concat $ccross {{}} $cross]
6249}
6250
6251proc assigncolor {id} {
6252    global colormap colors nextcolor
6253    global parents children children curview
6254
6255    if {[info exists colormap($id)]} return
6256    set ncolors [llength $colors]
6257    if {[info exists children($curview,$id)]} {
6258        set kids $children($curview,$id)
6259    } else {
6260        set kids {}
6261    }
6262    if {[llength $kids] == 1} {
6263        set child [lindex $kids 0]
6264        if {[info exists colormap($child)]
6265            && [llength $parents($curview,$child)] == 1} {
6266            set colormap($id) $colormap($child)
6267            return
6268        }
6269    }
6270    set badcolors {}
6271    set origbad {}
6272    foreach x [findcrossings $id] {
6273        if {$x eq {}} {
6274            # delimiter between corner crossings and other crossings
6275            if {[llength $badcolors] >= $ncolors - 1} break
6276            set origbad $badcolors
6277        }
6278        if {[info exists colormap($x)]
6279            && [lsearch -exact $badcolors $colormap($x)] < 0} {
6280            lappend badcolors $colormap($x)
6281        }
6282    }
6283    if {[llength $badcolors] >= $ncolors} {
6284        set badcolors $origbad
6285    }
6286    set origbad $badcolors
6287    if {[llength $badcolors] < $ncolors - 1} {
6288        foreach child $kids {
6289            if {[info exists colormap($child)]
6290                && [lsearch -exact $badcolors $colormap($child)] < 0} {
6291                lappend badcolors $colormap($child)
6292            }
6293            foreach p $parents($curview,$child) {
6294                if {[info exists colormap($p)]
6295                    && [lsearch -exact $badcolors $colormap($p)] < 0} {
6296                    lappend badcolors $colormap($p)
6297                }
6298            }
6299        }
6300        if {[llength $badcolors] >= $ncolors} {
6301            set badcolors $origbad
6302        }
6303    }
6304    for {set i 0} {$i <= $ncolors} {incr i} {
6305        set c [lindex $colors $nextcolor]
6306        if {[incr nextcolor] >= $ncolors} {
6307            set nextcolor 0
6308        }
6309        if {[lsearch -exact $badcolors $c]} break
6310    }
6311    set colormap($id) $c
6312}
6313
6314proc bindline {t id} {
6315    global canv
6316
6317    $canv bind $t <Enter> "lineenter %x %y $id"
6318    $canv bind $t <Motion> "linemotion %x %y $id"
6319    $canv bind $t <Leave> "lineleave $id"
6320    $canv bind $t <Button-1> "lineclick %x %y $id 1"
6321}
6322
6323proc drawtags {id x xt y1} {
6324    global idtags idheads idotherrefs mainhead
6325    global linespc lthickness
6326    global canv rowtextx curview fgcolor bgcolor ctxbut
6327
6328    set marks {}
6329    set ntags 0
6330    set nheads 0
6331    if {[info exists idtags($id)]} {
6332        set marks $idtags($id)
6333        set ntags [llength $marks]
6334    }
6335    if {[info exists idheads($id)]} {
6336        set marks [concat $marks $idheads($id)]
6337        set nheads [llength $idheads($id)]
6338    }
6339    if {[info exists idotherrefs($id)]} {
6340        set marks [concat $marks $idotherrefs($id)]
6341    }
6342    if {$marks eq {}} {
6343        return $xt
6344    }
6345
6346    set delta [expr {int(0.5 * ($linespc - $lthickness))}]
6347    set yt [expr {$y1 - 0.5 * $linespc}]
6348    set yb [expr {$yt + $linespc - 1}]
6349    set xvals {}
6350    set wvals {}
6351    set i -1
6352    foreach tag $marks {
6353        incr i
6354        if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
6355            set wid [font measure mainfontbold $tag]
6356        } else {
6357            set wid [font measure mainfont $tag]
6358        }
6359        lappend xvals $xt
6360        lappend wvals $wid
6361        set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
6362    }
6363    set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
6364               -width $lthickness -fill black -tags tag.$id]
6365    $canv lower $t
6366    foreach tag $marks x $xvals wid $wvals {
6367        set tag_quoted [string map {% %%} $tag]
6368        set xl [expr {$x + $delta}]
6369        set xr [expr {$x + $delta + $wid + $lthickness}]
6370        set font mainfont
6371        if {[incr ntags -1] >= 0} {
6372            # draw a tag
6373            set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
6374                       $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
6375                       -width 1 -outline black -fill yellow -tags tag.$id]
6376            $canv bind $t <1> [list showtag $tag_quoted 1]
6377            set rowtextx([rowofcommit $id]) [expr {$xr + $linespc}]
6378        } else {
6379            # draw a head or other ref
6380            if {[incr nheads -1] >= 0} {
6381                set col green
6382                if {$tag eq $mainhead} {
6383                    set font mainfontbold
6384                }
6385            } else {
6386                set col "#ddddff"
6387            }
6388            set xl [expr {$xl - $delta/2}]
6389            $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
6390                -width 1 -outline black -fill $col -tags tag.$id
6391            if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
6392                set rwid [font measure mainfont $remoteprefix]
6393                set xi [expr {$x + 1}]
6394                set yti [expr {$yt + 1}]
6395                set xri [expr {$x + $rwid}]
6396                $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
6397                        -width 0 -fill "#ffddaa" -tags tag.$id
6398            }
6399        }
6400        set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
6401                   -font $font -tags [list tag.$id text]]
6402        if {$ntags >= 0} {
6403            $canv bind $t <1> [list showtag $tag_quoted 1]
6404        } elseif {$nheads >= 0} {
6405            $canv bind $t $ctxbut [list headmenu %X %Y $id $tag_quoted]
6406        }
6407    }
6408    return $xt
6409}
6410
6411proc drawnotesign {xt y} {
6412    global linespc canv fgcolor
6413
6414    set orad [expr {$linespc / 3}]
6415    set t [$canv create rectangle [expr {$xt - $orad}] [expr {$y - $orad}] \
6416               [expr {$xt + $orad - 1}] [expr {$y + $orad - 1}] \
6417               -fill yellow -outline $fgcolor -width 1 -tags circle]
6418    set xt [expr {$xt + $orad * 3}]
6419    return $xt
6420}
6421
6422proc xcoord {i level ln} {
6423    global canvx0 xspc1 xspc2
6424
6425    set x [expr {$canvx0 + $i * $xspc1($ln)}]
6426    if {$i > 0 && $i == $level} {
6427        set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
6428    } elseif {$i > $level} {
6429        set x [expr {$x + $xspc2 - $xspc1($ln)}]
6430    }
6431    return $x
6432}
6433
6434proc show_status {msg} {
6435    global canv fgcolor
6436
6437    clear_display
6438    $canv create text 3 3 -anchor nw -text $msg -font mainfont \
6439        -tags text -fill $fgcolor
6440}
6441
6442# Don't change the text pane cursor if it is currently the hand cursor,
6443# showing that we are over a sha1 ID link.
6444proc settextcursor {c} {
6445    global ctext curtextcursor
6446
6447    if {[$ctext cget -cursor] == $curtextcursor} {
6448        $ctext config -cursor $c
6449    }
6450    set curtextcursor $c
6451}
6452
6453proc nowbusy {what {name {}}} {
6454    global isbusy busyname statusw
6455
6456    if {[array names isbusy] eq {}} {
6457        . config -cursor watch
6458        settextcursor watch
6459    }
6460    set isbusy($what) 1
6461    set busyname($what) $name
6462    if {$name ne {}} {
6463        $statusw conf -text $name
6464    }
6465}
6466
6467proc notbusy {what} {
6468    global isbusy maincursor textcursor busyname statusw
6469
6470    catch {
6471        unset isbusy($what)
6472        if {$busyname($what) ne {} &&
6473            [$statusw cget -text] eq $busyname($what)} {
6474            $statusw conf -text {}
6475        }
6476    }
6477    if {[array names isbusy] eq {}} {
6478        . config -cursor $maincursor
6479        settextcursor $textcursor
6480    }
6481}
6482
6483proc findmatches {f} {
6484    global findtype findstring
6485    if {$findtype == [mc "Regexp"]} {
6486        set matches [regexp -indices -all -inline $findstring $f]
6487    } else {
6488        set fs $findstring
6489        if {$findtype == [mc "IgnCase"]} {
6490            set f [string tolower $f]
6491            set fs [string tolower $fs]
6492        }
6493        set matches {}
6494        set i 0
6495        set l [string length $fs]
6496        while {[set j [string first $fs $f $i]] >= 0} {
6497            lappend matches [list $j [expr {$j+$l-1}]]
6498            set i [expr {$j + $l}]
6499        }
6500    }
6501    return $matches
6502}
6503
6504proc dofind {{dirn 1} {wrap 1}} {
6505    global findstring findstartline findcurline selectedline numcommits
6506    global gdttype filehighlight fh_serial find_dirn findallowwrap
6507
6508    if {[info exists find_dirn]} {
6509        if {$find_dirn == $dirn} return
6510        stopfinding
6511    }
6512    focus .
6513    if {$findstring eq {} || $numcommits == 0} return
6514    if {$selectedline eq {}} {
6515        set findstartline [lindex [visiblerows] [expr {$dirn < 0}]]
6516    } else {
6517        set findstartline $selectedline
6518    }
6519    set findcurline $findstartline
6520    nowbusy finding [mc "Searching"]
6521    if {$gdttype ne [mc "containing:"] && ![info exists filehighlight]} {
6522        after cancel do_file_hl $fh_serial
6523        do_file_hl $fh_serial
6524    }
6525    set find_dirn $dirn
6526    set findallowwrap $wrap
6527    run findmore
6528}
6529
6530proc stopfinding {} {
6531    global find_dirn findcurline fprogcoord
6532
6533    if {[info exists find_dirn]} {
6534        unset find_dirn
6535        unset findcurline
6536        notbusy finding
6537        set fprogcoord 0
6538        adjustprogress
6539    }
6540    stopblaming
6541}
6542
6543proc findmore {} {
6544    global commitdata commitinfo numcommits findpattern findloc
6545    global findstartline findcurline findallowwrap
6546    global find_dirn gdttype fhighlights fprogcoord
6547    global curview varcorder vrownum varccommits vrowmod
6548
6549    if {![info exists find_dirn]} {
6550        return 0
6551    }
6552    set fldtypes [list [mc "Headline"] [mc "Author"] "" [mc "Committer"] "" [mc "Comments"]]
6553    set l $findcurline
6554    set moretodo 0
6555    if {$find_dirn > 0} {
6556        incr l
6557        if {$l >= $numcommits} {
6558            set l 0
6559        }
6560        if {$l <= $findstartline} {
6561            set lim [expr {$findstartline + 1}]
6562        } else {
6563            set lim $numcommits
6564            set moretodo $findallowwrap
6565        }
6566    } else {
6567        if {$l == 0} {
6568            set l $numcommits
6569        }
6570        incr l -1
6571        if {$l >= $findstartline} {
6572            set lim [expr {$findstartline - 1}]
6573        } else {
6574            set lim -1
6575            set moretodo $findallowwrap
6576        }
6577    }
6578    set n [expr {($lim - $l) * $find_dirn}]
6579    if {$n > 500} {
6580        set n 500
6581        set moretodo 1
6582    }
6583    if {$l + ($find_dirn > 0? $n: 1) > $vrowmod($curview)} {
6584        update_arcrows $curview
6585    }
6586    set found 0
6587    set domore 1
6588    set ai [bsearch $vrownum($curview) $l]
6589    set a [lindex $varcorder($curview) $ai]
6590    set arow [lindex $vrownum($curview) $ai]
6591    set ids [lindex $varccommits($curview,$a)]
6592    set arowend [expr {$arow + [llength $ids]}]
6593    if {$gdttype eq [mc "containing:"]} {
6594        for {} {$n > 0} {incr n -1; incr l $find_dirn} {
6595            if {$l < $arow || $l >= $arowend} {
6596                incr ai $find_dirn
6597                set a [lindex $varcorder($curview) $ai]
6598                set arow [lindex $vrownum($curview) $ai]
6599                set ids [lindex $varccommits($curview,$a)]
6600                set arowend [expr {$arow + [llength $ids]}]
6601            }
6602            set id [lindex $ids [expr {$l - $arow}]]
6603            # shouldn't happen unless git log doesn't give all the commits...
6604            if {![info exists commitdata($id)] ||
6605                ![doesmatch $commitdata($id)]} {
6606                continue
6607            }
6608            if {![info exists commitinfo($id)]} {
6609                getcommit $id
6610            }
6611            set info $commitinfo($id)
6612            foreach f $info ty $fldtypes {
6613                if {$ty eq ""} continue
6614                if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
6615                    [doesmatch $f]} {
6616                    set found 1
6617                    break
6618                }
6619            }
6620            if {$found} break
6621        }
6622    } else {
6623        for {} {$n > 0} {incr n -1; incr l $find_dirn} {
6624            if {$l < $arow || $l >= $arowend} {
6625                incr ai $find_dirn
6626                set a [lindex $varcorder($curview) $ai]
6627                set arow [lindex $vrownum($curview) $ai]
6628                set ids [lindex $varccommits($curview,$a)]
6629                set arowend [expr {$arow + [llength $ids]}]
6630            }
6631            set id [lindex $ids [expr {$l - $arow}]]
6632            if {![info exists fhighlights($id)]} {
6633                # this sets fhighlights($id) to -1
6634                askfilehighlight $l $id
6635            }
6636            if {$fhighlights($id) > 0} {
6637                set found $domore
6638                break
6639            }
6640            if {$fhighlights($id) < 0} {
6641                if {$domore} {
6642                    set domore 0
6643                    set findcurline [expr {$l - $find_dirn}]
6644                }
6645            }
6646        }
6647    }
6648    if {$found || ($domore && !$moretodo)} {
6649        unset findcurline
6650        unset find_dirn
6651        notbusy finding
6652        set fprogcoord 0
6653        adjustprogress
6654        if {$found} {
6655            findselectline $l
6656        } else {
6657            bell
6658        }
6659        return 0
6660    }
6661    if {!$domore} {
6662        flushhighlights
6663    } else {
6664        set findcurline [expr {$l - $find_dirn}]
6665    }
6666    set n [expr {($findcurline - $findstartline) * $find_dirn - 1}]
6667    if {$n < 0} {
6668        incr n $numcommits
6669    }
6670    set fprogcoord [expr {$n * 1.0 / $numcommits}]
6671    adjustprogress
6672    return $domore
6673}
6674
6675proc findselectline {l} {
6676    global findloc commentend ctext findcurline markingmatches gdttype
6677
6678    set markingmatches [expr {$gdttype eq [mc "containing:"]}]
6679    set findcurline $l
6680    selectline $l 1
6681    if {$markingmatches &&
6682        ($findloc eq [mc "All fields"] || $findloc eq [mc "Comments"])} {
6683        # highlight the matches in the comments
6684        set f [$ctext get 1.0 $commentend]
6685        set matches [findmatches $f]
6686        foreach match $matches {
6687            set start [lindex $match 0]
6688            set end [expr {[lindex $match 1] + 1}]
6689            $ctext tag add found "1.0 + $start c" "1.0 + $end c"
6690        }
6691    }
6692    drawvisible
6693}
6694
6695# mark the bits of a headline or author that match a find string
6696proc markmatches {canv l str tag matches font row} {
6697    global selectedline
6698
6699    set bbox [$canv bbox $tag]
6700    set x0 [lindex $bbox 0]
6701    set y0 [lindex $bbox 1]
6702    set y1 [lindex $bbox 3]
6703    foreach match $matches {
6704        set start [lindex $match 0]
6705        set end [lindex $match 1]
6706        if {$start > $end} continue
6707        set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
6708        set xlen [font measure $font [string range $str 0 [expr {$end}]]]
6709        set t [$canv create rect [expr {$x0+$xoff}] $y0 \
6710                   [expr {$x0+$xlen+2}] $y1 \
6711                   -outline {} -tags [list match$l matches] -fill yellow]
6712        $canv lower $t
6713        if {$row == $selectedline} {
6714            $canv raise $t secsel
6715        }
6716    }
6717}
6718
6719proc unmarkmatches {} {
6720    global markingmatches
6721
6722    allcanvs delete matches
6723    set markingmatches 0
6724    stopfinding
6725}
6726
6727proc selcanvline {w x y} {
6728    global canv canvy0 ctext linespc
6729    global rowtextx
6730    set ymax [lindex [$canv cget -scrollregion] 3]
6731    if {$ymax == {}} return
6732    set yfrac [lindex [$canv yview] 0]
6733    set y [expr {$y + $yfrac * $ymax}]
6734    set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
6735    if {$l < 0} {
6736        set l 0
6737    }
6738    if {$w eq $canv} {
6739        set xmax [lindex [$canv cget -scrollregion] 2]
6740        set xleft [expr {[lindex [$canv xview] 0] * $xmax}]
6741        if {![info exists rowtextx($l)] || $xleft + $x < $rowtextx($l)} return
6742    }
6743    unmarkmatches
6744    selectline $l 1
6745}
6746
6747proc commit_descriptor {p} {
6748    global commitinfo
6749    if {![info exists commitinfo($p)]} {
6750        getcommit $p
6751    }
6752    set l "..."
6753    if {[llength $commitinfo($p)] > 1} {
6754        set l [lindex $commitinfo($p) 0]
6755    }
6756    return "$p ($l)\n"
6757}
6758
6759# append some text to the ctext widget, and make any SHA1 ID
6760# that we know about be a clickable link.
6761proc appendwithlinks {text tags} {
6762    global ctext linknum curview
6763
6764    set start [$ctext index "end - 1c"]
6765    $ctext insert end $text $tags
6766    set links [regexp -indices -all -inline {(?:\m|-g)[0-9a-f]{6,40}\M} $text]
6767    foreach l $links {
6768        set s [lindex $l 0]
6769        set e [lindex $l 1]
6770        set linkid [string range $text $s $e]
6771        incr e
6772        $ctext tag delete link$linknum
6773        $ctext tag add link$linknum "$start + $s c" "$start + $e c"
6774        setlink $linkid link$linknum
6775        incr linknum
6776    }
6777}
6778
6779proc setlink {id lk} {
6780    global curview ctext pendinglinks
6781
6782    if {[string range $id 0 1] eq "-g"} {
6783      set id [string range $id 2 end]
6784    }
6785
6786    set known 0
6787    if {[string length $id] < 40} {
6788        set matches [longid $id]
6789        if {[llength $matches] > 0} {
6790            if {[llength $matches] > 1} return
6791            set known 1
6792            set id [lindex $matches 0]
6793        }
6794    } else {
6795        set known [commitinview $id $curview]
6796    }
6797    if {$known} {
6798        $ctext tag conf $lk -foreground blue -underline 1
6799        $ctext tag bind $lk <1> [list selbyid $id]
6800        $ctext tag bind $lk <Enter> {linkcursor %W 1}
6801        $ctext tag bind $lk <Leave> {linkcursor %W -1}
6802    } else {
6803        lappend pendinglinks($id) $lk
6804        interestedin $id {makelink %P}
6805    }
6806}
6807
6808proc appendshortlink {id {pre {}} {post {}}} {
6809    global ctext linknum
6810
6811    $ctext insert end $pre
6812    $ctext tag delete link$linknum
6813    $ctext insert end [string range $id 0 7] link$linknum
6814    $ctext insert end $post
6815    setlink $id link$linknum
6816    incr linknum
6817}
6818
6819proc makelink {id} {
6820    global pendinglinks
6821
6822    if {![info exists pendinglinks($id)]} return
6823    foreach lk $pendinglinks($id) {
6824        setlink $id $lk
6825    }
6826    unset pendinglinks($id)
6827}
6828
6829proc linkcursor {w inc} {
6830    global linkentercount curtextcursor
6831
6832    if {[incr linkentercount $inc] > 0} {
6833        $w configure -cursor hand2
6834    } else {
6835        $w configure -cursor $curtextcursor
6836        if {$linkentercount < 0} {
6837            set linkentercount 0
6838        }
6839    }
6840}
6841
6842proc viewnextline {dir} {
6843    global canv linespc
6844
6845    $canv delete hover
6846    set ymax [lindex [$canv cget -scrollregion] 3]
6847    set wnow [$canv yview]
6848    set wtop [expr {[lindex $wnow 0] * $ymax}]
6849    set newtop [expr {$wtop + $dir * $linespc}]
6850    if {$newtop < 0} {
6851        set newtop 0
6852    } elseif {$newtop > $ymax} {
6853        set newtop $ymax
6854    }
6855    allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
6856}
6857
6858# add a list of tag or branch names at position pos
6859# returns the number of names inserted
6860proc appendrefs {pos ids var} {
6861    global ctext linknum curview $var maxrefs
6862
6863    if {[catch {$ctext index $pos}]} {
6864        return 0
6865    }
6866    $ctext conf -state normal
6867    $ctext delete $pos "$pos lineend"
6868    set tags {}
6869    foreach id $ids {
6870        foreach tag [set $var\($id\)] {
6871            lappend tags [list $tag $id]
6872        }
6873    }
6874    if {[llength $tags] > $maxrefs} {
6875        $ctext insert $pos "[mc "many"] ([llength $tags])"
6876    } else {
6877        set tags [lsort -index 0 -decreasing $tags]
6878        set sep {}
6879        foreach ti $tags {
6880            set id [lindex $ti 1]
6881            set lk link$linknum
6882            incr linknum
6883            $ctext tag delete $lk
6884            $ctext insert $pos $sep
6885            $ctext insert $pos [lindex $ti 0] $lk
6886            setlink $id $lk
6887            set sep ", "
6888        }
6889    }
6890    $ctext conf -state disabled
6891    return [llength $tags]
6892}
6893
6894# called when we have finished computing the nearby tags
6895proc dispneartags {delay} {
6896    global selectedline currentid showneartags tagphase
6897
6898    if {$selectedline eq {} || !$showneartags} return
6899    after cancel dispnexttag
6900    if {$delay} {
6901        after 200 dispnexttag
6902        set tagphase -1
6903    } else {
6904        after idle dispnexttag
6905        set tagphase 0
6906    }
6907}
6908
6909proc dispnexttag {} {
6910    global selectedline currentid showneartags tagphase ctext
6911
6912    if {$selectedline eq {} || !$showneartags} return
6913    switch -- $tagphase {
6914        0 {
6915            set dtags [desctags $currentid]
6916            if {$dtags ne {}} {
6917                appendrefs precedes $dtags idtags
6918            }
6919        }
6920        1 {
6921            set atags [anctags $currentid]
6922            if {$atags ne {}} {
6923                appendrefs follows $atags idtags
6924            }
6925        }
6926        2 {
6927            set dheads [descheads $currentid]
6928            if {$dheads ne {}} {
6929                if {[appendrefs branch $dheads idheads] > 1
6930                    && [$ctext get "branch -3c"] eq "h"} {
6931                    # turn "Branch" into "Branches"
6932                    $ctext conf -state normal
6933                    $ctext insert "branch -2c" "es"
6934                    $ctext conf -state disabled
6935                }
6936            }
6937        }
6938    }
6939    if {[incr tagphase] <= 2} {
6940        after idle dispnexttag
6941    }
6942}
6943
6944proc make_secsel {id} {
6945    global linehtag linentag linedtag canv canv2 canv3
6946
6947    if {![info exists linehtag($id)]} return
6948    $canv delete secsel
6949    set t [eval $canv create rect [$canv bbox $linehtag($id)] -outline {{}} \
6950               -tags secsel -fill [$canv cget -selectbackground]]
6951    $canv lower $t
6952    $canv2 delete secsel
6953    set t [eval $canv2 create rect [$canv2 bbox $linentag($id)] -outline {{}} \
6954               -tags secsel -fill [$canv2 cget -selectbackground]]
6955    $canv2 lower $t
6956    $canv3 delete secsel
6957    set t [eval $canv3 create rect [$canv3 bbox $linedtag($id)] -outline {{}} \
6958               -tags secsel -fill [$canv3 cget -selectbackground]]
6959    $canv3 lower $t
6960}
6961
6962proc make_idmark {id} {
6963    global linehtag canv fgcolor
6964
6965    if {![info exists linehtag($id)]} return
6966    $canv delete markid
6967    set t [eval $canv create rect [$canv bbox $linehtag($id)] \
6968               -tags markid -outline $fgcolor]
6969    $canv raise $t
6970}
6971
6972proc selectline {l isnew {desired_loc {}}} {
6973    global canv ctext commitinfo selectedline
6974    global canvy0 linespc parents children curview
6975    global currentid sha1entry
6976    global commentend idtags linknum
6977    global mergemax numcommits pending_select
6978    global cmitmode showneartags allcommits
6979    global targetrow targetid lastscrollrows
6980    global autoselect autosellen jump_to_here
6981
6982    catch {unset pending_select}
6983    $canv delete hover
6984    normalline
6985    unsel_reflist
6986    stopfinding
6987    if {$l < 0 || $l >= $numcommits} return
6988    set id [commitonrow $l]
6989    set targetid $id
6990    set targetrow $l
6991    set selectedline $l
6992    set currentid $id
6993    if {$lastscrollrows < $numcommits} {
6994        setcanvscroll
6995    }
6996
6997    set y [expr {$canvy0 + $l * $linespc}]
6998    set ymax [lindex [$canv cget -scrollregion] 3]
6999    set ytop [expr {$y - $linespc - 1}]
7000    set ybot [expr {$y + $linespc + 1}]
7001    set wnow [$canv yview]
7002    set wtop [expr {[lindex $wnow 0] * $ymax}]
7003    set wbot [expr {[lindex $wnow 1] * $ymax}]
7004    set wh [expr {$wbot - $wtop}]
7005    set newtop $wtop
7006    if {$ytop < $wtop} {
7007        if {$ybot < $wtop} {
7008            set newtop [expr {$y - $wh / 2.0}]
7009        } else {
7010            set newtop $ytop
7011            if {$newtop > $wtop - $linespc} {
7012                set newtop [expr {$wtop - $linespc}]
7013            }
7014        }
7015    } elseif {$ybot > $wbot} {
7016        if {$ytop > $wbot} {
7017            set newtop [expr {$y - $wh / 2.0}]
7018        } else {
7019            set newtop [expr {$ybot - $wh}]
7020            if {$newtop < $wtop + $linespc} {
7021                set newtop [expr {$wtop + $linespc}]
7022            }
7023        }
7024    }
7025    if {$newtop != $wtop} {
7026        if {$newtop < 0} {
7027            set newtop 0
7028        }
7029        allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
7030        drawvisible
7031    }
7032
7033    make_secsel $id
7034
7035    if {$isnew} {
7036        addtohistory [list selbyid $id 0] savecmitpos
7037    }
7038
7039    $sha1entry delete 0 end
7040    $sha1entry insert 0 $id
7041    if {$autoselect} {
7042        $sha1entry selection range 0 $autosellen
7043    }
7044    rhighlight_sel $id
7045
7046    $ctext conf -state normal
7047    clear_ctext
7048    set linknum 0
7049    if {![info exists commitinfo($id)]} {
7050        getcommit $id
7051    }
7052    set info $commitinfo($id)
7053    set date [formatdate [lindex $info 2]]
7054    $ctext insert end "[mc "Author"]: [lindex $info 1]  $date\n"
7055    set date [formatdate [lindex $info 4]]
7056    $ctext insert end "[mc "Committer"]: [lindex $info 3]  $date\n"
7057    if {[info exists idtags($id)]} {
7058        $ctext insert end [mc "Tags:"]
7059        foreach tag $idtags($id) {
7060            $ctext insert end " $tag"
7061        }
7062        $ctext insert end "\n"
7063    }
7064
7065    set headers {}
7066    set olds $parents($curview,$id)
7067    if {[llength $olds] > 1} {
7068        set np 0
7069        foreach p $olds {
7070            if {$np >= $mergemax} {
7071                set tag mmax
7072            } else {
7073                set tag m$np
7074            }
7075            $ctext insert end "[mc "Parent"]: " $tag
7076            appendwithlinks [commit_descriptor $p] {}
7077            incr np
7078        }
7079    } else {
7080        foreach p $olds {
7081            append headers "[mc "Parent"]: [commit_descriptor $p]"
7082        }
7083    }
7084
7085    foreach c $children($curview,$id) {
7086        append headers "[mc "Child"]:  [commit_descriptor $c]"
7087    }
7088
7089    # make anything that looks like a SHA1 ID be a clickable link
7090    appendwithlinks $headers {}
7091    if {$showneartags} {
7092        if {![info exists allcommits]} {
7093            getallcommits
7094        }
7095        $ctext insert end "[mc "Branch"]: "
7096        $ctext mark set branch "end -1c"
7097        $ctext mark gravity branch left
7098        $ctext insert end "\n[mc "Follows"]: "
7099        $ctext mark set follows "end -1c"
7100        $ctext mark gravity follows left
7101        $ctext insert end "\n[mc "Precedes"]: "
7102        $ctext mark set precedes "end -1c"
7103        $ctext mark gravity precedes left
7104        $ctext insert end "\n"
7105        dispneartags 1
7106    }
7107    $ctext insert end "\n"
7108    set comment [lindex $info 5]
7109    if {[string first "\r" $comment] >= 0} {
7110        set comment [string map {"\r" "\n    "} $comment]
7111    }
7112    appendwithlinks $comment {comment}
7113
7114    $ctext tag remove found 1.0 end
7115    $ctext conf -state disabled
7116    set commentend [$ctext index "end - 1c"]
7117
7118    set jump_to_here $desired_loc
7119    init_flist [mc "Comments"]
7120    if {$cmitmode eq "tree"} {
7121        gettree $id
7122    } elseif {[llength $olds] <= 1} {
7123        startdiff $id
7124    } else {
7125        mergediff $id
7126    }
7127}
7128
7129proc selfirstline {} {
7130    unmarkmatches
7131    selectline 0 1
7132}
7133
7134proc sellastline {} {
7135    global numcommits
7136    unmarkmatches
7137    set l [expr {$numcommits - 1}]
7138    selectline $l 1
7139}
7140
7141proc selnextline {dir} {
7142    global selectedline
7143    focus .
7144    if {$selectedline eq {}} return
7145    set l [expr {$selectedline + $dir}]
7146    unmarkmatches
7147    selectline $l 1
7148}
7149
7150proc selnextpage {dir} {
7151    global canv linespc selectedline numcommits
7152
7153    set lpp [expr {([winfo height $canv] - 2) / $linespc}]
7154    if {$lpp < 1} {
7155        set lpp 1
7156    }
7157    allcanvs yview scroll [expr {$dir * $lpp}] units
7158    drawvisible
7159    if {$selectedline eq {}} return
7160    set l [expr {$selectedline + $dir * $lpp}]
7161    if {$l < 0} {
7162        set l 0
7163    } elseif {$l >= $numcommits} {
7164        set l [expr $numcommits - 1]
7165    }
7166    unmarkmatches
7167    selectline $l 1
7168}
7169
7170proc unselectline {} {
7171    global selectedline currentid
7172
7173    set selectedline {}
7174    catch {unset currentid}
7175    allcanvs delete secsel
7176    rhighlight_none
7177}
7178
7179proc reselectline {} {
7180    global selectedline
7181
7182    if {$selectedline ne {}} {
7183        selectline $selectedline 0
7184    }
7185}
7186
7187proc addtohistory {cmd {saveproc {}}} {
7188    global history historyindex curview
7189
7190    unset_posvars
7191    save_position
7192    set elt [list $curview $cmd $saveproc {}]
7193    if {$historyindex > 0
7194        && [lindex $history [expr {$historyindex - 1}]] == $elt} {
7195        return
7196    }
7197
7198    if {$historyindex < [llength $history]} {
7199        set history [lreplace $history $historyindex end $elt]
7200    } else {
7201        lappend history $elt
7202    }
7203    incr historyindex
7204    if {$historyindex > 1} {
7205        .tf.bar.leftbut conf -state normal
7206    } else {
7207        .tf.bar.leftbut conf -state disabled
7208    }
7209    .tf.bar.rightbut conf -state disabled
7210}
7211
7212# save the scrolling position of the diff display pane
7213proc save_position {} {
7214    global historyindex history
7215
7216    if {$historyindex < 1} return
7217    set hi [expr {$historyindex - 1}]
7218    set fn [lindex $history $hi 2]
7219    if {$fn ne {}} {
7220        lset history $hi 3 [eval $fn]
7221    }
7222}
7223
7224proc unset_posvars {} {
7225    global last_posvars
7226
7227    if {[info exists last_posvars]} {
7228        foreach {var val} $last_posvars {
7229            global $var
7230            catch {unset $var}
7231        }
7232        unset last_posvars
7233    }
7234}
7235
7236proc godo {elt} {
7237    global curview last_posvars
7238
7239    set view [lindex $elt 0]
7240    set cmd [lindex $elt 1]
7241    set pv [lindex $elt 3]
7242    if {$curview != $view} {
7243        showview $view
7244    }
7245    unset_posvars
7246    foreach {var val} $pv {
7247        global $var
7248        set $var $val
7249    }
7250    set last_posvars $pv
7251    eval $cmd
7252}
7253
7254proc goback {} {
7255    global history historyindex
7256    focus .
7257
7258    if {$historyindex > 1} {
7259        save_position
7260        incr historyindex -1
7261        godo [lindex $history [expr {$historyindex - 1}]]
7262        .tf.bar.rightbut conf -state normal
7263    }
7264    if {$historyindex <= 1} {
7265        .tf.bar.leftbut conf -state disabled
7266    }
7267}
7268
7269proc goforw {} {
7270    global history historyindex
7271    focus .
7272
7273    if {$historyindex < [llength $history]} {
7274        save_position
7275        set cmd [lindex $history $historyindex]
7276        incr historyindex
7277        godo $cmd
7278        .tf.bar.leftbut conf -state normal
7279    }
7280    if {$historyindex >= [llength $history]} {
7281        .tf.bar.rightbut conf -state disabled
7282    }
7283}
7284
7285proc gettree {id} {
7286    global treefilelist treeidlist diffids diffmergeid treepending
7287    global nullid nullid2
7288
7289    set diffids $id
7290    catch {unset diffmergeid}
7291    if {![info exists treefilelist($id)]} {
7292        if {![info exists treepending]} {
7293            if {$id eq $nullid} {
7294                set cmd [list | git ls-files]
7295            } elseif {$id eq $nullid2} {
7296                set cmd [list | git ls-files --stage -t]
7297            } else {
7298                set cmd [list | git ls-tree -r $id]
7299            }
7300            if {[catch {set gtf [open $cmd r]}]} {
7301                return
7302            }
7303            set treepending $id
7304            set treefilelist($id) {}
7305            set treeidlist($id) {}
7306            fconfigure $gtf -blocking 0 -encoding binary
7307            filerun $gtf [list gettreeline $gtf $id]
7308        }
7309    } else {
7310        setfilelist $id
7311    }
7312}
7313
7314proc gettreeline {gtf id} {
7315    global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
7316
7317    set nl 0
7318    while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
7319        if {$diffids eq $nullid} {
7320            set fname $line
7321        } else {
7322            set i [string first "\t" $line]
7323            if {$i < 0} continue
7324            set fname [string range $line [expr {$i+1}] end]
7325            set line [string range $line 0 [expr {$i-1}]]
7326            if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
7327            set sha1 [lindex $line 2]
7328            lappend treeidlist($id) $sha1
7329        }
7330        if {[string index $fname 0] eq "\""} {
7331            set fname [lindex $fname 0]
7332        }
7333        set fname [encoding convertfrom $fname]
7334        lappend treefilelist($id) $fname
7335    }
7336    if {![eof $gtf]} {
7337        return [expr {$nl >= 1000? 2: 1}]
7338    }
7339    close $gtf
7340    unset treepending
7341    if {$cmitmode ne "tree"} {
7342        if {![info exists diffmergeid]} {
7343            gettreediffs $diffids
7344        }
7345    } elseif {$id ne $diffids} {
7346        gettree $diffids
7347    } else {
7348        setfilelist $id
7349    }
7350    return 0
7351}
7352
7353proc showfile {f} {
7354    global treefilelist treeidlist diffids nullid nullid2
7355    global ctext_file_names ctext_file_lines
7356    global ctext commentend
7357
7358    set i [lsearch -exact $treefilelist($diffids) $f]
7359    if {$i < 0} {
7360        puts "oops, $f not in list for id $diffids"
7361        return
7362    }
7363    if {$diffids eq $nullid} {
7364        if {[catch {set bf [open $f r]} err]} {
7365            puts "oops, can't read $f: $err"
7366            return
7367        }
7368    } else {
7369        set blob [lindex $treeidlist($diffids) $i]
7370        if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
7371            puts "oops, error reading blob $blob: $err"
7372            return
7373        }
7374    }
7375    fconfigure $bf -blocking 0 -encoding [get_path_encoding $f]
7376    filerun $bf [list getblobline $bf $diffids]
7377    $ctext config -state normal
7378    clear_ctext $commentend
7379    lappend ctext_file_names $f
7380    lappend ctext_file_lines [lindex [split $commentend "."] 0]
7381    $ctext insert end "\n"
7382    $ctext insert end "$f\n" filesep
7383    $ctext config -state disabled
7384    $ctext yview $commentend
7385    settabs 0
7386}
7387
7388proc getblobline {bf id} {
7389    global diffids cmitmode ctext
7390
7391    if {$id ne $diffids || $cmitmode ne "tree"} {
7392        catch {close $bf}
7393        return 0
7394    }
7395    $ctext config -state normal
7396    set nl 0
7397    while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
7398        $ctext insert end "$line\n"
7399    }
7400    if {[eof $bf]} {
7401        global jump_to_here ctext_file_names commentend
7402
7403        # delete last newline
7404        $ctext delete "end - 2c" "end - 1c"
7405        close $bf
7406        if {$jump_to_here ne {} &&
7407            [lindex $jump_to_here 0] eq [lindex $ctext_file_names 0]} {
7408            set lnum [expr {[lindex $jump_to_here 1] +
7409                            [lindex [split $commentend .] 0]}]
7410            mark_ctext_line $lnum
7411        }
7412        $ctext config -state disabled
7413        return 0
7414    }
7415    $ctext config -state disabled
7416    return [expr {$nl >= 1000? 2: 1}]
7417}
7418
7419proc mark_ctext_line {lnum} {
7420    global ctext markbgcolor
7421
7422    $ctext tag delete omark
7423    $ctext tag add omark $lnum.0 "$lnum.0 + 1 line"
7424    $ctext tag conf omark -background $markbgcolor
7425    $ctext see $lnum.0
7426}
7427
7428proc mergediff {id} {
7429    global diffmergeid
7430    global diffids treediffs
7431    global parents curview
7432
7433    set diffmergeid $id
7434    set diffids $id
7435    set treediffs($id) {}
7436    set np [llength $parents($curview,$id)]
7437    settabs $np
7438    getblobdiffs $id
7439}
7440
7441proc startdiff {ids} {
7442    global treediffs diffids treepending diffmergeid nullid nullid2
7443
7444    settabs 1
7445    set diffids $ids
7446    catch {unset diffmergeid}
7447    if {![info exists treediffs($ids)] ||
7448        [lsearch -exact $ids $nullid] >= 0 ||
7449        [lsearch -exact $ids $nullid2] >= 0} {
7450        if {![info exists treepending]} {
7451            gettreediffs $ids
7452        }
7453    } else {
7454        addtocflist $ids
7455    }
7456}
7457
7458# If the filename (name) is under any of the passed filter paths
7459# then return true to include the file in the listing.
7460proc path_filter {filter name} {
7461    set worktree [gitworktree]
7462    foreach p $filter {
7463        set fq_p [file normalize $p]
7464        set fq_n [file normalize [file join $worktree $name]]
7465        if {[string match [file normalize $fq_p]* $fq_n]} {
7466            return 1
7467        }
7468    }
7469    return 0
7470}
7471
7472proc addtocflist {ids} {
7473    global treediffs
7474
7475    add_flist $treediffs($ids)
7476    getblobdiffs $ids
7477}
7478
7479proc diffcmd {ids flags} {
7480    global log_showroot nullid nullid2
7481
7482    set i [lsearch -exact $ids $nullid]
7483    set j [lsearch -exact $ids $nullid2]
7484    if {$i >= 0} {
7485        if {[llength $ids] > 1 && $j < 0} {
7486            # comparing working directory with some specific revision
7487            set cmd [concat | git diff-index $flags]
7488            if {$i == 0} {
7489                lappend cmd -R [lindex $ids 1]
7490            } else {
7491                lappend cmd [lindex $ids 0]
7492            }
7493        } else {
7494            # comparing working directory with index
7495            set cmd [concat | git diff-files $flags]
7496            if {$j == 1} {
7497                lappend cmd -R
7498            }
7499        }
7500    } elseif {$j >= 0} {
7501        set cmd [concat | git diff-index --cached $flags]
7502        if {[llength $ids] > 1} {
7503            # comparing index with specific revision
7504            if {$j == 0} {
7505                lappend cmd -R [lindex $ids 1]
7506            } else {
7507                lappend cmd [lindex $ids 0]
7508            }
7509        } else {
7510            # comparing index with HEAD
7511            lappend cmd HEAD
7512        }
7513    } else {
7514        if {$log_showroot} {
7515            lappend flags --root
7516        }
7517        set cmd [concat | git diff-tree -r $flags $ids]
7518    }
7519    return $cmd
7520}
7521
7522proc gettreediffs {ids} {
7523    global treediff treepending
7524
7525    if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
7526
7527    set treepending $ids
7528    set treediff {}
7529    fconfigure $gdtf -blocking 0 -encoding binary
7530    filerun $gdtf [list gettreediffline $gdtf $ids]
7531}
7532
7533proc gettreediffline {gdtf ids} {
7534    global treediff treediffs treepending diffids diffmergeid
7535    global cmitmode vfilelimit curview limitdiffs perfile_attrs
7536
7537    set nr 0
7538    set sublist {}
7539    set max 1000
7540    if {$perfile_attrs} {
7541        # cache_gitattr is slow, and even slower on win32 where we
7542        # have to invoke it for only about 30 paths at a time
7543        set max 500
7544        if {[tk windowingsystem] == "win32"} {
7545            set max 120
7546        }
7547    }
7548    while {[incr nr] <= $max && [gets $gdtf line] >= 0} {
7549        set i [string first "\t" $line]
7550        if {$i >= 0} {
7551            set file [string range $line [expr {$i+1}] end]
7552            if {[string index $file 0] eq "\""} {
7553                set file [lindex $file 0]
7554            }
7555            set file [encoding convertfrom $file]
7556            if {$file ne [lindex $treediff end]} {
7557                lappend treediff $file
7558                lappend sublist $file
7559            }
7560        }
7561    }
7562    if {$perfile_attrs} {
7563        cache_gitattr encoding $sublist
7564    }
7565    if {![eof $gdtf]} {
7566        return [expr {$nr >= $max? 2: 1}]
7567    }
7568    close $gdtf
7569    if {$limitdiffs && $vfilelimit($curview) ne {}} {
7570        set flist {}
7571        foreach f $treediff {
7572            if {[path_filter $vfilelimit($curview) $f]} {
7573                lappend flist $f
7574            }
7575        }
7576        set treediffs($ids) $flist
7577    } else {
7578        set treediffs($ids) $treediff
7579    }
7580    unset treepending
7581    if {$cmitmode eq "tree" && [llength $diffids] == 1} {
7582        gettree $diffids
7583    } elseif {$ids != $diffids} {
7584        if {![info exists diffmergeid]} {
7585            gettreediffs $diffids
7586        }
7587    } else {
7588        addtocflist $ids
7589    }
7590    return 0
7591}
7592
7593# empty string or positive integer
7594proc diffcontextvalidate {v} {
7595    return [regexp {^(|[1-9][0-9]*)$} $v]
7596}
7597
7598proc diffcontextchange {n1 n2 op} {
7599    global diffcontextstring diffcontext
7600
7601    if {[string is integer -strict $diffcontextstring]} {
7602        if {$diffcontextstring >= 0} {
7603            set diffcontext $diffcontextstring
7604            reselectline
7605        }
7606    }
7607}
7608
7609proc changeignorespace {} {
7610    reselectline
7611}
7612
7613proc changeworddiff {name ix op} {
7614    reselectline
7615}
7616
7617proc getblobdiffs {ids} {
7618    global blobdifffd diffids env
7619    global diffinhdr treediffs
7620    global diffcontext
7621    global ignorespace
7622    global worddiff
7623    global limitdiffs vfilelimit curview
7624    global diffencoding targetline diffnparents
7625    global git_version currdiffsubmod
7626
7627    set textconv {}
7628    if {[package vcompare $git_version "1.6.1"] >= 0} {
7629        set textconv "--textconv"
7630    }
7631    set submodule {}
7632    if {[package vcompare $git_version "1.6.6"] >= 0} {
7633        set submodule "--submodule"
7634    }
7635    set cmd [diffcmd $ids "-p $textconv $submodule  -C --cc --no-commit-id -U$diffcontext"]
7636    if {$ignorespace} {
7637        append cmd " -w"
7638    }
7639    if {$worddiff ne [mc "Line diff"]} {
7640        append cmd " --word-diff=porcelain"
7641    }
7642    if {$limitdiffs && $vfilelimit($curview) ne {}} {
7643        set cmd [concat $cmd -- $vfilelimit($curview)]
7644    }
7645    if {[catch {set bdf [open $cmd r]} err]} {
7646        error_popup [mc "Error getting diffs: %s" $err]
7647        return
7648    }
7649    set targetline {}
7650    set diffnparents 0
7651    set diffinhdr 0
7652    set diffencoding [get_path_encoding {}]
7653    fconfigure $bdf -blocking 0 -encoding binary -eofchar {}
7654    set blobdifffd($ids) $bdf
7655    set currdiffsubmod ""
7656    filerun $bdf [list getblobdiffline $bdf $diffids]
7657}
7658
7659proc savecmitpos {} {
7660    global ctext cmitmode
7661
7662    if {$cmitmode eq "tree"} {
7663        return {}
7664    }
7665    return [list target_scrollpos [$ctext index @0,0]]
7666}
7667
7668proc savectextpos {} {
7669    global ctext
7670
7671    return [list target_scrollpos [$ctext index @0,0]]
7672}
7673
7674proc maybe_scroll_ctext {ateof} {
7675    global ctext target_scrollpos
7676
7677    if {![info exists target_scrollpos]} return
7678    if {!$ateof} {
7679        set nlines [expr {[winfo height $ctext]
7680                          / [font metrics textfont -linespace]}]
7681        if {[$ctext compare "$target_scrollpos + $nlines lines" <= end]} return
7682    }
7683    $ctext yview $target_scrollpos
7684    unset target_scrollpos
7685}
7686
7687proc setinlist {var i val} {
7688    global $var
7689
7690    while {[llength [set $var]] < $i} {
7691        lappend $var {}
7692    }
7693    if {[llength [set $var]] == $i} {
7694        lappend $var $val
7695    } else {
7696        lset $var $i $val
7697    }
7698}
7699
7700proc makediffhdr {fname ids} {
7701    global ctext curdiffstart treediffs diffencoding
7702    global ctext_file_names jump_to_here targetline diffline
7703
7704    set fname [encoding convertfrom $fname]
7705    set diffencoding [get_path_encoding $fname]
7706    set i [lsearch -exact $treediffs($ids) $fname]
7707    if {$i >= 0} {
7708        setinlist difffilestart $i $curdiffstart
7709    }
7710    lset ctext_file_names end $fname
7711    set l [expr {(78 - [string length $fname]) / 2}]
7712    set pad [string range "----------------------------------------" 1 $l]
7713    $ctext insert $curdiffstart "$pad $fname $pad" filesep
7714    set targetline {}
7715    if {$jump_to_here ne {} && [lindex $jump_to_here 0] eq $fname} {
7716        set targetline [lindex $jump_to_here 1]
7717    }
7718    set diffline 0
7719}
7720
7721proc getblobdiffline {bdf ids} {
7722    global diffids blobdifffd ctext curdiffstart
7723    global diffnexthead diffnextnote difffilestart
7724    global ctext_file_names ctext_file_lines
7725    global diffinhdr treediffs mergemax diffnparents
7726    global diffencoding jump_to_here targetline diffline currdiffsubmod
7727    global worddiff
7728
7729    set nr 0
7730    $ctext conf -state normal
7731    while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
7732        if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
7733            catch {close $bdf}
7734            return 0
7735        }
7736        if {![string compare -length 5 "diff " $line]} {
7737            if {![regexp {^diff (--cc|--git) } $line m type]} {
7738                set line [encoding convertfrom $line]
7739                $ctext insert end "$line\n" hunksep
7740                continue
7741            }
7742            # start of a new file
7743            set diffinhdr 1
7744            $ctext insert end "\n"
7745            set curdiffstart [$ctext index "end - 1c"]
7746            lappend ctext_file_names ""
7747            lappend ctext_file_lines [lindex [split $curdiffstart "."] 0]
7748            $ctext insert end "\n" filesep
7749
7750            if {$type eq "--cc"} {
7751                # start of a new file in a merge diff
7752                set fname [string range $line 10 end]
7753                if {[lsearch -exact $treediffs($ids) $fname] < 0} {
7754                    lappend treediffs($ids) $fname
7755                    add_flist [list $fname]
7756                }
7757
7758            } else {
7759                set line [string range $line 11 end]
7760                # If the name hasn't changed the length will be odd,
7761                # the middle char will be a space, and the two bits either
7762                # side will be a/name and b/name, or "a/name" and "b/name".
7763                # If the name has changed we'll get "rename from" and
7764                # "rename to" or "copy from" and "copy to" lines following
7765                # this, and we'll use them to get the filenames.
7766                # This complexity is necessary because spaces in the
7767                # filename(s) don't get escaped.
7768                set l [string length $line]
7769                set i [expr {$l / 2}]
7770                if {!(($l & 1) && [string index $line $i] eq " " &&
7771                      [string range $line 2 [expr {$i - 1}]] eq \
7772                          [string range $line [expr {$i + 3}] end])} {
7773                    continue
7774                }
7775                # unescape if quoted and chop off the a/ from the front
7776                if {[string index $line 0] eq "\""} {
7777                    set fname [string range [lindex $line 0] 2 end]
7778                } else {
7779                    set fname [string range $line 2 [expr {$i - 1}]]
7780                }
7781            }
7782            makediffhdr $fname $ids
7783
7784        } elseif {![string compare -length 16 "* Unmerged path " $line]} {
7785            set fname [encoding convertfrom [string range $line 16 end]]
7786            $ctext insert end "\n"
7787            set curdiffstart [$ctext index "end - 1c"]
7788            lappend ctext_file_names $fname
7789            lappend ctext_file_lines [lindex [split $curdiffstart "."] 0]
7790            $ctext insert end "$line\n" filesep
7791            set i [lsearch -exact $treediffs($ids) $fname]
7792            if {$i >= 0} {
7793                setinlist difffilestart $i $curdiffstart
7794            }
7795
7796        } elseif {![string compare -length 2 "@@" $line]} {
7797            regexp {^@@+} $line ats
7798            set line [encoding convertfrom $diffencoding $line]
7799            $ctext insert end "$line\n" hunksep
7800            if {[regexp { \+(\d+),\d+ @@} $line m nl]} {
7801                set diffline $nl
7802            }
7803            set diffnparents [expr {[string length $ats] - 1}]
7804            set diffinhdr 0
7805
7806        } elseif {![string compare -length 10 "Submodule " $line]} {
7807            # start of a new submodule
7808            if {[regexp -indices "\[0-9a-f\]+\\.\\." $line nameend]} {
7809                set fname [string range $line 10 [expr [lindex $nameend 0] - 2]]
7810            } else {
7811                set fname [string range $line 10 [expr [string first "contains " $line] - 2]]
7812            }
7813            if {$currdiffsubmod != $fname} {
7814                $ctext insert end "\n";     # Add newline after commit message
7815            }
7816            set curdiffstart [$ctext index "end - 1c"]
7817            lappend ctext_file_names ""
7818            if {$currdiffsubmod != $fname} {
7819                lappend ctext_file_lines $fname
7820                makediffhdr $fname $ids
7821                set currdiffsubmod $fname
7822                $ctext insert end "\n$line\n" filesep
7823            } else {
7824                $ctext insert end "$line\n" filesep
7825            }
7826        } elseif {![string compare -length 3 "  >" $line]} {
7827            set $currdiffsubmod ""
7828            set line [encoding convertfrom $diffencoding $line]
7829            $ctext insert end "$line\n" dresult
7830        } elseif {![string compare -length 3 "  <" $line]} {
7831            set $currdiffsubmod ""
7832            set line [encoding convertfrom $diffencoding $line]
7833            $ctext insert end "$line\n" d0
7834        } elseif {$diffinhdr} {
7835            if {![string compare -length 12 "rename from " $line]} {
7836                set fname [string range $line [expr 6 + [string first " from " $line] ] end]
7837                if {[string index $fname 0] eq "\""} {
7838                    set fname [lindex $fname 0]
7839                }
7840                set fname [encoding convertfrom $fname]
7841                set i [lsearch -exact $treediffs($ids) $fname]
7842                if {$i >= 0} {
7843                    setinlist difffilestart $i $curdiffstart
7844                }
7845            } elseif {![string compare -length 10 $line "rename to "] ||
7846                      ![string compare -length 8 $line "copy to "]} {
7847                set fname [string range $line [expr 4 + [string first " to " $line] ] end]
7848                if {[string index $fname 0] eq "\""} {
7849                    set fname [lindex $fname 0]
7850                }
7851                makediffhdr $fname $ids
7852            } elseif {[string compare -length 3 $line "---"] == 0} {
7853                # do nothing
7854                continue
7855            } elseif {[string compare -length 3 $line "+++"] == 0} {
7856                set diffinhdr 0
7857                continue
7858            }
7859            $ctext insert end "$line\n" filesep
7860
7861        } else {
7862            set line [string map {\x1A ^Z} \
7863                          [encoding convertfrom $diffencoding $line]]
7864            # parse the prefix - one ' ', '-' or '+' for each parent
7865            set prefix [string range $line 0 [expr {$diffnparents - 1}]]
7866            set tag [expr {$diffnparents > 1? "m": "d"}]
7867            set dowords [expr {$worddiff ne [mc "Line diff"] && $diffnparents == 1}]
7868            set words_pre_markup ""
7869            set words_post_markup ""
7870            if {[string trim $prefix " -+"] eq {}} {
7871                # prefix only has " ", "-" and "+" in it: normal diff line
7872                set num [string first "-" $prefix]
7873                if {$dowords} {
7874                    set line [string range $line 1 end]
7875                }
7876                if {$num >= 0} {
7877                    # removed line, first parent with line is $num
7878                    if {$num >= $mergemax} {
7879                        set num "max"
7880                    }
7881                    if {$dowords && $worddiff eq [mc "Markup words"]} {
7882                        $ctext insert end "\[-$line-\]" $tag$num
7883                    } else {
7884                        $ctext insert end "$line" $tag$num
7885                    }
7886                    if {!$dowords} {
7887                        $ctext insert end "\n" $tag$num
7888                    }
7889                } else {
7890                    set tags {}
7891                    if {[string first "+" $prefix] >= 0} {
7892                        # added line
7893                        lappend tags ${tag}result
7894                        if {$diffnparents > 1} {
7895                            set num [string first " " $prefix]
7896                            if {$num >= 0} {
7897                                if {$num >= $mergemax} {
7898                                    set num "max"
7899                                }
7900                                lappend tags m$num
7901                            }
7902                        }
7903                        set words_pre_markup "{+"
7904                        set words_post_markup "+}"
7905                    }
7906                    if {$targetline ne {}} {
7907                        if {$diffline == $targetline} {
7908                            set seehere [$ctext index "end - 1 chars"]
7909                            set targetline {}
7910                        } else {
7911                            incr diffline
7912                        }
7913                    }
7914                    if {$dowords && $worddiff eq [mc "Markup words"]} {
7915                        $ctext insert end "$words_pre_markup$line$words_post_markup" $tags
7916                    } else {
7917                        $ctext insert end "$line" $tags
7918                    }
7919                    if {!$dowords} {
7920                        $ctext insert end "\n" $tags
7921                    }
7922                }
7923            } elseif {$dowords && $prefix eq "~"} {
7924                $ctext insert end "\n" {}
7925            } else {
7926                # "\ No newline at end of file",
7927                # or something else we don't recognize
7928                $ctext insert end "$line\n" hunksep
7929            }
7930        }
7931    }
7932    if {[info exists seehere]} {
7933        mark_ctext_line [lindex [split $seehere .] 0]
7934    }
7935    maybe_scroll_ctext [eof $bdf]
7936    $ctext conf -state disabled
7937    if {[eof $bdf]} {
7938        catch {close $bdf}
7939        return 0
7940    }
7941    return [expr {$nr >= 1000? 2: 1}]
7942}
7943
7944proc changediffdisp {} {
7945    global ctext diffelide
7946
7947    $ctext tag conf d0 -elide [lindex $diffelide 0]
7948    $ctext tag conf dresult -elide [lindex $diffelide 1]
7949}
7950
7951proc highlightfile {cline} {
7952    global cflist cflist_top
7953
7954    $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
7955    $cflist tag add highlight $cline.0 "$cline.0 lineend"
7956    $cflist see $cline.0
7957    set cflist_top $cline
7958}
7959
7960proc highlightfile_for_scrollpos {topidx} {
7961    global difffilestart
7962
7963    if {![info exists difffilestart]} return
7964
7965    set top [lindex [split $topidx .] 0]
7966    if {$difffilestart eq {} || $top < [lindex $difffilestart 0]} {
7967        highlightfile 0
7968    } else {
7969        highlightfile [expr {[bsearch $difffilestart $top] + 2}]
7970    }
7971}
7972
7973proc prevfile {} {
7974    global difffilestart ctext cmitmode
7975
7976    if {$cmitmode eq "tree"} return
7977    set prev 0.0
7978    set here [$ctext index @0,0]
7979    foreach loc $difffilestart {
7980        if {[$ctext compare $loc >= $here]} {
7981            $ctext yview $prev
7982            return
7983        }
7984        set prev $loc
7985    }
7986    $ctext yview $prev
7987}
7988
7989proc nextfile {} {
7990    global difffilestart ctext cmitmode
7991
7992    if {$cmitmode eq "tree"} return
7993    set here [$ctext index @0,0]
7994    foreach loc $difffilestart {
7995        if {[$ctext compare $loc > $here]} {
7996            $ctext yview $loc
7997            return
7998        }
7999    }
8000}
8001
8002proc clear_ctext {{first 1.0}} {
8003    global ctext smarktop smarkbot
8004    global ctext_file_names ctext_file_lines
8005    global pendinglinks
8006
8007    set l [lindex [split $first .] 0]
8008    if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
8009        set smarktop $l
8010    }
8011    if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
8012        set smarkbot $l
8013    }
8014    $ctext delete $first end
8015    if {$first eq "1.0"} {
8016        catch {unset pendinglinks}
8017    }
8018    set ctext_file_names {}
8019    set ctext_file_lines {}
8020}
8021
8022proc settabs {{firstab {}}} {
8023    global firsttabstop tabstop ctext have_tk85
8024
8025    if {$firstab ne {} && $have_tk85} {
8026        set firsttabstop $firstab
8027    }
8028    set w [font measure textfont "0"]
8029    if {$firsttabstop != 0} {
8030        $ctext conf -tabs [list [expr {($firsttabstop + $tabstop) * $w}] \
8031                               [expr {($firsttabstop + 2 * $tabstop) * $w}]]
8032    } elseif {$have_tk85 || $tabstop != 8} {
8033        $ctext conf -tabs [expr {$tabstop * $w}]
8034    } else {
8035        $ctext conf -tabs {}
8036    }
8037}
8038
8039proc incrsearch {name ix op} {
8040    global ctext searchstring searchdirn
8041
8042    $ctext tag remove found 1.0 end
8043    if {[catch {$ctext index anchor}]} {
8044        # no anchor set, use start of selection, or of visible area
8045        set sel [$ctext tag ranges sel]
8046        if {$sel ne {}} {
8047            $ctext mark set anchor [lindex $sel 0]
8048        } elseif {$searchdirn eq "-forwards"} {
8049            $ctext mark set anchor @0,0
8050        } else {
8051            $ctext mark set anchor @0,[winfo height $ctext]
8052        }
8053    }
8054    if {$searchstring ne {}} {
8055        set here [$ctext search $searchdirn -- $searchstring anchor]
8056        if {$here ne {}} {
8057            $ctext see $here
8058            suppress_highlighting_file_for_current_scrollpos
8059            highlightfile_for_scrollpos $here
8060        }
8061        searchmarkvisible 1
8062    }
8063}
8064
8065proc dosearch {} {
8066    global sstring ctext searchstring searchdirn
8067
8068    focus $sstring
8069    $sstring icursor end
8070    set searchdirn -forwards
8071    if {$searchstring ne {}} {
8072        set sel [$ctext tag ranges sel]
8073        if {$sel ne {}} {
8074            set start "[lindex $sel 0] + 1c"
8075        } elseif {[catch {set start [$ctext index anchor]}]} {
8076            set start "@0,0"
8077        }
8078        set match [$ctext search -count mlen -- $searchstring $start]
8079        $ctext tag remove sel 1.0 end
8080        if {$match eq {}} {
8081            bell
8082            return
8083        }
8084        $ctext see $match
8085        suppress_highlighting_file_for_current_scrollpos
8086        highlightfile_for_scrollpos $match
8087        set mend "$match + $mlen c"
8088        $ctext tag add sel $match $mend
8089        $ctext mark unset anchor
8090    }
8091}
8092
8093proc dosearchback {} {
8094    global sstring ctext searchstring searchdirn
8095
8096    focus $sstring
8097    $sstring icursor end
8098    set searchdirn -backwards
8099    if {$searchstring ne {}} {
8100        set sel [$ctext tag ranges sel]
8101        if {$sel ne {}} {
8102            set start [lindex $sel 0]
8103        } elseif {[catch {set start [$ctext index anchor]}]} {
8104            set start @0,[winfo height $ctext]
8105        }
8106        set match [$ctext search -backwards -count ml -- $searchstring $start]
8107        $ctext tag remove sel 1.0 end
8108        if {$match eq {}} {
8109            bell
8110            return
8111        }
8112        $ctext see $match
8113        suppress_highlighting_file_for_current_scrollpos
8114        highlightfile_for_scrollpos $match
8115        set mend "$match + $ml c"
8116        $ctext tag add sel $match $mend
8117        $ctext mark unset anchor
8118    }
8119}
8120
8121proc searchmark {first last} {
8122    global ctext searchstring
8123
8124    set mend $first.0
8125    while {1} {
8126        set match [$ctext search -count mlen -- $searchstring $mend $last.end]
8127        if {$match eq {}} break
8128        set mend "$match + $mlen c"
8129        $ctext tag add found $match $mend
8130    }
8131}
8132
8133proc searchmarkvisible {doall} {
8134    global ctext smarktop smarkbot
8135
8136    set topline [lindex [split [$ctext index @0,0] .] 0]
8137    set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
8138    if {$doall || $botline < $smarktop || $topline > $smarkbot} {
8139        # no overlap with previous
8140        searchmark $topline $botline
8141        set smarktop $topline
8142        set smarkbot $botline
8143    } else {
8144        if {$topline < $smarktop} {
8145            searchmark $topline [expr {$smarktop-1}]
8146            set smarktop $topline
8147        }
8148        if {$botline > $smarkbot} {
8149            searchmark [expr {$smarkbot+1}] $botline
8150            set smarkbot $botline
8151        }
8152    }
8153}
8154
8155proc suppress_highlighting_file_for_current_scrollpos {} {
8156    global ctext suppress_highlighting_file_for_this_scrollpos
8157
8158    set suppress_highlighting_file_for_this_scrollpos [$ctext index @0,0]
8159}
8160
8161proc scrolltext {f0 f1} {
8162    global searchstring cmitmode ctext
8163    global suppress_highlighting_file_for_this_scrollpos
8164
8165    if {$cmitmode ne "tree"} {
8166        set topidx [$ctext index @0,0]
8167        if {![info exists suppress_highlighting_file_for_this_scrollpos]
8168            || $topidx ne $suppress_highlighting_file_for_this_scrollpos} {
8169            highlightfile_for_scrollpos $topidx
8170        }
8171    }
8172
8173    catch {unset suppress_highlighting_file_for_this_scrollpos}
8174
8175    .bleft.bottom.sb set $f0 $f1
8176    if {$searchstring ne {}} {
8177        searchmarkvisible 0
8178    }
8179}
8180
8181proc setcoords {} {
8182    global linespc charspc canvx0 canvy0
8183    global xspc1 xspc2 lthickness
8184
8185    set linespc [font metrics mainfont -linespace]
8186    set charspc [font measure mainfont "m"]
8187    set canvy0 [expr {int(3 + 0.5 * $linespc)}]
8188    set canvx0 [expr {int(3 + 0.5 * $linespc)}]
8189    set lthickness [expr {int($linespc / 9) + 1}]
8190    set xspc1(0) $linespc
8191    set xspc2 $linespc
8192}
8193
8194proc redisplay {} {
8195    global canv
8196    global selectedline
8197
8198    set ymax [lindex [$canv cget -scrollregion] 3]
8199    if {$ymax eq {} || $ymax == 0} return
8200    set span [$canv yview]
8201    clear_display
8202    setcanvscroll
8203    allcanvs yview moveto [lindex $span 0]
8204    drawvisible
8205    if {$selectedline ne {}} {
8206        selectline $selectedline 0
8207        allcanvs yview moveto [lindex $span 0]
8208    }
8209}
8210
8211proc parsefont {f n} {
8212    global fontattr
8213
8214    set fontattr($f,family) [lindex $n 0]
8215    set s [lindex $n 1]
8216    if {$s eq {} || $s == 0} {
8217        set s 10
8218    } elseif {$s < 0} {
8219        set s [expr {int(-$s / [winfo fpixels . 1p] + 0.5)}]
8220    }
8221    set fontattr($f,size) $s
8222    set fontattr($f,weight) normal
8223    set fontattr($f,slant) roman
8224    foreach style [lrange $n 2 end] {
8225        switch -- $style {
8226            "normal" -
8227            "bold"   {set fontattr($f,weight) $style}
8228            "roman" -
8229            "italic" {set fontattr($f,slant) $style}
8230        }
8231    }
8232}
8233
8234proc fontflags {f {isbold 0}} {
8235    global fontattr
8236
8237    return [list -family $fontattr($f,family) -size $fontattr($f,size) \
8238                -weight [expr {$isbold? "bold": $fontattr($f,weight)}] \
8239                -slant $fontattr($f,slant)]
8240}
8241
8242proc fontname {f} {
8243    global fontattr
8244
8245    set n [list $fontattr($f,family) $fontattr($f,size)]
8246    if {$fontattr($f,weight) eq "bold"} {
8247        lappend n "bold"
8248    }
8249    if {$fontattr($f,slant) eq "italic"} {
8250        lappend n "italic"
8251    }
8252    return $n
8253}
8254
8255proc incrfont {inc} {
8256    global mainfont textfont ctext canv cflist showrefstop
8257    global stopped entries fontattr
8258
8259    unmarkmatches
8260    set s $fontattr(mainfont,size)
8261    incr s $inc
8262    if {$s < 1} {
8263        set s 1
8264    }
8265    set fontattr(mainfont,size) $s
8266    font config mainfont -size $s
8267    font config mainfontbold -size $s
8268    set mainfont [fontname mainfont]
8269    set s $fontattr(textfont,size)
8270    incr s $inc
8271    if {$s < 1} {
8272        set s 1
8273    }
8274    set fontattr(textfont,size) $s
8275    font config textfont -size $s
8276    font config textfontbold -size $s
8277    set textfont [fontname textfont]
8278    setcoords
8279    settabs
8280    redisplay
8281}
8282
8283proc clearsha1 {} {
8284    global sha1entry sha1string
8285    if {[string length $sha1string] == 40} {
8286        $sha1entry delete 0 end
8287    }
8288}
8289
8290proc sha1change {n1 n2 op} {
8291    global sha1string currentid sha1but
8292    if {$sha1string == {}
8293        || ([info exists currentid] && $sha1string == $currentid)} {
8294        set state disabled
8295    } else {
8296        set state normal
8297    }
8298    if {[$sha1but cget -state] == $state} return
8299    if {$state == "normal"} {
8300        $sha1but conf -state normal -relief raised -text "[mc "Goto:"] "
8301    } else {
8302        $sha1but conf -state disabled -relief flat -text "[mc "SHA1 ID:"] "
8303    }
8304}
8305
8306proc gotocommit {} {
8307    global sha1string tagids headids curview varcid
8308
8309    if {$sha1string == {}
8310        || ([info exists currentid] && $sha1string == $currentid)} return
8311    if {[info exists tagids($sha1string)]} {
8312        set id $tagids($sha1string)
8313    } elseif {[info exists headids($sha1string)]} {
8314        set id $headids($sha1string)
8315    } else {
8316        set id [string tolower $sha1string]
8317        if {[regexp {^[0-9a-f]{4,39}$} $id]} {
8318            set matches [longid $id]
8319            if {$matches ne {}} {
8320                if {[llength $matches] > 1} {
8321                    error_popup [mc "Short SHA1 id %s is ambiguous" $id]
8322                    return
8323                }
8324                set id [lindex $matches 0]
8325            }
8326        } else {
8327            if {[catch {set id [exec git rev-parse --verify $sha1string]}]} {
8328                error_popup [mc "Revision %s is not known" $sha1string]
8329                return
8330            }
8331        }
8332    }
8333    if {[commitinview $id $curview]} {
8334        selectline [rowofcommit $id] 1
8335        return
8336    }
8337    if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
8338        set msg [mc "SHA1 id %s is not known" $sha1string]
8339    } else {
8340        set msg [mc "Revision %s is not in the current view" $sha1string]
8341    }
8342    error_popup $msg
8343}
8344
8345proc lineenter {x y id} {
8346    global hoverx hovery hoverid hovertimer
8347    global commitinfo canv
8348
8349    if {![info exists commitinfo($id)] && ![getcommit $id]} return
8350    set hoverx $x
8351    set hovery $y
8352    set hoverid $id
8353    if {[info exists hovertimer]} {
8354        after cancel $hovertimer
8355    }
8356    set hovertimer [after 500 linehover]
8357    $canv delete hover
8358}
8359
8360proc linemotion {x y id} {
8361    global hoverx hovery hoverid hovertimer
8362
8363    if {[info exists hoverid] && $id == $hoverid} {
8364        set hoverx $x
8365        set hovery $y
8366        if {[info exists hovertimer]} {
8367            after cancel $hovertimer
8368        }
8369        set hovertimer [after 500 linehover]
8370    }
8371}
8372
8373proc lineleave {id} {
8374    global hoverid hovertimer canv
8375
8376    if {[info exists hoverid] && $id == $hoverid} {
8377        $canv delete hover
8378        if {[info exists hovertimer]} {
8379            after cancel $hovertimer
8380            unset hovertimer
8381        }
8382        unset hoverid
8383    }
8384}
8385
8386proc linehover {} {
8387    global hoverx hovery hoverid hovertimer
8388    global canv linespc lthickness
8389    global commitinfo
8390
8391    set text [lindex $commitinfo($hoverid) 0]
8392    set ymax [lindex [$canv cget -scrollregion] 3]
8393    if {$ymax == {}} return
8394    set yfrac [lindex [$canv yview] 0]
8395    set x [expr {$hoverx + 2 * $linespc}]
8396    set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
8397    set x0 [expr {$x - 2 * $lthickness}]
8398    set y0 [expr {$y - 2 * $lthickness}]
8399    set x1 [expr {$x + [font measure mainfont $text] + 2 * $lthickness}]
8400    set y1 [expr {$y + $linespc + 2 * $lthickness}]
8401    set t [$canv create rectangle $x0 $y0 $x1 $y1 \
8402               -fill \#ffff80 -outline black -width 1 -tags hover]
8403    $canv raise $t
8404    set t [$canv create text $x $y -anchor nw -text $text -tags hover \
8405               -font mainfont]
8406    $canv raise $t
8407}
8408
8409proc clickisonarrow {id y} {
8410    global lthickness
8411
8412    set ranges [rowranges $id]
8413    set thresh [expr {2 * $lthickness + 6}]
8414    set n [expr {[llength $ranges] - 1}]
8415    for {set i 1} {$i < $n} {incr i} {
8416        set row [lindex $ranges $i]
8417        if {abs([yc $row] - $y) < $thresh} {
8418            return $i
8419        }
8420    }
8421    return {}
8422}
8423
8424proc arrowjump {id n y} {
8425    global canv
8426
8427    # 1 <-> 2, 3 <-> 4, etc...
8428    set n [expr {(($n - 1) ^ 1) + 1}]
8429    set row [lindex [rowranges $id] $n]
8430    set yt [yc $row]
8431    set ymax [lindex [$canv cget -scrollregion] 3]
8432    if {$ymax eq {} || $ymax <= 0} return
8433    set view [$canv yview]
8434    set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
8435    set yfrac [expr {$yt / $ymax - $yspan / 2}]
8436    if {$yfrac < 0} {
8437        set yfrac 0
8438    }
8439    allcanvs yview moveto $yfrac
8440}
8441
8442proc lineclick {x y id isnew} {
8443    global ctext commitinfo children canv thickerline curview
8444
8445    if {![info exists commitinfo($id)] && ![getcommit $id]} return
8446    unmarkmatches
8447    unselectline
8448    normalline
8449    $canv delete hover
8450    # draw this line thicker than normal
8451    set thickerline $id
8452    drawlines $id
8453    if {$isnew} {
8454        set ymax [lindex [$canv cget -scrollregion] 3]
8455        if {$ymax eq {}} return
8456        set yfrac [lindex [$canv yview] 0]
8457        set y [expr {$y + $yfrac * $ymax}]
8458    }
8459    set dirn [clickisonarrow $id $y]
8460    if {$dirn ne {}} {
8461        arrowjump $id $dirn $y
8462        return
8463    }
8464
8465    if {$isnew} {
8466        addtohistory [list lineclick $x $y $id 0] savectextpos
8467    }
8468    # fill the details pane with info about this line
8469    $ctext conf -state normal
8470    clear_ctext
8471    settabs 0
8472    $ctext insert end "[mc "Parent"]:\t"
8473    $ctext insert end $id link0
8474    setlink $id link0
8475    set info $commitinfo($id)
8476    $ctext insert end "\n\t[lindex $info 0]\n"
8477    $ctext insert end "\t[mc "Author"]:\t[lindex $info 1]\n"
8478    set date [formatdate [lindex $info 2]]
8479    $ctext insert end "\t[mc "Date"]:\t$date\n"
8480    set kids $children($curview,$id)
8481    if {$kids ne {}} {
8482        $ctext insert end "\n[mc "Children"]:"
8483        set i 0
8484        foreach child $kids {
8485            incr i
8486            if {![info exists commitinfo($child)] && ![getcommit $child]} continue
8487            set info $commitinfo($child)
8488            $ctext insert end "\n\t"
8489            $ctext insert end $child link$i
8490            setlink $child link$i
8491            $ctext insert end "\n\t[lindex $info 0]"
8492            $ctext insert end "\n\t[mc "Author"]:\t[lindex $info 1]"
8493            set date [formatdate [lindex $info 2]]
8494            $ctext insert end "\n\t[mc "Date"]:\t$date\n"
8495        }
8496    }
8497    maybe_scroll_ctext 1
8498    $ctext conf -state disabled
8499    init_flist {}
8500}
8501
8502proc normalline {} {
8503    global thickerline
8504    if {[info exists thickerline]} {
8505        set id $thickerline
8506        unset thickerline
8507        drawlines $id
8508    }
8509}
8510
8511proc selbyid {id {isnew 1}} {
8512    global curview
8513    if {[commitinview $id $curview]} {
8514        selectline [rowofcommit $id] $isnew
8515    }
8516}
8517
8518proc mstime {} {
8519    global startmstime
8520    if {![info exists startmstime]} {
8521        set startmstime [clock clicks -milliseconds]
8522    }
8523    return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
8524}
8525
8526proc rowmenu {x y id} {
8527    global rowctxmenu selectedline rowmenuid curview
8528    global nullid nullid2 fakerowmenu mainhead markedid
8529
8530    stopfinding
8531    set rowmenuid $id
8532    if {$selectedline eq {} || [rowofcommit $id] eq $selectedline} {
8533        set state disabled
8534    } else {
8535        set state normal
8536    }
8537    if {[info exists markedid] && $markedid ne $id} {
8538        set mstate normal
8539    } else {
8540        set mstate disabled
8541    }
8542    if {$id ne $nullid && $id ne $nullid2} {
8543        set menu $rowctxmenu
8544        if {$mainhead ne {}} {
8545            $menu entryconfigure 7 -label [mc "Reset %s branch to here" $mainhead] -state normal
8546        } else {
8547            $menu entryconfigure 7 -label [mc "Detached head: can't reset" $mainhead] -state disabled
8548        }
8549        $menu entryconfigure 9 -state $mstate
8550        $menu entryconfigure 10 -state $mstate
8551        $menu entryconfigure 11 -state $mstate
8552    } else {
8553        set menu $fakerowmenu
8554    }
8555    $menu entryconfigure [mca "Diff this -> selected"] -state $state
8556    $menu entryconfigure [mca "Diff selected -> this"] -state $state
8557    $menu entryconfigure [mca "Make patch"] -state $state
8558    $menu entryconfigure [mca "Diff this -> marked commit"] -state $mstate
8559    $menu entryconfigure [mca "Diff marked commit -> this"] -state $mstate
8560    tk_popup $menu $x $y
8561}
8562
8563proc markhere {} {
8564    global rowmenuid markedid canv
8565
8566    set markedid $rowmenuid
8567    make_idmark $markedid
8568}
8569
8570proc gotomark {} {
8571    global markedid
8572
8573    if {[info exists markedid]} {
8574        selbyid $markedid
8575    }
8576}
8577
8578proc replace_by_kids {l r} {
8579    global curview children
8580
8581    set id [commitonrow $r]
8582    set l [lreplace $l 0 0]
8583    foreach kid $children($curview,$id) {
8584        lappend l [rowofcommit $kid]
8585    }
8586    return [lsort -integer -decreasing -unique $l]
8587}
8588
8589proc find_common_desc {} {
8590    global markedid rowmenuid curview children
8591
8592    if {![info exists markedid]} return
8593    if {![commitinview $markedid $curview] ||
8594        ![commitinview $rowmenuid $curview]} return
8595    #set t1 [clock clicks -milliseconds]
8596    set l1 [list [rowofcommit $markedid]]
8597    set l2 [list [rowofcommit $rowmenuid]]
8598    while 1 {
8599        set r1 [lindex $l1 0]
8600        set r2 [lindex $l2 0]
8601        if {$r1 eq {} || $r2 eq {}} break
8602        if {$r1 == $r2} {
8603            selectline $r1 1
8604            break
8605        }
8606        if {$r1 > $r2} {
8607            set l1 [replace_by_kids $l1 $r1]
8608        } else {
8609            set l2 [replace_by_kids $l2 $r2]
8610        }
8611    }
8612    #set t2 [clock clicks -milliseconds]
8613    #puts "took [expr {$t2-$t1}]ms"
8614}
8615
8616proc compare_commits {} {
8617    global markedid rowmenuid curview children
8618
8619    if {![info exists markedid]} return
8620    if {![commitinview $markedid $curview]} return
8621    addtohistory [list do_cmp_commits $markedid $rowmenuid]
8622    do_cmp_commits $markedid $rowmenuid
8623}
8624
8625proc getpatchid {id} {
8626    global patchids
8627
8628    if {![info exists patchids($id)]} {
8629        set cmd [diffcmd [list $id] {-p --root}]
8630        # trim off the initial "|"
8631        set cmd [lrange $cmd 1 end]
8632        if {[catch {
8633            set x [eval exec $cmd | git patch-id]
8634            set patchids($id) [lindex $x 0]
8635        }]} {
8636            set patchids($id) "error"
8637        }
8638    }
8639    return $patchids($id)
8640}
8641
8642proc do_cmp_commits {a b} {
8643    global ctext curview parents children patchids commitinfo
8644
8645    $ctext conf -state normal
8646    clear_ctext
8647    init_flist {}
8648    for {set i 0} {$i < 100} {incr i} {
8649        set skipa 0
8650        set skipb 0
8651        if {[llength $parents($curview,$a)] > 1} {
8652            appendshortlink $a [mc "Skipping merge commit "] "\n"
8653            set skipa 1
8654        } else {
8655            set patcha [getpatchid $a]
8656        }
8657        if {[llength $parents($curview,$b)] > 1} {
8658            appendshortlink $b [mc "Skipping merge commit "] "\n"
8659            set skipb 1
8660        } else {
8661            set patchb [getpatchid $b]
8662        }
8663        if {!$skipa && !$skipb} {
8664            set heada [lindex $commitinfo($a) 0]
8665            set headb [lindex $commitinfo($b) 0]
8666            if {$patcha eq "error"} {
8667                appendshortlink $a [mc "Error getting patch ID for "] \
8668                    [mc " - stopping\n"]
8669                break
8670            }
8671            if {$patchb eq "error"} {
8672                appendshortlink $b [mc "Error getting patch ID for "] \
8673                    [mc " - stopping\n"]
8674                break
8675            }
8676            if {$patcha eq $patchb} {
8677                if {$heada eq $headb} {
8678                    appendshortlink $a [mc "Commit "]
8679                    appendshortlink $b " == " "  $heada\n"
8680                } else {
8681                    appendshortlink $a [mc "Commit "] "  $heada\n"
8682                    appendshortlink $b [mc " is the same patch as\n       "] \
8683                        "  $headb\n"
8684                }
8685                set skipa 1
8686                set skipb 1
8687            } else {
8688                $ctext insert end "\n"
8689                appendshortlink $a [mc "Commit "] "  $heada\n"
8690                appendshortlink $b [mc " differs from\n       "] \
8691                    "  $headb\n"
8692                $ctext insert end [mc "Diff of commits:\n\n"]
8693                $ctext conf -state disabled
8694                update
8695                diffcommits $a $b
8696                return
8697            }
8698        }
8699        if {$skipa} {
8700            set kids [real_children $curview,$a]
8701            if {[llength $kids] != 1} {
8702                $ctext insert end "\n"
8703                appendshortlink $a [mc "Commit "] \
8704                    [mc " has %s children - stopping\n" [llength $kids]]
8705                break
8706            }
8707            set a [lindex $kids 0]
8708        }
8709        if {$skipb} {
8710            set kids [real_children $curview,$b]
8711            if {[llength $kids] != 1} {
8712                appendshortlink $b [mc "Commit "] \
8713                    [mc " has %s children - stopping\n" [llength $kids]]
8714                break
8715            }
8716            set b [lindex $kids 0]
8717        }
8718    }
8719    $ctext conf -state disabled
8720}
8721
8722proc diffcommits {a b} {
8723    global diffcontext diffids blobdifffd diffinhdr currdiffsubmod
8724
8725    set tmpdir [gitknewtmpdir]
8726    set fna [file join $tmpdir "commit-[string range $a 0 7]"]
8727    set fnb [file join $tmpdir "commit-[string range $b 0 7]"]
8728    if {[catch {
8729        exec git diff-tree -p --pretty $a >$fna
8730        exec git diff-tree -p --pretty $b >$fnb
8731    } err]} {
8732        error_popup [mc "Error writing commit to file: %s" $err]
8733        return
8734    }
8735    if {[catch {
8736        set fd [open "| diff -U$diffcontext $fna $fnb" r]
8737    } err]} {
8738        error_popup [mc "Error diffing commits: %s" $err]
8739        return
8740    }
8741    set diffids [list commits $a $b]
8742    set blobdifffd($diffids) $fd
8743    set diffinhdr 0
8744    set currdiffsubmod ""
8745    filerun $fd [list getblobdiffline $fd $diffids]
8746}
8747
8748proc diffvssel {dirn} {
8749    global rowmenuid selectedline
8750
8751    if {$selectedline eq {}} return
8752    if {$dirn} {
8753        set oldid [commitonrow $selectedline]
8754        set newid $rowmenuid
8755    } else {
8756        set oldid $rowmenuid
8757        set newid [commitonrow $selectedline]
8758    }
8759    addtohistory [list doseldiff $oldid $newid] savectextpos
8760    doseldiff $oldid $newid
8761}
8762
8763proc diffvsmark {dirn} {
8764    global rowmenuid markedid
8765
8766    if {![info exists markedid]} return
8767    if {$dirn} {
8768        set oldid $markedid
8769        set newid $rowmenuid
8770    } else {
8771        set oldid $rowmenuid
8772        set newid $markedid
8773    }
8774    addtohistory [list doseldiff $oldid $newid] savectextpos
8775    doseldiff $oldid $newid
8776}
8777
8778proc doseldiff {oldid newid} {
8779    global ctext
8780    global commitinfo
8781
8782    $ctext conf -state normal
8783    clear_ctext
8784    init_flist [mc "Top"]
8785    $ctext insert end "[mc "From"] "
8786    $ctext insert end $oldid link0
8787    setlink $oldid link0
8788    $ctext insert end "\n     "
8789    $ctext insert end [lindex $commitinfo($oldid) 0]
8790    $ctext insert end "\n\n[mc "To"]   "
8791    $ctext insert end $newid link1
8792    setlink $newid link1
8793    $ctext insert end "\n     "
8794    $ctext insert end [lindex $commitinfo($newid) 0]
8795    $ctext insert end "\n"
8796    $ctext conf -state disabled
8797    $ctext tag remove found 1.0 end
8798    startdiff [list $oldid $newid]
8799}
8800
8801proc mkpatch {} {
8802    global rowmenuid currentid commitinfo patchtop patchnum NS
8803
8804    if {![info exists currentid]} return
8805    set oldid $currentid
8806    set oldhead [lindex $commitinfo($oldid) 0]
8807    set newid $rowmenuid
8808    set newhead [lindex $commitinfo($newid) 0]
8809    set top .patch
8810    set patchtop $top
8811    catch {destroy $top}
8812    ttk_toplevel $top
8813    make_transient $top .
8814    ${NS}::label $top.title -text [mc "Generate patch"]
8815    grid $top.title - -pady 10
8816    ${NS}::label $top.from -text [mc "From:"]
8817    ${NS}::entry $top.fromsha1 -width 40
8818    $top.fromsha1 insert 0 $oldid
8819    $top.fromsha1 conf -state readonly
8820    grid $top.from $top.fromsha1 -sticky w
8821    ${NS}::entry $top.fromhead -width 60
8822    $top.fromhead insert 0 $oldhead
8823    $top.fromhead conf -state readonly
8824    grid x $top.fromhead -sticky w
8825    ${NS}::label $top.to -text [mc "To:"]
8826    ${NS}::entry $top.tosha1 -width 40
8827    $top.tosha1 insert 0 $newid
8828    $top.tosha1 conf -state readonly
8829    grid $top.to $top.tosha1 -sticky w
8830    ${NS}::entry $top.tohead -width 60
8831    $top.tohead insert 0 $newhead
8832    $top.tohead conf -state readonly
8833    grid x $top.tohead -sticky w
8834    ${NS}::button $top.rev -text [mc "Reverse"] -command mkpatchrev
8835    grid $top.rev x -pady 10 -padx 5
8836    ${NS}::label $top.flab -text [mc "Output file:"]
8837    ${NS}::entry $top.fname -width 60
8838    $top.fname insert 0 [file normalize "patch$patchnum.patch"]
8839    incr patchnum
8840    grid $top.flab $top.fname -sticky w
8841    ${NS}::frame $top.buts
8842    ${NS}::button $top.buts.gen -text [mc "Generate"] -command mkpatchgo
8843    ${NS}::button $top.buts.can -text [mc "Cancel"] -command mkpatchcan
8844    bind $top <Key-Return> mkpatchgo
8845    bind $top <Key-Escape> mkpatchcan
8846    grid $top.buts.gen $top.buts.can
8847    grid columnconfigure $top.buts 0 -weight 1 -uniform a
8848    grid columnconfigure $top.buts 1 -weight 1 -uniform a
8849    grid $top.buts - -pady 10 -sticky ew
8850    focus $top.fname
8851}
8852
8853proc mkpatchrev {} {
8854    global patchtop
8855
8856    set oldid [$patchtop.fromsha1 get]
8857    set oldhead [$patchtop.fromhead get]
8858    set newid [$patchtop.tosha1 get]
8859    set newhead [$patchtop.tohead get]
8860    foreach e [list fromsha1 fromhead tosha1 tohead] \
8861            v [list $newid $newhead $oldid $oldhead] {
8862        $patchtop.$e conf -state normal
8863        $patchtop.$e delete 0 end
8864        $patchtop.$e insert 0 $v
8865        $patchtop.$e conf -state readonly
8866    }
8867}
8868
8869proc mkpatchgo {} {
8870    global patchtop nullid nullid2
8871
8872    set oldid [$patchtop.fromsha1 get]
8873    set newid [$patchtop.tosha1 get]
8874    set fname [$patchtop.fname get]
8875    set cmd [diffcmd [list $oldid $newid] -p]
8876    # trim off the initial "|"
8877    set cmd [lrange $cmd 1 end]
8878    lappend cmd >$fname &
8879    if {[catch {eval exec $cmd} err]} {
8880        error_popup "[mc "Error creating patch:"] $err" $patchtop
8881    }
8882    catch {destroy $patchtop}
8883    unset patchtop
8884}
8885
8886proc mkpatchcan {} {
8887    global patchtop
8888
8889    catch {destroy $patchtop}
8890    unset patchtop
8891}
8892
8893proc mktag {} {
8894    global rowmenuid mktagtop commitinfo NS
8895
8896    set top .maketag
8897    set mktagtop $top
8898    catch {destroy $top}
8899    ttk_toplevel $top
8900    make_transient $top .
8901    ${NS}::label $top.title -text [mc "Create tag"]
8902    grid $top.title - -pady 10
8903    ${NS}::label $top.id -text [mc "ID:"]
8904    ${NS}::entry $top.sha1 -width 40
8905    $top.sha1 insert 0 $rowmenuid
8906    $top.sha1 conf -state readonly
8907    grid $top.id $top.sha1 -sticky w
8908    ${NS}::entry $top.head -width 60
8909    $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
8910    $top.head conf -state readonly
8911    grid x $top.head -sticky w
8912    ${NS}::label $top.tlab -text [mc "Tag name:"]
8913    ${NS}::entry $top.tag -width 60
8914    grid $top.tlab $top.tag -sticky w
8915    ${NS}::label $top.op -text [mc "Tag message is optional"]
8916    grid $top.op -columnspan 2 -sticky we
8917    ${NS}::label $top.mlab -text [mc "Tag message:"]
8918    ${NS}::entry $top.msg -width 60
8919    grid $top.mlab $top.msg -sticky w
8920    ${NS}::frame $top.buts
8921    ${NS}::button $top.buts.gen -text [mc "Create"] -command mktaggo
8922    ${NS}::button $top.buts.can -text [mc "Cancel"] -command mktagcan
8923    bind $top <Key-Return> mktaggo
8924    bind $top <Key-Escape> mktagcan
8925    grid $top.buts.gen $top.buts.can
8926    grid columnconfigure $top.buts 0 -weight 1 -uniform a
8927    grid columnconfigure $top.buts 1 -weight 1 -uniform a
8928    grid $top.buts - -pady 10 -sticky ew
8929    focus $top.tag
8930}
8931
8932proc domktag {} {
8933    global mktagtop env tagids idtags
8934
8935    set id [$mktagtop.sha1 get]
8936    set tag [$mktagtop.tag get]
8937    set msg [$mktagtop.msg get]
8938    if {$tag == {}} {
8939        error_popup [mc "No tag name specified"] $mktagtop
8940        return 0
8941    }
8942    if {[info exists tagids($tag)]} {
8943        error_popup [mc "Tag \"%s\" already exists" $tag] $mktagtop
8944        return 0
8945    }
8946    if {[catch {
8947        if {$msg != {}} {
8948            exec git tag -a -m $msg $tag $id
8949        } else {
8950            exec git tag $tag $id
8951        }
8952    } err]} {
8953        error_popup "[mc "Error creating tag:"] $err" $mktagtop
8954        return 0
8955    }
8956
8957    set tagids($tag) $id
8958    lappend idtags($id) $tag
8959    redrawtags $id
8960    addedtag $id
8961    dispneartags 0
8962    run refill_reflist
8963    return 1
8964}
8965
8966proc redrawtags {id} {
8967    global canv linehtag idpos currentid curview cmitlisted markedid
8968    global canvxmax iddrawn circleitem mainheadid circlecolors
8969
8970    if {![commitinview $id $curview]} return
8971    if {![info exists iddrawn($id)]} return
8972    set row [rowofcommit $id]
8973    if {$id eq $mainheadid} {
8974        set ofill yellow
8975    } else {
8976        set ofill [lindex $circlecolors $cmitlisted($curview,$id)]
8977    }
8978    $canv itemconf $circleitem($row) -fill $ofill
8979    $canv delete tag.$id
8980    set xt [eval drawtags $id $idpos($id)]
8981    $canv coords $linehtag($id) $xt [lindex $idpos($id) 2]
8982    set text [$canv itemcget $linehtag($id) -text]
8983    set font [$canv itemcget $linehtag($id) -font]
8984    set xr [expr {$xt + [font measure $font $text]}]
8985    if {$xr > $canvxmax} {
8986        set canvxmax $xr
8987        setcanvscroll
8988    }
8989    if {[info exists currentid] && $currentid == $id} {
8990        make_secsel $id
8991    }
8992    if {[info exists markedid] && $markedid eq $id} {
8993        make_idmark $id
8994    }
8995}
8996
8997proc mktagcan {} {
8998    global mktagtop
8999
9000    catch {destroy $mktagtop}
9001    unset mktagtop
9002}
9003
9004proc mktaggo {} {
9005    if {![domktag]} return
9006    mktagcan
9007}
9008
9009proc writecommit {} {
9010    global rowmenuid wrcomtop commitinfo wrcomcmd NS
9011
9012    set top .writecommit
9013    set wrcomtop $top
9014    catch {destroy $top}
9015    ttk_toplevel $top
9016    make_transient $top .
9017    ${NS}::label $top.title -text [mc "Write commit to file"]
9018    grid $top.title - -pady 10
9019    ${NS}::label $top.id -text [mc "ID:"]
9020    ${NS}::entry $top.sha1 -width 40
9021    $top.sha1 insert 0 $rowmenuid
9022    $top.sha1 conf -state readonly
9023    grid $top.id $top.sha1 -sticky w
9024    ${NS}::entry $top.head -width 60
9025    $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
9026    $top.head conf -state readonly
9027    grid x $top.head -sticky w
9028    ${NS}::label $top.clab -text [mc "Command:"]
9029    ${NS}::entry $top.cmd -width 60 -textvariable wrcomcmd
9030    grid $top.clab $top.cmd -sticky w -pady 10
9031    ${NS}::label $top.flab -text [mc "Output file:"]
9032    ${NS}::entry $top.fname -width 60
9033    $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
9034    grid $top.flab $top.fname -sticky w
9035    ${NS}::frame $top.buts
9036    ${NS}::button $top.buts.gen -text [mc "Write"] -command wrcomgo
9037    ${NS}::button $top.buts.can -text [mc "Cancel"] -command wrcomcan
9038    bind $top <Key-Return> wrcomgo
9039    bind $top <Key-Escape> wrcomcan
9040    grid $top.buts.gen $top.buts.can
9041    grid columnconfigure $top.buts 0 -weight 1 -uniform a
9042    grid columnconfigure $top.buts 1 -weight 1 -uniform a
9043    grid $top.buts - -pady 10 -sticky ew
9044    focus $top.fname
9045}
9046
9047proc wrcomgo {} {
9048    global wrcomtop
9049
9050    set id [$wrcomtop.sha1 get]
9051    set cmd "echo $id | [$wrcomtop.cmd get]"
9052    set fname [$wrcomtop.fname get]
9053    if {[catch {exec sh -c $cmd >$fname &} err]} {
9054        error_popup "[mc "Error writing commit:"] $err" $wrcomtop
9055    }
9056    catch {destroy $wrcomtop}
9057    unset wrcomtop
9058}
9059
9060proc wrcomcan {} {
9061    global wrcomtop
9062
9063    catch {destroy $wrcomtop}
9064    unset wrcomtop
9065}
9066
9067proc mkbranch {} {
9068    global rowmenuid mkbrtop NS
9069
9070    set top .makebranch
9071    catch {destroy $top}
9072    ttk_toplevel $top
9073    make_transient $top .
9074    ${NS}::label $top.title -text [mc "Create new branch"]
9075    grid $top.title - -pady 10
9076    ${NS}::label $top.id -text [mc "ID:"]
9077    ${NS}::entry $top.sha1 -width 40
9078    $top.sha1 insert 0 $rowmenuid
9079    $top.sha1 conf -state readonly
9080    grid $top.id $top.sha1 -sticky w
9081    ${NS}::label $top.nlab -text [mc "Name:"]
9082    ${NS}::entry $top.name -width 40
9083    grid $top.nlab $top.name -sticky w
9084    ${NS}::frame $top.buts
9085    ${NS}::button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top]
9086    ${NS}::button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}"
9087    bind $top <Key-Return> [list mkbrgo $top]
9088    bind $top <Key-Escape> "catch {destroy $top}"
9089    grid $top.buts.go $top.buts.can
9090    grid columnconfigure $top.buts 0 -weight 1 -uniform a
9091    grid columnconfigure $top.buts 1 -weight 1 -uniform a
9092    grid $top.buts - -pady 10 -sticky ew
9093    focus $top.name
9094}
9095
9096proc mkbrgo {top} {
9097    global headids idheads
9098
9099    set name [$top.name get]
9100    set id [$top.sha1 get]
9101    set cmdargs {}
9102    set old_id {}
9103    if {$name eq {}} {
9104        error_popup [mc "Please specify a name for the new branch"] $top
9105        return
9106    }
9107    if {[info exists headids($name)]} {
9108        if {![confirm_popup [mc \
9109                "Branch '%s' already exists. Overwrite?" $name] $top]} {
9110            return
9111        }
9112        set old_id $headids($name)
9113        lappend cmdargs -f
9114    }
9115    catch {destroy $top}
9116    lappend cmdargs $name $id
9117    nowbusy newbranch
9118    update
9119    if {[catch {
9120        eval exec git branch $cmdargs
9121    } err]} {
9122        notbusy newbranch
9123        error_popup $err
9124    } else {
9125        notbusy newbranch
9126        if {$old_id ne {}} {
9127            movehead $id $name
9128            movedhead $id $name
9129            redrawtags $old_id
9130            redrawtags $id
9131        } else {
9132            set headids($name) $id
9133            lappend idheads($id) $name
9134            addedhead $id $name
9135            redrawtags $id
9136        }
9137        dispneartags 0
9138        run refill_reflist
9139    }
9140}
9141
9142proc exec_citool {tool_args {baseid {}}} {
9143    global commitinfo env
9144
9145    set save_env [array get env GIT_AUTHOR_*]
9146
9147    if {$baseid ne {}} {
9148        if {![info exists commitinfo($baseid)]} {
9149            getcommit $baseid
9150        }
9151        set author [lindex $commitinfo($baseid) 1]
9152        set date [lindex $commitinfo($baseid) 2]
9153        if {[regexp {^\s*(\S.*\S|\S)\s*<(.*)>\s*$} \
9154                    $author author name email]
9155            && $date ne {}} {
9156            set env(GIT_AUTHOR_NAME) $name
9157            set env(GIT_AUTHOR_EMAIL) $email
9158            set env(GIT_AUTHOR_DATE) $date
9159        }
9160    }
9161
9162    eval exec git citool $tool_args &
9163
9164    array unset env GIT_AUTHOR_*
9165    array set env $save_env
9166}
9167
9168proc cherrypick {} {
9169    global rowmenuid curview
9170    global mainhead mainheadid
9171    global gitdir
9172
9173    set oldhead [exec git rev-parse HEAD]
9174    set dheads [descheads $rowmenuid]
9175    if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
9176        set ok [confirm_popup [mc "Commit %s is already\
9177                included in branch %s -- really re-apply it?" \
9178                                   [string range $rowmenuid 0 7] $mainhead]]
9179        if {!$ok} return
9180    }
9181    nowbusy cherrypick [mc "Cherry-picking"]
9182    update
9183    # Unfortunately git-cherry-pick writes stuff to stderr even when
9184    # no error occurs, and exec takes that as an indication of error...
9185    if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
9186        notbusy cherrypick
9187        if {[regexp -line \
9188                 {Entry '(.*)' (would be overwritten by merge|not uptodate)} \
9189                 $err msg fname]} {
9190            error_popup [mc "Cherry-pick failed because of local changes\
9191                        to file '%s'.\nPlease commit, reset or stash\
9192                        your changes and try again." $fname]
9193        } elseif {[regexp -line \
9194                       {^(CONFLICT \(.*\):|Automatic cherry-pick failed|error: could not apply)} \
9195                       $err]} {
9196            if {[confirm_popup [mc "Cherry-pick failed because of merge\
9197                        conflict.\nDo you wish to run git citool to\
9198                        resolve it?"]]} {
9199                # Force citool to read MERGE_MSG
9200                file delete [file join $gitdir "GITGUI_MSG"]
9201                exec_citool {} $rowmenuid
9202            }
9203        } else {
9204            error_popup $err
9205        }
9206        run updatecommits
9207        return
9208    }
9209    set newhead [exec git rev-parse HEAD]
9210    if {$newhead eq $oldhead} {
9211        notbusy cherrypick
9212        error_popup [mc "No changes committed"]
9213        return
9214    }
9215    addnewchild $newhead $oldhead
9216    if {[commitinview $oldhead $curview]} {
9217        # XXX this isn't right if we have a path limit...
9218        insertrow $newhead $oldhead $curview
9219        if {$mainhead ne {}} {
9220            movehead $newhead $mainhead
9221            movedhead $newhead $mainhead
9222        }
9223        set mainheadid $newhead
9224        redrawtags $oldhead
9225        redrawtags $newhead
9226        selbyid $newhead
9227    }
9228    notbusy cherrypick
9229}
9230
9231proc resethead {} {
9232    global mainhead rowmenuid confirm_ok resettype NS
9233
9234    set confirm_ok 0
9235    set w ".confirmreset"
9236    ttk_toplevel $w
9237    make_transient $w .
9238    wm title $w [mc "Confirm reset"]
9239    ${NS}::label $w.m -text \
9240        [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]]
9241    pack $w.m -side top -fill x -padx 20 -pady 20
9242    ${NS}::labelframe $w.f -text [mc "Reset type:"]
9243    set resettype mixed
9244    ${NS}::radiobutton $w.f.soft -value soft -variable resettype \
9245        -text [mc "Soft: Leave working tree and index untouched"]
9246    grid $w.f.soft -sticky w
9247    ${NS}::radiobutton $w.f.mixed -value mixed -variable resettype \
9248        -text [mc "Mixed: Leave working tree untouched, reset index"]
9249    grid $w.f.mixed -sticky w
9250    ${NS}::radiobutton $w.f.hard -value hard -variable resettype \
9251        -text [mc "Hard: Reset working tree and index\n(discard ALL local changes)"]
9252    grid $w.f.hard -sticky w
9253    pack $w.f -side top -fill x -padx 4
9254    ${NS}::button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
9255    pack $w.ok -side left -fill x -padx 20 -pady 20
9256    ${NS}::button $w.cancel -text [mc Cancel] -command "destroy $w"
9257    bind $w <Key-Escape> [list destroy $w]
9258    pack $w.cancel -side right -fill x -padx 20 -pady 20
9259    bind $w <Visibility> "grab $w; focus $w"
9260    tkwait window $w
9261    if {!$confirm_ok} return
9262    if {[catch {set fd [open \
9263            [list | git reset --$resettype $rowmenuid 2>@1] r]} err]} {
9264        error_popup $err
9265    } else {
9266        dohidelocalchanges
9267        filerun $fd [list readresetstat $fd]
9268        nowbusy reset [mc "Resetting"]
9269        selbyid $rowmenuid
9270    }
9271}
9272
9273proc readresetstat {fd} {
9274    global mainhead mainheadid showlocalchanges rprogcoord
9275
9276    if {[gets $fd line] >= 0} {
9277        if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
9278            set rprogcoord [expr {1.0 * $m / $n}]
9279            adjustprogress
9280        }
9281        return 1
9282    }
9283    set rprogcoord 0
9284    adjustprogress
9285    notbusy reset
9286    if {[catch {close $fd} err]} {
9287        error_popup $err
9288    }
9289    set oldhead $mainheadid
9290    set newhead [exec git rev-parse HEAD]
9291    if {$newhead ne $oldhead} {
9292        movehead $newhead $mainhead
9293        movedhead $newhead $mainhead
9294        set mainheadid $newhead
9295        redrawtags $oldhead
9296        redrawtags $newhead
9297    }
9298    if {$showlocalchanges} {
9299        doshowlocalchanges
9300    }
9301    return 0
9302}
9303
9304# context menu for a head
9305proc headmenu {x y id head} {
9306    global headmenuid headmenuhead headctxmenu mainhead
9307
9308    stopfinding
9309    set headmenuid $id
9310    set headmenuhead $head
9311    set state normal
9312    if {[string match "remotes/*" $head]} {
9313        set state disabled
9314    }
9315    if {$head eq $mainhead} {
9316        set state disabled
9317    }
9318    $headctxmenu entryconfigure 0 -state $state
9319    $headctxmenu entryconfigure 1 -state $state
9320    tk_popup $headctxmenu $x $y
9321}
9322
9323proc cobranch {} {
9324    global headmenuid headmenuhead headids
9325    global showlocalchanges
9326
9327    # check the tree is clean first??
9328    nowbusy checkout [mc "Checking out"]
9329    update
9330    dohidelocalchanges
9331    if {[catch {
9332        set fd [open [list | git checkout $headmenuhead 2>@1] r]
9333    } err]} {
9334        notbusy checkout
9335        error_popup $err
9336        if {$showlocalchanges} {
9337            dodiffindex
9338        }
9339    } else {
9340        filerun $fd [list readcheckoutstat $fd $headmenuhead $headmenuid]
9341    }
9342}
9343
9344proc readcheckoutstat {fd newhead newheadid} {
9345    global mainhead mainheadid headids showlocalchanges progresscoords
9346    global viewmainheadid curview
9347
9348    if {[gets $fd line] >= 0} {
9349        if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
9350            set progresscoords [list 0 [expr {1.0 * $m / $n}]]
9351            adjustprogress
9352        }
9353        return 1
9354    }
9355    set progresscoords {0 0}
9356    adjustprogress
9357    notbusy checkout
9358    if {[catch {close $fd} err]} {
9359        error_popup $err
9360    }
9361    set oldmainid $mainheadid
9362    set mainhead $newhead
9363    set mainheadid $newheadid
9364    set viewmainheadid($curview) $newheadid
9365    redrawtags $oldmainid
9366    redrawtags $newheadid
9367    selbyid $newheadid
9368    if {$showlocalchanges} {
9369        dodiffindex
9370    }
9371}
9372
9373proc rmbranch {} {
9374    global headmenuid headmenuhead mainhead
9375    global idheads
9376
9377    set head $headmenuhead
9378    set id $headmenuid
9379    # this check shouldn't be needed any more...
9380    if {$head eq $mainhead} {
9381        error_popup [mc "Cannot delete the currently checked-out branch"]
9382        return
9383    }
9384    set dheads [descheads $id]
9385    if {[llength $dheads] == 1 && $idheads($dheads) eq $head} {
9386        # the stuff on this branch isn't on any other branch
9387        if {![confirm_popup [mc "The commits on branch %s aren't on any other\
9388                        branch.\nReally delete branch %s?" $head $head]]} return
9389    }
9390    nowbusy rmbranch
9391    update
9392    if {[catch {exec git branch -D $head} err]} {
9393        notbusy rmbranch
9394        error_popup $err
9395        return
9396    }
9397    removehead $id $head
9398    removedhead $id $head
9399    redrawtags $id
9400    notbusy rmbranch
9401    dispneartags 0
9402    run refill_reflist
9403}
9404
9405# Display a list of tags and heads
9406proc showrefs {} {
9407    global showrefstop bgcolor fgcolor selectbgcolor NS
9408    global bglist fglist reflistfilter reflist maincursor
9409
9410    set top .showrefs
9411    set showrefstop $top
9412    if {[winfo exists $top]} {
9413        raise $top
9414        refill_reflist
9415        return
9416    }
9417    ttk_toplevel $top
9418    wm title $top [mc "Tags and heads: %s" [file tail [pwd]]]
9419    make_transient $top .
9420    text $top.list -background $bgcolor -foreground $fgcolor \
9421        -selectbackground $selectbgcolor -font mainfont \
9422        -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
9423        -width 30 -height 20 -cursor $maincursor \
9424        -spacing1 1 -spacing3 1 -state disabled
9425    $top.list tag configure highlight -background $selectbgcolor
9426    lappend bglist $top.list
9427    lappend fglist $top.list
9428    ${NS}::scrollbar $top.ysb -command "$top.list yview" -orient vertical
9429    ${NS}::scrollbar $top.xsb -command "$top.list xview" -orient horizontal
9430    grid $top.list $top.ysb -sticky nsew
9431    grid $top.xsb x -sticky ew
9432    ${NS}::frame $top.f
9433    ${NS}::label $top.f.l -text "[mc "Filter"]: "
9434    ${NS}::entry $top.f.e -width 20 -textvariable reflistfilter
9435    set reflistfilter "*"
9436    trace add variable reflistfilter write reflistfilter_change
9437    pack $top.f.e -side right -fill x -expand 1
9438    pack $top.f.l -side left
9439    grid $top.f - -sticky ew -pady 2
9440    ${NS}::button $top.close -command [list destroy $top] -text [mc "Close"]
9441    bind $top <Key-Escape> [list destroy $top]
9442    grid $top.close -
9443    grid columnconfigure $top 0 -weight 1
9444    grid rowconfigure $top 0 -weight 1
9445    bind $top.list <1> {break}
9446    bind $top.list <B1-Motion> {break}
9447    bind $top.list <ButtonRelease-1> {sel_reflist %W %x %y; break}
9448    set reflist {}
9449    refill_reflist
9450}
9451
9452proc sel_reflist {w x y} {
9453    global showrefstop reflist headids tagids otherrefids
9454
9455    if {![winfo exists $showrefstop]} return
9456    set l [lindex [split [$w index "@$x,$y"] "."] 0]
9457    set ref [lindex $reflist [expr {$l-1}]]
9458    set n [lindex $ref 0]
9459    switch -- [lindex $ref 1] {
9460        "H" {selbyid $headids($n)}
9461        "T" {selbyid $tagids($n)}
9462        "o" {selbyid $otherrefids($n)}
9463    }
9464    $showrefstop.list tag add highlight $l.0 "$l.0 lineend"
9465}
9466
9467proc unsel_reflist {} {
9468    global showrefstop
9469
9470    if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
9471    $showrefstop.list tag remove highlight 0.0 end
9472}
9473
9474proc reflistfilter_change {n1 n2 op} {
9475    global reflistfilter
9476
9477    after cancel refill_reflist
9478    after 200 refill_reflist
9479}
9480
9481proc refill_reflist {} {
9482    global reflist reflistfilter showrefstop headids tagids otherrefids
9483    global curview
9484
9485    if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
9486    set refs {}
9487    foreach n [array names headids] {
9488        if {[string match $reflistfilter $n]} {
9489            if {[commitinview $headids($n) $curview]} {
9490                lappend refs [list $n H]
9491            } else {
9492                interestedin $headids($n) {run refill_reflist}
9493            }
9494        }
9495    }
9496    foreach n [array names tagids] {
9497        if {[string match $reflistfilter $n]} {
9498            if {[commitinview $tagids($n) $curview]} {
9499                lappend refs [list $n T]
9500            } else {
9501                interestedin $tagids($n) {run refill_reflist}
9502            }
9503        }
9504    }
9505    foreach n [array names otherrefids] {
9506        if {[string match $reflistfilter $n]} {
9507            if {[commitinview $otherrefids($n) $curview]} {
9508                lappend refs [list $n o]
9509            } else {
9510                interestedin $otherrefids($n) {run refill_reflist}
9511            }
9512        }
9513    }
9514    set refs [lsort -index 0 $refs]
9515    if {$refs eq $reflist} return
9516
9517    # Update the contents of $showrefstop.list according to the
9518    # differences between $reflist (old) and $refs (new)
9519    $showrefstop.list conf -state normal
9520    $showrefstop.list insert end "\n"
9521    set i 0
9522    set j 0
9523    while {$i < [llength $reflist] || $j < [llength $refs]} {
9524        if {$i < [llength $reflist]} {
9525            if {$j < [llength $refs]} {
9526                set cmp [string compare [lindex $reflist $i 0] \
9527                             [lindex $refs $j 0]]
9528                if {$cmp == 0} {
9529                    set cmp [string compare [lindex $reflist $i 1] \
9530                                 [lindex $refs $j 1]]
9531                }
9532            } else {
9533                set cmp -1
9534            }
9535        } else {
9536            set cmp 1
9537        }
9538        switch -- $cmp {
9539            -1 {
9540                $showrefstop.list delete "[expr {$j+1}].0" "[expr {$j+2}].0"
9541                incr i
9542            }
9543            0 {
9544                incr i
9545                incr j
9546            }
9547            1 {
9548                set l [expr {$j + 1}]
9549                $showrefstop.list image create $l.0 -align baseline \
9550                    -image reficon-[lindex $refs $j 1] -padx 2
9551                $showrefstop.list insert $l.1 "[lindex $refs $j 0]\n"
9552                incr j
9553            }
9554        }
9555    }
9556    set reflist $refs
9557    # delete last newline
9558    $showrefstop.list delete end-2c end-1c
9559    $showrefstop.list conf -state disabled
9560}
9561
9562# Stuff for finding nearby tags
9563proc getallcommits {} {
9564    global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate
9565    global idheads idtags idotherrefs allparents tagobjid
9566    global gitdir
9567
9568    if {![info exists allcommits]} {
9569        set nextarc 0
9570        set allcommits 0
9571        set seeds {}
9572        set allcwait 0
9573        set cachedarcs 0
9574        set allccache [file join $gitdir "gitk.cache"]
9575        if {![catch {
9576            set f [open $allccache r]
9577            set allcwait 1
9578            getcache $f
9579        }]} return
9580    }
9581
9582    if {$allcwait} {
9583        return
9584    }
9585    set cmd [list | git rev-list --parents]
9586    set allcupdate [expr {$seeds ne {}}]
9587    if {!$allcupdate} {
9588        set ids "--all"
9589    } else {
9590        set refs [concat [array names idheads] [array names idtags] \
9591                      [array names idotherrefs]]
9592        set ids {}
9593        set tagobjs {}
9594        foreach name [array names tagobjid] {
9595            lappend tagobjs $tagobjid($name)
9596        }
9597        foreach id [lsort -unique $refs] {
9598            if {![info exists allparents($id)] &&
9599                [lsearch -exact $tagobjs $id] < 0} {
9600                lappend ids $id
9601            }
9602        }
9603        if {$ids ne {}} {
9604            foreach id $seeds {
9605                lappend ids "^$id"
9606            }
9607        }
9608    }
9609    if {$ids ne {}} {
9610        set fd [open [concat $cmd $ids] r]
9611        fconfigure $fd -blocking 0
9612        incr allcommits
9613        nowbusy allcommits
9614        filerun $fd [list getallclines $fd]
9615    } else {
9616        dispneartags 0
9617    }
9618}
9619
9620# Since most commits have 1 parent and 1 child, we group strings of
9621# such commits into "arcs" joining branch/merge points (BMPs), which
9622# are commits that either don't have 1 parent or don't have 1 child.
9623#
9624# arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
9625# arcout(id) - outgoing arcs for BMP
9626# arcids(a) - list of IDs on arc including end but not start
9627# arcstart(a) - BMP ID at start of arc
9628# arcend(a) - BMP ID at end of arc
9629# growing(a) - arc a is still growing
9630# arctags(a) - IDs out of arcids (excluding end) that have tags
9631# archeads(a) - IDs out of arcids (excluding end) that have heads
9632# The start of an arc is at the descendent end, so "incoming" means
9633# coming from descendents, and "outgoing" means going towards ancestors.
9634
9635proc getallclines {fd} {
9636    global allparents allchildren idtags idheads nextarc
9637    global arcnos arcids arctags arcout arcend arcstart archeads growing
9638    global seeds allcommits cachedarcs allcupdate
9639
9640    set nid 0
9641    while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
9642        set id [lindex $line 0]
9643        if {[info exists allparents($id)]} {
9644            # seen it already
9645            continue
9646        }
9647        set cachedarcs 0
9648        set olds [lrange $line 1 end]
9649        set allparents($id) $olds
9650        if {![info exists allchildren($id)]} {
9651            set allchildren($id) {}
9652            set arcnos($id) {}
9653            lappend seeds $id
9654        } else {
9655            set a $arcnos($id)
9656            if {[llength $olds] == 1 && [llength $a] == 1} {
9657                lappend arcids($a) $id
9658                if {[info exists idtags($id)]} {
9659                    lappend arctags($a) $id
9660                }
9661                if {[info exists idheads($id)]} {
9662                    lappend archeads($a) $id
9663                }
9664                if {[info exists allparents($olds)]} {
9665                    # seen parent already
9666                    if {![info exists arcout($olds)]} {
9667                        splitarc $olds
9668                    }
9669                    lappend arcids($a) $olds
9670                    set arcend($a) $olds
9671                    unset growing($a)
9672                }
9673                lappend allchildren($olds) $id
9674                lappend arcnos($olds) $a
9675                continue
9676            }
9677        }
9678        foreach a $arcnos($id) {
9679            lappend arcids($a) $id
9680            set arcend($a) $id
9681            unset growing($a)
9682        }
9683
9684        set ao {}
9685        foreach p $olds {
9686            lappend allchildren($p) $id
9687            set a [incr nextarc]
9688            set arcstart($a) $id
9689            set archeads($a) {}
9690            set arctags($a) {}
9691            set archeads($a) {}
9692            set arcids($a) {}
9693            lappend ao $a
9694            set growing($a) 1
9695            if {[info exists allparents($p)]} {
9696                # seen it already, may need to make a new branch
9697                if {![info exists arcout($p)]} {
9698                    splitarc $p
9699                }
9700                lappend arcids($a) $p
9701                set arcend($a) $p
9702                unset growing($a)
9703            }
9704            lappend arcnos($p) $a
9705        }
9706        set arcout($id) $ao
9707    }
9708    if {$nid > 0} {
9709        global cached_dheads cached_dtags cached_atags
9710        catch {unset cached_dheads}
9711        catch {unset cached_dtags}
9712        catch {unset cached_atags}
9713    }
9714    if {![eof $fd]} {
9715        return [expr {$nid >= 1000? 2: 1}]
9716    }
9717    set cacheok 1
9718    if {[catch {
9719        fconfigure $fd -blocking 1
9720        close $fd
9721    } err]} {
9722        # got an error reading the list of commits
9723        # if we were updating, try rereading the whole thing again
9724        if {$allcupdate} {
9725            incr allcommits -1
9726            dropcache $err
9727            return
9728        }
9729        error_popup "[mc "Error reading commit topology information;\
9730                branch and preceding/following tag information\
9731                will be incomplete."]\n($err)"
9732        set cacheok 0
9733    }
9734    if {[incr allcommits -1] == 0} {
9735        notbusy allcommits
9736        if {$cacheok} {
9737            run savecache
9738        }
9739    }
9740    dispneartags 0
9741    return 0
9742}
9743
9744proc recalcarc {a} {
9745    global arctags archeads arcids idtags idheads
9746
9747    set at {}
9748    set ah {}
9749    foreach id [lrange $arcids($a) 0 end-1] {
9750        if {[info exists idtags($id)]} {
9751            lappend at $id
9752        }
9753        if {[info exists idheads($id)]} {
9754            lappend ah $id
9755        }
9756    }
9757    set arctags($a) $at
9758    set archeads($a) $ah
9759}
9760
9761proc splitarc {p} {
9762    global arcnos arcids nextarc arctags archeads idtags idheads
9763    global arcstart arcend arcout allparents growing
9764
9765    set a $arcnos($p)
9766    if {[llength $a] != 1} {
9767        puts "oops splitarc called but [llength $a] arcs already"
9768        return
9769    }
9770    set a [lindex $a 0]
9771    set i [lsearch -exact $arcids($a) $p]
9772    if {$i < 0} {
9773        puts "oops splitarc $p not in arc $a"
9774        return
9775    }
9776    set na [incr nextarc]
9777    if {[info exists arcend($a)]} {
9778        set arcend($na) $arcend($a)
9779    } else {
9780        set l [lindex $allparents([lindex $arcids($a) end]) 0]
9781        set j [lsearch -exact $arcnos($l) $a]
9782        set arcnos($l) [lreplace $arcnos($l) $j $j $na]
9783    }
9784    set tail [lrange $arcids($a) [expr {$i+1}] end]
9785    set arcids($a) [lrange $arcids($a) 0 $i]
9786    set arcend($a) $p
9787    set arcstart($na) $p
9788    set arcout($p) $na
9789    set arcids($na) $tail
9790    if {[info exists growing($a)]} {
9791        set growing($na) 1
9792        unset growing($a)
9793    }
9794
9795    foreach id $tail {
9796        if {[llength $arcnos($id)] == 1} {
9797            set arcnos($id) $na
9798        } else {
9799            set j [lsearch -exact $arcnos($id) $a]
9800            set arcnos($id) [lreplace $arcnos($id) $j $j $na]
9801        }
9802    }
9803
9804    # reconstruct tags and heads lists
9805    if {$arctags($a) ne {} || $archeads($a) ne {}} {
9806        recalcarc $a
9807        recalcarc $na
9808    } else {
9809        set arctags($na) {}
9810        set archeads($na) {}
9811    }
9812}
9813
9814# Update things for a new commit added that is a child of one
9815# existing commit.  Used when cherry-picking.
9816proc addnewchild {id p} {
9817    global allparents allchildren idtags nextarc
9818    global arcnos arcids arctags arcout arcend arcstart archeads growing
9819    global seeds allcommits
9820
9821    if {![info exists allcommits] || ![info exists arcnos($p)]} return
9822    set allparents($id) [list $p]
9823    set allchildren($id) {}
9824    set arcnos($id) {}
9825    lappend seeds $id
9826    lappend allchildren($p) $id
9827    set a [incr nextarc]
9828    set arcstart($a) $id
9829    set archeads($a) {}
9830    set arctags($a) {}
9831    set arcids($a) [list $p]
9832    set arcend($a) $p
9833    if {![info exists arcout($p)]} {
9834        splitarc $p
9835    }
9836    lappend arcnos($p) $a
9837    set arcout($id) [list $a]
9838}
9839
9840# This implements a cache for the topology information.
9841# The cache saves, for each arc, the start and end of the arc,
9842# the ids on the arc, and the outgoing arcs from the end.
9843proc readcache {f} {
9844    global arcnos arcids arcout arcstart arcend arctags archeads nextarc
9845    global idtags idheads allparents cachedarcs possible_seeds seeds growing
9846    global allcwait
9847
9848    set a $nextarc
9849    set lim $cachedarcs
9850    if {$lim - $a > 500} {
9851        set lim [expr {$a + 500}]
9852    }
9853    if {[catch {
9854        if {$a == $lim} {
9855            # finish reading the cache and setting up arctags, etc.
9856            set line [gets $f]
9857            if {$line ne "1"} {error "bad final version"}
9858            close $f
9859            foreach id [array names idtags] {
9860                if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
9861                    [llength $allparents($id)] == 1} {
9862                    set a [lindex $arcnos($id) 0]
9863                    if {$arctags($a) eq {}} {
9864                        recalcarc $a
9865                    }
9866                }
9867            }
9868            foreach id [array names idheads] {
9869                if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
9870                    [llength $allparents($id)] == 1} {
9871                    set a [lindex $arcnos($id) 0]
9872                    if {$archeads($a) eq {}} {
9873                        recalcarc $a
9874                    }
9875                }
9876            }
9877            foreach id [lsort -unique $possible_seeds] {
9878                if {$arcnos($id) eq {}} {
9879                    lappend seeds $id
9880                }
9881            }
9882            set allcwait 0
9883        } else {
9884            while {[incr a] <= $lim} {
9885                set line [gets $f]
9886                if {[llength $line] != 3} {error "bad line"}
9887                set s [lindex $line 0]
9888                set arcstart($a) $s
9889                lappend arcout($s) $a
9890                if {![info exists arcnos($s)]} {
9891                    lappend possible_seeds $s
9892                    set arcnos($s) {}
9893                }
9894                set e [lindex $line 1]
9895                if {$e eq {}} {
9896                    set growing($a) 1
9897                } else {
9898                    set arcend($a) $e
9899                    if {![info exists arcout($e)]} {
9900                        set arcout($e) {}
9901                    }
9902                }
9903                set arcids($a) [lindex $line 2]
9904                foreach id $arcids($a) {
9905                    lappend allparents($s) $id
9906                    set s $id
9907                    lappend arcnos($id) $a
9908                }
9909                if {![info exists allparents($s)]} {
9910                    set allparents($s) {}
9911                }
9912                set arctags($a) {}
9913                set archeads($a) {}
9914            }
9915            set nextarc [expr {$a - 1}]
9916        }
9917    } err]} {
9918        dropcache $err
9919        return 0
9920    }
9921    if {!$allcwait} {
9922        getallcommits
9923    }
9924    return $allcwait
9925}
9926
9927proc getcache {f} {
9928    global nextarc cachedarcs possible_seeds
9929
9930    if {[catch {
9931        set line [gets $f]
9932        if {[llength $line] != 2 || [lindex $line 0] ne "1"} {error "bad version"}
9933        # make sure it's an integer
9934        set cachedarcs [expr {int([lindex $line 1])}]
9935        if {$cachedarcs < 0} {error "bad number of arcs"}
9936        set nextarc 0
9937        set possible_seeds {}
9938        run readcache $f
9939    } err]} {
9940        dropcache $err
9941    }
9942    return 0
9943}
9944
9945proc dropcache {err} {
9946    global allcwait nextarc cachedarcs seeds
9947
9948    #puts "dropping cache ($err)"
9949    foreach v {arcnos arcout arcids arcstart arcend growing \
9950                   arctags archeads allparents allchildren} {
9951        global $v
9952        catch {unset $v}
9953    }
9954    set allcwait 0
9955    set nextarc 0
9956    set cachedarcs 0
9957    set seeds {}
9958    getallcommits
9959}
9960
9961proc writecache {f} {
9962    global cachearc cachedarcs allccache
9963    global arcstart arcend arcnos arcids arcout
9964
9965    set a $cachearc
9966    set lim $cachedarcs
9967    if {$lim - $a > 1000} {
9968        set lim [expr {$a + 1000}]
9969    }
9970    if {[catch {
9971        while {[incr a] <= $lim} {
9972            if {[info exists arcend($a)]} {
9973                puts $f [list $arcstart($a) $arcend($a) $arcids($a)]
9974            } else {
9975                puts $f [list $arcstart($a) {} $arcids($a)]
9976            }
9977        }
9978    } err]} {
9979        catch {close $f}
9980        catch {file delete $allccache}
9981        #puts "writing cache failed ($err)"
9982        return 0
9983    }
9984    set cachearc [expr {$a - 1}]
9985    if {$a > $cachedarcs} {
9986        puts $f "1"
9987        close $f
9988        return 0
9989    }
9990    return 1
9991}
9992
9993proc savecache {} {
9994    global nextarc cachedarcs cachearc allccache
9995
9996    if {$nextarc == $cachedarcs} return
9997    set cachearc 0
9998    set cachedarcs $nextarc
9999    catch {
10000        set f [open $allccache w]
10001        puts $f [list 1 $cachedarcs]
10002        run writecache $f
10003    }
10004}
10005
10006# Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
10007# or 0 if neither is true.
10008proc anc_or_desc {a b} {
10009    global arcout arcstart arcend arcnos cached_isanc
10010
10011    if {$arcnos($a) eq $arcnos($b)} {
10012        # Both are on the same arc(s); either both are the same BMP,
10013        # or if one is not a BMP, the other is also not a BMP or is
10014        # the BMP at end of the arc (and it only has 1 incoming arc).
10015        # Or both can be BMPs with no incoming arcs.
10016        if {$a eq $b || $arcnos($a) eq {}} {
10017            return 0
10018        }
10019        # assert {[llength $arcnos($a)] == 1}
10020        set arc [lindex $arcnos($a) 0]
10021        set i [lsearch -exact $arcids($arc) $a]
10022        set j [lsearch -exact $arcids($arc) $b]
10023        if {$i < 0 || $i > $j} {
10024            return 1
10025        } else {
10026            return -1
10027        }
10028    }
10029
10030    if {![info exists arcout($a)]} {
10031        set arc [lindex $arcnos($a) 0]
10032        if {[info exists arcend($arc)]} {
10033            set aend $arcend($arc)
10034        } else {
10035            set aend {}
10036        }
10037        set a $arcstart($arc)
10038    } else {
10039        set aend $a
10040    }
10041    if {![info exists arcout($b)]} {
10042        set arc [lindex $arcnos($b) 0]
10043        if {[info exists arcend($arc)]} {
10044            set bend $arcend($arc)
10045        } else {
10046            set bend {}
10047        }
10048        set b $arcstart($arc)
10049    } else {
10050        set bend $b
10051    }
10052    if {$a eq $bend} {
10053        return 1
10054    }
10055    if {$b eq $aend} {
10056        return -1
10057    }
10058    if {[info exists cached_isanc($a,$bend)]} {
10059        if {$cached_isanc($a,$bend)} {
10060            return 1
10061        }
10062    }
10063    if {[info exists cached_isanc($b,$aend)]} {
10064        if {$cached_isanc($b,$aend)} {
10065            return -1
10066        }
10067        if {[info exists cached_isanc($a,$bend)]} {
10068            return 0
10069        }
10070    }
10071
10072    set todo [list $a $b]
10073    set anc($a) a
10074    set anc($b) b
10075    for {set i 0} {$i < [llength $todo]} {incr i} {
10076        set x [lindex $todo $i]
10077        if {$anc($x) eq {}} {
10078            continue
10079        }
10080        foreach arc $arcnos($x) {
10081            set xd $arcstart($arc)
10082            if {$xd eq $bend} {
10083                set cached_isanc($a,$bend) 1
10084                set cached_isanc($b,$aend) 0
10085                return 1
10086            } elseif {$xd eq $aend} {
10087                set cached_isanc($b,$aend) 1
10088                set cached_isanc($a,$bend) 0
10089                return -1
10090            }
10091            if {![info exists anc($xd)]} {
10092                set anc($xd) $anc($x)
10093                lappend todo $xd
10094            } elseif {$anc($xd) ne $anc($x)} {
10095                set anc($xd) {}
10096            }
10097        }
10098    }
10099    set cached_isanc($a,$bend) 0
10100    set cached_isanc($b,$aend) 0
10101    return 0
10102}
10103
10104# This identifies whether $desc has an ancestor that is
10105# a growing tip of the graph and which is not an ancestor of $anc
10106# and returns 0 if so and 1 if not.
10107# If we subsequently discover a tag on such a growing tip, and that
10108# turns out to be a descendent of $anc (which it could, since we
10109# don't necessarily see children before parents), then $desc
10110# isn't a good choice to display as a descendent tag of
10111# $anc (since it is the descendent of another tag which is
10112# a descendent of $anc).  Similarly, $anc isn't a good choice to
10113# display as a ancestor tag of $desc.
10114#
10115proc is_certain {desc anc} {
10116    global arcnos arcout arcstart arcend growing problems
10117
10118    set certain {}
10119    if {[llength $arcnos($anc)] == 1} {
10120        # tags on the same arc are certain
10121        if {$arcnos($desc) eq $arcnos($anc)} {
10122            return 1
10123        }
10124        if {![info exists arcout($anc)]} {
10125            # if $anc is partway along an arc, use the start of the arc instead
10126            set a [lindex $arcnos($anc) 0]
10127            set anc $arcstart($a)
10128        }
10129    }
10130    if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
10131        set x $desc
10132    } else {
10133        set a [lindex $arcnos($desc) 0]
10134        set x $arcend($a)
10135    }
10136    if {$x == $anc} {
10137        return 1
10138    }
10139    set anclist [list $x]
10140    set dl($x) 1
10141    set nnh 1
10142    set ngrowanc 0
10143    for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
10144        set x [lindex $anclist $i]
10145        if {$dl($x)} {
10146            incr nnh -1
10147        }
10148        set done($x) 1
10149        foreach a $arcout($x) {
10150            if {[info exists growing($a)]} {
10151                if {![info exists growanc($x)] && $dl($x)} {
10152                    set growanc($x) 1
10153                    incr ngrowanc
10154                }
10155            } else {
10156                set y $arcend($a)
10157                if {[info exists dl($y)]} {
10158                    if {$dl($y)} {
10159                        if {!$dl($x)} {
10160                            set dl($y) 0
10161                            if {![info exists done($y)]} {
10162                                incr nnh -1
10163                            }
10164                            if {[info exists growanc($x)]} {
10165                                incr ngrowanc -1
10166                            }
10167                            set xl [list $y]
10168                            for {set k 0} {$k < [llength $xl]} {incr k} {
10169                                set z [lindex $xl $k]
10170                                foreach c $arcout($z) {
10171                                    if {[info exists arcend($c)]} {
10172                                        set v $arcend($c)
10173                                        if {[info exists dl($v)] && $dl($v)} {
10174                                            set dl($v) 0
10175                                            if {![info exists done($v)]} {
10176                                                incr nnh -1
10177                                            }
10178                                            if {[info exists growanc($v)]} {
10179                                                incr ngrowanc -1
10180                                            }
10181                                            lappend xl $v
10182                                        }
10183                                    }
10184                                }
10185                            }
10186                        }
10187                    }
10188                } elseif {$y eq $anc || !$dl($x)} {
10189                    set dl($y) 0
10190                    lappend anclist $y
10191                } else {
10192                    set dl($y) 1
10193                    lappend anclist $y
10194                    incr nnh
10195                }
10196            }
10197        }
10198    }
10199    foreach x [array names growanc] {
10200        if {$dl($x)} {
10201            return 0
10202        }
10203        return 0
10204    }
10205    return 1
10206}
10207
10208proc validate_arctags {a} {
10209    global arctags idtags
10210
10211    set i -1
10212    set na $arctags($a)
10213    foreach id $arctags($a) {
10214        incr i
10215        if {![info exists idtags($id)]} {
10216            set na [lreplace $na $i $i]
10217            incr i -1
10218        }
10219    }
10220    set arctags($a) $na
10221}
10222
10223proc validate_archeads {a} {
10224    global archeads idheads
10225
10226    set i -1
10227    set na $archeads($a)
10228    foreach id $archeads($a) {
10229        incr i
10230        if {![info exists idheads($id)]} {
10231            set na [lreplace $na $i $i]
10232            incr i -1
10233        }
10234    }
10235    set archeads($a) $na
10236}
10237
10238# Return the list of IDs that have tags that are descendents of id,
10239# ignoring IDs that are descendents of IDs already reported.
10240proc desctags {id} {
10241    global arcnos arcstart arcids arctags idtags allparents
10242    global growing cached_dtags
10243
10244    if {![info exists allparents($id)]} {
10245        return {}
10246    }
10247    set t1 [clock clicks -milliseconds]
10248    set argid $id
10249    if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
10250        # part-way along an arc; check that arc first
10251        set a [lindex $arcnos($id) 0]
10252        if {$arctags($a) ne {}} {
10253            validate_arctags $a
10254            set i [lsearch -exact $arcids($a) $id]
10255            set tid {}
10256            foreach t $arctags($a) {
10257                set j [lsearch -exact $arcids($a) $t]
10258                if {$j >= $i} break
10259                set tid $t
10260            }
10261            if {$tid ne {}} {
10262                return $tid
10263            }
10264        }
10265        set id $arcstart($a)
10266        if {[info exists idtags($id)]} {
10267            return $id
10268        }
10269    }
10270    if {[info exists cached_dtags($id)]} {
10271        return $cached_dtags($id)
10272    }
10273
10274    set origid $id
10275    set todo [list $id]
10276    set queued($id) 1
10277    set nc 1
10278    for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
10279        set id [lindex $todo $i]
10280        set done($id) 1
10281        set ta [info exists hastaggedancestor($id)]
10282        if {!$ta} {
10283            incr nc -1
10284        }
10285        # ignore tags on starting node
10286        if {!$ta && $i > 0} {
10287            if {[info exists idtags($id)]} {
10288                set tagloc($id) $id
10289                set ta 1
10290            } elseif {[info exists cached_dtags($id)]} {
10291                set tagloc($id) $cached_dtags($id)
10292                set ta 1
10293            }
10294        }
10295        foreach a $arcnos($id) {
10296            set d $arcstart($a)
10297            if {!$ta && $arctags($a) ne {}} {
10298                validate_arctags $a
10299                if {$arctags($a) ne {}} {
10300                    lappend tagloc($id) [lindex $arctags($a) end]
10301                }
10302            }
10303            if {$ta || $arctags($a) ne {}} {
10304                set tomark [list $d]
10305                for {set j 0} {$j < [llength $tomark]} {incr j} {
10306                    set dd [lindex $tomark $j]
10307                    if {![info exists hastaggedancestor($dd)]} {
10308                        if {[info exists done($dd)]} {
10309                            foreach b $arcnos($dd) {
10310                                lappend tomark $arcstart($b)
10311                            }
10312                            if {[info exists tagloc($dd)]} {
10313                                unset tagloc($dd)
10314                            }
10315                        } elseif {[info exists queued($dd)]} {
10316                            incr nc -1
10317                        }
10318                        set hastaggedancestor($dd) 1
10319                    }
10320                }
10321            }
10322            if {![info exists queued($d)]} {
10323                lappend todo $d
10324                set queued($d) 1
10325                if {![info exists hastaggedancestor($d)]} {
10326                    incr nc
10327                }
10328            }
10329        }
10330    }
10331    set tags {}
10332    foreach id [array names tagloc] {
10333        if {![info exists hastaggedancestor($id)]} {
10334            foreach t $tagloc($id) {
10335                if {[lsearch -exact $tags $t] < 0} {
10336                    lappend tags $t
10337                }
10338            }
10339        }
10340    }
10341    set t2 [clock clicks -milliseconds]
10342    set loopix $i
10343
10344    # remove tags that are descendents of other tags
10345    for {set i 0} {$i < [llength $tags]} {incr i} {
10346        set a [lindex $tags $i]
10347        for {set j 0} {$j < $i} {incr j} {
10348            set b [lindex $tags $j]
10349            set r [anc_or_desc $a $b]
10350            if {$r == 1} {
10351                set tags [lreplace $tags $j $j]
10352                incr j -1
10353                incr i -1
10354            } elseif {$r == -1} {
10355                set tags [lreplace $tags $i $i]
10356                incr i -1
10357                break
10358            }
10359        }
10360    }
10361
10362    if {[array names growing] ne {}} {
10363        # graph isn't finished, need to check if any tag could get
10364        # eclipsed by another tag coming later.  Simply ignore any
10365        # tags that could later get eclipsed.
10366        set ctags {}
10367        foreach t $tags {
10368            if {[is_certain $t $origid]} {
10369                lappend ctags $t
10370            }
10371        }
10372        if {$tags eq $ctags} {
10373            set cached_dtags($origid) $tags
10374        } else {
10375            set tags $ctags
10376        }
10377    } else {
10378        set cached_dtags($origid) $tags
10379    }
10380    set t3 [clock clicks -milliseconds]
10381    if {0 && $t3 - $t1 >= 100} {
10382        puts "iterating descendents ($loopix/[llength $todo] nodes) took\
10383            [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
10384    }
10385    return $tags
10386}
10387
10388proc anctags {id} {
10389    global arcnos arcids arcout arcend arctags idtags allparents
10390    global growing cached_atags
10391
10392    if {![info exists allparents($id)]} {
10393        return {}
10394    }
10395    set t1 [clock clicks -milliseconds]
10396    set argid $id
10397    if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
10398        # part-way along an arc; check that arc first
10399        set a [lindex $arcnos($id) 0]
10400        if {$arctags($a) ne {}} {
10401            validate_arctags $a
10402            set i [lsearch -exact $arcids($a) $id]
10403            foreach t $arctags($a) {
10404                set j [lsearch -exact $arcids($a) $t]
10405                if {$j > $i} {
10406                    return $t
10407                }
10408            }
10409        }
10410        if {![info exists arcend($a)]} {
10411            return {}
10412        }
10413        set id $arcend($a)
10414        if {[info exists idtags($id)]} {
10415            return $id
10416        }
10417    }
10418    if {[info exists cached_atags($id)]} {
10419        return $cached_atags($id)
10420    }
10421
10422    set origid $id
10423    set todo [list $id]
10424    set queued($id) 1
10425    set taglist {}
10426    set nc 1
10427    for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
10428        set id [lindex $todo $i]
10429        set done($id) 1
10430        set td [info exists hastaggeddescendent($id)]
10431        if {!$td} {
10432            incr nc -1
10433        }
10434        # ignore tags on starting node
10435        if {!$td && $i > 0} {
10436            if {[info exists idtags($id)]} {
10437                set tagloc($id) $id
10438                set td 1
10439            } elseif {[info exists cached_atags($id)]} {
10440                set tagloc($id) $cached_atags($id)
10441                set td 1
10442            }
10443        }
10444        foreach a $arcout($id) {
10445            if {!$td && $arctags($a) ne {}} {
10446                validate_arctags $a
10447                if {$arctags($a) ne {}} {
10448                    lappend tagloc($id) [lindex $arctags($a) 0]
10449                }
10450            }
10451            if {![info exists arcend($a)]} continue
10452            set d $arcend($a)
10453            if {$td || $arctags($a) ne {}} {
10454                set tomark [list $d]
10455                for {set j 0} {$j < [llength $tomark]} {incr j} {
10456                    set dd [lindex $tomark $j]
10457                    if {![info exists hastaggeddescendent($dd)]} {
10458                        if {[info exists done($dd)]} {
10459                            foreach b $arcout($dd) {
10460                                if {[info exists arcend($b)]} {
10461                                    lappend tomark $arcend($b)
10462                                }
10463                            }
10464                            if {[info exists tagloc($dd)]} {
10465                                unset tagloc($dd)
10466                            }
10467                        } elseif {[info exists queued($dd)]} {
10468                            incr nc -1
10469                        }
10470                        set hastaggeddescendent($dd) 1
10471                    }
10472                }
10473            }
10474            if {![info exists queued($d)]} {
10475                lappend todo $d
10476                set queued($d) 1
10477                if {![info exists hastaggeddescendent($d)]} {
10478                    incr nc
10479                }
10480            }
10481        }
10482    }
10483    set t2 [clock clicks -milliseconds]
10484    set loopix $i
10485    set tags {}
10486    foreach id [array names tagloc] {
10487        if {![info exists hastaggeddescendent($id)]} {
10488            foreach t $tagloc($id) {
10489                if {[lsearch -exact $tags $t] < 0} {
10490                    lappend tags $t
10491                }
10492            }
10493        }
10494    }
10495
10496    # remove tags that are ancestors of other tags
10497    for {set i 0} {$i < [llength $tags]} {incr i} {
10498        set a [lindex $tags $i]
10499        for {set j 0} {$j < $i} {incr j} {
10500            set b [lindex $tags $j]
10501            set r [anc_or_desc $a $b]
10502            if {$r == -1} {
10503                set tags [lreplace $tags $j $j]
10504                incr j -1
10505                incr i -1
10506            } elseif {$r == 1} {
10507                set tags [lreplace $tags $i $i]
10508                incr i -1
10509                break
10510            }
10511        }
10512    }
10513
10514    if {[array names growing] ne {}} {
10515        # graph isn't finished, need to check if any tag could get
10516        # eclipsed by another tag coming later.  Simply ignore any
10517        # tags that could later get eclipsed.
10518        set ctags {}
10519        foreach t $tags {
10520            if {[is_certain $origid $t]} {
10521                lappend ctags $t
10522            }
10523        }
10524        if {$tags eq $ctags} {
10525            set cached_atags($origid) $tags
10526        } else {
10527            set tags $ctags
10528        }
10529    } else {
10530        set cached_atags($origid) $tags
10531    }
10532    set t3 [clock clicks -milliseconds]
10533    if {0 && $t3 - $t1 >= 100} {
10534        puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
10535            [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
10536    }
10537    return $tags
10538}
10539
10540# Return the list of IDs that have heads that are descendents of id,
10541# including id itself if it has a head.
10542proc descheads {id} {
10543    global arcnos arcstart arcids archeads idheads cached_dheads
10544    global allparents
10545
10546    if {![info exists allparents($id)]} {
10547        return {}
10548    }
10549    set aret {}
10550    if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
10551        # part-way along an arc; check it first
10552        set a [lindex $arcnos($id) 0]
10553        if {$archeads($a) ne {}} {
10554            validate_archeads $a
10555            set i [lsearch -exact $arcids($a) $id]
10556            foreach t $archeads($a) {
10557                set j [lsearch -exact $arcids($a) $t]
10558                if {$j > $i} break
10559                lappend aret $t
10560            }
10561        }
10562        set id $arcstart($a)
10563    }
10564    set origid $id
10565    set todo [list $id]
10566    set seen($id) 1
10567    set ret {}
10568    for {set i 0} {$i < [llength $todo]} {incr i} {
10569        set id [lindex $todo $i]
10570        if {[info exists cached_dheads($id)]} {
10571            set ret [concat $ret $cached_dheads($id)]
10572        } else {
10573            if {[info exists idheads($id)]} {
10574                lappend ret $id
10575            }
10576            foreach a $arcnos($id) {
10577                if {$archeads($a) ne {}} {
10578                    validate_archeads $a
10579                    if {$archeads($a) ne {}} {
10580                        set ret [concat $ret $archeads($a)]
10581                    }
10582                }
10583                set d $arcstart($a)
10584                if {![info exists seen($d)]} {
10585                    lappend todo $d
10586                    set seen($d) 1
10587                }
10588            }
10589        }
10590    }
10591    set ret [lsort -unique $ret]
10592    set cached_dheads($origid) $ret
10593    return [concat $ret $aret]
10594}
10595
10596proc addedtag {id} {
10597    global arcnos arcout cached_dtags cached_atags
10598
10599    if {![info exists arcnos($id)]} return
10600    if {![info exists arcout($id)]} {
10601        recalcarc [lindex $arcnos($id) 0]
10602    }
10603    catch {unset cached_dtags}
10604    catch {unset cached_atags}
10605}
10606
10607proc addedhead {hid head} {
10608    global arcnos arcout cached_dheads
10609
10610    if {![info exists arcnos($hid)]} return
10611    if {![info exists arcout($hid)]} {
10612        recalcarc [lindex $arcnos($hid) 0]
10613    }
10614    catch {unset cached_dheads}
10615}
10616
10617proc removedhead {hid head} {
10618    global cached_dheads
10619
10620    catch {unset cached_dheads}
10621}
10622
10623proc movedhead {hid head} {
10624    global arcnos arcout cached_dheads
10625
10626    if {![info exists arcnos($hid)]} return
10627    if {![info exists arcout($hid)]} {
10628        recalcarc [lindex $arcnos($hid) 0]
10629    }
10630    catch {unset cached_dheads}
10631}
10632
10633proc changedrefs {} {
10634    global cached_dheads cached_dtags cached_atags cached_tagcontent
10635    global arctags archeads arcnos arcout idheads idtags
10636
10637    foreach id [concat [array names idheads] [array names idtags]] {
10638        if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
10639            set a [lindex $arcnos($id) 0]
10640            if {![info exists donearc($a)]} {
10641                recalcarc $a
10642                set donearc($a) 1
10643            }
10644        }
10645    }
10646    catch {unset cached_tagcontent}
10647    catch {unset cached_dtags}
10648    catch {unset cached_atags}
10649    catch {unset cached_dheads}
10650}
10651
10652proc rereadrefs {} {
10653    global idtags idheads idotherrefs mainheadid
10654
10655    set refids [concat [array names idtags] \
10656                    [array names idheads] [array names idotherrefs]]
10657    foreach id $refids {
10658        if {![info exists ref($id)]} {
10659            set ref($id) [listrefs $id]
10660        }
10661    }
10662    set oldmainhead $mainheadid
10663    readrefs
10664    changedrefs
10665    set refids [lsort -unique [concat $refids [array names idtags] \
10666                        [array names idheads] [array names idotherrefs]]]
10667    foreach id $refids {
10668        set v [listrefs $id]
10669        if {![info exists ref($id)] || $ref($id) != $v} {
10670            redrawtags $id
10671        }
10672    }
10673    if {$oldmainhead ne $mainheadid} {
10674        redrawtags $oldmainhead
10675        redrawtags $mainheadid
10676    }
10677    run refill_reflist
10678}
10679
10680proc listrefs {id} {
10681    global idtags idheads idotherrefs
10682
10683    set x {}
10684    if {[info exists idtags($id)]} {
10685        set x $idtags($id)
10686    }
10687    set y {}
10688    if {[info exists idheads($id)]} {
10689        set y $idheads($id)
10690    }
10691    set z {}
10692    if {[info exists idotherrefs($id)]} {
10693        set z $idotherrefs($id)
10694    }
10695    return [list $x $y $z]
10696}
10697
10698proc showtag {tag isnew} {
10699    global ctext cached_tagcontent tagids linknum tagobjid
10700
10701    if {$isnew} {
10702        addtohistory [list showtag $tag 0] savectextpos
10703    }
10704    $ctext conf -state normal
10705    clear_ctext
10706    settabs 0
10707    set linknum 0
10708    if {![info exists cached_tagcontent($tag)]} {
10709        catch {
10710           set cached_tagcontent($tag) [exec git cat-file tag $tag]
10711        }
10712    }
10713    if {[info exists cached_tagcontent($tag)]} {
10714        set text $cached_tagcontent($tag)
10715    } else {
10716        set text "[mc "Tag"]: $tag\n[mc "Id"]:  $tagids($tag)"
10717    }
10718    appendwithlinks $text {}
10719    maybe_scroll_ctext 1
10720    $ctext conf -state disabled
10721    init_flist {}
10722}
10723
10724proc doquit {} {
10725    global stopped
10726    global gitktmpdir
10727
10728    set stopped 100
10729    savestuff .
10730    destroy .
10731
10732    if {[info exists gitktmpdir]} {
10733        catch {file delete -force $gitktmpdir}
10734    }
10735}
10736
10737proc mkfontdisp {font top which} {
10738    global fontattr fontpref $font NS use_ttk
10739
10740    set fontpref($font) [set $font]
10741    ${NS}::button $top.${font}but -text $which \
10742        -command [list choosefont $font $which]
10743    ${NS}::label $top.$font -relief flat -font $font \
10744        -text $fontattr($font,family) -justify left
10745    grid x $top.${font}but $top.$font -sticky w
10746}
10747
10748proc choosefont {font which} {
10749    global fontparam fontlist fonttop fontattr
10750    global prefstop NS
10751
10752    set fontparam(which) $which
10753    set fontparam(font) $font
10754    set fontparam(family) [font actual $font -family]
10755    set fontparam(size) $fontattr($font,size)
10756    set fontparam(weight) $fontattr($font,weight)
10757    set fontparam(slant) $fontattr($font,slant)
10758    set top .gitkfont
10759    set fonttop $top
10760    if {![winfo exists $top]} {
10761        font create sample
10762        eval font config sample [font actual $font]
10763        ttk_toplevel $top
10764        make_transient $top $prefstop
10765        wm title $top [mc "Gitk font chooser"]
10766        ${NS}::label $top.l -textvariable fontparam(which)
10767        pack $top.l -side top
10768        set fontlist [lsort [font families]]
10769        ${NS}::frame $top.f
10770        listbox $top.f.fam -listvariable fontlist \
10771            -yscrollcommand [list $top.f.sb set]
10772        bind $top.f.fam <<ListboxSelect>> selfontfam
10773        ${NS}::scrollbar $top.f.sb -command [list $top.f.fam yview]
10774        pack $top.f.sb -side right -fill y
10775        pack $top.f.fam -side left -fill both -expand 1
10776        pack $top.f -side top -fill both -expand 1
10777        ${NS}::frame $top.g
10778        spinbox $top.g.size -from 4 -to 40 -width 4 \
10779            -textvariable fontparam(size) \
10780            -validatecommand {string is integer -strict %s}
10781        checkbutton $top.g.bold -padx 5 \
10782            -font {{Times New Roman} 12 bold} -text [mc "B"] -indicatoron 0 \
10783            -variable fontparam(weight) -onvalue bold -offvalue normal
10784        checkbutton $top.g.ital -padx 5 \
10785            -font {{Times New Roman} 12 italic} -text [mc "I"] -indicatoron 0  \
10786            -variable fontparam(slant) -onvalue italic -offvalue roman
10787        pack $top.g.size $top.g.bold $top.g.ital -side left
10788        pack $top.g -side top
10789        canvas $top.c -width 150 -height 50 -border 2 -relief sunk \
10790            -background white
10791        $top.c create text 100 25 -anchor center -text $which -font sample \
10792            -fill black -tags text
10793        bind $top.c <Configure> [list centertext $top.c]
10794        pack $top.c -side top -fill x
10795        ${NS}::frame $top.buts
10796        ${NS}::button $top.buts.ok -text [mc "OK"] -command fontok -default active
10797        ${NS}::button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal
10798        bind $top <Key-Return> fontok
10799        bind $top <Key-Escape> fontcan
10800        grid $top.buts.ok $top.buts.can
10801        grid columnconfigure $top.buts 0 -weight 1 -uniform a
10802        grid columnconfigure $top.buts 1 -weight 1 -uniform a
10803        pack $top.buts -side bottom -fill x
10804        trace add variable fontparam write chg_fontparam
10805    } else {
10806        raise $top
10807        $top.c itemconf text -text $which
10808    }
10809    set i [lsearch -exact $fontlist $fontparam(family)]
10810    if {$i >= 0} {
10811        $top.f.fam selection set $i
10812        $top.f.fam see $i
10813    }
10814}
10815
10816proc centertext {w} {
10817    $w coords text [expr {[winfo width $w] / 2}] [expr {[winfo height $w] / 2}]
10818}
10819
10820proc fontok {} {
10821    global fontparam fontpref prefstop
10822
10823    set f $fontparam(font)
10824    set fontpref($f) [list $fontparam(family) $fontparam(size)]
10825    if {$fontparam(weight) eq "bold"} {
10826        lappend fontpref($f) "bold"
10827    }
10828    if {$fontparam(slant) eq "italic"} {
10829        lappend fontpref($f) "italic"
10830    }
10831    set w $prefstop.notebook.fonts.$f
10832    $w conf -text $fontparam(family) -font $fontpref($f)
10833
10834    fontcan
10835}
10836
10837proc fontcan {} {
10838    global fonttop fontparam
10839
10840    if {[info exists fonttop]} {
10841        catch {destroy $fonttop}
10842        catch {font delete sample}
10843        unset fonttop
10844        unset fontparam
10845    }
10846}
10847
10848if {[package vsatisfies [package provide Tk] 8.6]} {
10849    # In Tk 8.6 we have a native font chooser dialog. Overwrite the above
10850    # function to make use of it.
10851    proc choosefont {font which} {
10852        tk fontchooser configure -title $which -font $font \
10853            -command [list on_choosefont $font $which]
10854        tk fontchooser show
10855    }
10856    proc on_choosefont {font which newfont} {
10857        global fontparam
10858        puts stderr "$font $newfont"
10859        array set f [font actual $newfont]
10860        set fontparam(which) $which
10861        set fontparam(font) $font
10862        set fontparam(family) $f(-family)
10863        set fontparam(size) $f(-size)
10864        set fontparam(weight) $f(-weight)
10865        set fontparam(slant) $f(-slant)
10866        fontok
10867    }
10868}
10869
10870proc selfontfam {} {
10871    global fonttop fontparam
10872
10873    set i [$fonttop.f.fam curselection]
10874    if {$i ne {}} {
10875        set fontparam(family) [$fonttop.f.fam get $i]
10876    }
10877}
10878
10879proc chg_fontparam {v sub op} {
10880    global fontparam
10881
10882    font config sample -$sub $fontparam($sub)
10883}
10884
10885# Create a property sheet tab page
10886proc create_prefs_page {w} {
10887    global NS
10888    set parent [join [lrange [split $w .] 0 end-1] .]
10889    if {[winfo class $parent] eq "TNotebook"} {
10890        ${NS}::frame $w
10891    } else {
10892        ${NS}::labelframe $w
10893    }
10894}
10895
10896proc prefspage_general {notebook} {
10897    global NS maxwidth maxgraphpct showneartags showlocalchanges
10898    global tabstop limitdiffs autoselect autosellen extdifftool perfile_attrs
10899    global hideremotes want_ttk have_ttk
10900
10901    set page [create_prefs_page $notebook.general]
10902
10903    ${NS}::label $page.ldisp -text [mc "Commit list display options"]
10904    grid $page.ldisp - -sticky w -pady 10
10905    ${NS}::label $page.spacer -text " "
10906    ${NS}::label $page.maxwidthl -text [mc "Maximum graph width (lines)"]
10907    spinbox $page.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
10908    grid $page.spacer $page.maxwidthl $page.maxwidth -sticky w
10909    ${NS}::label $page.maxpctl -text [mc "Maximum graph width (% of pane)"]
10910    spinbox $page.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
10911    grid x $page.maxpctl $page.maxpct -sticky w
10912    ${NS}::checkbutton $page.showlocal -text [mc "Show local changes"] \
10913        -variable showlocalchanges
10914    grid x $page.showlocal -sticky w
10915    ${NS}::checkbutton $page.autoselect -text [mc "Auto-select SHA1 (length)"] \
10916        -variable autoselect
10917    spinbox $page.autosellen -from 1 -to 40 -width 4 -textvariable autosellen
10918    grid x $page.autoselect $page.autosellen -sticky w
10919    ${NS}::checkbutton $page.hideremotes -text [mc "Hide remote refs"] \
10920        -variable hideremotes
10921    grid x $page.hideremotes -sticky w
10922
10923    ${NS}::label $page.ddisp -text [mc "Diff display options"]
10924    grid $page.ddisp - -sticky w -pady 10
10925    ${NS}::label $page.tabstopl -text [mc "Tab spacing"]
10926    spinbox $page.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
10927    grid x $page.tabstopl $page.tabstop -sticky w
10928    ${NS}::checkbutton $page.ntag -text [mc "Display nearby tags"] \
10929        -variable showneartags
10930    grid x $page.ntag -sticky w
10931    ${NS}::checkbutton $page.ldiff -text [mc "Limit diffs to listed paths"] \
10932        -variable limitdiffs
10933    grid x $page.ldiff -sticky w
10934    ${NS}::checkbutton $page.lattr -text [mc "Support per-file encodings"] \
10935        -variable perfile_attrs
10936    grid x $page.lattr -sticky w
10937
10938    ${NS}::entry $page.extdifft -textvariable extdifftool
10939    ${NS}::frame $page.extdifff
10940    ${NS}::label $page.extdifff.l -text [mc "External diff tool" ]
10941    ${NS}::button $page.extdifff.b -text [mc "Choose..."] -command choose_extdiff
10942    pack $page.extdifff.l $page.extdifff.b -side left
10943    pack configure $page.extdifff.l -padx 10
10944    grid x $page.extdifff $page.extdifft -sticky ew
10945
10946    ${NS}::label $page.lgen -text [mc "General options"]
10947    grid $page.lgen - -sticky w -pady 10
10948    ${NS}::checkbutton $page.want_ttk -variable want_ttk \
10949        -text [mc "Use themed widgets"]
10950    if {$have_ttk} {
10951        ${NS}::label $page.ttk_note -text [mc "(change requires restart)"]
10952    } else {
10953        ${NS}::label $page.ttk_note -text [mc "(currently unavailable)"]
10954    }
10955    grid x $page.want_ttk $page.ttk_note -sticky w
10956    return $page
10957}
10958
10959proc prefspage_colors {notebook} {
10960    global NS uicolor bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor
10961
10962    set page [create_prefs_page $notebook.colors]
10963
10964    ${NS}::label $page.cdisp -text [mc "Colors: press to choose"]
10965    grid $page.cdisp - -sticky w -pady 10
10966    label $page.ui -padx 40 -relief sunk -background $uicolor
10967    ${NS}::button $page.uibut -text [mc "Interface"] \
10968       -command [list choosecolor uicolor {} $page.ui [mc "interface"] setui]
10969    grid x $page.uibut $page.ui -sticky w
10970    label $page.bg -padx 40 -relief sunk -background $bgcolor
10971    ${NS}::button $page.bgbut -text [mc "Background"] \
10972        -command [list choosecolor bgcolor {} $page.bg [mc "background"] setbg]
10973    grid x $page.bgbut $page.bg -sticky w
10974    label $page.fg -padx 40 -relief sunk -background $fgcolor
10975    ${NS}::button $page.fgbut -text [mc "Foreground"] \
10976        -command [list choosecolor fgcolor {} $page.fg [mc "foreground"] setfg]
10977    grid x $page.fgbut $page.fg -sticky w
10978    label $page.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
10979    ${NS}::button $page.diffoldbut -text [mc "Diff: old lines"] \
10980        -command [list choosecolor diffcolors 0 $page.diffold [mc "diff old lines"] \
10981                      [list $ctext tag conf d0 -foreground]]
10982    grid x $page.diffoldbut $page.diffold -sticky w
10983    label $page.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
10984    ${NS}::button $page.diffnewbut -text [mc "Diff: new lines"] \
10985        -command [list choosecolor diffcolors 1 $page.diffnew [mc "diff new lines"] \
10986                      [list $ctext tag conf dresult -foreground]]
10987    grid x $page.diffnewbut $page.diffnew -sticky w
10988    label $page.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
10989    ${NS}::button $page.hunksepbut -text [mc "Diff: hunk header"] \
10990        -command [list choosecolor diffcolors 2 $page.hunksep \
10991                      [mc "diff hunk header"] \
10992                      [list $ctext tag conf hunksep -foreground]]
10993    grid x $page.hunksepbut $page.hunksep -sticky w
10994    label $page.markbgsep -padx 40 -relief sunk -background $markbgcolor
10995    ${NS}::button $page.markbgbut -text [mc "Marked line bg"] \
10996        -command [list choosecolor markbgcolor {} $page.markbgsep \
10997                      [mc "marked line background"] \
10998                      [list $ctext tag conf omark -background]]
10999    grid x $page.markbgbut $page.markbgsep -sticky w
11000    label $page.selbgsep -padx 40 -relief sunk -background $selectbgcolor
11001    ${NS}::button $page.selbgbut -text [mc "Select bg"] \
11002        -command [list choosecolor selectbgcolor {} $page.selbgsep [mc "background"] setselbg]
11003    grid x $page.selbgbut $page.selbgsep -sticky w
11004    return $page
11005}
11006
11007proc prefspage_fonts {notebook} {
11008    global NS
11009    set page [create_prefs_page $notebook.fonts]
11010    ${NS}::label $page.cfont -text [mc "Fonts: press to choose"]
11011    grid $page.cfont - -sticky w -pady 10
11012    mkfontdisp mainfont $page [mc "Main font"]
11013    mkfontdisp textfont $page [mc "Diff display font"]
11014    mkfontdisp uifont $page [mc "User interface font"]
11015    return $page
11016}
11017
11018proc doprefs {} {
11019    global maxwidth maxgraphpct use_ttk NS
11020    global oldprefs prefstop showneartags showlocalchanges
11021    global uicolor bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor
11022    global tabstop limitdiffs autoselect autosellen extdifftool perfile_attrs
11023    global hideremotes want_ttk have_ttk
11024
11025    set top .gitkprefs
11026    set prefstop $top
11027    if {[winfo exists $top]} {
11028        raise $top
11029        return
11030    }
11031    foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
11032                   limitdiffs tabstop perfile_attrs hideremotes want_ttk} {
11033        set oldprefs($v) [set $v]
11034    }
11035    ttk_toplevel $top
11036    wm title $top [mc "Gitk preferences"]
11037    make_transient $top .
11038
11039    if {[set use_notebook [expr {$use_ttk && [info command ::ttk::notebook] ne ""}]]} {
11040        set notebook [ttk::notebook $top.notebook]
11041    } else {
11042        set notebook [${NS}::frame $top.notebook -borderwidth 0 -relief flat]
11043    }
11044
11045    lappend pages [prefspage_general $notebook] [mc "General"]
11046    lappend pages [prefspage_colors $notebook] [mc "Colors"]
11047    lappend pages [prefspage_fonts $notebook] [mc "Fonts"]
11048    set col 0
11049    foreach {page title} $pages {
11050        if {$use_notebook} {
11051            $notebook add $page -text $title
11052        } else {
11053            set btn [${NS}::button $notebook.b_[string map {. X} $page] \
11054                         -text $title -command [list raise $page]]
11055            $page configure -text $title
11056            grid $btn -row 0 -column [incr col] -sticky w
11057            grid $page -row 1 -column 0 -sticky news -columnspan 100
11058        }
11059    }
11060
11061    if {!$use_notebook} {
11062        grid columnconfigure $notebook 0 -weight 1
11063        grid rowconfigure $notebook 1 -weight 1
11064        raise [lindex $pages 0]
11065    }
11066
11067    grid $notebook -sticky news -padx 2 -pady 2
11068    grid rowconfigure $top 0 -weight 1
11069    grid columnconfigure $top 0 -weight 1
11070
11071    ${NS}::frame $top.buts
11072    ${NS}::button $top.buts.ok -text [mc "OK"] -command prefsok -default active
11073    ${NS}::button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal
11074    bind $top <Key-Return> prefsok
11075    bind $top <Key-Escape> prefscan
11076    grid $top.buts.ok $top.buts.can
11077    grid columnconfigure $top.buts 0 -weight 1 -uniform a
11078    grid columnconfigure $top.buts 1 -weight 1 -uniform a
11079    grid $top.buts - - -pady 10 -sticky ew
11080    grid columnconfigure $top 2 -weight 1
11081    bind $top <Visibility> [list focus $top.buts.ok]
11082}
11083
11084proc choose_extdiff {} {
11085    global extdifftool
11086
11087    set prog [tk_getOpenFile -title [mc "External diff tool"] -multiple false]
11088    if {$prog ne {}} {
11089        set extdifftool $prog
11090    }
11091}
11092
11093proc choosecolor {v vi w x cmd} {
11094    global $v
11095
11096    set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
11097               -title [mc "Gitk: choose color for %s" $x]]
11098    if {$c eq {}} return
11099    $w conf -background $c
11100    lset $v $vi $c
11101    eval $cmd $c
11102}
11103
11104proc setselbg {c} {
11105    global bglist cflist
11106    foreach w $bglist {
11107        $w configure -selectbackground $c
11108    }
11109    $cflist tag configure highlight \
11110        -background [$cflist cget -selectbackground]
11111    allcanvs itemconf secsel -fill $c
11112}
11113
11114# This sets the background color and the color scheme for the whole UI.
11115# For some reason, tk_setPalette chooses a nasty dark red for selectColor
11116# if we don't specify one ourselves, which makes the checkbuttons and
11117# radiobuttons look bad.  This chooses white for selectColor if the
11118# background color is light, or black if it is dark.
11119proc setui {c} {
11120    if {[tk windowingsystem] eq "win32"} { return }
11121    set bg [winfo rgb . $c]
11122    set selc black
11123    if {[lindex $bg 0] + 1.5 * [lindex $bg 1] + 0.5 * [lindex $bg 2] > 100000} {
11124        set selc white
11125    }
11126    tk_setPalette background $c selectColor $selc
11127}
11128
11129proc setbg {c} {
11130    global bglist
11131
11132    foreach w $bglist {
11133        $w conf -background $c
11134    }
11135}
11136
11137proc setfg {c} {
11138    global fglist canv
11139
11140    foreach w $fglist {
11141        $w conf -foreground $c
11142    }
11143    allcanvs itemconf text -fill $c
11144    $canv itemconf circle -outline $c
11145    $canv itemconf markid -outline $c
11146}
11147
11148proc prefscan {} {
11149    global oldprefs prefstop
11150
11151    foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
11152                   limitdiffs tabstop perfile_attrs hideremotes want_ttk} {
11153        global $v
11154        set $v $oldprefs($v)
11155    }
11156    catch {destroy $prefstop}
11157    unset prefstop
11158    fontcan
11159}
11160
11161proc prefsok {} {
11162    global maxwidth maxgraphpct
11163    global oldprefs prefstop showneartags showlocalchanges
11164    global fontpref mainfont textfont uifont
11165    global limitdiffs treediffs perfile_attrs
11166    global hideremotes
11167
11168    catch {destroy $prefstop}
11169    unset prefstop
11170    fontcan
11171    set fontchanged 0
11172    if {$mainfont ne $fontpref(mainfont)} {
11173        set mainfont $fontpref(mainfont)
11174        parsefont mainfont $mainfont
11175        eval font configure mainfont [fontflags mainfont]
11176        eval font configure mainfontbold [fontflags mainfont 1]
11177        setcoords
11178        set fontchanged 1
11179    }
11180    if {$textfont ne $fontpref(textfont)} {
11181        set textfont $fontpref(textfont)
11182        parsefont textfont $textfont
11183        eval font configure textfont [fontflags textfont]
11184        eval font configure textfontbold [fontflags textfont 1]
11185    }
11186    if {$uifont ne $fontpref(uifont)} {
11187        set uifont $fontpref(uifont)
11188        parsefont uifont $uifont
11189        eval font configure uifont [fontflags uifont]
11190    }
11191    settabs
11192    if {$showlocalchanges != $oldprefs(showlocalchanges)} {
11193        if {$showlocalchanges} {
11194            doshowlocalchanges
11195        } else {
11196            dohidelocalchanges
11197        }
11198    }
11199    if {$limitdiffs != $oldprefs(limitdiffs) ||
11200        ($perfile_attrs && !$oldprefs(perfile_attrs))} {
11201        # treediffs elements are limited by path;
11202        # won't have encodings cached if perfile_attrs was just turned on
11203        catch {unset treediffs}
11204    }
11205    if {$fontchanged || $maxwidth != $oldprefs(maxwidth)
11206        || $maxgraphpct != $oldprefs(maxgraphpct)} {
11207        redisplay
11208    } elseif {$showneartags != $oldprefs(showneartags) ||
11209          $limitdiffs != $oldprefs(limitdiffs)} {
11210        reselectline
11211    }
11212    if {$hideremotes != $oldprefs(hideremotes)} {
11213        rereadrefs
11214    }
11215}
11216
11217proc formatdate {d} {
11218    global datetimeformat
11219    if {$d ne {}} {
11220        set d [clock format [lindex $d 0] -format $datetimeformat]
11221    }
11222    return $d
11223}
11224
11225# This list of encoding names and aliases is distilled from
11226# http://www.iana.org/assignments/character-sets.
11227# Not all of them are supported by Tcl.
11228set encoding_aliases {
11229    { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
11230      ISO646-US US-ASCII us IBM367 cp367 csASCII }
11231    { ISO-10646-UTF-1 csISO10646UTF1 }
11232    { ISO_646.basic:1983 ref csISO646basic1983 }
11233    { INVARIANT csINVARIANT }
11234    { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
11235    { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
11236    { NATS-SEFI iso-ir-8-1 csNATSSEFI }
11237    { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
11238    { NATS-DANO iso-ir-9-1 csNATSDANO }
11239    { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
11240    { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
11241    { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
11242    { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
11243    { ISO-2022-KR csISO2022KR }
11244    { EUC-KR csEUCKR }
11245    { ISO-2022-JP csISO2022JP }
11246    { ISO-2022-JP-2 csISO2022JP2 }
11247    { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
11248      csISO13JISC6220jp }
11249    { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
11250    { IT iso-ir-15 ISO646-IT csISO15Italian }
11251    { PT iso-ir-16 ISO646-PT csISO16Portuguese }
11252    { ES iso-ir-17 ISO646-ES csISO17Spanish }
11253    { greek7-old iso-ir-18 csISO18Greek7Old }
11254    { latin-greek iso-ir-19 csISO19LatinGreek }
11255    { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
11256    { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
11257    { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
11258    { ISO_5427 iso-ir-37 csISO5427Cyrillic }
11259    { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
11260    { BS_viewdata iso-ir-47 csISO47BSViewdata }
11261    { INIS iso-ir-49 csISO49INIS }
11262    { INIS-8 iso-ir-50 csISO50INIS8 }
11263    { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
11264    { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
11265    { ISO_5428:1980 iso-ir-55 csISO5428Greek }
11266    { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
11267    { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
11268    { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
11269      csISO60Norwegian1 }
11270    { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
11271    { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
11272    { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
11273    { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
11274    { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
11275    { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
11276    { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
11277    { greek7 iso-ir-88 csISO88Greek7 }
11278    { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
11279    { iso-ir-90 csISO90 }
11280    { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
11281    { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
11282      csISO92JISC62991984b }
11283    { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
11284    { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
11285    { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
11286      csISO95JIS62291984handadd }
11287    { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
11288    { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
11289    { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
11290    { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
11291      CP819 csISOLatin1 }
11292    { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
11293    { T.61-7bit iso-ir-102 csISO102T617bit }
11294    { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
11295    { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
11296    { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
11297    { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
11298    { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
11299    { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
11300    { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
11301    { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
11302      arabic csISOLatinArabic }
11303    { ISO_8859-6-E csISO88596E ISO-8859-6-E }
11304    { ISO_8859-6-I csISO88596I ISO-8859-6-I }
11305    { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
11306      greek greek8 csISOLatinGreek }
11307    { T.101-G2 iso-ir-128 csISO128T101G2 }
11308    { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
11309      csISOLatinHebrew }
11310    { ISO_8859-8-E csISO88598E ISO-8859-8-E }
11311    { ISO_8859-8-I csISO88598I ISO-8859-8-I }
11312    { CSN_369103 iso-ir-139 csISO139CSN369103 }
11313    { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
11314    { ISO_6937-2-add iso-ir-142 csISOTextComm }
11315    { IEC_P27-1 iso-ir-143 csISO143IECP271 }
11316    { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
11317      csISOLatinCyrillic }
11318    { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
11319    { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
11320    { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
11321    { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
11322    { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
11323    { ISO_6937-2-25 iso-ir-152 csISO6937Add }
11324    { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
11325    { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
11326    { ISO_10367-box iso-ir-155 csISO10367Box }
11327    { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
11328    { latin-lap lap iso-ir-158 csISO158Lap }
11329    { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
11330    { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
11331    { us-dk csUSDK }
11332    { dk-us csDKUS }
11333    { JIS_X0201 X0201 csHalfWidthKatakana }
11334    { KSC5636 ISO646-KR csKSC5636 }
11335    { ISO-10646-UCS-2 csUnicode }
11336    { ISO-10646-UCS-4 csUCS4 }
11337    { DEC-MCS dec csDECMCS }
11338    { hp-roman8 roman8 r8 csHPRoman8 }
11339    { macintosh mac csMacintosh }
11340    { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
11341      csIBM037 }
11342    { IBM038 EBCDIC-INT cp038 csIBM038 }
11343    { IBM273 CP273 csIBM273 }
11344    { IBM274 EBCDIC-BE CP274 csIBM274 }
11345    { IBM275 EBCDIC-BR cp275 csIBM275 }
11346    { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
11347    { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
11348    { IBM280 CP280 ebcdic-cp-it csIBM280 }
11349    { IBM281 EBCDIC-JP-E cp281 csIBM281 }
11350    { IBM284 CP284 ebcdic-cp-es csIBM284 }
11351    { IBM285 CP285 ebcdic-cp-gb csIBM285 }
11352    { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
11353    { IBM297 cp297 ebcdic-cp-fr csIBM297 }
11354    { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
11355    { IBM423 cp423 ebcdic-cp-gr csIBM423 }
11356    { IBM424 cp424 ebcdic-cp-he csIBM424 }
11357    { IBM437 cp437 437 csPC8CodePage437 }
11358    { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
11359    { IBM775 cp775 csPC775Baltic }
11360    { IBM850 cp850 850 csPC850Multilingual }
11361    { IBM851 cp851 851 csIBM851 }
11362    { IBM852 cp852 852 csPCp852 }
11363    { IBM855 cp855 855 csIBM855 }
11364    { IBM857 cp857 857 csIBM857 }
11365    { IBM860 cp860 860 csIBM860 }
11366    { IBM861 cp861 861 cp-is csIBM861 }
11367    { IBM862 cp862 862 csPC862LatinHebrew }
11368    { IBM863 cp863 863 csIBM863 }
11369    { IBM864 cp864 csIBM864 }
11370    { IBM865 cp865 865 csIBM865 }
11371    { IBM866 cp866 866 csIBM866 }
11372    { IBM868 CP868 cp-ar csIBM868 }
11373    { IBM869 cp869 869 cp-gr csIBM869 }
11374    { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
11375    { IBM871 CP871 ebcdic-cp-is csIBM871 }
11376    { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
11377    { IBM891 cp891 csIBM891 }
11378    { IBM903 cp903 csIBM903 }
11379    { IBM904 cp904 904 csIBBM904 }
11380    { IBM905 CP905 ebcdic-cp-tr csIBM905 }
11381    { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
11382    { IBM1026 CP1026 csIBM1026 }
11383    { EBCDIC-AT-DE csIBMEBCDICATDE }
11384    { EBCDIC-AT-DE-A csEBCDICATDEA }
11385    { EBCDIC-CA-FR csEBCDICCAFR }
11386    { EBCDIC-DK-NO csEBCDICDKNO }
11387    { EBCDIC-DK-NO-A csEBCDICDKNOA }
11388    { EBCDIC-FI-SE csEBCDICFISE }
11389    { EBCDIC-FI-SE-A csEBCDICFISEA }
11390    { EBCDIC-FR csEBCDICFR }
11391    { EBCDIC-IT csEBCDICIT }
11392    { EBCDIC-PT csEBCDICPT }
11393    { EBCDIC-ES csEBCDICES }
11394    { EBCDIC-ES-A csEBCDICESA }
11395    { EBCDIC-ES-S csEBCDICESS }
11396    { EBCDIC-UK csEBCDICUK }
11397    { EBCDIC-US csEBCDICUS }
11398    { UNKNOWN-8BIT csUnknown8BiT }
11399    { MNEMONIC csMnemonic }
11400    { MNEM csMnem }
11401    { VISCII csVISCII }
11402    { VIQR csVIQR }
11403    { KOI8-R csKOI8R }
11404    { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
11405    { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
11406    { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
11407    { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
11408    { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
11409    { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
11410    { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
11411    { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
11412    { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
11413    { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
11414    { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
11415    { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
11416    { IBM1047 IBM-1047 }
11417    { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
11418    { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
11419    { UNICODE-1-1 csUnicode11 }
11420    { CESU-8 csCESU-8 }
11421    { BOCU-1 csBOCU-1 }
11422    { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
11423    { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
11424      l8 }
11425    { ISO-8859-15 ISO_8859-15 Latin-9 }
11426    { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
11427    { GBK CP936 MS936 windows-936 }
11428    { JIS_Encoding csJISEncoding }
11429    { Shift_JIS MS_Kanji csShiftJIS ShiftJIS Shift-JIS }
11430    { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
11431      EUC-JP }
11432    { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
11433    { ISO-10646-UCS-Basic csUnicodeASCII }
11434    { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
11435    { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
11436    { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
11437    { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
11438    { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
11439    { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
11440    { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
11441    { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
11442    { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
11443    { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
11444    { Adobe-Standard-Encoding csAdobeStandardEncoding }
11445    { Ventura-US csVenturaUS }
11446    { Ventura-International csVenturaInternational }
11447    { PC8-Danish-Norwegian csPC8DanishNorwegian }
11448    { PC8-Turkish csPC8Turkish }
11449    { IBM-Symbols csIBMSymbols }
11450    { IBM-Thai csIBMThai }
11451    { HP-Legal csHPLegal }
11452    { HP-Pi-font csHPPiFont }
11453    { HP-Math8 csHPMath8 }
11454    { Adobe-Symbol-Encoding csHPPSMath }
11455    { HP-DeskTop csHPDesktop }
11456    { Ventura-Math csVenturaMath }
11457    { Microsoft-Publishing csMicrosoftPublishing }
11458    { Windows-31J csWindows31J }
11459    { GB2312 csGB2312 }
11460    { Big5 csBig5 }
11461}
11462
11463proc tcl_encoding {enc} {
11464    global encoding_aliases tcl_encoding_cache
11465    if {[info exists tcl_encoding_cache($enc)]} {
11466        return $tcl_encoding_cache($enc)
11467    }
11468    set names [encoding names]
11469    set lcnames [string tolower $names]
11470    set enc [string tolower $enc]
11471    set i [lsearch -exact $lcnames $enc]
11472    if {$i < 0} {
11473        # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
11474        if {[regsub {^(iso|cp|ibm|jis)[-_]} $enc {\1} encx]} {
11475            set i [lsearch -exact $lcnames $encx]
11476        }
11477    }
11478    if {$i < 0} {
11479        foreach l $encoding_aliases {
11480            set ll [string tolower $l]
11481            if {[lsearch -exact $ll $enc] < 0} continue
11482            # look through the aliases for one that tcl knows about
11483            foreach e $ll {
11484                set i [lsearch -exact $lcnames $e]
11485                if {$i < 0} {
11486                    if {[regsub {^(iso|cp|ibm|jis)[-_]} $e {\1} ex]} {
11487                        set i [lsearch -exact $lcnames $ex]
11488                    }
11489                }
11490                if {$i >= 0} break
11491            }
11492            break
11493        }
11494    }
11495    set tclenc {}
11496    if {$i >= 0} {
11497        set tclenc [lindex $names $i]
11498    }
11499    set tcl_encoding_cache($enc) $tclenc
11500    return $tclenc
11501}
11502
11503proc gitattr {path attr default} {
11504    global path_attr_cache
11505    if {[info exists path_attr_cache($attr,$path)]} {
11506        set r $path_attr_cache($attr,$path)
11507    } else {
11508        set r "unspecified"
11509        if {![catch {set line [exec git check-attr $attr -- $path]}]} {
11510            regexp "(.*): $attr: (.*)" $line m f r
11511        }
11512        set path_attr_cache($attr,$path) $r
11513    }
11514    if {$r eq "unspecified"} {
11515        return $default
11516    }
11517    return $r
11518}
11519
11520proc cache_gitattr {attr pathlist} {
11521    global path_attr_cache
11522    set newlist {}
11523    foreach path $pathlist {
11524        if {![info exists path_attr_cache($attr,$path)]} {
11525            lappend newlist $path
11526        }
11527    }
11528    set lim 1000
11529    if {[tk windowingsystem] == "win32"} {
11530        # windows has a 32k limit on the arguments to a command...
11531        set lim 30
11532    }
11533    while {$newlist ne {}} {
11534        set head [lrange $newlist 0 [expr {$lim - 1}]]
11535        set newlist [lrange $newlist $lim end]
11536        if {![catch {set rlist [eval exec git check-attr $attr -- $head]}]} {
11537            foreach row [split $rlist "\n"] {
11538                if {[regexp "(.*): $attr: (.*)" $row m path value]} {
11539                    if {[string index $path 0] eq "\""} {
11540                        set path [encoding convertfrom [lindex $path 0]]
11541                    }
11542                    set path_attr_cache($attr,$path) $value
11543                }
11544            }
11545        }
11546    }
11547}
11548
11549proc get_path_encoding {path} {
11550    global gui_encoding perfile_attrs
11551    set tcl_enc $gui_encoding
11552    if {$path ne {} && $perfile_attrs} {
11553        set enc2 [tcl_encoding [gitattr $path encoding $tcl_enc]]
11554        if {$enc2 ne {}} {
11555            set tcl_enc $enc2
11556        }
11557    }
11558    return $tcl_enc
11559}
11560
11561# First check that Tcl/Tk is recent enough
11562if {[catch {package require Tk 8.4} err]} {
11563    show_error {} . "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
11564                     Gitk requires at least Tcl/Tk 8.4." list
11565    exit 1
11566}
11567
11568# Unset GIT_TRACE var if set
11569if { [info exists ::env(GIT_TRACE)] } {
11570    unset ::env(GIT_TRACE)
11571}
11572
11573# defaults...
11574set wrcomcmd "git diff-tree --stdin -p --pretty"
11575
11576set gitencoding {}
11577catch {
11578    set gitencoding [exec git config --get i18n.commitencoding]
11579}
11580catch {
11581    set gitencoding [exec git config --get i18n.logoutputencoding]
11582}
11583if {$gitencoding == ""} {
11584    set gitencoding "utf-8"
11585}
11586set tclencoding [tcl_encoding $gitencoding]
11587if {$tclencoding == {}} {
11588    puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
11589}
11590
11591set gui_encoding [encoding system]
11592catch {
11593    set enc [exec git config --get gui.encoding]
11594    if {$enc ne {}} {
11595        set tclenc [tcl_encoding $enc]
11596        if {$tclenc ne {}} {
11597            set gui_encoding $tclenc
11598        } else {
11599            puts stderr "Warning: encoding $enc is not supported by Tcl/Tk"
11600        }
11601    }
11602}
11603
11604set log_showroot true
11605catch {
11606    set log_showroot [exec git config --bool --get log.showroot]
11607}
11608
11609if {[tk windowingsystem] eq "aqua"} {
11610    set mainfont {{Lucida Grande} 9}
11611    set textfont {Monaco 9}
11612    set uifont {{Lucida Grande} 9 bold}
11613} elseif {![catch {::tk::pkgconfig get fontsystem} xft] && $xft eq "xft"} {
11614    # fontconfig!
11615    set mainfont {sans 9}
11616    set textfont {monospace 9}
11617    set uifont {sans 9 bold}
11618} else {
11619    set mainfont {Helvetica 9}
11620    set textfont {Courier 9}
11621    set uifont {Helvetica 9 bold}
11622}
11623set tabstop 8
11624set findmergefiles 0
11625set maxgraphpct 50
11626set maxwidth 16
11627set revlistorder 0
11628set fastdate 0
11629set uparrowlen 5
11630set downarrowlen 5
11631set mingaplen 100
11632set cmitmode "patch"
11633set wrapcomment "none"
11634set showneartags 1
11635set hideremotes 0
11636set maxrefs 20
11637set maxlinelen 200
11638set showlocalchanges 1
11639set limitdiffs 1
11640set datetimeformat "%Y-%m-%d %H:%M:%S"
11641set autoselect 1
11642set autosellen 40
11643set perfile_attrs 0
11644set want_ttk 1
11645
11646if {[tk windowingsystem] eq "aqua"} {
11647    set extdifftool "opendiff"
11648} else {
11649    set extdifftool "meld"
11650}
11651
11652set colors {green red blue magenta darkgrey brown orange}
11653if {[tk windowingsystem] eq "win32"} {
11654    set uicolor SystemButtonFace
11655    set bgcolor SystemWindow
11656    set fgcolor SystemButtonText
11657    set selectbgcolor SystemHighlight
11658} else {
11659    set uicolor grey85
11660    set bgcolor white
11661    set fgcolor black
11662    set selectbgcolor gray85
11663}
11664set diffcolors {red "#00a000" blue}
11665set diffcontext 3
11666set ignorespace 0
11667set worddiff ""
11668set markbgcolor "#e0e0ff"
11669
11670set circlecolors {white blue gray blue blue}
11671
11672# button for popping up context menus
11673if {[tk windowingsystem] eq "aqua"} {
11674    set ctxbut <Button-2>
11675} else {
11676    set ctxbut <Button-3>
11677}
11678
11679## For msgcat loading, first locate the installation location.
11680if { [info exists ::env(GITK_MSGSDIR)] } {
11681    ## Msgsdir was manually set in the environment.
11682    set gitk_msgsdir $::env(GITK_MSGSDIR)
11683} else {
11684    ## Let's guess the prefix from argv0.
11685    set gitk_prefix [file dirname [file dirname [file normalize $argv0]]]
11686    set gitk_libdir [file join $gitk_prefix share gitk lib]
11687    set gitk_msgsdir [file join $gitk_libdir msgs]
11688    unset gitk_prefix
11689}
11690
11691## Internationalization (i18n) through msgcat and gettext. See
11692## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
11693package require msgcat
11694namespace import ::msgcat::mc
11695## And eventually load the actual message catalog
11696::msgcat::mcload $gitk_msgsdir
11697
11698catch {source ~/.gitk}
11699
11700parsefont mainfont $mainfont
11701eval font create mainfont [fontflags mainfont]
11702eval font create mainfontbold [fontflags mainfont 1]
11703
11704parsefont textfont $textfont
11705eval font create textfont [fontflags textfont]
11706eval font create textfontbold [fontflags textfont 1]
11707
11708parsefont uifont $uifont
11709eval font create uifont [fontflags uifont]
11710
11711setui $uicolor
11712
11713setoptions
11714
11715# check that we can find a .git directory somewhere...
11716if {[catch {set gitdir [exec git rev-parse --git-dir]}]} {
11717    show_error {} . [mc "Cannot find a git repository here."]
11718    exit 1
11719}
11720
11721set selecthead {}
11722set selectheadid {}
11723
11724set revtreeargs {}
11725set cmdline_files {}
11726set i 0
11727set revtreeargscmd {}
11728foreach arg $argv {
11729    switch -glob -- $arg {
11730        "" { }
11731        "--" {
11732            set cmdline_files [lrange $argv [expr {$i + 1}] end]
11733            break
11734        }
11735        "--select-commit=*" {
11736            set selecthead [string range $arg 16 end]
11737        }
11738        "--argscmd=*" {
11739            set revtreeargscmd [string range $arg 10 end]
11740        }
11741        default {
11742            lappend revtreeargs $arg
11743        }
11744    }
11745    incr i
11746}
11747
11748if {$selecthead eq "HEAD"} {
11749    set selecthead {}
11750}
11751
11752if {$i >= [llength $argv] && $revtreeargs ne {}} {
11753    # no -- on command line, but some arguments (other than --argscmd)
11754    if {[catch {
11755        set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
11756        set cmdline_files [split $f "\n"]
11757        set n [llength $cmdline_files]
11758        set revtreeargs [lrange $revtreeargs 0 end-$n]
11759        # Unfortunately git rev-parse doesn't produce an error when
11760        # something is both a revision and a filename.  To be consistent
11761        # with git log and git rev-list, check revtreeargs for filenames.
11762        foreach arg $revtreeargs {
11763            if {[file exists $arg]} {
11764                show_error {} . [mc "Ambiguous argument '%s': both revision\
11765                                 and filename" $arg]
11766                exit 1
11767            }
11768        }
11769    } err]} {
11770        # unfortunately we get both stdout and stderr in $err,
11771        # so look for "fatal:".
11772        set i [string first "fatal:" $err]
11773        if {$i > 0} {
11774            set err [string range $err [expr {$i + 6}] end]
11775        }
11776        show_error {} . "[mc "Bad arguments to gitk:"]\n$err"
11777        exit 1
11778    }
11779}
11780
11781set nullid "0000000000000000000000000000000000000000"
11782set nullid2 "0000000000000000000000000000000000000001"
11783set nullfile "/dev/null"
11784
11785set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
11786if {![info exists have_ttk]} {
11787    set have_ttk [llength [info commands ::ttk::style]]
11788}
11789set use_ttk [expr {$have_ttk && $want_ttk}]
11790set NS [expr {$use_ttk ? "ttk" : ""}]
11791
11792regexp {^git version ([\d.]*\d)} [exec git version] _ git_version
11793
11794set show_notes {}
11795if {[package vcompare $git_version "1.6.6.2"] >= 0} {
11796    set show_notes "--show-notes"
11797}
11798
11799set appname "gitk"
11800
11801set runq {}
11802set history {}
11803set historyindex 0
11804set fh_serial 0
11805set nhl_names {}
11806set highlight_paths {}
11807set findpattern {}
11808set searchdirn -forwards
11809set boldids {}
11810set boldnameids {}
11811set diffelide {0 0}
11812set markingmatches 0
11813set linkentercount 0
11814set need_redisplay 0
11815set nrows_drawn 0
11816set firsttabstop 0
11817
11818set nextviewnum 1
11819set curview 0
11820set selectedview 0
11821set selectedhlview [mc "None"]
11822set highlight_related [mc "None"]
11823set highlight_files {}
11824set viewfiles(0) {}
11825set viewperm(0) 0
11826set viewargs(0) {}
11827set viewargscmd(0) {}
11828
11829set selectedline {}
11830set numcommits 0
11831set loginstance 0
11832set cmdlineok 0
11833set stopped 0
11834set stuffsaved 0
11835set patchnum 0
11836set lserial 0
11837set hasworktree [hasworktree]
11838set cdup {}
11839if {[expr {[exec git rev-parse --is-inside-work-tree] == "true"}]} {
11840    set cdup [exec git rev-parse --show-cdup]
11841}
11842set worktree [exec git rev-parse --show-toplevel]
11843setcoords
11844makewindow
11845catch {
11846    image create photo gitlogo      -width 16 -height 16
11847
11848    image create photo gitlogominus -width  4 -height  2
11849    gitlogominus put #C00000 -to 0 0 4 2
11850    gitlogo copy gitlogominus -to  1 5
11851    gitlogo copy gitlogominus -to  6 5
11852    gitlogo copy gitlogominus -to 11 5
11853    image delete gitlogominus
11854
11855    image create photo gitlogoplus  -width  4 -height  4
11856    gitlogoplus  put #008000 -to 1 0 3 4
11857    gitlogoplus  put #008000 -to 0 1 4 3
11858    gitlogo copy gitlogoplus  -to  1 9
11859    gitlogo copy gitlogoplus  -to  6 9
11860    gitlogo copy gitlogoplus  -to 11 9
11861    image delete gitlogoplus
11862
11863    image create photo gitlogo32    -width 32 -height 32
11864    gitlogo32 copy gitlogo -zoom 2 2
11865
11866    wm iconphoto . -default gitlogo gitlogo32
11867}
11868# wait for the window to become visible
11869tkwait visibility .
11870wm title . "$appname: [reponame]"
11871update
11872readrefs
11873
11874if {$cmdline_files ne {} || $revtreeargs ne {} || $revtreeargscmd ne {}} {
11875    # create a view for the files/dirs specified on the command line
11876    set curview 1
11877    set selectedview 1
11878    set nextviewnum 2
11879    set viewname(1) [mc "Command line"]
11880    set viewfiles(1) $cmdline_files
11881    set viewargs(1) $revtreeargs
11882    set viewargscmd(1) $revtreeargscmd
11883    set viewperm(1) 0
11884    set vdatemode(1) 0
11885    addviewmenu 1
11886    .bar.view entryconf [mca "Edit view..."] -state normal
11887    .bar.view entryconf [mca "Delete view"] -state normal
11888}
11889
11890if {[info exists permviews]} {
11891    foreach v $permviews {
11892        set n $nextviewnum
11893        incr nextviewnum
11894        set viewname($n) [lindex $v 0]
11895        set viewfiles($n) [lindex $v 1]
11896        set viewargs($n) [lindex $v 2]
11897        set viewargscmd($n) [lindex $v 3]
11898        set viewperm($n) 1
11899        addviewmenu $n
11900    }
11901}
11902
11903if {[tk windowingsystem] eq "win32"} {
11904    focus -force .
11905}
11906
11907getcommits {}
11908
11909# Local variables:
11910# mode: tcl
11911# indent-tabs-mode: t
11912# tab-width: 8
11913# End: