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