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