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