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