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