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