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