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