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