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