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