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