gitkon commit gitk: Implement a user-friendly Edit View dialog (218a900)
   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 d1 -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 diffmergeid 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
3249proc external_blame {parent_idx {line {}}} {
3250    global flist_menu_file
3251    global nullid nullid2
3252    global parentlist selectedline currentid
3253
3254    if {$parent_idx > 0} {
3255        set base_commit [lindex $parentlist $selectedline [expr {$parent_idx-1}]]
3256    } else {
3257        set base_commit $currentid
3258    }
3259
3260    if {$base_commit eq {} || $base_commit eq $nullid || $base_commit eq $nullid2} {
3261        error_popup [mc "No such commit"]
3262        return
3263    }
3264
3265    set cmdline [list git gui blame]
3266    if {$line ne {} && $line > 1} {
3267        lappend cmdline "--line=$line"
3268    }
3269    lappend cmdline $base_commit $flist_menu_file
3270    if {[catch {eval exec $cmdline &} err]} {
3271        error_popup "[mc "git gui blame: command failed:"] $err"
3272    }
3273}
3274
3275proc show_line_source {} {
3276    global cmitmode currentid parents curview blamestuff blameinst
3277    global diff_menu_line diff_menu_filebase flist_menu_file
3278
3279    if {$cmitmode eq "tree"} {
3280        set id $currentid
3281        set line [expr {$diff_menu_line - $diff_menu_filebase}]
3282    } else {
3283        set h [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
3284        if {$h eq {}} return
3285        set pi [lindex $h 0]
3286        if {$pi == 0} {
3287            mark_ctext_line $diff_menu_line
3288            return
3289        }
3290        set id [lindex $parents($curview,$currentid) [expr {$pi - 1}]]
3291        set line [lindex $h 1]
3292    }
3293    if {[catch {
3294        set f [open [list | git blame -p -L$line,+1 $id -- $flist_menu_file] r]
3295    } err]} {
3296        error_popup [mc "Couldn't start git blame: %s" $err]
3297        return
3298    }
3299    fconfigure $f -blocking 0
3300    set i [reg_instance $f]
3301    set blamestuff($i) {}
3302    set blameinst $i
3303    filerun $f [list read_line_source $f $i]
3304}
3305
3306proc stopblaming {} {
3307    global blameinst
3308
3309    if {[info exists blameinst]} {
3310        stop_instance $blameinst
3311        unset blameinst
3312    }
3313}
3314
3315proc read_line_source {fd inst} {
3316    global blamestuff curview commfd blameinst
3317
3318    while {[gets $fd line] >= 0} {
3319        lappend blamestuff($inst) $line
3320    }
3321    if {![eof $fd]} {
3322        return 1
3323    }
3324    unset commfd($inst)
3325    unset blameinst
3326    fconfigure $fd -blocking 1
3327    if {[catch {close $fd} err]} {
3328        error_popup [mc "Error running git blame: %s" $err]
3329        return 0
3330    }
3331
3332    set fname {}
3333    set line [split [lindex $blamestuff($inst) 0] " "]
3334    set id [lindex $line 0]
3335    set lnum [lindex $line 1]
3336    if {[string length $id] == 40 && [string is xdigit $id] &&
3337        [string is digit -strict $lnum]} {
3338        # look for "filename" line
3339        foreach l $blamestuff($inst) {
3340            if {[string match "filename *" $l]} {
3341                set fname [string range $l 9 end]
3342                break
3343            }
3344        }
3345    }
3346    if {$fname ne {}} {
3347        # all looks good, select it
3348        if {[commitinview $id $curview]} {
3349            selectline [rowofcommit $id] 1 [list $fname $lnum]
3350        } else {
3351            error_popup [mc "That line comes from commit %s, \
3352                             which is not in this view" [shortids $id]]
3353        }
3354    } else {
3355        puts "oops couldn't parse git blame output"
3356    }
3357    return 0
3358}
3359
3360# delete $dir when we see eof on $f (presumably because the child has exited)
3361proc delete_at_eof {f dir} {
3362    while {[gets $f line] >= 0} {}
3363    if {[eof $f]} {
3364        if {[catch {close $f} err]} {
3365            error_popup "[mc "External diff viewer failed:"] $err"
3366        }
3367        file delete -force $dir
3368        return 0
3369    }
3370    return 1
3371}
3372
3373# Functions for adding and removing shell-type quoting
3374
3375proc shellquote {str} {
3376    if {![string match "*\['\"\\ \t]*" $str]} {
3377        return $str
3378    }
3379    if {![string match "*\['\"\\]*" $str]} {
3380        return "\"$str\""
3381    }
3382    if {![string match "*'*" $str]} {
3383        return "'$str'"
3384    }
3385    return "\"[string map {\" \\\" \\ \\\\} $str]\""
3386}
3387
3388proc shellarglist {l} {
3389    set str {}
3390    foreach a $l {
3391        if {$str ne {}} {
3392            append str " "
3393        }
3394        append str [shellquote $a]
3395    }
3396    return $str
3397}
3398
3399proc shelldequote {str} {
3400    set ret {}
3401    set used -1
3402    while {1} {
3403        incr used
3404        if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
3405            append ret [string range $str $used end]
3406            set used [string length $str]
3407            break
3408        }
3409        set first [lindex $first 0]
3410        set ch [string index $str $first]
3411        if {$first > $used} {
3412            append ret [string range $str $used [expr {$first - 1}]]
3413            set used $first
3414        }
3415        if {$ch eq " " || $ch eq "\t"} break
3416        incr used
3417        if {$ch eq "'"} {
3418            set first [string first "'" $str $used]
3419            if {$first < 0} {
3420                error "unmatched single-quote"
3421            }
3422            append ret [string range $str $used [expr {$first - 1}]]
3423            set used $first
3424            continue
3425        }
3426        if {$ch eq "\\"} {
3427            if {$used >= [string length $str]} {
3428                error "trailing backslash"
3429            }
3430            append ret [string index $str $used]
3431            continue
3432        }
3433        # here ch == "\""
3434        while {1} {
3435            if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
3436                error "unmatched double-quote"
3437            }
3438            set first [lindex $first 0]
3439            set ch [string index $str $first]
3440            if {$first > $used} {
3441                append ret [string range $str $used [expr {$first - 1}]]
3442                set used $first
3443            }
3444            if {$ch eq "\""} break
3445            incr used
3446            append ret [string index $str $used]
3447            incr used
3448        }
3449    }
3450    return [list $used $ret]
3451}
3452
3453proc shellsplit {str} {
3454    set l {}
3455    while {1} {
3456        set str [string trimleft $str]
3457        if {$str eq {}} break
3458        set dq [shelldequote $str]
3459        set n [lindex $dq 0]
3460        set word [lindex $dq 1]
3461        set str [string range $str $n end]
3462        lappend l $word
3463    }
3464    return $l
3465}
3466
3467# Code to implement multiple views
3468
3469proc newview {ishighlight} {
3470    global nextviewnum newviewname newishighlight
3471    global revtreeargs viewargscmd newviewopts curview
3472
3473    set newishighlight $ishighlight
3474    set top .gitkview
3475    if {[winfo exists $top]} {
3476        raise $top
3477        return
3478    }
3479    set newviewname($nextviewnum) "[mc "View"] $nextviewnum"
3480    set newviewopts($nextviewnum,perm) 0
3481    set newviewopts($nextviewnum,cmd)  $viewargscmd($curview)
3482    decode_view_opts $nextviewnum $revtreeargs
3483    vieweditor $top $nextviewnum [mc "Gitk view definition"]
3484}
3485
3486set known_view_options {
3487    {perm    b    . {}               {mc "Remember this view"}}
3488    {args    t50= + {}               {mc "Commits to include (arguments to git log):"}}
3489    {all     b    * "--all"          {mc "Use all refs"}}
3490    {dorder  b    . {"--date-order" "-d"}      {mc "Strictly sort by date"}}
3491    {lright  b    . "--left-right"   {mc "Mark branch sides"}}
3492    {since   t15  + {"--since=*" "--after=*"}  {mc "Since date:"}}
3493    {until   t15  . {"--until=*" "--before=*"} {mc "Until date:"}}
3494    {limit   t10  + "--max-count=*"  {mc "Max count:"}}
3495    {skip    t10  . "--skip=*"       {mc "Skip:"}}
3496    {first   b    . "--first-parent" {mc "Limit to first parent"}}
3497    {cmd     t50= + {}               {mc "Command to generate more commits to include:"}}
3498    }
3499
3500proc encode_view_opts {n} {
3501    global known_view_options newviewopts
3502
3503    set rargs [list]
3504    foreach opt $known_view_options {
3505        set patterns [lindex $opt 3]
3506        if {$patterns eq {}} continue
3507        set pattern [lindex $patterns 0]
3508
3509        set val $newviewopts($n,[lindex $opt 0])
3510        
3511        if {[lindex $opt 1] eq "b"} {
3512            if {$val} {
3513                lappend rargs $pattern
3514            }
3515        } else {
3516            set val [string trim $val]
3517            if {$val ne {}} {
3518                set pfix [string range $pattern 0 end-1]
3519                lappend rargs $pfix$val
3520            }
3521        }
3522    }
3523    return [concat $rargs [shellsplit $newviewopts($n,args)]]
3524}
3525
3526proc decode_view_opts {n view_args} {
3527    global known_view_options newviewopts
3528
3529    foreach opt $known_view_options {
3530        if {[lindex $opt 1] eq "b"} {
3531            set val 0
3532        } else {
3533            set val {}
3534        }
3535        set newviewopts($n,[lindex $opt 0]) $val
3536    }
3537    set oargs [list]
3538    foreach arg $view_args {
3539        if {[regexp -- {^-([0-9]+)$} $arg arg cnt]
3540            && ![info exists found(limit)]} {
3541            set newviewopts($n,limit) $cnt
3542            set found(limit) 1
3543            continue
3544        }
3545        catch { unset val }
3546        foreach opt $known_view_options {
3547            set id [lindex $opt 0]
3548            if {[info exists found($id)]} continue
3549            foreach pattern [lindex $opt 3] {
3550                if {![string match $pattern $arg]} continue
3551                if {[lindex $opt 1] ne "b"} {
3552                    set size [string length $pattern]
3553                    set val [string range $arg [expr {$size-1}] end]
3554                } else {
3555                    set val 1
3556                }
3557                set newviewopts($n,$id) $val
3558                set found($id) 1
3559                break
3560            }
3561            if {[info exists val]} break
3562        }
3563        if {[info exists val]} continue
3564        lappend oargs $arg
3565    }
3566    set newviewopts($n,args) [shellarglist $oargs]
3567}
3568
3569proc editview {} {
3570    global curview
3571    global viewname viewperm newviewname newviewopts
3572    global viewargs viewargscmd
3573
3574    set top .gitkvedit-$curview
3575    if {[winfo exists $top]} {
3576        raise $top
3577        return
3578    }
3579    set newviewname($curview)      $viewname($curview)
3580    set newviewopts($curview,perm) $viewperm($curview)
3581    set newviewopts($curview,cmd)  $viewargscmd($curview)
3582    decode_view_opts $curview $viewargs($curview)
3583    vieweditor $top $curview "Gitk: edit view $viewname($curview)"
3584}
3585
3586proc vieweditor {top n title} {
3587    global newviewname newviewopts viewfiles bgcolor
3588    global known_view_options
3589
3590    toplevel $top
3591    wm title $top $title
3592    wm transient $top .
3593
3594    # View name
3595    frame $top.nfr
3596    label $top.nl -text [mc "Name"]
3597    entry $top.name -width 20 -textvariable newviewname($n)
3598    pack $top.nfr -in $top -fill x -pady 5 -padx 3
3599    pack $top.nl -in $top.nfr -side left -padx {0 30}
3600    pack $top.name -in $top.nfr -side left
3601
3602    # View options
3603    set cframe $top.nfr
3604    set cexpand 0
3605    set cnt 0
3606    foreach opt $known_view_options {
3607        set id [lindex $opt 0]
3608        set type [lindex $opt 1]
3609        set flags [lindex $opt 2]
3610        set title [eval [lindex $opt 4]]
3611        set lxpad 0
3612
3613        if {$flags eq "+" || $flags eq "*"} {
3614            set cframe $top.fr$cnt
3615            incr cnt
3616            frame $cframe
3617            pack $cframe -in $top -fill x -pady 3 -padx 3
3618            set cexpand [expr {$flags eq "*"}]
3619        } else {
3620            set lxpad 5
3621        }
3622
3623        if {$type eq "b"} {
3624            checkbutton $cframe.c_$id -text $title -variable newviewopts($n,$id)
3625            pack $cframe.c_$id -in $cframe -side left \
3626                -padx [list $lxpad 0] -expand $cexpand -anchor w
3627        } elseif {[regexp {^t(\d+)$} $type type sz]} {
3628            message $cframe.l_$id -aspect 1500 -text $title
3629            entry $cframe.e_$id -width $sz -background $bgcolor \
3630                -textvariable newviewopts($n,$id)
3631            pack $cframe.l_$id -in $cframe -side left -padx [list $lxpad 0]
3632            pack $cframe.e_$id -in $cframe -side left -expand 1 -fill x
3633        } elseif {[regexp {^t(\d+)=$} $type type sz]} {
3634            message $cframe.l_$id -aspect 1500 -text $title
3635            entry $cframe.e_$id -width $sz -background $bgcolor \
3636                -textvariable newviewopts($n,$id)
3637            pack $cframe.l_$id -in $cframe -side top -pady [list 3 0] -anchor w
3638            pack $cframe.e_$id -in $cframe -side top -fill x
3639        }
3640    }
3641
3642    # Path list
3643    message $top.l -aspect 1500 \
3644        -text [mc "Enter files and directories to include, one per line:"]
3645    pack $top.l -in $top -side top -pady [list 7 0] -anchor w -padx 3
3646    text $top.t -width 40 -height 5 -background $bgcolor -font uifont
3647    if {[info exists viewfiles($n)]} {
3648        foreach f $viewfiles($n) {
3649            $top.t insert end $f
3650            $top.t insert end "\n"
3651        }
3652        $top.t delete {end - 1c} end
3653        $top.t mark set insert 0.0
3654    }
3655    pack $top.t -in $top -side top -pady [list 0 5] -fill both -expand 1 -padx 3
3656    frame $top.buts
3657    button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n]
3658    button $top.buts.apply -text [mc "Apply (F5)"] -command [list newviewok $top $n 1]
3659    button $top.buts.can -text [mc "Cancel"] -command [list destroy $top]
3660    bind $top <Control-Return> [list newviewok $top $n]
3661    bind $top <F5> [list newviewok $top $n 1]
3662    bind $top <Escape> [list destroy $top]
3663    grid $top.buts.ok $top.buts.apply $top.buts.can
3664    grid columnconfigure $top.buts 0 -weight 1 -uniform a
3665    grid columnconfigure $top.buts 1 -weight 1 -uniform a
3666    grid columnconfigure $top.buts 2 -weight 1 -uniform a
3667    pack $top.buts -in $top -side top -fill x
3668    focus $top.t
3669}
3670
3671proc doviewmenu {m first cmd op argv} {
3672    set nmenu [$m index end]
3673    for {set i $first} {$i <= $nmenu} {incr i} {
3674        if {[$m entrycget $i -command] eq $cmd} {
3675            eval $m $op $i $argv
3676            break
3677        }
3678    }
3679}
3680
3681proc allviewmenus {n op args} {
3682    # global viewhlmenu
3683
3684    doviewmenu .bar.view 5 [list showview $n] $op $args
3685    # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
3686}
3687
3688proc newviewok {top n {apply 0}} {
3689    global nextviewnum newviewperm newviewname newishighlight
3690    global viewname viewfiles viewperm selectedview curview
3691    global viewargs viewargscmd newviewopts viewhlmenu
3692
3693    if {[catch {
3694        set newargs [encode_view_opts $n]
3695    } err]} {
3696        error_popup "[mc "Error in commit selection arguments:"] $err" $top
3697        return
3698    }
3699    set files {}
3700    foreach f [split [$top.t get 0.0 end] "\n"] {
3701        set ft [string trim $f]
3702        if {$ft ne {}} {
3703            lappend files $ft
3704        }
3705    }
3706    if {![info exists viewfiles($n)]} {
3707        # creating a new view
3708        incr nextviewnum
3709        set viewname($n) $newviewname($n)
3710        set viewperm($n) $newviewopts($n,perm)
3711        set viewfiles($n) $files
3712        set viewargs($n) $newargs
3713        set viewargscmd($n) $newviewopts($n,cmd)
3714        addviewmenu $n
3715        if {!$newishighlight} {
3716            run showview $n
3717        } else {
3718            run addvhighlight $n
3719        }
3720    } else {
3721        # editing an existing view
3722        set viewperm($n) $newviewopts($n,perm)
3723        if {$newviewname($n) ne $viewname($n)} {
3724            set viewname($n) $newviewname($n)
3725            doviewmenu .bar.view 5 [list showview $n] \
3726                entryconf [list -label $viewname($n)]
3727            # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
3728                # entryconf [list -label $viewname($n) -value $viewname($n)]
3729        }
3730        if {$files ne $viewfiles($n) || $newargs ne $viewargs($n) || \
3731                $newviewopts($n,cmd) ne $viewargscmd($n)} {
3732            set viewfiles($n) $files
3733            set viewargs($n) $newargs
3734            set viewargscmd($n) $newviewopts($n,cmd)
3735            if {$curview == $n} {
3736                run reloadcommits
3737            }
3738        }
3739    }
3740    if {$apply} return
3741    catch {destroy $top}
3742}
3743
3744proc delview {} {
3745    global curview viewperm hlview selectedhlview
3746
3747    if {$curview == 0} return
3748    if {[info exists hlview] && $hlview == $curview} {
3749        set selectedhlview [mc "None"]
3750        unset hlview
3751    }
3752    allviewmenus $curview delete
3753    set viewperm($curview) 0
3754    showview 0
3755}
3756
3757proc addviewmenu {n} {
3758    global viewname viewhlmenu
3759
3760    .bar.view add radiobutton -label $viewname($n) \
3761        -command [list showview $n] -variable selectedview -value $n
3762    #$viewhlmenu add radiobutton -label $viewname($n) \
3763    #   -command [list addvhighlight $n] -variable selectedhlview
3764}
3765
3766proc showview {n} {
3767    global curview cached_commitrow ordertok
3768    global displayorder parentlist rowidlist rowisopt rowfinal
3769    global colormap rowtextx nextcolor canvxmax
3770    global numcommits viewcomplete
3771    global selectedline currentid canv canvy0
3772    global treediffs
3773    global pending_select mainheadid
3774    global commitidx
3775    global selectedview
3776    global hlview selectedhlview commitinterest
3777
3778    if {$n == $curview} return
3779    set selid {}
3780    set ymax [lindex [$canv cget -scrollregion] 3]
3781    set span [$canv yview]
3782    set ytop [expr {[lindex $span 0] * $ymax}]
3783    set ybot [expr {[lindex $span 1] * $ymax}]
3784    set yscreen [expr {($ybot - $ytop) / 2}]
3785    if {$selectedline ne {}} {
3786        set selid $currentid
3787        set y [yc $selectedline]
3788        if {$ytop < $y && $y < $ybot} {
3789            set yscreen [expr {$y - $ytop}]
3790        }
3791    } elseif {[info exists pending_select]} {
3792        set selid $pending_select
3793        unset pending_select
3794    }
3795    unselectline
3796    normalline
3797    catch {unset treediffs}
3798    clear_display
3799    if {[info exists hlview] && $hlview == $n} {
3800        unset hlview
3801        set selectedhlview [mc "None"]
3802    }
3803    catch {unset commitinterest}
3804    catch {unset cached_commitrow}
3805    catch {unset ordertok}
3806
3807    set curview $n
3808    set selectedview $n
3809    .bar.view entryconf [mca "Edit view..."] -state [expr {$n == 0? "disabled": "normal"}]
3810    .bar.view entryconf [mca "Delete view"] -state [expr {$n == 0? "disabled": "normal"}]
3811
3812    run refill_reflist
3813    if {![info exists viewcomplete($n)]} {
3814        getcommits $selid
3815        return
3816    }
3817
3818    set displayorder {}
3819    set parentlist {}
3820    set rowidlist {}
3821    set rowisopt {}
3822    set rowfinal {}
3823    set numcommits $commitidx($n)
3824
3825    catch {unset colormap}
3826    catch {unset rowtextx}
3827    set nextcolor 0
3828    set canvxmax [$canv cget -width]
3829    set curview $n
3830    set row 0
3831    setcanvscroll
3832    set yf 0
3833    set row {}
3834    if {$selid ne {} && [commitinview $selid $n]} {
3835        set row [rowofcommit $selid]
3836        # try to get the selected row in the same position on the screen
3837        set ymax [lindex [$canv cget -scrollregion] 3]
3838        set ytop [expr {[yc $row] - $yscreen}]
3839        if {$ytop < 0} {
3840            set ytop 0
3841        }
3842        set yf [expr {$ytop * 1.0 / $ymax}]
3843    }
3844    allcanvs yview moveto $yf
3845    drawvisible
3846    if {$row ne {}} {
3847        selectline $row 0
3848    } elseif {!$viewcomplete($n)} {
3849        reset_pending_select $selid
3850    } else {
3851        reset_pending_select {}
3852
3853        if {[commitinview $pending_select $curview]} {
3854            selectline [rowofcommit $pending_select] 1
3855        } else {
3856            set row [first_real_row]
3857            if {$row < $numcommits} {
3858                selectline $row 0
3859            }
3860        }
3861    }
3862    if {!$viewcomplete($n)} {
3863        if {$numcommits == 0} {
3864            show_status [mc "Reading commits..."]
3865        }
3866    } elseif {$numcommits == 0} {
3867        show_status [mc "No commits selected"]
3868    }
3869}
3870
3871# Stuff relating to the highlighting facility
3872
3873proc ishighlighted {id} {
3874    global vhighlights fhighlights nhighlights rhighlights
3875
3876    if {[info exists nhighlights($id)] && $nhighlights($id) > 0} {
3877        return $nhighlights($id)
3878    }
3879    if {[info exists vhighlights($id)] && $vhighlights($id) > 0} {
3880        return $vhighlights($id)
3881    }
3882    if {[info exists fhighlights($id)] && $fhighlights($id) > 0} {
3883        return $fhighlights($id)
3884    }
3885    if {[info exists rhighlights($id)] && $rhighlights($id) > 0} {
3886        return $rhighlights($id)
3887    }
3888    return 0
3889}
3890
3891proc bolden {row font} {
3892    global canv linehtag selectedline boldrows
3893
3894    lappend boldrows $row
3895    $canv itemconf $linehtag($row) -font $font
3896    if {$row == $selectedline} {
3897        $canv delete secsel
3898        set t [eval $canv create rect [$canv bbox $linehtag($row)] \
3899                   -outline {{}} -tags secsel \
3900                   -fill [$canv cget -selectbackground]]
3901        $canv lower $t
3902    }
3903}
3904
3905proc bolden_name {row font} {
3906    global canv2 linentag selectedline boldnamerows
3907
3908    lappend boldnamerows $row
3909    $canv2 itemconf $linentag($row) -font $font
3910    if {$row == $selectedline} {
3911        $canv2 delete secsel
3912        set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
3913                   -outline {{}} -tags secsel \
3914                   -fill [$canv2 cget -selectbackground]]
3915        $canv2 lower $t
3916    }
3917}
3918
3919proc unbolden {} {
3920    global boldrows
3921
3922    set stillbold {}
3923    foreach row $boldrows {
3924        if {![ishighlighted [commitonrow $row]]} {
3925            bolden $row mainfont
3926        } else {
3927            lappend stillbold $row
3928        }
3929    }
3930    set boldrows $stillbold
3931}
3932
3933proc addvhighlight {n} {
3934    global hlview viewcomplete curview vhl_done commitidx
3935
3936    if {[info exists hlview]} {
3937        delvhighlight
3938    }
3939    set hlview $n
3940    if {$n != $curview && ![info exists viewcomplete($n)]} {
3941        start_rev_list $n
3942    }
3943    set vhl_done $commitidx($hlview)
3944    if {$vhl_done > 0} {
3945        drawvisible
3946    }
3947}
3948
3949proc delvhighlight {} {
3950    global hlview vhighlights
3951
3952    if {![info exists hlview]} return
3953    unset hlview
3954    catch {unset vhighlights}
3955    unbolden
3956}
3957
3958proc vhighlightmore {} {
3959    global hlview vhl_done commitidx vhighlights curview
3960
3961    set max $commitidx($hlview)
3962    set vr [visiblerows]
3963    set r0 [lindex $vr 0]
3964    set r1 [lindex $vr 1]
3965    for {set i $vhl_done} {$i < $max} {incr i} {
3966        set id [commitonrow $i $hlview]
3967        if {[commitinview $id $curview]} {
3968            set row [rowofcommit $id]
3969            if {$r0 <= $row && $row <= $r1} {
3970                if {![highlighted $row]} {
3971                    bolden $row mainfontbold
3972                }
3973                set vhighlights($id) 1
3974            }
3975        }
3976    }
3977    set vhl_done $max
3978    return 0
3979}
3980
3981proc askvhighlight {row id} {
3982    global hlview vhighlights iddrawn
3983
3984    if {[commitinview $id $hlview]} {
3985        if {[info exists iddrawn($id)] && ![ishighlighted $id]} {
3986            bolden $row mainfontbold
3987        }
3988        set vhighlights($id) 1
3989    } else {
3990        set vhighlights($id) 0
3991    }
3992}
3993
3994proc hfiles_change {} {
3995    global highlight_files filehighlight fhighlights fh_serial
3996    global highlight_paths gdttype
3997
3998    if {[info exists filehighlight]} {
3999        # delete previous highlights
4000        catch {close $filehighlight}
4001        unset filehighlight
4002        catch {unset fhighlights}
4003        unbolden
4004        unhighlight_filelist
4005    }
4006    set highlight_paths {}
4007    after cancel do_file_hl $fh_serial
4008    incr fh_serial
4009    if {$highlight_files ne {}} {
4010        after 300 do_file_hl $fh_serial
4011    }
4012}
4013
4014proc gdttype_change {name ix op} {
4015    global gdttype highlight_files findstring findpattern
4016
4017    stopfinding
4018    if {$findstring ne {}} {
4019        if {$gdttype eq [mc "containing:"]} {
4020            if {$highlight_files ne {}} {
4021                set highlight_files {}
4022                hfiles_change
4023            }
4024            findcom_change
4025        } else {
4026            if {$findpattern ne {}} {
4027                set findpattern {}
4028                findcom_change
4029            }
4030            set highlight_files $findstring
4031            hfiles_change
4032        }
4033        drawvisible
4034    }
4035    # enable/disable findtype/findloc menus too
4036}
4037
4038proc find_change {name ix op} {
4039    global gdttype findstring highlight_files
4040
4041    stopfinding
4042    if {$gdttype eq [mc "containing:"]} {
4043        findcom_change
4044    } else {
4045        if {$highlight_files ne $findstring} {
4046            set highlight_files $findstring
4047            hfiles_change
4048        }
4049    }
4050    drawvisible
4051}
4052
4053proc findcom_change args {
4054    global nhighlights boldnamerows
4055    global findpattern findtype findstring gdttype
4056
4057    stopfinding
4058    # delete previous highlights, if any
4059    foreach row $boldnamerows {
4060        bolden_name $row mainfont
4061    }
4062    set boldnamerows {}
4063    catch {unset nhighlights}
4064    unbolden
4065    unmarkmatches
4066    if {$gdttype ne [mc "containing:"] || $findstring eq {}} {
4067        set findpattern {}
4068    } elseif {$findtype eq [mc "Regexp"]} {
4069        set findpattern $findstring
4070    } else {
4071        set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
4072                   $findstring]
4073        set findpattern "*$e*"
4074    }
4075}
4076
4077proc makepatterns {l} {
4078    set ret {}
4079    foreach e $l {
4080        set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
4081        if {[string index $ee end] eq "/"} {
4082            lappend ret "$ee*"
4083        } else {
4084            lappend ret $ee
4085            lappend ret "$ee/*"
4086        }
4087    }
4088    return $ret
4089}
4090
4091proc do_file_hl {serial} {
4092    global highlight_files filehighlight highlight_paths gdttype fhl_list
4093
4094    if {$gdttype eq [mc "touching paths:"]} {
4095        if {[catch {set paths [shellsplit $highlight_files]}]} return
4096        set highlight_paths [makepatterns $paths]
4097        highlight_filelist
4098        set gdtargs [concat -- $paths]
4099    } elseif {$gdttype eq [mc "adding/removing string:"]} {
4100        set gdtargs [list "-S$highlight_files"]
4101    } else {
4102        # must be "containing:", i.e. we're searching commit info
4103        return
4104    }
4105    set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
4106    set filehighlight [open $cmd r+]
4107    fconfigure $filehighlight -blocking 0
4108    filerun $filehighlight readfhighlight
4109    set fhl_list {}
4110    drawvisible
4111    flushhighlights
4112}
4113
4114proc flushhighlights {} {
4115    global filehighlight fhl_list
4116
4117    if {[info exists filehighlight]} {
4118        lappend fhl_list {}
4119        puts $filehighlight ""
4120        flush $filehighlight
4121    }
4122}
4123
4124proc askfilehighlight {row id} {
4125    global filehighlight fhighlights fhl_list
4126
4127    lappend fhl_list $id
4128    set fhighlights($id) -1
4129    puts $filehighlight $id
4130}
4131
4132proc readfhighlight {} {
4133    global filehighlight fhighlights curview iddrawn
4134    global fhl_list find_dirn
4135
4136    if {![info exists filehighlight]} {
4137        return 0
4138    }
4139    set nr 0
4140    while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
4141        set line [string trim $line]
4142        set i [lsearch -exact $fhl_list $line]
4143        if {$i < 0} continue
4144        for {set j 0} {$j < $i} {incr j} {
4145            set id [lindex $fhl_list $j]
4146            set fhighlights($id) 0
4147        }
4148        set fhl_list [lrange $fhl_list [expr {$i+1}] end]
4149        if {$line eq {}} continue
4150        if {![commitinview $line $curview]} continue
4151        set row [rowofcommit $line]
4152        if {[info exists iddrawn($line)] && ![ishighlighted $line]} {
4153            bolden $row mainfontbold
4154        }
4155        set fhighlights($line) 1
4156    }
4157    if {[eof $filehighlight]} {
4158        # strange...
4159        puts "oops, git diff-tree died"
4160        catch {close $filehighlight}
4161        unset filehighlight
4162        return 0
4163    }
4164    if {[info exists find_dirn]} {
4165        run findmore
4166    }
4167    return 1
4168}
4169
4170proc doesmatch {f} {
4171    global findtype findpattern
4172
4173    if {$findtype eq [mc "Regexp"]} {
4174        return [regexp $findpattern $f]
4175    } elseif {$findtype eq [mc "IgnCase"]} {
4176        return [string match -nocase $findpattern $f]
4177    } else {
4178        return [string match $findpattern $f]
4179    }
4180}
4181
4182proc askfindhighlight {row id} {
4183    global nhighlights commitinfo iddrawn
4184    global findloc
4185    global markingmatches
4186
4187    if {![info exists commitinfo($id)]} {
4188        getcommit $id
4189    }
4190    set info $commitinfo($id)
4191    set isbold 0
4192    set fldtypes [list [mc Headline] [mc Author] [mc Date] [mc Committer] [mc CDate] [mc Comments]]
4193    foreach f $info ty $fldtypes {
4194        if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
4195            [doesmatch $f]} {
4196            if {$ty eq [mc "Author"]} {
4197                set isbold 2
4198                break
4199            }
4200            set isbold 1
4201        }
4202    }
4203    if {$isbold && [info exists iddrawn($id)]} {
4204        if {![ishighlighted $id]} {
4205            bolden $row mainfontbold
4206            if {$isbold > 1} {
4207                bolden_name $row mainfontbold
4208            }
4209        }
4210        if {$markingmatches} {
4211            markrowmatches $row $id
4212        }
4213    }
4214    set nhighlights($id) $isbold
4215}
4216
4217proc markrowmatches {row id} {
4218    global canv canv2 linehtag linentag commitinfo findloc
4219
4220    set headline [lindex $commitinfo($id) 0]
4221    set author [lindex $commitinfo($id) 1]
4222    $canv delete match$row
4223    $canv2 delete match$row
4224    if {$findloc eq [mc "All fields"] || $findloc eq [mc "Headline"]} {
4225        set m [findmatches $headline]
4226        if {$m ne {}} {
4227            markmatches $canv $row $headline $linehtag($row) $m \
4228                [$canv itemcget $linehtag($row) -font] $row
4229        }
4230    }
4231    if {$findloc eq [mc "All fields"] || $findloc eq [mc "Author"]} {
4232        set m [findmatches $author]
4233        if {$m ne {}} {
4234            markmatches $canv2 $row $author $linentag($row) $m \
4235                [$canv2 itemcget $linentag($row) -font] $row
4236        }
4237    }
4238}
4239
4240proc vrel_change {name ix op} {
4241    global highlight_related
4242
4243    rhighlight_none
4244    if {$highlight_related ne [mc "None"]} {
4245        run drawvisible
4246    }
4247}
4248
4249# prepare for testing whether commits are descendents or ancestors of a
4250proc rhighlight_sel {a} {
4251    global descendent desc_todo ancestor anc_todo
4252    global highlight_related
4253
4254    catch {unset descendent}
4255    set desc_todo [list $a]
4256    catch {unset ancestor}
4257    set anc_todo [list $a]
4258    if {$highlight_related ne [mc "None"]} {
4259        rhighlight_none
4260        run drawvisible
4261    }
4262}
4263
4264proc rhighlight_none {} {
4265    global rhighlights
4266
4267    catch {unset rhighlights}
4268    unbolden
4269}
4270
4271proc is_descendent {a} {
4272    global curview children descendent desc_todo
4273
4274    set v $curview
4275    set la [rowofcommit $a]
4276    set todo $desc_todo
4277    set leftover {}
4278    set done 0
4279    for {set i 0} {$i < [llength $todo]} {incr i} {
4280        set do [lindex $todo $i]
4281        if {[rowofcommit $do] < $la} {
4282            lappend leftover $do
4283            continue
4284        }
4285        foreach nk $children($v,$do) {
4286            if {![info exists descendent($nk)]} {
4287                set descendent($nk) 1
4288                lappend todo $nk
4289                if {$nk eq $a} {
4290                    set done 1
4291                }
4292            }
4293        }
4294        if {$done} {
4295            set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
4296            return
4297        }
4298    }
4299    set descendent($a) 0
4300    set desc_todo $leftover
4301}
4302
4303proc is_ancestor {a} {
4304    global curview parents ancestor anc_todo
4305
4306    set v $curview
4307    set la [rowofcommit $a]
4308    set todo $anc_todo
4309    set leftover {}
4310    set done 0
4311    for {set i 0} {$i < [llength $todo]} {incr i} {
4312        set do [lindex $todo $i]
4313        if {![commitinview $do $v] || [rowofcommit $do] > $la} {
4314            lappend leftover $do
4315            continue
4316        }
4317        foreach np $parents($v,$do) {
4318            if {![info exists ancestor($np)]} {
4319                set ancestor($np) 1
4320                lappend todo $np
4321                if {$np eq $a} {
4322                    set done 1
4323                }
4324            }
4325        }
4326        if {$done} {
4327            set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
4328            return
4329        }
4330    }
4331    set ancestor($a) 0
4332    set anc_todo $leftover
4333}
4334
4335proc askrelhighlight {row id} {
4336    global descendent highlight_related iddrawn rhighlights
4337    global selectedline ancestor
4338
4339    if {$selectedline eq {}} return
4340    set isbold 0
4341    if {$highlight_related eq [mc "Descendant"] ||
4342        $highlight_related eq [mc "Not descendant"]} {
4343        if {![info exists descendent($id)]} {
4344            is_descendent $id
4345        }
4346        if {$descendent($id) == ($highlight_related eq [mc "Descendant"])} {
4347            set isbold 1
4348        }
4349    } elseif {$highlight_related eq [mc "Ancestor"] ||
4350              $highlight_related eq [mc "Not ancestor"]} {
4351        if {![info exists ancestor($id)]} {
4352            is_ancestor $id
4353        }
4354        if {$ancestor($id) == ($highlight_related eq [mc "Ancestor"])} {
4355            set isbold 1
4356        }
4357    }
4358    if {[info exists iddrawn($id)]} {
4359        if {$isbold && ![ishighlighted $id]} {
4360            bolden $row mainfontbold
4361        }
4362    }
4363    set rhighlights($id) $isbold
4364}
4365
4366# Graph layout functions
4367
4368proc shortids {ids} {
4369    set res {}
4370    foreach id $ids {
4371        if {[llength $id] > 1} {
4372            lappend res [shortids $id]
4373        } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
4374            lappend res [string range $id 0 7]
4375        } else {
4376            lappend res $id
4377        }
4378    }
4379    return $res
4380}
4381
4382proc ntimes {n o} {
4383    set ret {}
4384    set o [list $o]
4385    for {set mask 1} {$mask <= $n} {incr mask $mask} {
4386        if {($n & $mask) != 0} {
4387            set ret [concat $ret $o]
4388        }
4389        set o [concat $o $o]
4390    }
4391    return $ret
4392}
4393
4394proc ordertoken {id} {
4395    global ordertok curview varcid varcstart varctok curview parents children
4396    global nullid nullid2
4397
4398    if {[info exists ordertok($id)]} {
4399        return $ordertok($id)
4400    }
4401    set origid $id
4402    set todo {}
4403    while {1} {
4404        if {[info exists varcid($curview,$id)]} {
4405            set a $varcid($curview,$id)
4406            set p [lindex $varcstart($curview) $a]
4407        } else {
4408            set p [lindex $children($curview,$id) 0]
4409        }
4410        if {[info exists ordertok($p)]} {
4411            set tok $ordertok($p)
4412            break
4413        }
4414        set id [first_real_child $curview,$p]
4415        if {$id eq {}} {
4416            # it's a root
4417            set tok [lindex $varctok($curview) $varcid($curview,$p)]
4418            break
4419        }
4420        if {[llength $parents($curview,$id)] == 1} {
4421            lappend todo [list $p {}]
4422        } else {
4423            set j [lsearch -exact $parents($curview,$id) $p]
4424            if {$j < 0} {
4425                puts "oops didn't find [shortids $p] in parents of [shortids $id]"
4426            }
4427            lappend todo [list $p [strrep $j]]
4428        }
4429    }
4430    for {set i [llength $todo]} {[incr i -1] >= 0} {} {
4431        set p [lindex $todo $i 0]
4432        append tok [lindex $todo $i 1]
4433        set ordertok($p) $tok
4434    }
4435    set ordertok($origid) $tok
4436    return $tok
4437}
4438
4439# Work out where id should go in idlist so that order-token
4440# values increase from left to right
4441proc idcol {idlist id {i 0}} {
4442    set t [ordertoken $id]
4443    if {$i < 0} {
4444        set i 0
4445    }
4446    if {$i >= [llength $idlist] || $t < [ordertoken [lindex $idlist $i]]} {
4447        if {$i > [llength $idlist]} {
4448            set i [llength $idlist]
4449        }
4450        while {[incr i -1] >= 0 && $t < [ordertoken [lindex $idlist $i]]} {}
4451        incr i
4452    } else {
4453        if {$t > [ordertoken [lindex $idlist $i]]} {
4454            while {[incr i] < [llength $idlist] &&
4455                   $t >= [ordertoken [lindex $idlist $i]]} {}
4456        }
4457    }
4458    return $i
4459}
4460
4461proc initlayout {} {
4462    global rowidlist rowisopt rowfinal displayorder parentlist
4463    global numcommits canvxmax canv
4464    global nextcolor
4465    global colormap rowtextx
4466
4467    set numcommits 0
4468    set displayorder {}
4469    set parentlist {}
4470    set nextcolor 0
4471    set rowidlist {}
4472    set rowisopt {}
4473    set rowfinal {}
4474    set canvxmax [$canv cget -width]
4475    catch {unset colormap}
4476    catch {unset rowtextx}
4477    setcanvscroll
4478}
4479
4480proc setcanvscroll {} {
4481    global canv canv2 canv3 numcommits linespc canvxmax canvy0
4482    global lastscrollset lastscrollrows
4483
4484    set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
4485    $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
4486    $canv2 conf -scrollregion [list 0 0 0 $ymax]
4487    $canv3 conf -scrollregion [list 0 0 0 $ymax]
4488    set lastscrollset [clock clicks -milliseconds]
4489    set lastscrollrows $numcommits
4490}
4491
4492proc visiblerows {} {
4493    global canv numcommits linespc
4494
4495    set ymax [lindex [$canv cget -scrollregion] 3]
4496    if {$ymax eq {} || $ymax == 0} return
4497    set f [$canv yview]
4498    set y0 [expr {int([lindex $f 0] * $ymax)}]
4499    set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
4500    if {$r0 < 0} {
4501        set r0 0
4502    }
4503    set y1 [expr {int([lindex $f 1] * $ymax)}]
4504    set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
4505    if {$r1 >= $numcommits} {
4506        set r1 [expr {$numcommits - 1}]
4507    }
4508    return [list $r0 $r1]
4509}
4510
4511proc layoutmore {} {
4512    global commitidx viewcomplete curview
4513    global numcommits pending_select curview
4514    global lastscrollset lastscrollrows
4515
4516    if {$lastscrollrows < 100 || $viewcomplete($curview) ||
4517        [clock clicks -milliseconds] - $lastscrollset > 500} {
4518        setcanvscroll
4519    }
4520    if {[info exists pending_select] &&
4521        [commitinview $pending_select $curview]} {
4522        update
4523        selectline [rowofcommit $pending_select] 1
4524    }
4525    drawvisible
4526}
4527
4528proc doshowlocalchanges {} {
4529    global curview mainheadid
4530
4531    if {$mainheadid eq {}} return
4532    if {[commitinview $mainheadid $curview]} {
4533        dodiffindex
4534    } else {
4535        interestedin $mainheadid dodiffindex
4536    }
4537}
4538
4539proc dohidelocalchanges {} {
4540    global nullid nullid2 lserial curview
4541
4542    if {[commitinview $nullid $curview]} {
4543        removefakerow $nullid
4544    }
4545    if {[commitinview $nullid2 $curview]} {
4546        removefakerow $nullid2
4547    }
4548    incr lserial
4549}
4550
4551# spawn off a process to do git diff-index --cached HEAD
4552proc dodiffindex {} {
4553    global lserial showlocalchanges
4554    global isworktree
4555
4556    if {!$showlocalchanges || !$isworktree} return
4557    incr lserial
4558    set fd [open "|git diff-index --cached HEAD" r]
4559    fconfigure $fd -blocking 0
4560    set i [reg_instance $fd]
4561    filerun $fd [list readdiffindex $fd $lserial $i]
4562}
4563
4564proc readdiffindex {fd serial inst} {
4565    global mainheadid nullid nullid2 curview commitinfo commitdata lserial
4566
4567    set isdiff 1
4568    if {[gets $fd line] < 0} {
4569        if {![eof $fd]} {
4570            return 1
4571        }
4572        set isdiff 0
4573    }
4574    # we only need to see one line and we don't really care what it says...
4575    stop_instance $inst
4576
4577    if {$serial != $lserial} {
4578        return 0
4579    }
4580
4581    # now see if there are any local changes not checked in to the index
4582    set fd [open "|git diff-files" r]
4583    fconfigure $fd -blocking 0
4584    set i [reg_instance $fd]
4585    filerun $fd [list readdifffiles $fd $serial $i]
4586
4587    if {$isdiff && ![commitinview $nullid2 $curview]} {
4588        # add the line for the changes in the index to the graph
4589        set hl [mc "Local changes checked in to index but not committed"]
4590        set commitinfo($nullid2) [list  $hl {} {} {} {} "    $hl\n"]
4591        set commitdata($nullid2) "\n    $hl\n"
4592        if {[commitinview $nullid $curview]} {
4593            removefakerow $nullid
4594        }
4595        insertfakerow $nullid2 $mainheadid
4596    } elseif {!$isdiff && [commitinview $nullid2 $curview]} {
4597        removefakerow $nullid2
4598    }
4599    return 0
4600}
4601
4602proc readdifffiles {fd serial inst} {
4603    global mainheadid nullid nullid2 curview
4604    global commitinfo commitdata lserial
4605
4606    set isdiff 1
4607    if {[gets $fd line] < 0} {
4608        if {![eof $fd]} {
4609            return 1
4610        }
4611        set isdiff 0
4612    }
4613    # we only need to see one line and we don't really care what it says...
4614    stop_instance $inst
4615
4616    if {$serial != $lserial} {
4617        return 0
4618    }
4619
4620    if {$isdiff && ![commitinview $nullid $curview]} {
4621        # add the line for the local diff to the graph
4622        set hl [mc "Local uncommitted changes, not checked in to index"]
4623        set commitinfo($nullid) [list  $hl {} {} {} {} "    $hl\n"]
4624        set commitdata($nullid) "\n    $hl\n"
4625        if {[commitinview $nullid2 $curview]} {
4626            set p $nullid2
4627        } else {
4628            set p $mainheadid
4629        }
4630        insertfakerow $nullid $p
4631    } elseif {!$isdiff && [commitinview $nullid $curview]} {
4632        removefakerow $nullid
4633    }
4634    return 0
4635}
4636
4637proc nextuse {id row} {
4638    global curview children
4639
4640    if {[info exists children($curview,$id)]} {
4641        foreach kid $children($curview,$id) {
4642            if {![commitinview $kid $curview]} {
4643                return -1
4644            }
4645            if {[rowofcommit $kid] > $row} {
4646                return [rowofcommit $kid]
4647            }
4648        }
4649    }
4650    if {[commitinview $id $curview]} {
4651        return [rowofcommit $id]
4652    }
4653    return -1
4654}
4655
4656proc prevuse {id row} {
4657    global curview children
4658
4659    set ret -1
4660    if {[info exists children($curview,$id)]} {
4661        foreach kid $children($curview,$id) {
4662            if {![commitinview $kid $curview]} break
4663            if {[rowofcommit $kid] < $row} {
4664                set ret [rowofcommit $kid]
4665            }
4666        }
4667    }
4668    return $ret
4669}
4670
4671proc make_idlist {row} {
4672    global displayorder parentlist uparrowlen downarrowlen mingaplen
4673    global commitidx curview children
4674
4675    set r [expr {$row - $mingaplen - $downarrowlen - 1}]
4676    if {$r < 0} {
4677        set r 0
4678    }
4679    set ra [expr {$row - $downarrowlen}]
4680    if {$ra < 0} {
4681        set ra 0
4682    }
4683    set rb [expr {$row + $uparrowlen}]
4684    if {$rb > $commitidx($curview)} {
4685        set rb $commitidx($curview)
4686    }
4687    make_disporder $r [expr {$rb + 1}]
4688    set ids {}
4689    for {} {$r < $ra} {incr r} {
4690        set nextid [lindex $displayorder [expr {$r + 1}]]
4691        foreach p [lindex $parentlist $r] {
4692            if {$p eq $nextid} continue
4693            set rn [nextuse $p $r]
4694            if {$rn >= $row &&
4695                $rn <= $r + $downarrowlen + $mingaplen + $uparrowlen} {
4696                lappend ids [list [ordertoken $p] $p]
4697            }
4698        }
4699    }
4700    for {} {$r < $row} {incr r} {
4701        set nextid [lindex $displayorder [expr {$r + 1}]]
4702        foreach p [lindex $parentlist $r] {
4703            if {$p eq $nextid} continue
4704            set rn [nextuse $p $r]
4705            if {$rn < 0 || $rn >= $row} {
4706                lappend ids [list [ordertoken $p] $p]
4707            }
4708        }
4709    }
4710    set id [lindex $displayorder $row]
4711    lappend ids [list [ordertoken $id] $id]
4712    while {$r < $rb} {
4713        foreach p [lindex $parentlist $r] {
4714            set firstkid [lindex $children($curview,$p) 0]
4715            if {[rowofcommit $firstkid] < $row} {
4716                lappend ids [list [ordertoken $p] $p]
4717            }
4718        }
4719        incr r
4720        set id [lindex $displayorder $r]
4721        if {$id ne {}} {
4722            set firstkid [lindex $children($curview,$id) 0]
4723            if {$firstkid ne {} && [rowofcommit $firstkid] < $row} {
4724                lappend ids [list [ordertoken $id] $id]
4725            }
4726        }
4727    }
4728    set idlist {}
4729    foreach idx [lsort -unique $ids] {
4730        lappend idlist [lindex $idx 1]
4731    }
4732    return $idlist
4733}
4734
4735proc rowsequal {a b} {
4736    while {[set i [lsearch -exact $a {}]] >= 0} {
4737        set a [lreplace $a $i $i]
4738    }
4739    while {[set i [lsearch -exact $b {}]] >= 0} {
4740        set b [lreplace $b $i $i]
4741    }
4742    return [expr {$a eq $b}]
4743}
4744
4745proc makeupline {id row rend col} {
4746    global rowidlist uparrowlen downarrowlen mingaplen
4747
4748    for {set r $rend} {1} {set r $rstart} {
4749        set rstart [prevuse $id $r]
4750        if {$rstart < 0} return
4751        if {$rstart < $row} break
4752    }
4753    if {$rstart + $uparrowlen + $mingaplen + $downarrowlen < $rend} {
4754        set rstart [expr {$rend - $uparrowlen - 1}]
4755    }
4756    for {set r $rstart} {[incr r] <= $row} {} {
4757        set idlist [lindex $rowidlist $r]
4758        if {$idlist ne {} && [lsearch -exact $idlist $id] < 0} {
4759            set col [idcol $idlist $id $col]
4760            lset rowidlist $r [linsert $idlist $col $id]
4761            changedrow $r
4762        }
4763    }
4764}
4765
4766proc layoutrows {row endrow} {
4767    global rowidlist rowisopt rowfinal displayorder
4768    global uparrowlen downarrowlen maxwidth mingaplen
4769    global children parentlist
4770    global commitidx viewcomplete curview
4771
4772    make_disporder [expr {$row - 1}] [expr {$endrow + $uparrowlen}]
4773    set idlist {}
4774    if {$row > 0} {
4775        set rm1 [expr {$row - 1}]
4776        foreach id [lindex $rowidlist $rm1] {
4777            if {$id ne {}} {
4778                lappend idlist $id
4779            }
4780        }
4781        set final [lindex $rowfinal $rm1]
4782    }
4783    for {} {$row < $endrow} {incr row} {
4784        set rm1 [expr {$row - 1}]
4785        if {$rm1 < 0 || $idlist eq {}} {
4786            set idlist [make_idlist $row]
4787            set final 1
4788        } else {
4789            set id [lindex $displayorder $rm1]
4790            set col [lsearch -exact $idlist $id]
4791            set idlist [lreplace $idlist $col $col]
4792            foreach p [lindex $parentlist $rm1] {
4793                if {[lsearch -exact $idlist $p] < 0} {
4794                    set col [idcol $idlist $p $col]
4795                    set idlist [linsert $idlist $col $p]
4796                    # if not the first child, we have to insert a line going up
4797                    if {$id ne [lindex $children($curview,$p) 0]} {
4798                        makeupline $p $rm1 $row $col
4799                    }
4800                }
4801            }
4802            set id [lindex $displayorder $row]
4803            if {$row > $downarrowlen} {
4804                set termrow [expr {$row - $downarrowlen - 1}]
4805                foreach p [lindex $parentlist $termrow] {
4806                    set i [lsearch -exact $idlist $p]
4807                    if {$i < 0} continue
4808                    set nr [nextuse $p $termrow]
4809                    if {$nr < 0 || $nr >= $row + $mingaplen + $uparrowlen} {
4810                        set idlist [lreplace $idlist $i $i]
4811                    }
4812                }
4813            }
4814            set col [lsearch -exact $idlist $id]
4815            if {$col < 0} {
4816                set col [idcol $idlist $id]
4817                set idlist [linsert $idlist $col $id]
4818                if {$children($curview,$id) ne {}} {
4819                    makeupline $id $rm1 $row $col
4820                }
4821            }
4822            set r [expr {$row + $uparrowlen - 1}]
4823            if {$r < $commitidx($curview)} {
4824                set x $col
4825                foreach p [lindex $parentlist $r] {
4826                    if {[lsearch -exact $idlist $p] >= 0} continue
4827                    set fk [lindex $children($curview,$p) 0]
4828                    if {[rowofcommit $fk] < $row} {
4829                        set x [idcol $idlist $p $x]
4830                        set idlist [linsert $idlist $x $p]
4831                    }
4832                }
4833                if {[incr r] < $commitidx($curview)} {
4834                    set p [lindex $displayorder $r]
4835                    if {[lsearch -exact $idlist $p] < 0} {
4836                        set fk [lindex $children($curview,$p) 0]
4837                        if {$fk ne {} && [rowofcommit $fk] < $row} {
4838                            set x [idcol $idlist $p $x]
4839                            set idlist [linsert $idlist $x $p]
4840                        }
4841                    }
4842                }
4843            }
4844        }
4845        if {$final && !$viewcomplete($curview) &&
4846            $row + $uparrowlen + $mingaplen + $downarrowlen
4847                >= $commitidx($curview)} {
4848            set final 0
4849        }
4850        set l [llength $rowidlist]
4851        if {$row == $l} {
4852            lappend rowidlist $idlist
4853            lappend rowisopt 0
4854            lappend rowfinal $final
4855        } elseif {$row < $l} {
4856            if {![rowsequal $idlist [lindex $rowidlist $row]]} {
4857                lset rowidlist $row $idlist
4858                changedrow $row
4859            }
4860            lset rowfinal $row $final
4861        } else {
4862            set pad [ntimes [expr {$row - $l}] {}]
4863            set rowidlist [concat $rowidlist $pad]
4864            lappend rowidlist $idlist
4865            set rowfinal [concat $rowfinal $pad]
4866            lappend rowfinal $final
4867            set rowisopt [concat $rowisopt [ntimes [expr {$row - $l + 1}] 0]]
4868        }
4869    }
4870    return $row
4871}
4872
4873proc changedrow {row} {
4874    global displayorder iddrawn rowisopt need_redisplay
4875
4876    set l [llength $rowisopt]
4877    if {$row < $l} {
4878        lset rowisopt $row 0
4879        if {$row + 1 < $l} {
4880            lset rowisopt [expr {$row + 1}] 0
4881            if {$row + 2 < $l} {
4882                lset rowisopt [expr {$row + 2}] 0
4883            }
4884        }
4885    }
4886    set id [lindex $displayorder $row]
4887    if {[info exists iddrawn($id)]} {
4888        set need_redisplay 1
4889    }
4890}
4891
4892proc insert_pad {row col npad} {
4893    global rowidlist
4894
4895    set pad [ntimes $npad {}]
4896    set idlist [lindex $rowidlist $row]
4897    set bef [lrange $idlist 0 [expr {$col - 1}]]
4898    set aft [lrange $idlist $col end]
4899    set i [lsearch -exact $aft {}]
4900    if {$i > 0} {
4901        set aft [lreplace $aft $i $i]
4902    }
4903    lset rowidlist $row [concat $bef $pad $aft]
4904    changedrow $row
4905}
4906
4907proc optimize_rows {row col endrow} {
4908    global rowidlist rowisopt displayorder curview children
4909
4910    if {$row < 1} {
4911        set row 1
4912    }
4913    for {} {$row < $endrow} {incr row; set col 0} {
4914        if {[lindex $rowisopt $row]} continue
4915        set haspad 0
4916        set y0 [expr {$row - 1}]
4917        set ym [expr {$row - 2}]
4918        set idlist [lindex $rowidlist $row]
4919        set previdlist [lindex $rowidlist $y0]
4920        if {$idlist eq {} || $previdlist eq {}} continue
4921        if {$ym >= 0} {
4922            set pprevidlist [lindex $rowidlist $ym]
4923            if {$pprevidlist eq {}} continue
4924        } else {
4925            set pprevidlist {}
4926        }
4927        set x0 -1
4928        set xm -1
4929        for {} {$col < [llength $idlist]} {incr col} {
4930            set id [lindex $idlist $col]
4931            if {[lindex $previdlist $col] eq $id} continue
4932            if {$id eq {}} {
4933                set haspad 1
4934                continue
4935            }
4936            set x0 [lsearch -exact $previdlist $id]
4937            if {$x0 < 0} continue
4938            set z [expr {$x0 - $col}]
4939            set isarrow 0
4940            set z0 {}
4941            if {$ym >= 0} {
4942                set xm [lsearch -exact $pprevidlist $id]
4943                if {$xm >= 0} {
4944                    set z0 [expr {$xm - $x0}]
4945                }
4946            }
4947            if {$z0 eq {}} {
4948                # if row y0 is the first child of $id then it's not an arrow
4949                if {[lindex $children($curview,$id) 0] ne
4950                    [lindex $displayorder $y0]} {
4951                    set isarrow 1
4952                }
4953            }
4954            if {!$isarrow && $id ne [lindex $displayorder $row] &&
4955                [lsearch -exact [lindex $rowidlist [expr {$row+1}]] $id] < 0} {
4956                set isarrow 1
4957            }
4958            # Looking at lines from this row to the previous row,
4959            # make them go straight up if they end in an arrow on
4960            # the previous row; otherwise make them go straight up
4961            # or at 45 degrees.
4962            if {$z < -1 || ($z < 0 && $isarrow)} {
4963                # Line currently goes left too much;
4964                # insert pads in the previous row, then optimize it
4965                set npad [expr {-1 - $z + $isarrow}]
4966                insert_pad $y0 $x0 $npad
4967                if {$y0 > 0} {
4968                    optimize_rows $y0 $x0 $row
4969                }
4970                set previdlist [lindex $rowidlist $y0]
4971                set x0 [lsearch -exact $previdlist $id]
4972                set z [expr {$x0 - $col}]
4973                if {$z0 ne {}} {
4974                    set pprevidlist [lindex $rowidlist $ym]
4975                    set xm [lsearch -exact $pprevidlist $id]
4976                    set z0 [expr {$xm - $x0}]
4977                }
4978            } elseif {$z > 1 || ($z > 0 && $isarrow)} {
4979                # Line currently goes right too much;
4980                # insert pads in this line
4981                set npad [expr {$z - 1 + $isarrow}]
4982                insert_pad $row $col $npad
4983                set idlist [lindex $rowidlist $row]
4984                incr col $npad
4985                set z [expr {$x0 - $col}]
4986                set haspad 1
4987            }
4988            if {$z0 eq {} && !$isarrow && $ym >= 0} {
4989                # this line links to its first child on row $row-2
4990                set id [lindex $displayorder $ym]
4991                set xc [lsearch -exact $pprevidlist $id]
4992                if {$xc >= 0} {
4993                    set z0 [expr {$xc - $x0}]
4994                }
4995            }
4996            # avoid lines jigging left then immediately right
4997            if {$z0 ne {} && $z < 0 && $z0 > 0} {
4998                insert_pad $y0 $x0 1
4999                incr x0
5000                optimize_rows $y0 $x0 $row
5001                set previdlist [lindex $rowidlist $y0]
5002            }
5003        }
5004        if {!$haspad} {
5005            # Find the first column that doesn't have a line going right
5006            for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
5007                set id [lindex $idlist $col]
5008                if {$id eq {}} break
5009                set x0 [lsearch -exact $previdlist $id]
5010                if {$x0 < 0} {
5011                    # check if this is the link to the first child
5012                    set kid [lindex $displayorder $y0]
5013                    if {[lindex $children($curview,$id) 0] eq $kid} {
5014                        # it is, work out offset to child
5015                        set x0 [lsearch -exact $previdlist $kid]
5016                    }
5017                }
5018                if {$x0 <= $col} break
5019            }
5020            # Insert a pad at that column as long as it has a line and
5021            # isn't the last column
5022            if {$x0 >= 0 && [incr col] < [llength $idlist]} {
5023                set idlist [linsert $idlist $col {}]
5024                lset rowidlist $row $idlist
5025                changedrow $row
5026            }
5027        }
5028    }
5029}
5030
5031proc xc {row col} {
5032    global canvx0 linespc
5033    return [expr {$canvx0 + $col * $linespc}]
5034}
5035
5036proc yc {row} {
5037    global canvy0 linespc
5038    return [expr {$canvy0 + $row * $linespc}]
5039}
5040
5041proc linewidth {id} {
5042    global thickerline lthickness
5043
5044    set wid $lthickness
5045    if {[info exists thickerline] && $id eq $thickerline} {
5046        set wid [expr {2 * $lthickness}]
5047    }
5048    return $wid
5049}
5050
5051proc rowranges {id} {
5052    global curview children uparrowlen downarrowlen
5053    global rowidlist
5054
5055    set kids $children($curview,$id)
5056    if {$kids eq {}} {
5057        return {}
5058    }
5059    set ret {}
5060    lappend kids $id
5061    foreach child $kids {
5062        if {![commitinview $child $curview]} break
5063        set row [rowofcommit $child]
5064        if {![info exists prev]} {
5065            lappend ret [expr {$row + 1}]
5066        } else {
5067            if {$row <= $prevrow} {
5068                puts "oops children of [shortids $id] out of order [shortids $child] $row <= [shortids $prev] $prevrow"
5069            }
5070            # see if the line extends the whole way from prevrow to row
5071            if {$row > $prevrow + $uparrowlen + $downarrowlen &&
5072                [lsearch -exact [lindex $rowidlist \
5073                            [expr {int(($row + $prevrow) / 2)}]] $id] < 0} {
5074                # it doesn't, see where it ends
5075                set r [expr {$prevrow + $downarrowlen}]
5076                if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
5077                    while {[incr r -1] > $prevrow &&
5078                           [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
5079                } else {
5080                    while {[incr r] <= $row &&
5081                           [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
5082                    incr r -1
5083                }
5084                lappend ret $r
5085                # see where it starts up again
5086                set r [expr {$row - $uparrowlen}]
5087                if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
5088                    while {[incr r] < $row &&
5089                           [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
5090                } else {
5091                    while {[incr r -1] >= $prevrow &&
5092                           [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
5093                    incr r
5094                }
5095                lappend ret $r
5096            }
5097        }
5098        if {$child eq $id} {
5099            lappend ret $row
5100        }
5101        set prev $child
5102        set prevrow $row
5103    }
5104    return $ret
5105}
5106
5107proc drawlineseg {id row endrow arrowlow} {
5108    global rowidlist displayorder iddrawn linesegs
5109    global canv colormap linespc curview maxlinelen parentlist
5110
5111    set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
5112    set le [expr {$row + 1}]
5113    set arrowhigh 1
5114    while {1} {
5115        set c [lsearch -exact [lindex $rowidlist $le] $id]
5116        if {$c < 0} {
5117            incr le -1
5118            break
5119        }
5120        lappend cols $c
5121        set x [lindex $displayorder $le]
5122        if {$x eq $id} {
5123            set arrowhigh 0
5124            break
5125        }
5126        if {[info exists iddrawn($x)] || $le == $endrow} {
5127            set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
5128            if {$c >= 0} {
5129                lappend cols $c
5130                set arrowhigh 0
5131            }
5132            break
5133        }
5134        incr le
5135    }
5136    if {$le <= $row} {
5137        return $row
5138    }
5139
5140    set lines {}
5141    set i 0
5142    set joinhigh 0
5143    if {[info exists linesegs($id)]} {
5144        set lines $linesegs($id)
5145        foreach li $lines {
5146            set r0 [lindex $li 0]
5147            if {$r0 > $row} {
5148                if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
5149                    set joinhigh 1
5150                }
5151                break
5152            }
5153            incr i
5154        }
5155    }
5156    set joinlow 0
5157    if {$i > 0} {
5158        set li [lindex $lines [expr {$i-1}]]
5159        set r1 [lindex $li 1]
5160        if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
5161            set joinlow 1
5162        }
5163    }
5164
5165    set x [lindex $cols [expr {$le - $row}]]
5166    set xp [lindex $cols [expr {$le - 1 - $row}]]
5167    set dir [expr {$xp - $x}]
5168    if {$joinhigh} {
5169        set ith [lindex $lines $i 2]
5170        set coords [$canv coords $ith]
5171        set ah [$canv itemcget $ith -arrow]
5172        set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
5173        set x2 [lindex $cols [expr {$le + 1 - $row}]]
5174        if {$x2 ne {} && $x - $x2 == $dir} {
5175            set coords [lrange $coords 0 end-2]
5176        }
5177    } else {
5178        set coords [list [xc $le $x] [yc $le]]
5179    }
5180    if {$joinlow} {
5181        set itl [lindex $lines [expr {$i-1}] 2]
5182        set al [$canv itemcget $itl -arrow]
5183        set arrowlow [expr {$al eq "last" || $al eq "both"}]
5184    } elseif {$arrowlow} {
5185        if {[lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0 ||
5186            [lsearch -exact [lindex $parentlist [expr {$row-1}]] $id] >= 0} {
5187            set arrowlow 0
5188        }
5189    }
5190    set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
5191    for {set y $le} {[incr y -1] > $row} {} {
5192        set x $xp
5193        set xp [lindex $cols [expr {$y - 1 - $row}]]
5194        set ndir [expr {$xp - $x}]
5195        if {$dir != $ndir || $xp < 0} {
5196            lappend coords [xc $y $x] [yc $y]
5197        }
5198        set dir $ndir
5199    }
5200    if {!$joinlow} {
5201        if {$xp < 0} {
5202            # join parent line to first child
5203            set ch [lindex $displayorder $row]
5204            set xc [lsearch -exact [lindex $rowidlist $row] $ch]
5205            if {$xc < 0} {
5206                puts "oops: drawlineseg: child $ch not on row $row"
5207            } elseif {$xc != $x} {
5208                if {($arrowhigh && $le == $row + 1) || $dir == 0} {
5209                    set d [expr {int(0.5 * $linespc)}]
5210                    set x1 [xc $row $x]
5211                    if {$xc < $x} {
5212                        set x2 [expr {$x1 - $d}]
5213                    } else {
5214                        set x2 [expr {$x1 + $d}]
5215                    }
5216                    set y2 [yc $row]
5217                    set y1 [expr {$y2 + $d}]
5218                    lappend coords $x1 $y1 $x2 $y2
5219                } elseif {$xc < $x - 1} {
5220                    lappend coords [xc $row [expr {$x-1}]] [yc $row]
5221                } elseif {$xc > $x + 1} {
5222                    lappend coords [xc $row [expr {$x+1}]] [yc $row]
5223                }
5224                set x $xc
5225            }
5226            lappend coords [xc $row $x] [yc $row]
5227        } else {
5228            set xn [xc $row $xp]
5229            set yn [yc $row]
5230            lappend coords $xn $yn
5231        }
5232        if {!$joinhigh} {
5233            assigncolor $id
5234            set t [$canv create line $coords -width [linewidth $id] \
5235                       -fill $colormap($id) -tags lines.$id -arrow $arrow]
5236            $canv lower $t
5237            bindline $t $id
5238            set lines [linsert $lines $i [list $row $le $t]]
5239        } else {
5240            $canv coords $ith $coords
5241            if {$arrow ne $ah} {
5242                $canv itemconf $ith -arrow $arrow
5243            }
5244            lset lines $i 0 $row
5245        }
5246    } else {
5247        set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
5248        set ndir [expr {$xo - $xp}]
5249        set clow [$canv coords $itl]
5250        if {$dir == $ndir} {
5251            set clow [lrange $clow 2 end]
5252        }
5253        set coords [concat $coords $clow]
5254        if {!$joinhigh} {
5255            lset lines [expr {$i-1}] 1 $le
5256        } else {
5257            # coalesce two pieces
5258            $canv delete $ith
5259            set b [lindex $lines [expr {$i-1}] 0]
5260            set e [lindex $lines $i 1]
5261            set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
5262        }
5263        $canv coords $itl $coords
5264        if {$arrow ne $al} {
5265            $canv itemconf $itl -arrow $arrow
5266        }
5267    }
5268
5269    set linesegs($id) $lines
5270    return $le
5271}
5272
5273proc drawparentlinks {id row} {
5274    global rowidlist canv colormap curview parentlist
5275    global idpos linespc
5276
5277    set rowids [lindex $rowidlist $row]
5278    set col [lsearch -exact $rowids $id]
5279    if {$col < 0} return
5280    set olds [lindex $parentlist $row]
5281    set row2 [expr {$row + 1}]
5282    set x [xc $row $col]
5283    set y [yc $row]
5284    set y2 [yc $row2]
5285    set d [expr {int(0.5 * $linespc)}]
5286    set ymid [expr {$y + $d}]
5287    set ids [lindex $rowidlist $row2]
5288    # rmx = right-most X coord used
5289    set rmx 0
5290    foreach p $olds {
5291        set i [lsearch -exact $ids $p]
5292        if {$i < 0} {
5293            puts "oops, parent $p of $id not in list"
5294            continue
5295        }
5296        set x2 [xc $row2 $i]
5297        if {$x2 > $rmx} {
5298            set rmx $x2
5299        }
5300        set j [lsearch -exact $rowids $p]
5301        if {$j < 0} {
5302            # drawlineseg will do this one for us
5303            continue
5304        }
5305        assigncolor $p
5306        # should handle duplicated parents here...
5307        set coords [list $x $y]
5308        if {$i != $col} {
5309            # if attaching to a vertical segment, draw a smaller
5310            # slant for visual distinctness
5311            if {$i == $j} {
5312                if {$i < $col} {
5313                    lappend coords [expr {$x2 + $d}] $y $x2 $ymid
5314                } else {
5315                    lappend coords [expr {$x2 - $d}] $y $x2 $ymid
5316                }
5317            } elseif {$i < $col && $i < $j} {
5318                # segment slants towards us already
5319                lappend coords [xc $row $j] $y
5320            } else {
5321                if {$i < $col - 1} {
5322                    lappend coords [expr {$x2 + $linespc}] $y
5323                } elseif {$i > $col + 1} {
5324                    lappend coords [expr {$x2 - $linespc}] $y
5325                }
5326                lappend coords $x2 $y2
5327            }
5328        } else {
5329            lappend coords $x2 $y2
5330        }
5331        set t [$canv create line $coords -width [linewidth $p] \
5332                   -fill $colormap($p) -tags lines.$p]
5333        $canv lower $t
5334        bindline $t $p
5335    }
5336    if {$rmx > [lindex $idpos($id) 1]} {
5337        lset idpos($id) 1 $rmx
5338        redrawtags $id
5339    }
5340}
5341
5342proc drawlines {id} {
5343    global canv
5344
5345    $canv itemconf lines.$id -width [linewidth $id]
5346}
5347
5348proc drawcmittext {id row col} {
5349    global linespc canv canv2 canv3 fgcolor curview
5350    global cmitlisted commitinfo rowidlist parentlist
5351    global rowtextx idpos idtags idheads idotherrefs
5352    global linehtag linentag linedtag selectedline
5353    global canvxmax boldrows boldnamerows fgcolor
5354    global mainheadid nullid nullid2 circleitem circlecolors ctxbut
5355
5356    # listed is 0 for boundary, 1 for normal, 2 for negative, 3 for left, 4 for right
5357    set listed $cmitlisted($curview,$id)
5358    if {$id eq $nullid} {
5359        set ofill red
5360    } elseif {$id eq $nullid2} {
5361        set ofill green
5362    } elseif {$id eq $mainheadid} {
5363        set ofill yellow
5364    } else {
5365        set ofill [lindex $circlecolors $listed]
5366    }
5367    set x [xc $row $col]
5368    set y [yc $row]
5369    set orad [expr {$linespc / 3}]
5370    if {$listed <= 2} {
5371        set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
5372                   [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
5373                   -fill $ofill -outline $fgcolor -width 1 -tags circle]
5374    } elseif {$listed == 3} {
5375        # triangle pointing left for left-side commits
5376        set t [$canv create polygon \
5377                   [expr {$x - $orad}] $y \
5378                   [expr {$x + $orad - 1}] [expr {$y - $orad}] \
5379                   [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
5380                   -fill $ofill -outline $fgcolor -width 1 -tags circle]
5381    } else {
5382        # triangle pointing right for right-side commits
5383        set t [$canv create polygon \
5384                   [expr {$x + $orad - 1}] $y \
5385                   [expr {$x - $orad}] [expr {$y - $orad}] \
5386                   [expr {$x - $orad}] [expr {$y + $orad - 1}] \
5387                   -fill $ofill -outline $fgcolor -width 1 -tags circle]
5388    }
5389    set circleitem($row) $t
5390    $canv raise $t
5391    $canv bind $t <1> {selcanvline {} %x %y}
5392    set rmx [llength [lindex $rowidlist $row]]
5393    set olds [lindex $parentlist $row]
5394    if {$olds ne {}} {
5395        set nextids [lindex $rowidlist [expr {$row + 1}]]
5396        foreach p $olds {
5397            set i [lsearch -exact $nextids $p]
5398            if {$i > $rmx} {
5399                set rmx $i
5400            }
5401        }
5402    }
5403    set xt [xc $row $rmx]
5404    set rowtextx($row) $xt
5405    set idpos($id) [list $x $xt $y]
5406    if {[info exists idtags($id)] || [info exists idheads($id)]
5407        || [info exists idotherrefs($id)]} {
5408        set xt [drawtags $id $x $xt $y]
5409    }
5410    set headline [lindex $commitinfo($id) 0]
5411    set name [lindex $commitinfo($id) 1]
5412    set date [lindex $commitinfo($id) 2]
5413    set date [formatdate $date]
5414    set font mainfont
5415    set nfont mainfont
5416    set isbold [ishighlighted $id]
5417    if {$isbold > 0} {
5418        lappend boldrows $row
5419        set font mainfontbold
5420        if {$isbold > 1} {
5421            lappend boldnamerows $row
5422            set nfont mainfontbold
5423        }
5424    }
5425    set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
5426                            -text $headline -font $font -tags text]
5427    $canv bind $linehtag($row) $ctxbut "rowmenu %X %Y $id"
5428    set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
5429                            -text $name -font $nfont -tags text]
5430    set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
5431                            -text $date -font mainfont -tags text]
5432    if {$selectedline == $row} {
5433        make_secsel $row
5434    }
5435    set xr [expr {$xt + [font measure $font $headline]}]
5436    if {$xr > $canvxmax} {
5437        set canvxmax $xr
5438        setcanvscroll
5439    }
5440}
5441
5442proc drawcmitrow {row} {
5443    global displayorder rowidlist nrows_drawn
5444    global iddrawn markingmatches
5445    global commitinfo numcommits
5446    global filehighlight fhighlights findpattern nhighlights
5447    global hlview vhighlights
5448    global highlight_related rhighlights
5449
5450    if {$row >= $numcommits} return
5451
5452    set id [lindex $displayorder $row]
5453    if {[info exists hlview] && ![info exists vhighlights($id)]} {
5454        askvhighlight $row $id
5455    }
5456    if {[info exists filehighlight] && ![info exists fhighlights($id)]} {
5457        askfilehighlight $row $id
5458    }
5459    if {$findpattern ne {} && ![info exists nhighlights($id)]} {
5460        askfindhighlight $row $id
5461    }
5462    if {$highlight_related ne [mc "None"] && ![info exists rhighlights($id)]} {
5463        askrelhighlight $row $id
5464    }
5465    if {![info exists iddrawn($id)]} {
5466        set col [lsearch -exact [lindex $rowidlist $row] $id]
5467        if {$col < 0} {
5468            puts "oops, row $row id $id not in list"
5469            return
5470        }
5471        if {![info exists commitinfo($id)]} {
5472            getcommit $id
5473        }
5474        assigncolor $id
5475        drawcmittext $id $row $col
5476        set iddrawn($id) 1
5477        incr nrows_drawn
5478    }
5479    if {$markingmatches} {
5480        markrowmatches $row $id
5481    }
5482}
5483
5484proc drawcommits {row {endrow {}}} {
5485    global numcommits iddrawn displayorder curview need_redisplay
5486    global parentlist rowidlist rowfinal uparrowlen downarrowlen nrows_drawn
5487
5488    if {$row < 0} {
5489        set row 0
5490    }
5491    if {$endrow eq {}} {
5492        set endrow $row
5493    }
5494    if {$endrow >= $numcommits} {
5495        set endrow [expr {$numcommits - 1}]
5496    }
5497
5498    set rl1 [expr {$row - $downarrowlen - 3}]
5499    if {$rl1 < 0} {
5500        set rl1 0
5501    }
5502    set ro1 [expr {$row - 3}]
5503    if {$ro1 < 0} {
5504        set ro1 0
5505    }
5506    set r2 [expr {$endrow + $uparrowlen + 3}]
5507    if {$r2 > $numcommits} {
5508        set r2 $numcommits
5509    }
5510    for {set r $rl1} {$r < $r2} {incr r} {
5511        if {[lindex $rowidlist $r] ne {} && [lindex $rowfinal $r]} {
5512            if {$rl1 < $r} {
5513                layoutrows $rl1 $r
5514            }
5515            set rl1 [expr {$r + 1}]
5516        }
5517    }
5518    if {$rl1 < $r} {
5519        layoutrows $rl1 $r
5520    }
5521    optimize_rows $ro1 0 $r2
5522    if {$need_redisplay || $nrows_drawn > 2000} {
5523        clear_display
5524        drawvisible
5525    }
5526
5527    # make the lines join to already-drawn rows either side
5528    set r [expr {$row - 1}]
5529    if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
5530        set r $row
5531    }
5532    set er [expr {$endrow + 1}]
5533    if {$er >= $numcommits ||
5534        ![info exists iddrawn([lindex $displayorder $er])]} {
5535        set er $endrow
5536    }
5537    for {} {$r <= $er} {incr r} {
5538        set id [lindex $displayorder $r]
5539        set wasdrawn [info exists iddrawn($id)]
5540        drawcmitrow $r
5541        if {$r == $er} break
5542        set nextid [lindex $displayorder [expr {$r + 1}]]
5543        if {$wasdrawn && [info exists iddrawn($nextid)]} continue
5544        drawparentlinks $id $r
5545
5546        set rowids [lindex $rowidlist $r]
5547        foreach lid $rowids {
5548            if {$lid eq {}} continue
5549            if {[info exists lineend($lid)] && $lineend($lid) > $r} continue
5550            if {$lid eq $id} {
5551                # see if this is the first child of any of its parents
5552                foreach p [lindex $parentlist $r] {
5553                    if {[lsearch -exact $rowids $p] < 0} {
5554                        # make this line extend up to the child
5555                        set lineend($p) [drawlineseg $p $r $er 0]
5556                    }
5557                }
5558            } else {
5559                set lineend($lid) [drawlineseg $lid $r $er 1]
5560            }
5561        }
5562    }
5563}
5564
5565proc undolayout {row} {
5566    global uparrowlen mingaplen downarrowlen
5567    global rowidlist rowisopt rowfinal need_redisplay
5568
5569    set r [expr {$row - ($uparrowlen + $mingaplen + $downarrowlen)}]
5570    if {$r < 0} {
5571        set r 0
5572    }
5573    if {[llength $rowidlist] > $r} {
5574        incr r -1
5575        set rowidlist [lrange $rowidlist 0 $r]
5576        set rowfinal [lrange $rowfinal 0 $r]
5577        set rowisopt [lrange $rowisopt 0 $r]
5578        set need_redisplay 1
5579        run drawvisible
5580    }
5581}
5582
5583proc drawvisible {} {
5584    global canv linespc curview vrowmod selectedline targetrow targetid
5585    global need_redisplay cscroll numcommits
5586
5587    set fs [$canv yview]
5588    set ymax [lindex [$canv cget -scrollregion] 3]
5589    if {$ymax eq {} || $ymax == 0 || $numcommits == 0} return
5590    set f0 [lindex $fs 0]
5591    set f1 [lindex $fs 1]
5592    set y0 [expr {int($f0 * $ymax)}]
5593    set y1 [expr {int($f1 * $ymax)}]
5594
5595    if {[info exists targetid]} {
5596        if {[commitinview $targetid $curview]} {
5597            set r [rowofcommit $targetid]
5598            if {$r != $targetrow} {
5599                # Fix up the scrollregion and change the scrolling position
5600                # now that our target row has moved.
5601                set diff [expr {($r - $targetrow) * $linespc}]
5602                set targetrow $r
5603                setcanvscroll
5604                set ymax [lindex [$canv cget -scrollregion] 3]
5605                incr y0 $diff
5606                incr y1 $diff
5607                set f0 [expr {$y0 / $ymax}]
5608                set f1 [expr {$y1 / $ymax}]
5609                allcanvs yview moveto $f0
5610                $cscroll set $f0 $f1
5611                set need_redisplay 1
5612            }
5613        } else {
5614            unset targetid
5615        }
5616    }
5617
5618    set row [expr {int(($y0 - 3) / $linespc) - 1}]
5619    set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
5620    if {$endrow >= $vrowmod($curview)} {
5621        update_arcrows $curview
5622    }
5623    if {$selectedline ne {} &&
5624        $row <= $selectedline && $selectedline <= $endrow} {
5625        set targetrow $selectedline
5626    } elseif {[info exists targetid]} {
5627        set targetrow [expr {int(($row + $endrow) / 2)}]
5628    }
5629    if {[info exists targetrow]} {
5630        if {$targetrow >= $numcommits} {
5631            set targetrow [expr {$numcommits - 1}]
5632        }
5633        set targetid [commitonrow $targetrow]
5634    }
5635    drawcommits $row $endrow
5636}
5637
5638proc clear_display {} {
5639    global iddrawn linesegs need_redisplay nrows_drawn
5640    global vhighlights fhighlights nhighlights rhighlights
5641    global linehtag linentag linedtag boldrows boldnamerows
5642
5643    allcanvs delete all
5644    catch {unset iddrawn}
5645    catch {unset linesegs}
5646    catch {unset linehtag}
5647    catch {unset linentag}
5648    catch {unset linedtag}
5649    set boldrows {}
5650    set boldnamerows {}
5651    catch {unset vhighlights}
5652    catch {unset fhighlights}
5653    catch {unset nhighlights}
5654    catch {unset rhighlights}
5655    set need_redisplay 0
5656    set nrows_drawn 0
5657}
5658
5659proc findcrossings {id} {
5660    global rowidlist parentlist numcommits displayorder
5661
5662    set cross {}
5663    set ccross {}
5664    foreach {s e} [rowranges $id] {
5665        if {$e >= $numcommits} {
5666            set e [expr {$numcommits - 1}]
5667        }
5668        if {$e <= $s} continue
5669        for {set row $e} {[incr row -1] >= $s} {} {
5670            set x [lsearch -exact [lindex $rowidlist $row] $id]
5671            if {$x < 0} break
5672            set olds [lindex $parentlist $row]
5673            set kid [lindex $displayorder $row]
5674            set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
5675            if {$kidx < 0} continue
5676            set nextrow [lindex $rowidlist [expr {$row + 1}]]
5677            foreach p $olds {
5678                set px [lsearch -exact $nextrow $p]
5679                if {$px < 0} continue
5680                if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
5681                    if {[lsearch -exact $ccross $p] >= 0} continue
5682                    if {$x == $px + ($kidx < $px? -1: 1)} {
5683                        lappend ccross $p
5684                    } elseif {[lsearch -exact $cross $p] < 0} {
5685                        lappend cross $p
5686                    }
5687                }
5688            }
5689        }
5690    }
5691    return [concat $ccross {{}} $cross]
5692}
5693
5694proc assigncolor {id} {
5695    global colormap colors nextcolor
5696    global parents children children curview
5697
5698    if {[info exists colormap($id)]} return
5699    set ncolors [llength $colors]
5700    if {[info exists children($curview,$id)]} {
5701        set kids $children($curview,$id)
5702    } else {
5703        set kids {}
5704    }
5705    if {[llength $kids] == 1} {
5706        set child [lindex $kids 0]
5707        if {[info exists colormap($child)]
5708            && [llength $parents($curview,$child)] == 1} {
5709            set colormap($id) $colormap($child)
5710            return
5711        }
5712    }
5713    set badcolors {}
5714    set origbad {}
5715    foreach x [findcrossings $id] {
5716        if {$x eq {}} {
5717            # delimiter between corner crossings and other crossings
5718            if {[llength $badcolors] >= $ncolors - 1} break
5719            set origbad $badcolors
5720        }
5721        if {[info exists colormap($x)]
5722            && [lsearch -exact $badcolors $colormap($x)] < 0} {
5723            lappend badcolors $colormap($x)
5724        }
5725    }
5726    if {[llength $badcolors] >= $ncolors} {
5727        set badcolors $origbad
5728    }
5729    set origbad $badcolors
5730    if {[llength $badcolors] < $ncolors - 1} {
5731        foreach child $kids {
5732            if {[info exists colormap($child)]
5733                && [lsearch -exact $badcolors $colormap($child)] < 0} {
5734                lappend badcolors $colormap($child)
5735            }
5736            foreach p $parents($curview,$child) {
5737                if {[info exists colormap($p)]
5738                    && [lsearch -exact $badcolors $colormap($p)] < 0} {
5739                    lappend badcolors $colormap($p)
5740                }
5741            }
5742        }
5743        if {[llength $badcolors] >= $ncolors} {
5744            set badcolors $origbad
5745        }
5746    }
5747    for {set i 0} {$i <= $ncolors} {incr i} {
5748        set c [lindex $colors $nextcolor]
5749        if {[incr nextcolor] >= $ncolors} {
5750            set nextcolor 0
5751        }
5752        if {[lsearch -exact $badcolors $c]} break
5753    }
5754    set colormap($id) $c
5755}
5756
5757proc bindline {t id} {
5758    global canv
5759
5760    $canv bind $t <Enter> "lineenter %x %y $id"
5761    $canv bind $t <Motion> "linemotion %x %y $id"
5762    $canv bind $t <Leave> "lineleave $id"
5763    $canv bind $t <Button-1> "lineclick %x %y $id 1"
5764}
5765
5766proc drawtags {id x xt y1} {
5767    global idtags idheads idotherrefs mainhead
5768    global linespc lthickness
5769    global canv rowtextx curview fgcolor bgcolor ctxbut
5770
5771    set marks {}
5772    set ntags 0
5773    set nheads 0
5774    if {[info exists idtags($id)]} {
5775        set marks $idtags($id)
5776        set ntags [llength $marks]
5777    }
5778    if {[info exists idheads($id)]} {
5779        set marks [concat $marks $idheads($id)]
5780        set nheads [llength $idheads($id)]
5781    }
5782    if {[info exists idotherrefs($id)]} {
5783        set marks [concat $marks $idotherrefs($id)]
5784    }
5785    if {$marks eq {}} {
5786        return $xt
5787    }
5788
5789    set delta [expr {int(0.5 * ($linespc - $lthickness))}]
5790    set yt [expr {$y1 - 0.5 * $linespc}]
5791    set yb [expr {$yt + $linespc - 1}]
5792    set xvals {}
5793    set wvals {}
5794    set i -1
5795    foreach tag $marks {
5796        incr i
5797        if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
5798            set wid [font measure mainfontbold $tag]
5799        } else {
5800            set wid [font measure mainfont $tag]
5801        }
5802        lappend xvals $xt
5803        lappend wvals $wid
5804        set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
5805    }
5806    set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
5807               -width $lthickness -fill black -tags tag.$id]
5808    $canv lower $t
5809    foreach tag $marks x $xvals wid $wvals {
5810        set xl [expr {$x + $delta}]
5811        set xr [expr {$x + $delta + $wid + $lthickness}]
5812        set font mainfont
5813        if {[incr ntags -1] >= 0} {
5814            # draw a tag
5815            set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
5816                       $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
5817                       -width 1 -outline black -fill yellow -tags tag.$id]
5818            $canv bind $t <1> [list showtag $tag 1]
5819            set rowtextx([rowofcommit $id]) [expr {$xr + $linespc}]
5820        } else {
5821            # draw a head or other ref
5822            if {[incr nheads -1] >= 0} {
5823                set col green
5824                if {$tag eq $mainhead} {
5825                    set font mainfontbold
5826                }
5827            } else {
5828                set col "#ddddff"
5829            }
5830            set xl [expr {$xl - $delta/2}]
5831            $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
5832                -width 1 -outline black -fill $col -tags tag.$id
5833            if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
5834                set rwid [font measure mainfont $remoteprefix]
5835                set xi [expr {$x + 1}]
5836                set yti [expr {$yt + 1}]
5837                set xri [expr {$x + $rwid}]
5838                $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
5839                        -width 0 -fill "#ffddaa" -tags tag.$id
5840            }
5841        }
5842        set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
5843                   -font $font -tags [list tag.$id text]]
5844        if {$ntags >= 0} {
5845            $canv bind $t <1> [list showtag $tag 1]
5846        } elseif {$nheads >= 0} {
5847            $canv bind $t $ctxbut [list headmenu %X %Y $id $tag]
5848        }
5849    }
5850    return $xt
5851}
5852
5853proc xcoord {i level ln} {
5854    global canvx0 xspc1 xspc2
5855
5856    set x [expr {$canvx0 + $i * $xspc1($ln)}]
5857    if {$i > 0 && $i == $level} {
5858        set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
5859    } elseif {$i > $level} {
5860        set x [expr {$x + $xspc2 - $xspc1($ln)}]
5861    }
5862    return $x
5863}
5864
5865proc show_status {msg} {
5866    global canv fgcolor
5867
5868    clear_display
5869    $canv create text 3 3 -anchor nw -text $msg -font mainfont \
5870        -tags text -fill $fgcolor
5871}
5872
5873# Don't change the text pane cursor if it is currently the hand cursor,
5874# showing that we are over a sha1 ID link.
5875proc settextcursor {c} {
5876    global ctext curtextcursor
5877
5878    if {[$ctext cget -cursor] == $curtextcursor} {
5879        $ctext config -cursor $c
5880    }
5881    set curtextcursor $c
5882}
5883
5884proc nowbusy {what {name {}}} {
5885    global isbusy busyname statusw
5886
5887    if {[array names isbusy] eq {}} {
5888        . config -cursor watch
5889        settextcursor watch
5890    }
5891    set isbusy($what) 1
5892    set busyname($what) $name
5893    if {$name ne {}} {
5894        $statusw conf -text $name
5895    }
5896}
5897
5898proc notbusy {what} {
5899    global isbusy maincursor textcursor busyname statusw
5900
5901    catch {
5902        unset isbusy($what)
5903        if {$busyname($what) ne {} &&
5904            [$statusw cget -text] eq $busyname($what)} {
5905            $statusw conf -text {}
5906        }
5907    }
5908    if {[array names isbusy] eq {}} {
5909        . config -cursor $maincursor
5910        settextcursor $textcursor
5911    }
5912}
5913
5914proc findmatches {f} {
5915    global findtype findstring
5916    if {$findtype == [mc "Regexp"]} {
5917        set matches [regexp -indices -all -inline $findstring $f]
5918    } else {
5919        set fs $findstring
5920        if {$findtype == [mc "IgnCase"]} {
5921            set f [string tolower $f]
5922            set fs [string tolower $fs]
5923        }
5924        set matches {}
5925        set i 0
5926        set l [string length $fs]
5927        while {[set j [string first $fs $f $i]] >= 0} {
5928            lappend matches [list $j [expr {$j+$l-1}]]
5929            set i [expr {$j + $l}]
5930        }
5931    }
5932    return $matches
5933}
5934
5935proc dofind {{dirn 1} {wrap 1}} {
5936    global findstring findstartline findcurline selectedline numcommits
5937    global gdttype filehighlight fh_serial find_dirn findallowwrap
5938
5939    if {[info exists find_dirn]} {
5940        if {$find_dirn == $dirn} return
5941        stopfinding
5942    }
5943    focus .
5944    if {$findstring eq {} || $numcommits == 0} return
5945    if {$selectedline eq {}} {
5946        set findstartline [lindex [visiblerows] [expr {$dirn < 0}]]
5947    } else {
5948        set findstartline $selectedline
5949    }
5950    set findcurline $findstartline
5951    nowbusy finding [mc "Searching"]
5952    if {$gdttype ne [mc "containing:"] && ![info exists filehighlight]} {
5953        after cancel do_file_hl $fh_serial
5954        do_file_hl $fh_serial
5955    }
5956    set find_dirn $dirn
5957    set findallowwrap $wrap
5958    run findmore
5959}
5960
5961proc stopfinding {} {
5962    global find_dirn findcurline fprogcoord
5963
5964    if {[info exists find_dirn]} {
5965        unset find_dirn
5966        unset findcurline
5967        notbusy finding
5968        set fprogcoord 0
5969        adjustprogress
5970    }
5971    stopblaming
5972}
5973
5974proc findmore {} {
5975    global commitdata commitinfo numcommits findpattern findloc
5976    global findstartline findcurline findallowwrap
5977    global find_dirn gdttype fhighlights fprogcoord
5978    global curview varcorder vrownum varccommits vrowmod
5979
5980    if {![info exists find_dirn]} {
5981        return 0
5982    }
5983    set fldtypes [list [mc "Headline"] [mc "Author"] [mc "Date"] [mc "Committer"] [mc "CDate"] [mc "Comments"]]
5984    set l $findcurline
5985    set moretodo 0
5986    if {$find_dirn > 0} {
5987        incr l
5988        if {$l >= $numcommits} {
5989            set l 0
5990        }
5991        if {$l <= $findstartline} {
5992            set lim [expr {$findstartline + 1}]
5993        } else {
5994            set lim $numcommits
5995            set moretodo $findallowwrap
5996        }
5997    } else {
5998        if {$l == 0} {
5999            set l $numcommits
6000        }
6001        incr l -1
6002        if {$l >= $findstartline} {
6003            set lim [expr {$findstartline - 1}]
6004        } else {
6005            set lim -1
6006            set moretodo $findallowwrap
6007        }
6008    }
6009    set n [expr {($lim - $l) * $find_dirn}]
6010    if {$n > 500} {
6011        set n 500
6012        set moretodo 1
6013    }
6014    if {$l + ($find_dirn > 0? $n: 1) > $vrowmod($curview)} {
6015        update_arcrows $curview
6016    }
6017    set found 0
6018    set domore 1
6019    set ai [bsearch $vrownum($curview) $l]
6020    set a [lindex $varcorder($curview) $ai]
6021    set arow [lindex $vrownum($curview) $ai]
6022    set ids [lindex $varccommits($curview,$a)]
6023    set arowend [expr {$arow + [llength $ids]}]
6024    if {$gdttype eq [mc "containing:"]} {
6025        for {} {$n > 0} {incr n -1; incr l $find_dirn} {
6026            if {$l < $arow || $l >= $arowend} {
6027                incr ai $find_dirn
6028                set a [lindex $varcorder($curview) $ai]
6029                set arow [lindex $vrownum($curview) $ai]
6030                set ids [lindex $varccommits($curview,$a)]
6031                set arowend [expr {$arow + [llength $ids]}]
6032            }
6033            set id [lindex $ids [expr {$l - $arow}]]
6034            # shouldn't happen unless git log doesn't give all the commits...
6035            if {![info exists commitdata($id)] ||
6036                ![doesmatch $commitdata($id)]} {
6037                continue
6038            }
6039            if {![info exists commitinfo($id)]} {
6040                getcommit $id
6041            }
6042            set info $commitinfo($id)
6043            foreach f $info ty $fldtypes {
6044                if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
6045                    [doesmatch $f]} {
6046                    set found 1
6047                    break
6048                }
6049            }
6050            if {$found} break
6051        }
6052    } else {
6053        for {} {$n > 0} {incr n -1; incr l $find_dirn} {
6054            if {$l < $arow || $l >= $arowend} {
6055                incr ai $find_dirn
6056                set a [lindex $varcorder($curview) $ai]
6057                set arow [lindex $vrownum($curview) $ai]
6058                set ids [lindex $varccommits($curview,$a)]
6059                set arowend [expr {$arow + [llength $ids]}]
6060            }
6061            set id [lindex $ids [expr {$l - $arow}]]
6062            if {![info exists fhighlights($id)]} {
6063                # this sets fhighlights($id) to -1
6064                askfilehighlight $l $id
6065            }
6066            if {$fhighlights($id) > 0} {
6067                set found $domore
6068                break
6069            }
6070            if {$fhighlights($id) < 0} {
6071                if {$domore} {
6072                    set domore 0
6073                    set findcurline [expr {$l - $find_dirn}]
6074                }
6075            }
6076        }
6077    }
6078    if {$found || ($domore && !$moretodo)} {
6079        unset findcurline
6080        unset find_dirn
6081        notbusy finding
6082        set fprogcoord 0
6083        adjustprogress
6084        if {$found} {
6085            findselectline $l
6086        } else {
6087            bell
6088        }
6089        return 0
6090    }
6091    if {!$domore} {
6092        flushhighlights
6093    } else {
6094        set findcurline [expr {$l - $find_dirn}]
6095    }
6096    set n [expr {($findcurline - $findstartline) * $find_dirn - 1}]
6097    if {$n < 0} {
6098        incr n $numcommits
6099    }
6100    set fprogcoord [expr {$n * 1.0 / $numcommits}]
6101    adjustprogress
6102    return $domore
6103}
6104
6105proc findselectline {l} {
6106    global findloc commentend ctext findcurline markingmatches gdttype
6107
6108    set markingmatches 1
6109    set findcurline $l
6110    selectline $l 1
6111    if {$findloc == [mc "All fields"] || $findloc == [mc "Comments"]} {
6112        # highlight the matches in the comments
6113        set f [$ctext get 1.0 $commentend]
6114        set matches [findmatches $f]
6115        foreach match $matches {
6116            set start [lindex $match 0]
6117            set end [expr {[lindex $match 1] + 1}]
6118            $ctext tag add found "1.0 + $start c" "1.0 + $end c"
6119        }
6120    }
6121    drawvisible
6122}
6123
6124# mark the bits of a headline or author that match a find string
6125proc markmatches {canv l str tag matches font row} {
6126    global selectedline
6127
6128    set bbox [$canv bbox $tag]
6129    set x0 [lindex $bbox 0]
6130    set y0 [lindex $bbox 1]
6131    set y1 [lindex $bbox 3]
6132    foreach match $matches {
6133        set start [lindex $match 0]
6134        set end [lindex $match 1]
6135        if {$start > $end} continue
6136        set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
6137        set xlen [font measure $font [string range $str 0 [expr {$end}]]]
6138        set t [$canv create rect [expr {$x0+$xoff}] $y0 \
6139                   [expr {$x0+$xlen+2}] $y1 \
6140                   -outline {} -tags [list match$l matches] -fill yellow]
6141        $canv lower $t
6142        if {$row == $selectedline} {
6143            $canv raise $t secsel
6144        }
6145    }
6146}
6147
6148proc unmarkmatches {} {
6149    global markingmatches
6150
6151    allcanvs delete matches
6152    set markingmatches 0
6153    stopfinding
6154}
6155
6156proc selcanvline {w x y} {
6157    global canv canvy0 ctext linespc
6158    global rowtextx
6159    set ymax [lindex [$canv cget -scrollregion] 3]
6160    if {$ymax == {}} return
6161    set yfrac [lindex [$canv yview] 0]
6162    set y [expr {$y + $yfrac * $ymax}]
6163    set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
6164    if {$l < 0} {
6165        set l 0
6166    }
6167    if {$w eq $canv} {
6168        set xmax [lindex [$canv cget -scrollregion] 2]
6169        set xleft [expr {[lindex [$canv xview] 0] * $xmax}]
6170        if {![info exists rowtextx($l)] || $xleft + $x < $rowtextx($l)} return
6171    }
6172    unmarkmatches
6173    selectline $l 1
6174}
6175
6176proc commit_descriptor {p} {
6177    global commitinfo
6178    if {![info exists commitinfo($p)]} {
6179        getcommit $p
6180    }
6181    set l "..."
6182    if {[llength $commitinfo($p)] > 1} {
6183        set l [lindex $commitinfo($p) 0]
6184    }
6185    return "$p ($l)\n"
6186}
6187
6188# append some text to the ctext widget, and make any SHA1 ID
6189# that we know about be a clickable link.
6190proc appendwithlinks {text tags} {
6191    global ctext linknum curview
6192
6193    set start [$ctext index "end - 1c"]
6194    $ctext insert end $text $tags
6195    set links [regexp -indices -all -inline {\m[0-9a-f]{6,40}\M} $text]
6196    foreach l $links {
6197        set s [lindex $l 0]
6198        set e [lindex $l 1]
6199        set linkid [string range $text $s $e]
6200        incr e
6201        $ctext tag delete link$linknum
6202        $ctext tag add link$linknum "$start + $s c" "$start + $e c"
6203        setlink $linkid link$linknum
6204        incr linknum
6205    }
6206}
6207
6208proc setlink {id lk} {
6209    global curview ctext pendinglinks
6210
6211    set known 0
6212    if {[string length $id] < 40} {
6213        set matches [longid $id]
6214        if {[llength $matches] > 0} {
6215            if {[llength $matches] > 1} return
6216            set known 1
6217            set id [lindex $matches 0]
6218        }
6219    } else {
6220        set known [commitinview $id $curview]
6221    }
6222    if {$known} {
6223        $ctext tag conf $lk -foreground blue -underline 1
6224        $ctext tag bind $lk <1> [list selbyid $id]
6225        $ctext tag bind $lk <Enter> {linkcursor %W 1}
6226        $ctext tag bind $lk <Leave> {linkcursor %W -1}
6227    } else {
6228        lappend pendinglinks($id) $lk
6229        interestedin $id {makelink %P}
6230    }
6231}
6232
6233proc makelink {id} {
6234    global pendinglinks
6235
6236    if {![info exists pendinglinks($id)]} return
6237    foreach lk $pendinglinks($id) {
6238        setlink $id $lk
6239    }
6240    unset pendinglinks($id)
6241}
6242
6243proc linkcursor {w inc} {
6244    global linkentercount curtextcursor
6245
6246    if {[incr linkentercount $inc] > 0} {
6247        $w configure -cursor hand2
6248    } else {
6249        $w configure -cursor $curtextcursor
6250        if {$linkentercount < 0} {
6251            set linkentercount 0
6252        }
6253    }
6254}
6255
6256proc viewnextline {dir} {
6257    global canv linespc
6258
6259    $canv delete hover
6260    set ymax [lindex [$canv cget -scrollregion] 3]
6261    set wnow [$canv yview]
6262    set wtop [expr {[lindex $wnow 0] * $ymax}]
6263    set newtop [expr {$wtop + $dir * $linespc}]
6264    if {$newtop < 0} {
6265        set newtop 0
6266    } elseif {$newtop > $ymax} {
6267        set newtop $ymax
6268    }
6269    allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
6270}
6271
6272# add a list of tag or branch names at position pos
6273# returns the number of names inserted
6274proc appendrefs {pos ids var} {
6275    global ctext linknum curview $var maxrefs
6276
6277    if {[catch {$ctext index $pos}]} {
6278        return 0
6279    }
6280    $ctext conf -state normal
6281    $ctext delete $pos "$pos lineend"
6282    set tags {}
6283    foreach id $ids {
6284        foreach tag [set $var\($id\)] {
6285            lappend tags [list $tag $id]
6286        }
6287    }
6288    if {[llength $tags] > $maxrefs} {
6289        $ctext insert $pos "many ([llength $tags])"
6290    } else {
6291        set tags [lsort -index 0 -decreasing $tags]
6292        set sep {}
6293        foreach ti $tags {
6294            set id [lindex $ti 1]
6295            set lk link$linknum
6296            incr linknum
6297            $ctext tag delete $lk
6298            $ctext insert $pos $sep
6299            $ctext insert $pos [lindex $ti 0] $lk
6300            setlink $id $lk
6301            set sep ", "
6302        }
6303    }
6304    $ctext conf -state disabled
6305    return [llength $tags]
6306}
6307
6308# called when we have finished computing the nearby tags
6309proc dispneartags {delay} {
6310    global selectedline currentid showneartags tagphase
6311
6312    if {$selectedline eq {} || !$showneartags} return
6313    after cancel dispnexttag
6314    if {$delay} {
6315        after 200 dispnexttag
6316        set tagphase -1
6317    } else {
6318        after idle dispnexttag
6319        set tagphase 0
6320    }
6321}
6322
6323proc dispnexttag {} {
6324    global selectedline currentid showneartags tagphase ctext
6325
6326    if {$selectedline eq {} || !$showneartags} return
6327    switch -- $tagphase {
6328        0 {
6329            set dtags [desctags $currentid]
6330            if {$dtags ne {}} {
6331                appendrefs precedes $dtags idtags
6332            }
6333        }
6334        1 {
6335            set atags [anctags $currentid]
6336            if {$atags ne {}} {
6337                appendrefs follows $atags idtags
6338            }
6339        }
6340        2 {
6341            set dheads [descheads $currentid]
6342            if {$dheads ne {}} {
6343                if {[appendrefs branch $dheads idheads] > 1
6344                    && [$ctext get "branch -3c"] eq "h"} {
6345                    # turn "Branch" into "Branches"
6346                    $ctext conf -state normal
6347                    $ctext insert "branch -2c" "es"
6348                    $ctext conf -state disabled
6349                }
6350            }
6351        }
6352    }
6353    if {[incr tagphase] <= 2} {
6354        after idle dispnexttag
6355    }
6356}
6357
6358proc make_secsel {l} {
6359    global linehtag linentag linedtag canv canv2 canv3
6360
6361    if {![info exists linehtag($l)]} return
6362    $canv delete secsel
6363    set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
6364               -tags secsel -fill [$canv cget -selectbackground]]
6365    $canv lower $t
6366    $canv2 delete secsel
6367    set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
6368               -tags secsel -fill [$canv2 cget -selectbackground]]
6369    $canv2 lower $t
6370    $canv3 delete secsel
6371    set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
6372               -tags secsel -fill [$canv3 cget -selectbackground]]
6373    $canv3 lower $t
6374}
6375
6376proc selectline {l isnew {desired_loc {}}} {
6377    global canv ctext commitinfo selectedline
6378    global canvy0 linespc parents children curview
6379    global currentid sha1entry
6380    global commentend idtags linknum
6381    global mergemax numcommits pending_select
6382    global cmitmode showneartags allcommits
6383    global targetrow targetid lastscrollrows
6384    global autoselect jump_to_here
6385
6386    catch {unset pending_select}
6387    $canv delete hover
6388    normalline
6389    unsel_reflist
6390    stopfinding
6391    if {$l < 0 || $l >= $numcommits} return
6392    set id [commitonrow $l]
6393    set targetid $id
6394    set targetrow $l
6395    set selectedline $l
6396    set currentid $id
6397    if {$lastscrollrows < $numcommits} {
6398        setcanvscroll
6399    }
6400
6401    set y [expr {$canvy0 + $l * $linespc}]
6402    set ymax [lindex [$canv cget -scrollregion] 3]
6403    set ytop [expr {$y - $linespc - 1}]
6404    set ybot [expr {$y + $linespc + 1}]
6405    set wnow [$canv yview]
6406    set wtop [expr {[lindex $wnow 0] * $ymax}]
6407    set wbot [expr {[lindex $wnow 1] * $ymax}]
6408    set wh [expr {$wbot - $wtop}]
6409    set newtop $wtop
6410    if {$ytop < $wtop} {
6411        if {$ybot < $wtop} {
6412            set newtop [expr {$y - $wh / 2.0}]
6413        } else {
6414            set newtop $ytop
6415            if {$newtop > $wtop - $linespc} {
6416                set newtop [expr {$wtop - $linespc}]
6417            }
6418        }
6419    } elseif {$ybot > $wbot} {
6420        if {$ytop > $wbot} {
6421            set newtop [expr {$y - $wh / 2.0}]
6422        } else {
6423            set newtop [expr {$ybot - $wh}]
6424            if {$newtop < $wtop + $linespc} {
6425                set newtop [expr {$wtop + $linespc}]
6426            }
6427        }
6428    }
6429    if {$newtop != $wtop} {
6430        if {$newtop < 0} {
6431            set newtop 0
6432        }
6433        allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
6434        drawvisible
6435    }
6436
6437    make_secsel $l
6438
6439    if {$isnew} {
6440        addtohistory [list selbyid $id]
6441    }
6442
6443    $sha1entry delete 0 end
6444    $sha1entry insert 0 $id
6445    if {$autoselect} {
6446        $sha1entry selection from 0
6447        $sha1entry selection to end
6448    }
6449    rhighlight_sel $id
6450
6451    $ctext conf -state normal
6452    clear_ctext
6453    set linknum 0
6454    if {![info exists commitinfo($id)]} {
6455        getcommit $id
6456    }
6457    set info $commitinfo($id)
6458    set date [formatdate [lindex $info 2]]
6459    $ctext insert end "[mc "Author"]: [lindex $info 1]  $date\n"
6460    set date [formatdate [lindex $info 4]]
6461    $ctext insert end "[mc "Committer"]: [lindex $info 3]  $date\n"
6462    if {[info exists idtags($id)]} {
6463        $ctext insert end [mc "Tags:"]
6464        foreach tag $idtags($id) {
6465            $ctext insert end " $tag"
6466        }
6467        $ctext insert end "\n"
6468    }
6469
6470    set headers {}
6471    set olds $parents($curview,$id)
6472    if {[llength $olds] > 1} {
6473        set np 0
6474        foreach p $olds {
6475            if {$np >= $mergemax} {
6476                set tag mmax
6477            } else {
6478                set tag m$np
6479            }
6480            $ctext insert end "[mc "Parent"]: " $tag
6481            appendwithlinks [commit_descriptor $p] {}
6482            incr np
6483        }
6484    } else {
6485        foreach p $olds {
6486            append headers "[mc "Parent"]: [commit_descriptor $p]"
6487        }
6488    }
6489
6490    foreach c $children($curview,$id) {
6491        append headers "[mc "Child"]:  [commit_descriptor $c]"
6492    }
6493
6494    # make anything that looks like a SHA1 ID be a clickable link
6495    appendwithlinks $headers {}
6496    if {$showneartags} {
6497        if {![info exists allcommits]} {
6498            getallcommits
6499        }
6500        $ctext insert end "[mc "Branch"]: "
6501        $ctext mark set branch "end -1c"
6502        $ctext mark gravity branch left
6503        $ctext insert end "\n[mc "Follows"]: "
6504        $ctext mark set follows "end -1c"
6505        $ctext mark gravity follows left
6506        $ctext insert end "\n[mc "Precedes"]: "
6507        $ctext mark set precedes "end -1c"
6508        $ctext mark gravity precedes left
6509        $ctext insert end "\n"
6510        dispneartags 1
6511    }
6512    $ctext insert end "\n"
6513    set comment [lindex $info 5]
6514    if {[string first "\r" $comment] >= 0} {
6515        set comment [string map {"\r" "\n    "} $comment]
6516    }
6517    appendwithlinks $comment {comment}
6518
6519    $ctext tag remove found 1.0 end
6520    $ctext conf -state disabled
6521    set commentend [$ctext index "end - 1c"]
6522
6523    set jump_to_here $desired_loc
6524    init_flist [mc "Comments"]
6525    if {$cmitmode eq "tree"} {
6526        gettree $id
6527    } elseif {[llength $olds] <= 1} {
6528        startdiff $id
6529    } else {
6530        mergediff $id
6531    }
6532}
6533
6534proc selfirstline {} {
6535    unmarkmatches
6536    selectline 0 1
6537}
6538
6539proc sellastline {} {
6540    global numcommits
6541    unmarkmatches
6542    set l [expr {$numcommits - 1}]
6543    selectline $l 1
6544}
6545
6546proc selnextline {dir} {
6547    global selectedline
6548    focus .
6549    if {$selectedline eq {}} return
6550    set l [expr {$selectedline + $dir}]
6551    unmarkmatches
6552    selectline $l 1
6553}
6554
6555proc selnextpage {dir} {
6556    global canv linespc selectedline numcommits
6557
6558    set lpp [expr {([winfo height $canv] - 2) / $linespc}]
6559    if {$lpp < 1} {
6560        set lpp 1
6561    }
6562    allcanvs yview scroll [expr {$dir * $lpp}] units
6563    drawvisible
6564    if {$selectedline eq {}} return
6565    set l [expr {$selectedline + $dir * $lpp}]
6566    if {$l < 0} {
6567        set l 0
6568    } elseif {$l >= $numcommits} {
6569        set l [expr $numcommits - 1]
6570    }
6571    unmarkmatches
6572    selectline $l 1
6573}
6574
6575proc unselectline {} {
6576    global selectedline currentid
6577
6578    set selectedline {}
6579    catch {unset currentid}
6580    allcanvs delete secsel
6581    rhighlight_none
6582}
6583
6584proc reselectline {} {
6585    global selectedline
6586
6587    if {$selectedline ne {}} {
6588        selectline $selectedline 0
6589    }
6590}
6591
6592proc addtohistory {cmd} {
6593    global history historyindex curview
6594
6595    set elt [list $curview $cmd]
6596    if {$historyindex > 0
6597        && [lindex $history [expr {$historyindex - 1}]] == $elt} {
6598        return
6599    }
6600
6601    if {$historyindex < [llength $history]} {
6602        set history [lreplace $history $historyindex end $elt]
6603    } else {
6604        lappend history $elt
6605    }
6606    incr historyindex
6607    if {$historyindex > 1} {
6608        .tf.bar.leftbut conf -state normal
6609    } else {
6610        .tf.bar.leftbut conf -state disabled
6611    }
6612    .tf.bar.rightbut conf -state disabled
6613}
6614
6615proc godo {elt} {
6616    global curview
6617
6618    set view [lindex $elt 0]
6619    set cmd [lindex $elt 1]
6620    if {$curview != $view} {
6621        showview $view
6622    }
6623    eval $cmd
6624}
6625
6626proc goback {} {
6627    global history historyindex
6628    focus .
6629
6630    if {$historyindex > 1} {
6631        incr historyindex -1
6632        godo [lindex $history [expr {$historyindex - 1}]]
6633        .tf.bar.rightbut conf -state normal
6634    }
6635    if {$historyindex <= 1} {
6636        .tf.bar.leftbut conf -state disabled
6637    }
6638}
6639
6640proc goforw {} {
6641    global history historyindex
6642    focus .
6643
6644    if {$historyindex < [llength $history]} {
6645        set cmd [lindex $history $historyindex]
6646        incr historyindex
6647        godo $cmd
6648        .tf.bar.leftbut conf -state normal
6649    }
6650    if {$historyindex >= [llength $history]} {
6651        .tf.bar.rightbut conf -state disabled
6652    }
6653}
6654
6655proc gettree {id} {
6656    global treefilelist treeidlist diffids diffmergeid treepending
6657    global nullid nullid2
6658
6659    set diffids $id
6660    catch {unset diffmergeid}
6661    if {![info exists treefilelist($id)]} {
6662        if {![info exists treepending]} {
6663            if {$id eq $nullid} {
6664                set cmd [list | git ls-files]
6665            } elseif {$id eq $nullid2} {
6666                set cmd [list | git ls-files --stage -t]
6667            } else {
6668                set cmd [list | git ls-tree -r $id]
6669            }
6670            if {[catch {set gtf [open $cmd r]}]} {
6671                return
6672            }
6673            set treepending $id
6674            set treefilelist($id) {}
6675            set treeidlist($id) {}
6676            fconfigure $gtf -blocking 0 -encoding binary
6677            filerun $gtf [list gettreeline $gtf $id]
6678        }
6679    } else {
6680        setfilelist $id
6681    }
6682}
6683
6684proc gettreeline {gtf id} {
6685    global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
6686
6687    set nl 0
6688    while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
6689        if {$diffids eq $nullid} {
6690            set fname $line
6691        } else {
6692            set i [string first "\t" $line]
6693            if {$i < 0} continue
6694            set fname [string range $line [expr {$i+1}] end]
6695            set line [string range $line 0 [expr {$i-1}]]
6696            if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
6697            set sha1 [lindex $line 2]
6698            lappend treeidlist($id) $sha1
6699        }
6700        if {[string index $fname 0] eq "\""} {
6701            set fname [lindex $fname 0]
6702        }
6703        set fname [encoding convertfrom $fname]
6704        lappend treefilelist($id) $fname
6705    }
6706    if {![eof $gtf]} {
6707        return [expr {$nl >= 1000? 2: 1}]
6708    }
6709    close $gtf
6710    unset treepending
6711    if {$cmitmode ne "tree"} {
6712        if {![info exists diffmergeid]} {
6713            gettreediffs $diffids
6714        }
6715    } elseif {$id ne $diffids} {
6716        gettree $diffids
6717    } else {
6718        setfilelist $id
6719    }
6720    return 0
6721}
6722
6723proc showfile {f} {
6724    global treefilelist treeidlist diffids nullid nullid2
6725    global ctext_file_names ctext_file_lines
6726    global ctext commentend
6727
6728    set i [lsearch -exact $treefilelist($diffids) $f]
6729    if {$i < 0} {
6730        puts "oops, $f not in list for id $diffids"
6731        return
6732    }
6733    if {$diffids eq $nullid} {
6734        if {[catch {set bf [open $f r]} err]} {
6735            puts "oops, can't read $f: $err"
6736            return
6737        }
6738    } else {
6739        set blob [lindex $treeidlist($diffids) $i]
6740        if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
6741            puts "oops, error reading blob $blob: $err"
6742            return
6743        }
6744    }
6745    fconfigure $bf -blocking 0 -encoding [get_path_encoding $f]
6746    filerun $bf [list getblobline $bf $diffids]
6747    $ctext config -state normal
6748    clear_ctext $commentend
6749    lappend ctext_file_names $f
6750    lappend ctext_file_lines [lindex [split $commentend "."] 0]
6751    $ctext insert end "\n"
6752    $ctext insert end "$f\n" filesep
6753    $ctext config -state disabled
6754    $ctext yview $commentend
6755    settabs 0
6756}
6757
6758proc getblobline {bf id} {
6759    global diffids cmitmode ctext
6760
6761    if {$id ne $diffids || $cmitmode ne "tree"} {
6762        catch {close $bf}
6763        return 0
6764    }
6765    $ctext config -state normal
6766    set nl 0
6767    while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
6768        $ctext insert end "$line\n"
6769    }
6770    if {[eof $bf]} {
6771        global jump_to_here ctext_file_names commentend
6772
6773        # delete last newline
6774        $ctext delete "end - 2c" "end - 1c"
6775        close $bf
6776        if {$jump_to_here ne {} &&
6777            [lindex $jump_to_here 0] eq [lindex $ctext_file_names 0]} {
6778            set lnum [expr {[lindex $jump_to_here 1] +
6779                            [lindex [split $commentend .] 0]}]
6780            mark_ctext_line $lnum
6781        }
6782        return 0
6783    }
6784    $ctext config -state disabled
6785    return [expr {$nl >= 1000? 2: 1}]
6786}
6787
6788proc mark_ctext_line {lnum} {
6789    global ctext markbgcolor
6790
6791    $ctext tag delete omark
6792    $ctext tag add omark $lnum.0 "$lnum.0 + 1 line"
6793    $ctext tag conf omark -background $markbgcolor
6794    $ctext see $lnum.0
6795}
6796
6797proc mergediff {id} {
6798    global diffmergeid mdifffd
6799    global diffids treediffs
6800    global parents
6801    global diffcontext
6802    global diffencoding
6803    global limitdiffs vfilelimit curview
6804    global targetline
6805
6806    set diffmergeid $id
6807    set diffids $id
6808    set treediffs($id) {}
6809    set targetline {}
6810    # this doesn't seem to actually affect anything...
6811    set cmd [concat | git diff-tree --no-commit-id --cc -U$diffcontext $id]
6812    if {$limitdiffs && $vfilelimit($curview) ne {}} {
6813        set cmd [concat $cmd -- $vfilelimit($curview)]
6814    }
6815    if {[catch {set mdf [open $cmd r]} err]} {
6816        error_popup "[mc "Error getting merge diffs:"] $err"
6817        return
6818    }
6819    fconfigure $mdf -blocking 0 -encoding binary
6820    set mdifffd($id) $mdf
6821    set np [llength $parents($curview,$id)]
6822    set diffencoding [get_path_encoding {}]
6823    settabs $np
6824    filerun $mdf [list getmergediffline $mdf $id $np]
6825}
6826
6827proc getmergediffline {mdf id np} {
6828    global diffmergeid ctext cflist mergemax
6829    global difffilestart mdifffd treediffs
6830    global ctext_file_names ctext_file_lines
6831    global diffencoding jump_to_here targetline diffline
6832
6833    $ctext conf -state normal
6834    set nr 0
6835    while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
6836        if {![info exists diffmergeid] || $id != $diffmergeid
6837            || $mdf != $mdifffd($id)} {
6838            close $mdf
6839            return 0
6840        }
6841        if {[regexp {^diff --cc (.*)} $line match fname]} {
6842            # start of a new file
6843            set fname [encoding convertfrom $fname]
6844            $ctext insert end "\n"
6845            set here [$ctext index "end - 1c"]
6846            lappend difffilestart $here
6847            lappend treediffs($id) $fname
6848            add_flist [list $fname]
6849            lappend ctext_file_names $fname
6850            lappend ctext_file_lines [lindex [split $here "."] 0]
6851            set diffencoding [get_path_encoding $fname]
6852            set l [expr {(78 - [string length $fname]) / 2}]
6853            set pad [string range "----------------------------------------" 1 $l]
6854            $ctext insert end "$pad $fname $pad\n" filesep
6855            set targetline {}
6856            if {$jump_to_here ne {} && [lindex $jump_to_here 0] eq $fname} {
6857                set targetline [lindex $jump_to_here 1]
6858            }
6859            set diffline 0
6860        } elseif {[regexp {^@@} $line]} {
6861            set line [encoding convertfrom $diffencoding $line]
6862            $ctext insert end "$line\n" hunksep
6863            if {[regexp { \+(\d+),\d+ @@} $line m nl]} {
6864                set diffline $nl
6865            }
6866        } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
6867            # do nothing
6868        } else {
6869            set line [encoding convertfrom $diffencoding $line]
6870            # parse the prefix - one ' ', '-' or '+' for each parent
6871            set spaces {}
6872            set minuses {}
6873            set pluses {}
6874            set isbad 0
6875            for {set j 0} {$j < $np} {incr j} {
6876                set c [string range $line $j $j]
6877                if {$c == " "} {
6878                    lappend spaces $j
6879                } elseif {$c == "-"} {
6880                    lappend minuses $j
6881                } elseif {$c == "+"} {
6882                    lappend pluses $j
6883                } else {
6884                    set isbad 1
6885                    break
6886                }
6887            }
6888            set tags {}
6889            set num {}
6890            if {!$isbad && $minuses ne {} && $pluses eq {}} {
6891                # line doesn't appear in result, parents in $minuses have the line
6892                set num [lindex $minuses 0]
6893            } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
6894                # line appears in result, parents in $pluses don't have the line
6895                lappend tags mresult
6896                set num [lindex $spaces 0]
6897            }
6898            if {$num ne {}} {
6899                if {$num >= $mergemax} {
6900                    set num "max"
6901                }
6902                lappend tags m$num
6903            }
6904            $ctext insert end "$line\n" $tags
6905            if {$targetline ne {} && $minuses eq {}} {
6906                if {$diffline == $targetline} {
6907                    set here [$ctext index "end - 1 line"]
6908                    mark_ctext_line [lindex [split $here .] 0]
6909                    set targetline {}
6910                } else {
6911                    incr diffline
6912                }
6913            }
6914        }
6915    }
6916    $ctext conf -state disabled
6917    if {[eof $mdf]} {
6918        close $mdf
6919        return 0
6920    }
6921    return [expr {$nr >= 1000? 2: 1}]
6922}
6923
6924proc startdiff {ids} {
6925    global treediffs diffids treepending diffmergeid nullid nullid2
6926
6927    settabs 1
6928    set diffids $ids
6929    catch {unset diffmergeid}
6930    if {![info exists treediffs($ids)] ||
6931        [lsearch -exact $ids $nullid] >= 0 ||
6932        [lsearch -exact $ids $nullid2] >= 0} {
6933        if {![info exists treepending]} {
6934            gettreediffs $ids
6935        }
6936    } else {
6937        addtocflist $ids
6938    }
6939}
6940
6941proc path_filter {filter name} {
6942    foreach p $filter {
6943        set l [string length $p]
6944        if {[string index $p end] eq "/"} {
6945            if {[string compare -length $l $p $name] == 0} {
6946                return 1
6947            }
6948        } else {
6949            if {[string compare -length $l $p $name] == 0 &&
6950                ([string length $name] == $l ||
6951                 [string index $name $l] eq "/")} {
6952                return 1
6953            }
6954        }
6955    }
6956    return 0
6957}
6958
6959proc addtocflist {ids} {
6960    global treediffs
6961
6962    add_flist $treediffs($ids)
6963    getblobdiffs $ids
6964}
6965
6966proc diffcmd {ids flags} {
6967    global nullid nullid2
6968
6969    set i [lsearch -exact $ids $nullid]
6970    set j [lsearch -exact $ids $nullid2]
6971    if {$i >= 0} {
6972        if {[llength $ids] > 1 && $j < 0} {
6973            # comparing working directory with some specific revision
6974            set cmd [concat | git diff-index $flags]
6975            if {$i == 0} {
6976                lappend cmd -R [lindex $ids 1]
6977            } else {
6978                lappend cmd [lindex $ids 0]
6979            }
6980        } else {
6981            # comparing working directory with index
6982            set cmd [concat | git diff-files $flags]
6983            if {$j == 1} {
6984                lappend cmd -R
6985            }
6986        }
6987    } elseif {$j >= 0} {
6988        set cmd [concat | git diff-index --cached $flags]
6989        if {[llength $ids] > 1} {
6990            # comparing index with specific revision
6991            if {$i == 0} {
6992                lappend cmd -R [lindex $ids 1]
6993            } else {
6994                lappend cmd [lindex $ids 0]
6995            }
6996        } else {
6997            # comparing index with HEAD
6998            lappend cmd HEAD
6999        }
7000    } else {
7001        set cmd [concat | git diff-tree -r $flags $ids]
7002    }
7003    return $cmd
7004}
7005
7006proc gettreediffs {ids} {
7007    global treediff treepending
7008
7009    if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
7010
7011    set treepending $ids
7012    set treediff {}
7013    fconfigure $gdtf -blocking 0 -encoding binary
7014    filerun $gdtf [list gettreediffline $gdtf $ids]
7015}
7016
7017proc gettreediffline {gdtf ids} {
7018    global treediff treediffs treepending diffids diffmergeid
7019    global cmitmode vfilelimit curview limitdiffs perfile_attrs
7020
7021    set nr 0
7022    set sublist {}
7023    set max 1000
7024    if {$perfile_attrs} {
7025        # cache_gitattr is slow, and even slower on win32 where we
7026        # have to invoke it for only about 30 paths at a time
7027        set max 500
7028        if {[tk windowingsystem] == "win32"} {
7029            set max 120
7030        }
7031    }
7032    while {[incr nr] <= $max && [gets $gdtf line] >= 0} {
7033        set i [string first "\t" $line]
7034        if {$i >= 0} {
7035            set file [string range $line [expr {$i+1}] end]
7036            if {[string index $file 0] eq "\""} {
7037                set file [lindex $file 0]
7038            }
7039            set file [encoding convertfrom $file]
7040            lappend treediff $file
7041            lappend sublist $file
7042        }
7043    }
7044    if {$perfile_attrs} {
7045        cache_gitattr encoding $sublist
7046    }
7047    if {![eof $gdtf]} {
7048        return [expr {$nr >= $max? 2: 1}]
7049    }
7050    close $gdtf
7051    if {$limitdiffs && $vfilelimit($curview) ne {}} {
7052        set flist {}
7053        foreach f $treediff {
7054            if {[path_filter $vfilelimit($curview) $f]} {
7055                lappend flist $f
7056            }
7057        }
7058        set treediffs($ids) $flist
7059    } else {
7060        set treediffs($ids) $treediff
7061    }
7062    unset treepending
7063    if {$cmitmode eq "tree"} {
7064        gettree $diffids
7065    } elseif {$ids != $diffids} {
7066        if {![info exists diffmergeid]} {
7067            gettreediffs $diffids
7068        }
7069    } else {
7070        addtocflist $ids
7071    }
7072    return 0
7073}
7074
7075# empty string or positive integer
7076proc diffcontextvalidate {v} {
7077    return [regexp {^(|[1-9][0-9]*)$} $v]
7078}
7079
7080proc diffcontextchange {n1 n2 op} {
7081    global diffcontextstring diffcontext
7082
7083    if {[string is integer -strict $diffcontextstring]} {
7084        if {$diffcontextstring > 0} {
7085            set diffcontext $diffcontextstring
7086            reselectline
7087        }
7088    }
7089}
7090
7091proc changeignorespace {} {
7092    reselectline
7093}
7094
7095proc getblobdiffs {ids} {
7096    global blobdifffd diffids env
7097    global diffinhdr treediffs
7098    global diffcontext
7099    global ignorespace
7100    global limitdiffs vfilelimit curview
7101    global diffencoding targetline
7102
7103    set cmd [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"]
7104    if {$ignorespace} {
7105        append cmd " -w"
7106    }
7107    if {$limitdiffs && $vfilelimit($curview) ne {}} {
7108        set cmd [concat $cmd -- $vfilelimit($curview)]
7109    }
7110    if {[catch {set bdf [open $cmd r]} err]} {
7111        puts "error getting diffs: $err"
7112        return
7113    }
7114    set targetline {}
7115    set diffinhdr 0
7116    set diffencoding [get_path_encoding {}]
7117    fconfigure $bdf -blocking 0 -encoding binary
7118    set blobdifffd($ids) $bdf
7119    filerun $bdf [list getblobdiffline $bdf $diffids]
7120}
7121
7122proc setinlist {var i val} {
7123    global $var
7124
7125    while {[llength [set $var]] < $i} {
7126        lappend $var {}
7127    }
7128    if {[llength [set $var]] == $i} {
7129        lappend $var $val
7130    } else {
7131        lset $var $i $val
7132    }
7133}
7134
7135proc makediffhdr {fname ids} {
7136    global ctext curdiffstart treediffs
7137    global ctext_file_names jump_to_here targetline diffline
7138
7139    set i [lsearch -exact $treediffs($ids) $fname]
7140    if {$i >= 0} {
7141        setinlist difffilestart $i $curdiffstart
7142    }
7143    set ctext_file_names [lreplace $ctext_file_names end end $fname]
7144    set l [expr {(78 - [string length $fname]) / 2}]
7145    set pad [string range "----------------------------------------" 1 $l]
7146    $ctext insert $curdiffstart "$pad $fname $pad" filesep
7147    set targetline {}
7148    if {$jump_to_here ne {} && [lindex $jump_to_here 0] eq $fname} {
7149        set targetline [lindex $jump_to_here 1]
7150    }
7151    set diffline 0
7152}
7153
7154proc getblobdiffline {bdf ids} {
7155    global diffids blobdifffd ctext curdiffstart
7156    global diffnexthead diffnextnote difffilestart
7157    global ctext_file_names ctext_file_lines
7158    global diffinhdr treediffs
7159    global diffencoding jump_to_here targetline diffline
7160
7161    set nr 0
7162    $ctext conf -state normal
7163    while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
7164        if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
7165            close $bdf
7166            return 0
7167        }
7168        if {![string compare -length 11 "diff --git " $line]} {
7169            # trim off "diff --git "
7170            set line [string range $line 11 end]
7171            set diffinhdr 1
7172            # start of a new file
7173            $ctext insert end "\n"
7174            set curdiffstart [$ctext index "end - 1c"]
7175            lappend ctext_file_names ""
7176            lappend ctext_file_lines [lindex [split $curdiffstart "."] 0]
7177            $ctext insert end "\n" filesep
7178            # If the name hasn't changed the length will be odd,
7179            # the middle char will be a space, and the two bits either
7180            # side will be a/name and b/name, or "a/name" and "b/name".
7181            # If the name has changed we'll get "rename from" and
7182            # "rename to" or "copy from" and "copy to" lines following this,
7183            # and we'll use them to get the filenames.
7184            # This complexity is necessary because spaces in the filename(s)
7185            # don't get escaped.
7186            set l [string length $line]
7187            set i [expr {$l / 2}]
7188            if {!(($l & 1) && [string index $line $i] eq " " &&
7189                  [string range $line 2 [expr {$i - 1}]] eq \
7190                      [string range $line [expr {$i + 3}] end])} {
7191                continue
7192            }
7193            # unescape if quoted and chop off the a/ from the front
7194            if {[string index $line 0] eq "\""} {
7195                set fname [string range [lindex $line 0] 2 end]
7196            } else {
7197                set fname [string range $line 2 [expr {$i - 1}]]
7198            }
7199            set fname [encoding convertfrom $fname]
7200            set diffencoding [get_path_encoding $fname]
7201            makediffhdr $fname $ids
7202
7203        } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \
7204                       $line match f1l f1c f2l f2c rest]} {
7205            set line [encoding convertfrom $diffencoding $line]
7206            $ctext insert end "$line\n" hunksep
7207            set diffinhdr 0
7208            set diffline $f2l
7209
7210        } elseif {$diffinhdr} {
7211            if {![string compare -length 12 "rename from " $line]} {
7212                set fname [string range $line [expr 6 + [string first " from " $line] ] end]
7213                if {[string index $fname 0] eq "\""} {
7214                    set fname [lindex $fname 0]
7215                }
7216                set fname [encoding convertfrom $fname]
7217                set i [lsearch -exact $treediffs($ids) $fname]
7218                if {$i >= 0} {
7219                    setinlist difffilestart $i $curdiffstart
7220                }
7221            } elseif {![string compare -length 10 $line "rename to "] ||
7222                      ![string compare -length 8 $line "copy to "]} {
7223                set fname [string range $line [expr 4 + [string first " to " $line] ] end]
7224                if {[string index $fname 0] eq "\""} {
7225                    set fname [lindex $fname 0]
7226                }
7227                set fname [encoding convertfrom $fname]
7228                set diffencoding [get_path_encoding $fname]
7229                makediffhdr $fname $ids
7230            } elseif {[string compare -length 3 $line "---"] == 0} {
7231                # do nothing
7232                continue
7233            } elseif {[string compare -length 3 $line "+++"] == 0} {
7234                set diffinhdr 0
7235                continue
7236            }
7237            $ctext insert end "$line\n" filesep
7238
7239        } else {
7240            set line [encoding convertfrom $diffencoding $line]
7241            set x [string range $line 0 0]
7242            set here [$ctext index "end - 1 chars"]
7243            if {$x == "-" || $x == "+"} {
7244                set tag [expr {$x == "+"}]
7245                $ctext insert end "$line\n" d$tag
7246            } elseif {$x == " "} {
7247                $ctext insert end "$line\n"
7248            } else {
7249                # "\ No newline at end of file",
7250                # or something else we don't recognize
7251                $ctext insert end "$line\n" hunksep
7252            }
7253            if {$targetline ne {} && ($x eq " " || $x eq "+")} {
7254                if {$diffline == $targetline} {
7255                    mark_ctext_line [lindex [split $here .] 0]
7256                    set targetline {}
7257                } else {
7258                    incr diffline
7259                }
7260            }
7261        }
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 d1 -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 d1 -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 {}