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