gitkon commit gitk: Limit diff display to listed paths by default (7a39a17)
   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
4917    set diffmergeid $id
4918    set diffids $id
4919    # this doesn't seem to actually affect anything...
4920    set env(GIT_DIFF_OPTS) $diffopts
4921    set cmd [concat | git diff-tree --no-commit-id --cc $id]
4922    if {[catch {set mdf [open $cmd r]} err]} {
4923        error_popup "Error getting merge diffs: $err"
4924        return
4925    }
4926    fconfigure $mdf -blocking 0
4927    set mdifffd($id) $mdf
4928    set np [llength [lindex $parentlist $l]]
4929    filerun $mdf [list getmergediffline $mdf $id $np]
4930}
4931
4932proc getmergediffline {mdf id np} {
4933    global diffmergeid ctext cflist mergemax
4934    global difffilestart mdifffd
4935
4936    $ctext conf -state normal
4937    set nr 0
4938    while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
4939        if {![info exists diffmergeid] || $id != $diffmergeid
4940            || $mdf != $mdifffd($id)} {
4941            close $mdf
4942            return 0
4943        }
4944        if {[regexp {^diff --cc (.*)} $line match fname]} {
4945            # start of a new file
4946            $ctext insert end "\n"
4947            set here [$ctext index "end - 1c"]
4948            lappend difffilestart $here
4949            add_flist [list $fname]
4950            set l [expr {(78 - [string length $fname]) / 2}]
4951            set pad [string range "----------------------------------------" 1 $l]
4952            $ctext insert end "$pad $fname $pad\n" filesep
4953        } elseif {[regexp {^@@} $line]} {
4954            $ctext insert end "$line\n" hunksep
4955        } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
4956            # do nothing
4957        } else {
4958            # parse the prefix - one ' ', '-' or '+' for each parent
4959            set spaces {}
4960            set minuses {}
4961            set pluses {}
4962            set isbad 0
4963            for {set j 0} {$j < $np} {incr j} {
4964                set c [string range $line $j $j]
4965                if {$c == " "} {
4966                    lappend spaces $j
4967                } elseif {$c == "-"} {
4968                    lappend minuses $j
4969                } elseif {$c == "+"} {
4970                    lappend pluses $j
4971                } else {
4972                    set isbad 1
4973                    break
4974                }
4975            }
4976            set tags {}
4977            set num {}
4978            if {!$isbad && $minuses ne {} && $pluses eq {}} {
4979                # line doesn't appear in result, parents in $minuses have the line
4980                set num [lindex $minuses 0]
4981            } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
4982                # line appears in result, parents in $pluses don't have the line
4983                lappend tags mresult
4984                set num [lindex $spaces 0]
4985            }
4986            if {$num ne {}} {
4987                if {$num >= $mergemax} {
4988                    set num "max"
4989                }
4990                lappend tags m$num
4991            }
4992            $ctext insert end "$line\n" $tags
4993        }
4994    }
4995    $ctext conf -state disabled
4996    if {[eof $mdf]} {
4997        close $mdf
4998        return 0
4999    }
5000    return [expr {$nr >= 1000? 2: 1}]
5001}
5002
5003proc startdiff {ids} {
5004    global treediffs diffids treepending diffmergeid nullid nullid2
5005
5006    set diffids $ids
5007    catch {unset diffmergeid}
5008    if {![info exists treediffs($ids)] ||
5009        [lsearch -exact $ids $nullid] >= 0 ||
5010        [lsearch -exact $ids $nullid2] >= 0} {
5011        if {![info exists treepending]} {
5012            gettreediffs $ids
5013        }
5014    } else {
5015        addtocflist $ids
5016    }
5017}
5018
5019proc path_filter {filter name} {
5020    foreach p $filter {
5021        set l [string length $p]
5022        if {[string compare -length $l $p $name] == 0 &&
5023            ([string length $name] == $l || [string index $name $l] eq "/")} {
5024            return 1
5025        }
5026    }
5027    return 0
5028}
5029
5030proc addtocflist {ids} {
5031    global treediffs cflist viewfiles curview limitdiffs
5032
5033    if {$limitdiffs && $viewfiles($curview) ne {}} {
5034        set flist {}
5035        foreach f $treediffs($ids) {
5036            if {[path_filter $viewfiles($curview) $f]} {
5037                lappend flist $f
5038            }
5039        }
5040    } else {
5041        set flist $treediffs($ids)
5042    }
5043    add_flist $flist
5044    getblobdiffs $ids
5045}
5046
5047proc diffcmd {ids flags} {
5048    global nullid nullid2
5049
5050    set i [lsearch -exact $ids $nullid]
5051    set j [lsearch -exact $ids $nullid2]
5052    if {$i >= 0} {
5053        if {[llength $ids] > 1 && $j < 0} {
5054            # comparing working directory with some specific revision
5055            set cmd [concat | git diff-index $flags]
5056            if {$i == 0} {
5057                lappend cmd -R [lindex $ids 1]
5058            } else {
5059                lappend cmd [lindex $ids 0]
5060            }
5061        } else {
5062            # comparing working directory with index
5063            set cmd [concat | git diff-files $flags]
5064            if {$j == 1} {
5065                lappend cmd -R
5066            }
5067        }
5068    } elseif {$j >= 0} {
5069        set cmd [concat | git diff-index --cached $flags]
5070        if {[llength $ids] > 1} {
5071            # comparing index with specific revision
5072            if {$i == 0} {
5073                lappend cmd -R [lindex $ids 1]
5074            } else {
5075                lappend cmd [lindex $ids 0]
5076            }
5077        } else {
5078            # comparing index with HEAD
5079            lappend cmd HEAD
5080        }
5081    } else {
5082        set cmd [concat | git diff-tree -r $flags $ids]
5083    }
5084    return $cmd
5085}
5086
5087proc gettreediffs {ids} {
5088    global treediff treepending
5089
5090    set treepending $ids
5091    set treediff {}
5092    if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
5093    fconfigure $gdtf -blocking 0
5094    filerun $gdtf [list gettreediffline $gdtf $ids]
5095}
5096
5097proc gettreediffline {gdtf ids} {
5098    global treediff treediffs treepending diffids diffmergeid
5099    global cmitmode
5100
5101    set nr 0
5102    while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} {
5103        set i [string first "\t" $line]
5104        if {$i >= 0} {
5105            set file [string range $line [expr {$i+1}] end]
5106            if {[string index $file 0] eq "\""} {
5107                set file [lindex $file 0]
5108            }
5109            lappend treediff $file
5110        }
5111    }
5112    if {![eof $gdtf]} {
5113        return [expr {$nr >= 1000? 2: 1}]
5114    }
5115    close $gdtf
5116    set treediffs($ids) $treediff
5117    unset treepending
5118    if {$cmitmode eq "tree"} {
5119        gettree $diffids
5120    } elseif {$ids != $diffids} {
5121        if {![info exists diffmergeid]} {
5122            gettreediffs $diffids
5123        }
5124    } else {
5125        addtocflist $ids
5126    }
5127    return 0
5128}
5129
5130# empty string or positive integer
5131proc diffcontextvalidate {v} {
5132    return [regexp {^(|[1-9][0-9]*)$} $v]
5133}
5134
5135proc diffcontextchange {n1 n2 op} {
5136    global diffcontextstring diffcontext
5137
5138    if {[string is integer -strict $diffcontextstring]} {
5139        if {$diffcontextstring > 0} {
5140            set diffcontext $diffcontextstring
5141            reselectline
5142        }
5143    }
5144}
5145
5146proc getblobdiffs {ids} {
5147    global diffopts blobdifffd diffids env
5148    global diffinhdr treediffs
5149    global diffcontext
5150    global limitdiffs viewfiles curview
5151
5152    set env(GIT_DIFF_OPTS) $diffopts
5153    set cmd [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"]
5154    if {$limitdiffs && $viewfiles($curview) ne {}} {
5155        set cmd [concat $cmd $viewfiles($curview)]
5156    }
5157    if {[catch {set bdf [open $cmd r]} err]} {
5158        puts "error getting diffs: $err"
5159        return
5160    }
5161    set diffinhdr 0
5162    fconfigure $bdf -blocking 0
5163    set blobdifffd($ids) $bdf
5164    filerun $bdf [list getblobdiffline $bdf $diffids]
5165}
5166
5167proc setinlist {var i val} {
5168    global $var
5169
5170    while {[llength [set $var]] < $i} {
5171        lappend $var {}
5172    }
5173    if {[llength [set $var]] == $i} {
5174        lappend $var $val
5175    } else {
5176        lset $var $i $val
5177    }
5178}
5179
5180proc makediffhdr {fname ids} {
5181    global ctext curdiffstart treediffs
5182
5183    set i [lsearch -exact $treediffs($ids) $fname]
5184    if {$i >= 0} {
5185        setinlist difffilestart $i $curdiffstart
5186    }
5187    set l [expr {(78 - [string length $fname]) / 2}]
5188    set pad [string range "----------------------------------------" 1 $l]
5189    $ctext insert $curdiffstart "$pad $fname $pad" filesep
5190}
5191
5192proc getblobdiffline {bdf ids} {
5193    global diffids blobdifffd ctext curdiffstart
5194    global diffnexthead diffnextnote difffilestart
5195    global diffinhdr treediffs
5196
5197    set nr 0
5198    $ctext conf -state normal
5199    while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
5200        if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
5201            close $bdf
5202            return 0
5203        }
5204        if {![string compare -length 11 "diff --git " $line]} {
5205            # trim off "diff --git "
5206            set line [string range $line 11 end]
5207            set diffinhdr 1
5208            # start of a new file
5209            $ctext insert end "\n"
5210            set curdiffstart [$ctext index "end - 1c"]
5211            $ctext insert end "\n" filesep
5212            # If the name hasn't changed the length will be odd,
5213            # the middle char will be a space, and the two bits either
5214            # side will be a/name and b/name, or "a/name" and "b/name".
5215            # If the name has changed we'll get "rename from" and
5216            # "rename to" or "copy from" and "copy to" lines following this,
5217            # and we'll use them to get the filenames.
5218            # This complexity is necessary because spaces in the filename(s)
5219            # don't get escaped.
5220            set l [string length $line]
5221            set i [expr {$l / 2}]
5222            if {!(($l & 1) && [string index $line $i] eq " " &&
5223                  [string range $line 2 [expr {$i - 1}]] eq \
5224                      [string range $line [expr {$i + 3}] end])} {
5225                continue
5226            }
5227            # unescape if quoted and chop off the a/ from the front
5228            if {[string index $line 0] eq "\""} {
5229                set fname [string range [lindex $line 0] 2 end]
5230            } else {
5231                set fname [string range $line 2 [expr {$i - 1}]]
5232            }
5233            makediffhdr $fname $ids
5234
5235        } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \
5236                       $line match f1l f1c f2l f2c rest]} {
5237            $ctext insert end "$line\n" hunksep
5238            set diffinhdr 0
5239
5240        } elseif {$diffinhdr} {
5241            if {![string compare -length 12 "rename from " $line]} {
5242                set fname [string range $line [expr 6 + [string first " from " $line] ] end]
5243                if {[string index $fname 0] eq "\""} {
5244                    set fname [lindex $fname 0]
5245                }
5246                set i [lsearch -exact $treediffs($ids) $fname]
5247                if {$i >= 0} {
5248                    setinlist difffilestart $i $curdiffstart
5249                }
5250            } elseif {![string compare -length 10 $line "rename to "] ||
5251                      ![string compare -length 8 $line "copy to "]} {
5252                set fname [string range $line [expr 4 + [string first " to " $line] ] end]
5253                if {[string index $fname 0] eq "\""} {
5254                    set fname [lindex $fname 0]
5255                }
5256                makediffhdr $fname $ids
5257            } elseif {[string compare -length 3 $line "---"] == 0} {
5258                # do nothing
5259                continue
5260            } elseif {[string compare -length 3 $line "+++"] == 0} {
5261                set diffinhdr 0
5262                continue
5263            }
5264            $ctext insert end "$line\n" filesep
5265
5266        } else {
5267            set x [string range $line 0 0]
5268            if {$x == "-" || $x == "+"} {
5269                set tag [expr {$x == "+"}]
5270                $ctext insert end "$line\n" d$tag
5271            } elseif {$x == " "} {
5272                $ctext insert end "$line\n"
5273            } else {
5274                # "\ No newline at end of file",
5275                # or something else we don't recognize
5276                $ctext insert end "$line\n" hunksep
5277            }
5278        }
5279    }
5280    $ctext conf -state disabled
5281    if {[eof $bdf]} {
5282        close $bdf
5283        return 0
5284    }
5285    return [expr {$nr >= 1000? 2: 1}]
5286}
5287
5288proc changediffdisp {} {
5289    global ctext diffelide
5290
5291    $ctext tag conf d0 -elide [lindex $diffelide 0]
5292    $ctext tag conf d1 -elide [lindex $diffelide 1]
5293}
5294
5295proc prevfile {} {
5296    global difffilestart ctext
5297    set prev [lindex $difffilestart 0]
5298    set here [$ctext index @0,0]
5299    foreach loc $difffilestart {
5300        if {[$ctext compare $loc >= $here]} {
5301            $ctext yview $prev
5302            return
5303        }
5304        set prev $loc
5305    }
5306    $ctext yview $prev
5307}
5308
5309proc nextfile {} {
5310    global difffilestart ctext
5311    set here [$ctext index @0,0]
5312    foreach loc $difffilestart {
5313        if {[$ctext compare $loc > $here]} {
5314            $ctext yview $loc
5315            return
5316        }
5317    }
5318}
5319
5320proc clear_ctext {{first 1.0}} {
5321    global ctext smarktop smarkbot
5322
5323    set l [lindex [split $first .] 0]
5324    if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
5325        set smarktop $l
5326    }
5327    if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
5328        set smarkbot $l
5329    }
5330    $ctext delete $first end
5331}
5332
5333proc incrsearch {name ix op} {
5334    global ctext searchstring searchdirn
5335
5336    $ctext tag remove found 1.0 end
5337    if {[catch {$ctext index anchor}]} {
5338        # no anchor set, use start of selection, or of visible area
5339        set sel [$ctext tag ranges sel]
5340        if {$sel ne {}} {
5341            $ctext mark set anchor [lindex $sel 0]
5342        } elseif {$searchdirn eq "-forwards"} {
5343            $ctext mark set anchor @0,0
5344        } else {
5345            $ctext mark set anchor @0,[winfo height $ctext]
5346        }
5347    }
5348    if {$searchstring ne {}} {
5349        set here [$ctext search $searchdirn -- $searchstring anchor]
5350        if {$here ne {}} {
5351            $ctext see $here
5352        }
5353        searchmarkvisible 1
5354    }
5355}
5356
5357proc dosearch {} {
5358    global sstring ctext searchstring searchdirn
5359
5360    focus $sstring
5361    $sstring icursor end
5362    set searchdirn -forwards
5363    if {$searchstring ne {}} {
5364        set sel [$ctext tag ranges sel]
5365        if {$sel ne {}} {
5366            set start "[lindex $sel 0] + 1c"
5367        } elseif {[catch {set start [$ctext index anchor]}]} {
5368            set start "@0,0"
5369        }
5370        set match [$ctext search -count mlen -- $searchstring $start]
5371        $ctext tag remove sel 1.0 end
5372        if {$match eq {}} {
5373            bell
5374            return
5375        }
5376        $ctext see $match
5377        set mend "$match + $mlen c"
5378        $ctext tag add sel $match $mend
5379        $ctext mark unset anchor
5380    }
5381}
5382
5383proc dosearchback {} {
5384    global sstring ctext searchstring searchdirn
5385
5386    focus $sstring
5387    $sstring icursor end
5388    set searchdirn -backwards
5389    if {$searchstring ne {}} {
5390        set sel [$ctext tag ranges sel]
5391        if {$sel ne {}} {
5392            set start [lindex $sel 0]
5393        } elseif {[catch {set start [$ctext index anchor]}]} {
5394            set start @0,[winfo height $ctext]
5395        }
5396        set match [$ctext search -backwards -count ml -- $searchstring $start]
5397        $ctext tag remove sel 1.0 end
5398        if {$match eq {}} {
5399            bell
5400            return
5401        }
5402        $ctext see $match
5403        set mend "$match + $ml c"
5404        $ctext tag add sel $match $mend
5405        $ctext mark unset anchor
5406    }
5407}
5408
5409proc searchmark {first last} {
5410    global ctext searchstring
5411
5412    set mend $first.0
5413    while {1} {
5414        set match [$ctext search -count mlen -- $searchstring $mend $last.end]
5415        if {$match eq {}} break
5416        set mend "$match + $mlen c"
5417        $ctext tag add found $match $mend
5418    }
5419}
5420
5421proc searchmarkvisible {doall} {
5422    global ctext smarktop smarkbot
5423
5424    set topline [lindex [split [$ctext index @0,0] .] 0]
5425    set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
5426    if {$doall || $botline < $smarktop || $topline > $smarkbot} {
5427        # no overlap with previous
5428        searchmark $topline $botline
5429        set smarktop $topline
5430        set smarkbot $botline
5431    } else {
5432        if {$topline < $smarktop} {
5433            searchmark $topline [expr {$smarktop-1}]
5434            set smarktop $topline
5435        }
5436        if {$botline > $smarkbot} {
5437            searchmark [expr {$smarkbot+1}] $botline
5438            set smarkbot $botline
5439        }
5440    }
5441}
5442
5443proc scrolltext {f0 f1} {
5444    global searchstring
5445
5446    .bleft.sb set $f0 $f1
5447    if {$searchstring ne {}} {
5448        searchmarkvisible 0
5449    }
5450}
5451
5452proc setcoords {} {
5453    global linespc charspc canvx0 canvy0 mainfont
5454    global xspc1 xspc2 lthickness
5455
5456    set linespc [font metrics $mainfont -linespace]
5457    set charspc [font measure $mainfont "m"]
5458    set canvy0 [expr {int(3 + 0.5 * $linespc)}]
5459    set canvx0 [expr {int(3 + 0.5 * $linespc)}]
5460    set lthickness [expr {int($linespc / 9) + 1}]
5461    set xspc1(0) $linespc
5462    set xspc2 $linespc
5463}
5464
5465proc redisplay {} {
5466    global canv
5467    global selectedline
5468
5469    set ymax [lindex [$canv cget -scrollregion] 3]
5470    if {$ymax eq {} || $ymax == 0} return
5471    set span [$canv yview]
5472    clear_display
5473    setcanvscroll
5474    allcanvs yview moveto [lindex $span 0]
5475    drawvisible
5476    if {[info exists selectedline]} {
5477        selectline $selectedline 0
5478        allcanvs yview moveto [lindex $span 0]
5479    }
5480}
5481
5482proc incrfont {inc} {
5483    global mainfont textfont ctext canv phase cflist showrefstop
5484    global charspc tabstop
5485    global stopped entries
5486    unmarkmatches
5487    set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
5488    set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
5489    setcoords
5490    $ctext conf -font $textfont -tabs "[expr {$tabstop * $charspc}]"
5491    $cflist conf -font $textfont
5492    $ctext tag conf filesep -font [concat $textfont bold]
5493    foreach e $entries {
5494        $e conf -font $mainfont
5495    }
5496    if {$phase eq "getcommits"} {
5497        $canv itemconf textitems -font $mainfont
5498    }
5499    if {[info exists showrefstop] && [winfo exists $showrefstop]} {
5500        $showrefstop.list conf -font $mainfont
5501    }
5502    redisplay
5503}
5504
5505proc clearsha1 {} {
5506    global sha1entry sha1string
5507    if {[string length $sha1string] == 40} {
5508        $sha1entry delete 0 end
5509    }
5510}
5511
5512proc sha1change {n1 n2 op} {
5513    global sha1string currentid sha1but
5514    if {$sha1string == {}
5515        || ([info exists currentid] && $sha1string == $currentid)} {
5516        set state disabled
5517    } else {
5518        set state normal
5519    }
5520    if {[$sha1but cget -state] == $state} return
5521    if {$state == "normal"} {
5522        $sha1but conf -state normal -relief raised -text "Goto: "
5523    } else {
5524        $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
5525    }
5526}
5527
5528proc gotocommit {} {
5529    global sha1string currentid commitrow tagids headids
5530    global displayorder numcommits curview
5531
5532    if {$sha1string == {}
5533        || ([info exists currentid] && $sha1string == $currentid)} return
5534    if {[info exists tagids($sha1string)]} {
5535        set id $tagids($sha1string)
5536    } elseif {[info exists headids($sha1string)]} {
5537        set id $headids($sha1string)
5538    } else {
5539        set id [string tolower $sha1string]
5540        if {[regexp {^[0-9a-f]{4,39}$} $id]} {
5541            set matches {}
5542            foreach i $displayorder {
5543                if {[string match $id* $i]} {
5544                    lappend matches $i
5545                }
5546            }
5547            if {$matches ne {}} {
5548                if {[llength $matches] > 1} {
5549                    error_popup "Short SHA1 id $id is ambiguous"
5550                    return
5551                }
5552                set id [lindex $matches 0]
5553            }
5554        }
5555    }
5556    if {[info exists commitrow($curview,$id)]} {
5557        selectline $commitrow($curview,$id) 1
5558        return
5559    }
5560    if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
5561        set type "SHA1 id"
5562    } else {
5563        set type "Tag/Head"
5564    }
5565    error_popup "$type $sha1string is not known"
5566}
5567
5568proc lineenter {x y id} {
5569    global hoverx hovery hoverid hovertimer
5570    global commitinfo canv
5571
5572    if {![info exists commitinfo($id)] && ![getcommit $id]} return
5573    set hoverx $x
5574    set hovery $y
5575    set hoverid $id
5576    if {[info exists hovertimer]} {
5577        after cancel $hovertimer
5578    }
5579    set hovertimer [after 500 linehover]
5580    $canv delete hover
5581}
5582
5583proc linemotion {x y id} {
5584    global hoverx hovery hoverid hovertimer
5585
5586    if {[info exists hoverid] && $id == $hoverid} {
5587        set hoverx $x
5588        set hovery $y
5589        if {[info exists hovertimer]} {
5590            after cancel $hovertimer
5591        }
5592        set hovertimer [after 500 linehover]
5593    }
5594}
5595
5596proc lineleave {id} {
5597    global hoverid hovertimer canv
5598
5599    if {[info exists hoverid] && $id == $hoverid} {
5600        $canv delete hover
5601        if {[info exists hovertimer]} {
5602            after cancel $hovertimer
5603            unset hovertimer
5604        }
5605        unset hoverid
5606    }
5607}
5608
5609proc linehover {} {
5610    global hoverx hovery hoverid hovertimer
5611    global canv linespc lthickness
5612    global commitinfo mainfont
5613
5614    set text [lindex $commitinfo($hoverid) 0]
5615    set ymax [lindex [$canv cget -scrollregion] 3]
5616    if {$ymax == {}} return
5617    set yfrac [lindex [$canv yview] 0]
5618    set x [expr {$hoverx + 2 * $linespc}]
5619    set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
5620    set x0 [expr {$x - 2 * $lthickness}]
5621    set y0 [expr {$y - 2 * $lthickness}]
5622    set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
5623    set y1 [expr {$y + $linespc + 2 * $lthickness}]
5624    set t [$canv create rectangle $x0 $y0 $x1 $y1 \
5625               -fill \#ffff80 -outline black -width 1 -tags hover]
5626    $canv raise $t
5627    set t [$canv create text $x $y -anchor nw -text $text -tags hover \
5628               -font $mainfont]
5629    $canv raise $t
5630}
5631
5632proc clickisonarrow {id y} {
5633    global lthickness
5634
5635    set ranges [rowranges $id]
5636    set thresh [expr {2 * $lthickness + 6}]
5637    set n [expr {[llength $ranges] - 1}]
5638    for {set i 1} {$i < $n} {incr i} {
5639        set row [lindex $ranges $i]
5640        if {abs([yc $row] - $y) < $thresh} {
5641            return $i
5642        }
5643    }
5644    return {}
5645}
5646
5647proc arrowjump {id n y} {
5648    global canv
5649
5650    # 1 <-> 2, 3 <-> 4, etc...
5651    set n [expr {(($n - 1) ^ 1) + 1}]
5652    set row [lindex [rowranges $id] $n]
5653    set yt [yc $row]
5654    set ymax [lindex [$canv cget -scrollregion] 3]
5655    if {$ymax eq {} || $ymax <= 0} return
5656    set view [$canv yview]
5657    set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
5658    set yfrac [expr {$yt / $ymax - $yspan / 2}]
5659    if {$yfrac < 0} {
5660        set yfrac 0
5661    }
5662    allcanvs yview moveto $yfrac
5663}
5664
5665proc lineclick {x y id isnew} {
5666    global ctext commitinfo children canv thickerline curview
5667
5668    if {![info exists commitinfo($id)] && ![getcommit $id]} return
5669    unmarkmatches
5670    unselectline
5671    normalline
5672    $canv delete hover
5673    # draw this line thicker than normal
5674    set thickerline $id
5675    drawlines $id
5676    if {$isnew} {
5677        set ymax [lindex [$canv cget -scrollregion] 3]
5678        if {$ymax eq {}} return
5679        set yfrac [lindex [$canv yview] 0]
5680        set y [expr {$y + $yfrac * $ymax}]
5681    }
5682    set dirn [clickisonarrow $id $y]
5683    if {$dirn ne {}} {
5684        arrowjump $id $dirn $y
5685        return
5686    }
5687
5688    if {$isnew} {
5689        addtohistory [list lineclick $x $y $id 0]
5690    }
5691    # fill the details pane with info about this line
5692    $ctext conf -state normal
5693    clear_ctext
5694    $ctext tag conf link -foreground blue -underline 1
5695    $ctext tag bind link <Enter> { %W configure -cursor hand2 }
5696    $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
5697    $ctext insert end "Parent:\t"
5698    $ctext insert end $id [list link link0]
5699    $ctext tag bind link0 <1> [list selbyid $id]
5700    set info $commitinfo($id)
5701    $ctext insert end "\n\t[lindex $info 0]\n"
5702    $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
5703    set date [formatdate [lindex $info 2]]
5704    $ctext insert end "\tDate:\t$date\n"
5705    set kids $children($curview,$id)
5706    if {$kids ne {}} {
5707        $ctext insert end "\nChildren:"
5708        set i 0
5709        foreach child $kids {
5710            incr i
5711            if {![info exists commitinfo($child)] && ![getcommit $child]} continue
5712            set info $commitinfo($child)
5713            $ctext insert end "\n\t"
5714            $ctext insert end $child [list link link$i]
5715            $ctext tag bind link$i <1> [list selbyid $child]
5716            $ctext insert end "\n\t[lindex $info 0]"
5717            $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
5718            set date [formatdate [lindex $info 2]]
5719            $ctext insert end "\n\tDate:\t$date\n"
5720        }
5721    }
5722    $ctext conf -state disabled
5723    init_flist {}
5724}
5725
5726proc normalline {} {
5727    global thickerline
5728    if {[info exists thickerline]} {
5729        set id $thickerline
5730        unset thickerline
5731        drawlines $id
5732    }
5733}
5734
5735proc selbyid {id} {
5736    global commitrow curview
5737    if {[info exists commitrow($curview,$id)]} {
5738        selectline $commitrow($curview,$id) 1
5739    }
5740}
5741
5742proc mstime {} {
5743    global startmstime
5744    if {![info exists startmstime]} {
5745        set startmstime [clock clicks -milliseconds]
5746    }
5747    return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
5748}
5749
5750proc rowmenu {x y id} {
5751    global rowctxmenu commitrow selectedline rowmenuid curview
5752    global nullid nullid2 fakerowmenu mainhead
5753
5754    set rowmenuid $id
5755    if {![info exists selectedline]
5756        || $commitrow($curview,$id) eq $selectedline} {
5757        set state disabled
5758    } else {
5759        set state normal
5760    }
5761    if {$id ne $nullid && $id ne $nullid2} {
5762        set menu $rowctxmenu
5763        $menu entryconfigure 7 -label "Reset $mainhead branch to here"
5764    } else {
5765        set menu $fakerowmenu
5766    }
5767    $menu entryconfigure "Diff this*" -state $state
5768    $menu entryconfigure "Diff selected*" -state $state
5769    $menu entryconfigure "Make patch" -state $state
5770    tk_popup $menu $x $y
5771}
5772
5773proc diffvssel {dirn} {
5774    global rowmenuid selectedline displayorder
5775
5776    if {![info exists selectedline]} return
5777    if {$dirn} {
5778        set oldid [lindex $displayorder $selectedline]
5779        set newid $rowmenuid
5780    } else {
5781        set oldid $rowmenuid
5782        set newid [lindex $displayorder $selectedline]
5783    }
5784    addtohistory [list doseldiff $oldid $newid]
5785    doseldiff $oldid $newid
5786}
5787
5788proc doseldiff {oldid newid} {
5789    global ctext
5790    global commitinfo
5791
5792    $ctext conf -state normal
5793    clear_ctext
5794    init_flist "Top"
5795    $ctext insert end "From "
5796    $ctext tag conf link -foreground blue -underline 1
5797    $ctext tag bind link <Enter> { %W configure -cursor hand2 }
5798    $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
5799    $ctext tag bind link0 <1> [list selbyid $oldid]
5800    $ctext insert end $oldid [list link link0]
5801    $ctext insert end "\n     "
5802    $ctext insert end [lindex $commitinfo($oldid) 0]
5803    $ctext insert end "\n\nTo   "
5804    $ctext tag bind link1 <1> [list selbyid $newid]
5805    $ctext insert end $newid [list link link1]
5806    $ctext insert end "\n     "
5807    $ctext insert end [lindex $commitinfo($newid) 0]
5808    $ctext insert end "\n"
5809    $ctext conf -state disabled
5810    $ctext tag remove found 1.0 end
5811    startdiff [list $oldid $newid]
5812}
5813
5814proc mkpatch {} {
5815    global rowmenuid currentid commitinfo patchtop patchnum
5816
5817    if {![info exists currentid]} return
5818    set oldid $currentid
5819    set oldhead [lindex $commitinfo($oldid) 0]
5820    set newid $rowmenuid
5821    set newhead [lindex $commitinfo($newid) 0]
5822    set top .patch
5823    set patchtop $top
5824    catch {destroy $top}
5825    toplevel $top
5826    label $top.title -text "Generate patch"
5827    grid $top.title - -pady 10
5828    label $top.from -text "From:"
5829    entry $top.fromsha1 -width 40 -relief flat
5830    $top.fromsha1 insert 0 $oldid
5831    $top.fromsha1 conf -state readonly
5832    grid $top.from $top.fromsha1 -sticky w
5833    entry $top.fromhead -width 60 -relief flat
5834    $top.fromhead insert 0 $oldhead
5835    $top.fromhead conf -state readonly
5836    grid x $top.fromhead -sticky w
5837    label $top.to -text "To:"
5838    entry $top.tosha1 -width 40 -relief flat
5839    $top.tosha1 insert 0 $newid
5840    $top.tosha1 conf -state readonly
5841    grid $top.to $top.tosha1 -sticky w
5842    entry $top.tohead -width 60 -relief flat
5843    $top.tohead insert 0 $newhead
5844    $top.tohead conf -state readonly
5845    grid x $top.tohead -sticky w
5846    button $top.rev -text "Reverse" -command mkpatchrev -padx 5
5847    grid $top.rev x -pady 10
5848    label $top.flab -text "Output file:"
5849    entry $top.fname -width 60
5850    $top.fname insert 0 [file normalize "patch$patchnum.patch"]
5851    incr patchnum
5852    grid $top.flab $top.fname -sticky w
5853    frame $top.buts
5854    button $top.buts.gen -text "Generate" -command mkpatchgo
5855    button $top.buts.can -text "Cancel" -command mkpatchcan
5856    grid $top.buts.gen $top.buts.can
5857    grid columnconfigure $top.buts 0 -weight 1 -uniform a
5858    grid columnconfigure $top.buts 1 -weight 1 -uniform a
5859    grid $top.buts - -pady 10 -sticky ew
5860    focus $top.fname
5861}
5862
5863proc mkpatchrev {} {
5864    global patchtop
5865
5866    set oldid [$patchtop.fromsha1 get]
5867    set oldhead [$patchtop.fromhead get]
5868    set newid [$patchtop.tosha1 get]
5869    set newhead [$patchtop.tohead get]
5870    foreach e [list fromsha1 fromhead tosha1 tohead] \
5871            v [list $newid $newhead $oldid $oldhead] {
5872        $patchtop.$e conf -state normal
5873        $patchtop.$e delete 0 end
5874        $patchtop.$e insert 0 $v
5875        $patchtop.$e conf -state readonly
5876    }
5877}
5878
5879proc mkpatchgo {} {
5880    global patchtop nullid nullid2
5881
5882    set oldid [$patchtop.fromsha1 get]
5883    set newid [$patchtop.tosha1 get]
5884    set fname [$patchtop.fname get]
5885    set cmd [diffcmd [list $oldid $newid] -p]
5886    lappend cmd >$fname &
5887    if {[catch {eval exec $cmd} err]} {
5888        error_popup "Error creating patch: $err"
5889    }
5890    catch {destroy $patchtop}
5891    unset patchtop
5892}
5893
5894proc mkpatchcan {} {
5895    global patchtop
5896
5897    catch {destroy $patchtop}
5898    unset patchtop
5899}
5900
5901proc mktag {} {
5902    global rowmenuid mktagtop commitinfo
5903
5904    set top .maketag
5905    set mktagtop $top
5906    catch {destroy $top}
5907    toplevel $top
5908    label $top.title -text "Create tag"
5909    grid $top.title - -pady 10
5910    label $top.id -text "ID:"
5911    entry $top.sha1 -width 40 -relief flat
5912    $top.sha1 insert 0 $rowmenuid
5913    $top.sha1 conf -state readonly
5914    grid $top.id $top.sha1 -sticky w
5915    entry $top.head -width 60 -relief flat
5916    $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
5917    $top.head conf -state readonly
5918    grid x $top.head -sticky w
5919    label $top.tlab -text "Tag name:"
5920    entry $top.tag -width 60
5921    grid $top.tlab $top.tag -sticky w
5922    frame $top.buts
5923    button $top.buts.gen -text "Create" -command mktaggo
5924    button $top.buts.can -text "Cancel" -command mktagcan
5925    grid $top.buts.gen $top.buts.can
5926    grid columnconfigure $top.buts 0 -weight 1 -uniform a
5927    grid columnconfigure $top.buts 1 -weight 1 -uniform a
5928    grid $top.buts - -pady 10 -sticky ew
5929    focus $top.tag
5930}
5931
5932proc domktag {} {
5933    global mktagtop env tagids idtags
5934
5935    set id [$mktagtop.sha1 get]
5936    set tag [$mktagtop.tag get]
5937    if {$tag == {}} {
5938        error_popup "No tag name specified"
5939        return
5940    }
5941    if {[info exists tagids($tag)]} {
5942        error_popup "Tag \"$tag\" already exists"
5943        return
5944    }
5945    if {[catch {
5946        set dir [gitdir]
5947        set fname [file join $dir "refs/tags" $tag]
5948        set f [open $fname w]
5949        puts $f $id
5950        close $f
5951    } err]} {
5952        error_popup "Error creating tag: $err"
5953        return
5954    }
5955
5956    set tagids($tag) $id
5957    lappend idtags($id) $tag
5958    redrawtags $id
5959    addedtag $id
5960    dispneartags 0
5961    run refill_reflist
5962}
5963
5964proc redrawtags {id} {
5965    global canv linehtag commitrow idpos selectedline curview
5966    global mainfont canvxmax iddrawn
5967
5968    if {![info exists commitrow($curview,$id)]} return
5969    if {![info exists iddrawn($id)]} return
5970    drawcommits $commitrow($curview,$id)
5971    $canv delete tag.$id
5972    set xt [eval drawtags $id $idpos($id)]
5973    $canv coords $linehtag($commitrow($curview,$id)) $xt [lindex $idpos($id) 2]
5974    set text [$canv itemcget $linehtag($commitrow($curview,$id)) -text]
5975    set xr [expr {$xt + [font measure $mainfont $text]}]
5976    if {$xr > $canvxmax} {
5977        set canvxmax $xr
5978        setcanvscroll
5979    }
5980    if {[info exists selectedline]
5981        && $selectedline == $commitrow($curview,$id)} {
5982        selectline $selectedline 0
5983    }
5984}
5985
5986proc mktagcan {} {
5987    global mktagtop
5988
5989    catch {destroy $mktagtop}
5990    unset mktagtop
5991}
5992
5993proc mktaggo {} {
5994    domktag
5995    mktagcan
5996}
5997
5998proc writecommit {} {
5999    global rowmenuid wrcomtop commitinfo wrcomcmd
6000
6001    set top .writecommit
6002    set wrcomtop $top
6003    catch {destroy $top}
6004    toplevel $top
6005    label $top.title -text "Write commit to file"
6006    grid $top.title - -pady 10
6007    label $top.id -text "ID:"
6008    entry $top.sha1 -width 40 -relief flat
6009    $top.sha1 insert 0 $rowmenuid
6010    $top.sha1 conf -state readonly
6011    grid $top.id $top.sha1 -sticky w
6012    entry $top.head -width 60 -relief flat
6013    $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
6014    $top.head conf -state readonly
6015    grid x $top.head -sticky w
6016    label $top.clab -text "Command:"
6017    entry $top.cmd -width 60 -textvariable wrcomcmd
6018    grid $top.clab $top.cmd -sticky w -pady 10
6019    label $top.flab -text "Output file:"
6020    entry $top.fname -width 60
6021    $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
6022    grid $top.flab $top.fname -sticky w
6023    frame $top.buts
6024    button $top.buts.gen -text "Write" -command wrcomgo
6025    button $top.buts.can -text "Cancel" -command wrcomcan
6026    grid $top.buts.gen $top.buts.can
6027    grid columnconfigure $top.buts 0 -weight 1 -uniform a
6028    grid columnconfigure $top.buts 1 -weight 1 -uniform a
6029    grid $top.buts - -pady 10 -sticky ew
6030    focus $top.fname
6031}
6032
6033proc wrcomgo {} {
6034    global wrcomtop
6035
6036    set id [$wrcomtop.sha1 get]
6037    set cmd "echo $id | [$wrcomtop.cmd get]"
6038    set fname [$wrcomtop.fname get]
6039    if {[catch {exec sh -c $cmd >$fname &} err]} {
6040        error_popup "Error writing commit: $err"
6041    }
6042    catch {destroy $wrcomtop}
6043    unset wrcomtop
6044}
6045
6046proc wrcomcan {} {
6047    global wrcomtop
6048
6049    catch {destroy $wrcomtop}
6050    unset wrcomtop
6051}
6052
6053proc mkbranch {} {
6054    global rowmenuid mkbrtop
6055
6056    set top .makebranch
6057    catch {destroy $top}
6058    toplevel $top
6059    label $top.title -text "Create new branch"
6060    grid $top.title - -pady 10
6061    label $top.id -text "ID:"
6062    entry $top.sha1 -width 40 -relief flat
6063    $top.sha1 insert 0 $rowmenuid
6064    $top.sha1 conf -state readonly
6065    grid $top.id $top.sha1 -sticky w
6066    label $top.nlab -text "Name:"
6067    entry $top.name -width 40
6068    grid $top.nlab $top.name -sticky w
6069    frame $top.buts
6070    button $top.buts.go -text "Create" -command [list mkbrgo $top]
6071    button $top.buts.can -text "Cancel" -command "catch {destroy $top}"
6072    grid $top.buts.go $top.buts.can
6073    grid columnconfigure $top.buts 0 -weight 1 -uniform a
6074    grid columnconfigure $top.buts 1 -weight 1 -uniform a
6075    grid $top.buts - -pady 10 -sticky ew
6076    focus $top.name
6077}
6078
6079proc mkbrgo {top} {
6080    global headids idheads
6081
6082    set name [$top.name get]
6083    set id [$top.sha1 get]
6084    if {$name eq {}} {
6085        error_popup "Please specify a name for the new branch"
6086        return
6087    }
6088    catch {destroy $top}
6089    nowbusy newbranch
6090    update
6091    if {[catch {
6092        exec git branch $name $id
6093    } err]} {
6094        notbusy newbranch
6095        error_popup $err
6096    } else {
6097        set headids($name) $id
6098        lappend idheads($id) $name
6099        addedhead $id $name
6100        notbusy newbranch
6101        redrawtags $id
6102        dispneartags 0
6103        run refill_reflist
6104    }
6105}
6106
6107proc cherrypick {} {
6108    global rowmenuid curview commitrow
6109    global mainhead
6110
6111    set oldhead [exec git rev-parse HEAD]
6112    set dheads [descheads $rowmenuid]
6113    if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
6114        set ok [confirm_popup "Commit [string range $rowmenuid 0 7] is already\
6115                        included in branch $mainhead -- really re-apply it?"]
6116        if {!$ok} return
6117    }
6118    nowbusy cherrypick
6119    update
6120    # Unfortunately git-cherry-pick writes stuff to stderr even when
6121    # no error occurs, and exec takes that as an indication of error...
6122    if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
6123        notbusy cherrypick
6124        error_popup $err
6125        return
6126    }
6127    set newhead [exec git rev-parse HEAD]
6128    if {$newhead eq $oldhead} {
6129        notbusy cherrypick
6130        error_popup "No changes committed"
6131        return
6132    }
6133    addnewchild $newhead $oldhead
6134    if {[info exists commitrow($curview,$oldhead)]} {
6135        insertrow $commitrow($curview,$oldhead) $newhead
6136        if {$mainhead ne {}} {
6137            movehead $newhead $mainhead
6138            movedhead $newhead $mainhead
6139        }
6140        redrawtags $oldhead
6141        redrawtags $newhead
6142    }
6143    notbusy cherrypick
6144}
6145
6146proc resethead {} {
6147    global mainheadid mainhead rowmenuid confirm_ok resettype
6148    global showlocalchanges
6149
6150    set confirm_ok 0
6151    set w ".confirmreset"
6152    toplevel $w
6153    wm transient $w .
6154    wm title $w "Confirm reset"
6155    message $w.m -text \
6156        "Reset branch $mainhead to [string range $rowmenuid 0 7]?" \
6157        -justify center -aspect 1000
6158    pack $w.m -side top -fill x -padx 20 -pady 20
6159    frame $w.f -relief sunken -border 2
6160    message $w.f.rt -text "Reset type:" -aspect 1000
6161    grid $w.f.rt -sticky w
6162    set resettype mixed
6163    radiobutton $w.f.soft -value soft -variable resettype -justify left \
6164        -text "Soft: Leave working tree and index untouched"
6165    grid $w.f.soft -sticky w
6166    radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
6167        -text "Mixed: Leave working tree untouched, reset index"
6168    grid $w.f.mixed -sticky w
6169    radiobutton $w.f.hard -value hard -variable resettype -justify left \
6170        -text "Hard: Reset working tree and index\n(discard ALL local changes)"
6171    grid $w.f.hard -sticky w
6172    pack $w.f -side top -fill x
6173    button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
6174    pack $w.ok -side left -fill x -padx 20 -pady 20
6175    button $w.cancel -text Cancel -command "destroy $w"
6176    pack $w.cancel -side right -fill x -padx 20 -pady 20
6177    bind $w <Visibility> "grab $w; focus $w"
6178    tkwait window $w
6179    if {!$confirm_ok} return
6180    if {[catch {set fd [open \
6181            [list | sh -c "git reset --$resettype $rowmenuid 2>&1"] r]} err]} {
6182        error_popup $err
6183    } else {
6184        dohidelocalchanges
6185        set w ".resetprogress"
6186        filerun $fd [list readresetstat $fd $w]
6187        toplevel $w
6188        wm transient $w
6189        wm title $w "Reset progress"
6190        message $w.m -text "Reset in progress, please wait..." \
6191            -justify center -aspect 1000
6192        pack $w.m -side top -fill x -padx 20 -pady 5
6193        canvas $w.c -width 150 -height 20 -bg white
6194        $w.c create rect 0 0 0 20 -fill green -tags rect
6195        pack $w.c -side top -fill x -padx 20 -pady 5 -expand 1
6196        nowbusy reset
6197    }
6198}
6199
6200proc readresetstat {fd w} {
6201    global mainhead mainheadid showlocalchanges
6202
6203    if {[gets $fd line] >= 0} {
6204        if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
6205            set x [expr {($m * 150) / $n}]
6206            $w.c coords rect 0 0 $x 20
6207        }
6208        return 1
6209    }
6210    destroy $w
6211    notbusy reset
6212    if {[catch {close $fd} err]} {
6213        error_popup $err
6214    }
6215    set oldhead $mainheadid
6216    set newhead [exec git rev-parse HEAD]
6217    if {$newhead ne $oldhead} {
6218        movehead $newhead $mainhead
6219        movedhead $newhead $mainhead
6220        set mainheadid $newhead
6221        redrawtags $oldhead
6222        redrawtags $newhead
6223    }
6224    if {$showlocalchanges} {
6225        doshowlocalchanges
6226    }
6227    return 0
6228}
6229
6230# context menu for a head
6231proc headmenu {x y id head} {
6232    global headmenuid headmenuhead headctxmenu mainhead
6233
6234    set headmenuid $id
6235    set headmenuhead $head
6236    set state normal
6237    if {$head eq $mainhead} {
6238        set state disabled
6239    }
6240    $headctxmenu entryconfigure 0 -state $state
6241    $headctxmenu entryconfigure 1 -state $state
6242    tk_popup $headctxmenu $x $y
6243}
6244
6245proc cobranch {} {
6246    global headmenuid headmenuhead mainhead headids
6247    global showlocalchanges mainheadid
6248
6249    # check the tree is clean first??
6250    set oldmainhead $mainhead
6251    nowbusy checkout
6252    update
6253    dohidelocalchanges
6254    if {[catch {
6255        exec git checkout -q $headmenuhead
6256    } err]} {
6257        notbusy checkout
6258        error_popup $err
6259    } else {
6260        notbusy checkout
6261        set mainhead $headmenuhead
6262        set mainheadid $headmenuid
6263        if {[info exists headids($oldmainhead)]} {
6264            redrawtags $headids($oldmainhead)
6265        }
6266        redrawtags $headmenuid
6267    }
6268    if {$showlocalchanges} {
6269        dodiffindex
6270    }
6271}
6272
6273proc rmbranch {} {
6274    global headmenuid headmenuhead mainhead
6275    global idheads
6276
6277    set head $headmenuhead
6278    set id $headmenuid
6279    # this check shouldn't be needed any more...
6280    if {$head eq $mainhead} {
6281        error_popup "Cannot delete the currently checked-out branch"
6282        return
6283    }
6284    set dheads [descheads $id]
6285    if {[llength $dheads] == 1 && $idheads($dheads) eq $head} {
6286        # the stuff on this branch isn't on any other branch
6287        if {![confirm_popup "The commits on branch $head aren't on any other\
6288                        branch.\nReally delete branch $head?"]} return
6289    }
6290    nowbusy rmbranch
6291    update
6292    if {[catch {exec git branch -D $head} err]} {
6293        notbusy rmbranch
6294        error_popup $err
6295        return
6296    }
6297    removehead $id $head
6298    removedhead $id $head
6299    redrawtags $id
6300    notbusy rmbranch
6301    dispneartags 0
6302    run refill_reflist
6303}
6304
6305# Display a list of tags and heads
6306proc showrefs {} {
6307    global showrefstop bgcolor fgcolor selectbgcolor mainfont
6308    global bglist fglist uifont reflistfilter reflist maincursor
6309
6310    set top .showrefs
6311    set showrefstop $top
6312    if {[winfo exists $top]} {
6313        raise $top
6314        refill_reflist
6315        return
6316    }
6317    toplevel $top
6318    wm title $top "Tags and heads: [file tail [pwd]]"
6319    text $top.list -background $bgcolor -foreground $fgcolor \
6320        -selectbackground $selectbgcolor -font $mainfont \
6321        -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
6322        -width 30 -height 20 -cursor $maincursor \
6323        -spacing1 1 -spacing3 1 -state disabled
6324    $top.list tag configure highlight -background $selectbgcolor
6325    lappend bglist $top.list
6326    lappend fglist $top.list
6327    scrollbar $top.ysb -command "$top.list yview" -orient vertical
6328    scrollbar $top.xsb -command "$top.list xview" -orient horizontal
6329    grid $top.list $top.ysb -sticky nsew
6330    grid $top.xsb x -sticky ew
6331    frame $top.f
6332    label $top.f.l -text "Filter: " -font $uifont
6333    entry $top.f.e -width 20 -textvariable reflistfilter -font $uifont
6334    set reflistfilter "*"
6335    trace add variable reflistfilter write reflistfilter_change
6336    pack $top.f.e -side right -fill x -expand 1
6337    pack $top.f.l -side left
6338    grid $top.f - -sticky ew -pady 2
6339    button $top.close -command [list destroy $top] -text "Close" \
6340        -font $uifont
6341    grid $top.close -
6342    grid columnconfigure $top 0 -weight 1
6343    grid rowconfigure $top 0 -weight 1
6344    bind $top.list <1> {break}
6345    bind $top.list <B1-Motion> {break}
6346    bind $top.list <ButtonRelease-1> {sel_reflist %W %x %y; break}
6347    set reflist {}
6348    refill_reflist
6349}
6350
6351proc sel_reflist {w x y} {
6352    global showrefstop reflist headids tagids otherrefids
6353
6354    if {![winfo exists $showrefstop]} return
6355    set l [lindex [split [$w index "@$x,$y"] "."] 0]
6356    set ref [lindex $reflist [expr {$l-1}]]
6357    set n [lindex $ref 0]
6358    switch -- [lindex $ref 1] {
6359        "H" {selbyid $headids($n)}
6360        "T" {selbyid $tagids($n)}
6361        "o" {selbyid $otherrefids($n)}
6362    }
6363    $showrefstop.list tag add highlight $l.0 "$l.0 lineend"
6364}
6365
6366proc unsel_reflist {} {
6367    global showrefstop
6368
6369    if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
6370    $showrefstop.list tag remove highlight 0.0 end
6371}
6372
6373proc reflistfilter_change {n1 n2 op} {
6374    global reflistfilter
6375
6376    after cancel refill_reflist
6377    after 200 refill_reflist
6378}
6379
6380proc refill_reflist {} {
6381    global reflist reflistfilter showrefstop headids tagids otherrefids
6382    global commitrow curview commitinterest
6383
6384    if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
6385    set refs {}
6386    foreach n [array names headids] {
6387        if {[string match $reflistfilter $n]} {
6388            if {[info exists commitrow($curview,$headids($n))]} {
6389                lappend refs [list $n H]
6390            } else {
6391                set commitinterest($headids($n)) {run refill_reflist}
6392            }
6393        }
6394    }
6395    foreach n [array names tagids] {
6396        if {[string match $reflistfilter $n]} {
6397            if {[info exists commitrow($curview,$tagids($n))]} {
6398                lappend refs [list $n T]
6399            } else {
6400                set commitinterest($tagids($n)) {run refill_reflist}
6401            }
6402        }
6403    }
6404    foreach n [array names otherrefids] {
6405        if {[string match $reflistfilter $n]} {
6406            if {[info exists commitrow($curview,$otherrefids($n))]} {
6407                lappend refs [list $n o]
6408            } else {
6409                set commitinterest($otherrefids($n)) {run refill_reflist}
6410            }
6411        }
6412    }
6413    set refs [lsort -index 0 $refs]
6414    if {$refs eq $reflist} return
6415
6416    # Update the contents of $showrefstop.list according to the
6417    # differences between $reflist (old) and $refs (new)
6418    $showrefstop.list conf -state normal
6419    $showrefstop.list insert end "\n"
6420    set i 0
6421    set j 0
6422    while {$i < [llength $reflist] || $j < [llength $refs]} {
6423        if {$i < [llength $reflist]} {
6424            if {$j < [llength $refs]} {
6425                set cmp [string compare [lindex $reflist $i 0] \
6426                             [lindex $refs $j 0]]
6427                if {$cmp == 0} {
6428                    set cmp [string compare [lindex $reflist $i 1] \
6429                                 [lindex $refs $j 1]]
6430                }
6431            } else {
6432                set cmp -1
6433            }
6434        } else {
6435            set cmp 1
6436        }
6437        switch -- $cmp {
6438            -1 {
6439                $showrefstop.list delete "[expr {$j+1}].0" "[expr {$j+2}].0"
6440                incr i
6441            }
6442            0 {
6443                incr i
6444                incr j
6445            }
6446            1 {
6447                set l [expr {$j + 1}]
6448                $showrefstop.list image create $l.0 -align baseline \
6449                    -image reficon-[lindex $refs $j 1] -padx 2
6450                $showrefstop.list insert $l.1 "[lindex $refs $j 0]\n"
6451                incr j
6452            }
6453        }
6454    }
6455    set reflist $refs
6456    # delete last newline
6457    $showrefstop.list delete end-2c end-1c
6458    $showrefstop.list conf -state disabled
6459}
6460
6461# Stuff for finding nearby tags
6462proc getallcommits {} {
6463    global allcommits allids nbmp nextarc seeds
6464
6465    if {![info exists allcommits]} {
6466        set allids {}
6467        set nbmp 0
6468        set nextarc 0
6469        set allcommits 0
6470        set seeds {}
6471    }
6472
6473    set cmd [concat | git rev-list --all --parents]
6474    foreach id $seeds {
6475        lappend cmd "^$id"
6476    }
6477    set fd [open $cmd r]
6478    fconfigure $fd -blocking 0
6479    incr allcommits
6480    nowbusy allcommits
6481    filerun $fd [list getallclines $fd]
6482}
6483
6484# Since most commits have 1 parent and 1 child, we group strings of
6485# such commits into "arcs" joining branch/merge points (BMPs), which
6486# are commits that either don't have 1 parent or don't have 1 child.
6487#
6488# arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
6489# arcout(id) - outgoing arcs for BMP
6490# arcids(a) - list of IDs on arc including end but not start
6491# arcstart(a) - BMP ID at start of arc
6492# arcend(a) - BMP ID at end of arc
6493# growing(a) - arc a is still growing
6494# arctags(a) - IDs out of arcids (excluding end) that have tags
6495# archeads(a) - IDs out of arcids (excluding end) that have heads
6496# The start of an arc is at the descendent end, so "incoming" means
6497# coming from descendents, and "outgoing" means going towards ancestors.
6498
6499proc getallclines {fd} {
6500    global allids allparents allchildren idtags idheads nextarc nbmp
6501    global arcnos arcids arctags arcout arcend arcstart archeads growing
6502    global seeds allcommits
6503
6504    set nid 0
6505    while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
6506        set id [lindex $line 0]
6507        if {[info exists allparents($id)]} {
6508            # seen it already
6509            continue
6510        }
6511        lappend allids $id
6512        set olds [lrange $line 1 end]
6513        set allparents($id) $olds
6514        if {![info exists allchildren($id)]} {
6515            set allchildren($id) {}
6516            set arcnos($id) {}
6517            lappend seeds $id
6518        } else {
6519            set a $arcnos($id)
6520            if {[llength $olds] == 1 && [llength $a] == 1} {
6521                lappend arcids($a) $id
6522                if {[info exists idtags($id)]} {
6523                    lappend arctags($a) $id
6524                }
6525                if {[info exists idheads($id)]} {
6526                    lappend archeads($a) $id
6527                }
6528                if {[info exists allparents($olds)]} {
6529                    # seen parent already
6530                    if {![info exists arcout($olds)]} {
6531                        splitarc $olds
6532                    }
6533                    lappend arcids($a) $olds
6534                    set arcend($a) $olds
6535                    unset growing($a)
6536                }
6537                lappend allchildren($olds) $id
6538                lappend arcnos($olds) $a
6539                continue
6540            }
6541        }
6542        incr nbmp
6543        foreach a $arcnos($id) {
6544            lappend arcids($a) $id
6545            set arcend($a) $id
6546            unset growing($a)
6547        }
6548
6549        set ao {}
6550        foreach p $olds {
6551            lappend allchildren($p) $id
6552            set a [incr nextarc]
6553            set arcstart($a) $id
6554            set archeads($a) {}
6555            set arctags($a) {}
6556            set archeads($a) {}
6557            set arcids($a) {}
6558            lappend ao $a
6559            set growing($a) 1
6560            if {[info exists allparents($p)]} {
6561                # seen it already, may need to make a new branch
6562                if {![info exists arcout($p)]} {
6563                    splitarc $p
6564                }
6565                lappend arcids($a) $p
6566                set arcend($a) $p
6567                unset growing($a)
6568            }
6569            lappend arcnos($p) $a
6570        }
6571        set arcout($id) $ao
6572    }
6573    if {$nid > 0} {
6574        global cached_dheads cached_dtags cached_atags
6575        catch {unset cached_dheads}
6576        catch {unset cached_dtags}
6577        catch {unset cached_atags}
6578    }
6579    if {![eof $fd]} {
6580        return [expr {$nid >= 1000? 2: 1}]
6581    }
6582    close $fd
6583    if {[incr allcommits -1] == 0} {
6584        notbusy allcommits
6585    }
6586    dispneartags 0
6587    return 0
6588}
6589
6590proc recalcarc {a} {
6591    global arctags archeads arcids idtags idheads
6592
6593    set at {}
6594    set ah {}
6595    foreach id [lrange $arcids($a) 0 end-1] {
6596        if {[info exists idtags($id)]} {
6597            lappend at $id
6598        }
6599        if {[info exists idheads($id)]} {
6600            lappend ah $id
6601        }
6602    }
6603    set arctags($a) $at
6604    set archeads($a) $ah
6605}
6606
6607proc splitarc {p} {
6608    global arcnos arcids nextarc nbmp arctags archeads idtags idheads
6609    global arcstart arcend arcout allparents growing
6610
6611    set a $arcnos($p)
6612    if {[llength $a] != 1} {
6613        puts "oops splitarc called but [llength $a] arcs already"
6614        return
6615    }
6616    set a [lindex $a 0]
6617    set i [lsearch -exact $arcids($a) $p]
6618    if {$i < 0} {
6619        puts "oops splitarc $p not in arc $a"
6620        return
6621    }
6622    set na [incr nextarc]
6623    if {[info exists arcend($a)]} {
6624        set arcend($na) $arcend($a)
6625    } else {
6626        set l [lindex $allparents([lindex $arcids($a) end]) 0]
6627        set j [lsearch -exact $arcnos($l) $a]
6628        set arcnos($l) [lreplace $arcnos($l) $j $j $na]
6629    }
6630    set tail [lrange $arcids($a) [expr {$i+1}] end]
6631    set arcids($a) [lrange $arcids($a) 0 $i]
6632    set arcend($a) $p
6633    set arcstart($na) $p
6634    set arcout($p) $na
6635    set arcids($na) $tail
6636    if {[info exists growing($a)]} {
6637        set growing($na) 1
6638        unset growing($a)
6639    }
6640    incr nbmp
6641
6642    foreach id $tail {
6643        if {[llength $arcnos($id)] == 1} {
6644            set arcnos($id) $na
6645        } else {
6646            set j [lsearch -exact $arcnos($id) $a]
6647            set arcnos($id) [lreplace $arcnos($id) $j $j $na]
6648        }
6649    }
6650
6651    # reconstruct tags and heads lists
6652    if {$arctags($a) ne {} || $archeads($a) ne {}} {
6653        recalcarc $a
6654        recalcarc $na
6655    } else {
6656        set arctags($na) {}
6657        set archeads($na) {}
6658    }
6659}
6660
6661# Update things for a new commit added that is a child of one
6662# existing commit.  Used when cherry-picking.
6663proc addnewchild {id p} {
6664    global allids allparents allchildren idtags nextarc nbmp
6665    global arcnos arcids arctags arcout arcend arcstart archeads growing
6666    global seeds allcommits
6667
6668    if {![info exists allcommits] || ![info exists arcnos($p)]} return
6669    lappend allids $id
6670    set allparents($id) [list $p]
6671    set allchildren($id) {}
6672    set arcnos($id) {}
6673    lappend seeds $id
6674    incr nbmp
6675    lappend allchildren($p) $id
6676    set a [incr nextarc]
6677    set arcstart($a) $id
6678    set archeads($a) {}
6679    set arctags($a) {}
6680    set arcids($a) [list $p]
6681    set arcend($a) $p
6682    if {![info exists arcout($p)]} {
6683        splitarc $p
6684    }
6685    lappend arcnos($p) $a
6686    set arcout($id) [list $a]
6687}
6688
6689# Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
6690# or 0 if neither is true.
6691proc anc_or_desc {a b} {
6692    global arcout arcstart arcend arcnos cached_isanc
6693
6694    if {$arcnos($a) eq $arcnos($b)} {
6695        # Both are on the same arc(s); either both are the same BMP,
6696        # or if one is not a BMP, the other is also not a BMP or is
6697        # the BMP at end of the arc (and it only has 1 incoming arc).
6698        # Or both can be BMPs with no incoming arcs.
6699        if {$a eq $b || $arcnos($a) eq {}} {
6700            return 0
6701        }
6702        # assert {[llength $arcnos($a)] == 1}
6703        set arc [lindex $arcnos($a) 0]
6704        set i [lsearch -exact $arcids($arc) $a]
6705        set j [lsearch -exact $arcids($arc) $b]
6706        if {$i < 0 || $i > $j} {
6707            return 1
6708        } else {
6709            return -1
6710        }
6711    }
6712
6713    if {![info exists arcout($a)]} {
6714        set arc [lindex $arcnos($a) 0]
6715        if {[info exists arcend($arc)]} {
6716            set aend $arcend($arc)
6717        } else {
6718            set aend {}
6719        }
6720        set a $arcstart($arc)
6721    } else {
6722        set aend $a
6723    }
6724    if {![info exists arcout($b)]} {
6725        set arc [lindex $arcnos($b) 0]
6726        if {[info exists arcend($arc)]} {
6727            set bend $arcend($arc)
6728        } else {
6729            set bend {}
6730        }
6731        set b $arcstart($arc)
6732    } else {
6733        set bend $b
6734    }
6735    if {$a eq $bend} {
6736        return 1
6737    }
6738    if {$b eq $aend} {
6739        return -1
6740    }
6741    if {[info exists cached_isanc($a,$bend)]} {
6742        if {$cached_isanc($a,$bend)} {
6743            return 1
6744        }
6745    }
6746    if {[info exists cached_isanc($b,$aend)]} {
6747        if {$cached_isanc($b,$aend)} {
6748            return -1
6749        }
6750        if {[info exists cached_isanc($a,$bend)]} {
6751            return 0
6752        }
6753    }
6754
6755    set todo [list $a $b]
6756    set anc($a) a
6757    set anc($b) b
6758    for {set i 0} {$i < [llength $todo]} {incr i} {
6759        set x [lindex $todo $i]
6760        if {$anc($x) eq {}} {
6761            continue
6762        }
6763        foreach arc $arcnos($x) {
6764            set xd $arcstart($arc)
6765            if {$xd eq $bend} {
6766                set cached_isanc($a,$bend) 1
6767                set cached_isanc($b,$aend) 0
6768                return 1
6769            } elseif {$xd eq $aend} {
6770                set cached_isanc($b,$aend) 1
6771                set cached_isanc($a,$bend) 0
6772                return -1
6773            }
6774            if {![info exists anc($xd)]} {
6775                set anc($xd) $anc($x)
6776                lappend todo $xd
6777            } elseif {$anc($xd) ne $anc($x)} {
6778                set anc($xd) {}
6779            }
6780        }
6781    }
6782    set cached_isanc($a,$bend) 0
6783    set cached_isanc($b,$aend) 0
6784    return 0
6785}
6786
6787# This identifies whether $desc has an ancestor that is
6788# a growing tip of the graph and which is not an ancestor of $anc
6789# and returns 0 if so and 1 if not.
6790# If we subsequently discover a tag on such a growing tip, and that
6791# turns out to be a descendent of $anc (which it could, since we
6792# don't necessarily see children before parents), then $desc
6793# isn't a good choice to display as a descendent tag of
6794# $anc (since it is the descendent of another tag which is
6795# a descendent of $anc).  Similarly, $anc isn't a good choice to
6796# display as a ancestor tag of $desc.
6797#
6798proc is_certain {desc anc} {
6799    global arcnos arcout arcstart arcend growing problems
6800
6801    set certain {}
6802    if {[llength $arcnos($anc)] == 1} {
6803        # tags on the same arc are certain
6804        if {$arcnos($desc) eq $arcnos($anc)} {
6805            return 1
6806        }
6807        if {![info exists arcout($anc)]} {
6808            # if $anc is partway along an arc, use the start of the arc instead
6809            set a [lindex $arcnos($anc) 0]
6810            set anc $arcstart($a)
6811        }
6812    }
6813    if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
6814        set x $desc
6815    } else {
6816        set a [lindex $arcnos($desc) 0]
6817        set x $arcend($a)
6818    }
6819    if {$x == $anc} {
6820        return 1
6821    }
6822    set anclist [list $x]
6823    set dl($x) 1
6824    set nnh 1
6825    set ngrowanc 0
6826    for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
6827        set x [lindex $anclist $i]
6828        if {$dl($x)} {
6829            incr nnh -1
6830        }
6831        set done($x) 1
6832        foreach a $arcout($x) {
6833            if {[info exists growing($a)]} {
6834                if {![info exists growanc($x)] && $dl($x)} {
6835                    set growanc($x) 1
6836                    incr ngrowanc
6837                }
6838            } else {
6839                set y $arcend($a)
6840                if {[info exists dl($y)]} {
6841                    if {$dl($y)} {
6842                        if {!$dl($x)} {
6843                            set dl($y) 0
6844                            if {![info exists done($y)]} {
6845                                incr nnh -1
6846                            }
6847                            if {[info exists growanc($x)]} {
6848                                incr ngrowanc -1
6849                            }
6850                            set xl [list $y]
6851                            for {set k 0} {$k < [llength $xl]} {incr k} {
6852                                set z [lindex $xl $k]
6853                                foreach c $arcout($z) {
6854                                    if {[info exists arcend($c)]} {
6855                                        set v $arcend($c)
6856                                        if {[info exists dl($v)] && $dl($v)} {
6857                                            set dl($v) 0
6858                                            if {![info exists done($v)]} {
6859                                                incr nnh -1
6860                                            }
6861                                            if {[info exists growanc($v)]} {
6862                                                incr ngrowanc -1
6863                                            }
6864                                            lappend xl $v
6865                                        }
6866                                    }
6867                                }
6868                            }
6869                        }
6870                    }
6871                } elseif {$y eq $anc || !$dl($x)} {
6872                    set dl($y) 0
6873                    lappend anclist $y
6874                } else {
6875                    set dl($y) 1
6876                    lappend anclist $y
6877                    incr nnh
6878                }
6879            }
6880        }
6881    }
6882    foreach x [array names growanc] {
6883        if {$dl($x)} {
6884            return 0
6885        }
6886        return 0
6887    }
6888    return 1
6889}
6890
6891proc validate_arctags {a} {
6892    global arctags idtags
6893
6894    set i -1
6895    set na $arctags($a)
6896    foreach id $arctags($a) {
6897        incr i
6898        if {![info exists idtags($id)]} {
6899            set na [lreplace $na $i $i]
6900            incr i -1
6901        }
6902    }
6903    set arctags($a) $na
6904}
6905
6906proc validate_archeads {a} {
6907    global archeads idheads
6908
6909    set i -1
6910    set na $archeads($a)
6911    foreach id $archeads($a) {
6912        incr i
6913        if {![info exists idheads($id)]} {
6914            set na [lreplace $na $i $i]
6915            incr i -1
6916        }
6917    }
6918    set archeads($a) $na
6919}
6920
6921# Return the list of IDs that have tags that are descendents of id,
6922# ignoring IDs that are descendents of IDs already reported.
6923proc desctags {id} {
6924    global arcnos arcstart arcids arctags idtags allparents
6925    global growing cached_dtags
6926
6927    if {![info exists allparents($id)]} {
6928        return {}
6929    }
6930    set t1 [clock clicks -milliseconds]
6931    set argid $id
6932    if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
6933        # part-way along an arc; check that arc first
6934        set a [lindex $arcnos($id) 0]
6935        if {$arctags($a) ne {}} {
6936            validate_arctags $a
6937            set i [lsearch -exact $arcids($a) $id]
6938            set tid {}
6939            foreach t $arctags($a) {
6940                set j [lsearch -exact $arcids($a) $t]
6941                if {$j >= $i} break
6942                set tid $t
6943            }
6944            if {$tid ne {}} {
6945                return $tid
6946            }
6947        }
6948        set id $arcstart($a)
6949        if {[info exists idtags($id)]} {
6950            return $id
6951        }
6952    }
6953    if {[info exists cached_dtags($id)]} {
6954        return $cached_dtags($id)
6955    }
6956
6957    set origid $id
6958    set todo [list $id]
6959    set queued($id) 1
6960    set nc 1
6961    for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
6962        set id [lindex $todo $i]
6963        set done($id) 1
6964        set ta [info exists hastaggedancestor($id)]
6965        if {!$ta} {
6966            incr nc -1
6967        }
6968        # ignore tags on starting node
6969        if {!$ta && $i > 0} {
6970            if {[info exists idtags($id)]} {
6971                set tagloc($id) $id
6972                set ta 1
6973            } elseif {[info exists cached_dtags($id)]} {
6974                set tagloc($id) $cached_dtags($id)
6975                set ta 1
6976            }
6977        }
6978        foreach a $arcnos($id) {
6979            set d $arcstart($a)
6980            if {!$ta && $arctags($a) ne {}} {
6981                validate_arctags $a
6982                if {$arctags($a) ne {}} {
6983                    lappend tagloc($id) [lindex $arctags($a) end]
6984                }
6985            }
6986            if {$ta || $arctags($a) ne {}} {
6987                set tomark [list $d]
6988                for {set j 0} {$j < [llength $tomark]} {incr j} {
6989                    set dd [lindex $tomark $j]
6990                    if {![info exists hastaggedancestor($dd)]} {
6991                        if {[info exists done($dd)]} {
6992                            foreach b $arcnos($dd) {
6993                                lappend tomark $arcstart($b)
6994                            }
6995                            if {[info exists tagloc($dd)]} {
6996                                unset tagloc($dd)
6997                            }
6998                        } elseif {[info exists queued($dd)]} {
6999                            incr nc -1
7000                        }
7001                        set hastaggedancestor($dd) 1
7002                    }
7003                }
7004            }
7005            if {![info exists queued($d)]} {
7006                lappend todo $d
7007                set queued($d) 1
7008                if {![info exists hastaggedancestor($d)]} {
7009                    incr nc
7010                }
7011            }
7012        }
7013    }
7014    set tags {}
7015    foreach id [array names tagloc] {
7016        if {![info exists hastaggedancestor($id)]} {
7017            foreach t $tagloc($id) {
7018                if {[lsearch -exact $tags $t] < 0} {
7019                    lappend tags $t
7020                }
7021            }
7022        }
7023    }
7024    set t2 [clock clicks -milliseconds]
7025    set loopix $i
7026
7027    # remove tags that are descendents of other tags
7028    for {set i 0} {$i < [llength $tags]} {incr i} {
7029        set a [lindex $tags $i]
7030        for {set j 0} {$j < $i} {incr j} {
7031            set b [lindex $tags $j]
7032            set r [anc_or_desc $a $b]
7033            if {$r == 1} {
7034                set tags [lreplace $tags $j $j]
7035                incr j -1
7036                incr i -1
7037            } elseif {$r == -1} {
7038                set tags [lreplace $tags $i $i]
7039                incr i -1
7040                break
7041            }
7042        }
7043    }
7044
7045    if {[array names growing] ne {}} {
7046        # graph isn't finished, need to check if any tag could get
7047        # eclipsed by another tag coming later.  Simply ignore any
7048        # tags that could later get eclipsed.
7049        set ctags {}
7050        foreach t $tags {
7051            if {[is_certain $t $origid]} {
7052                lappend ctags $t
7053            }
7054        }
7055        if {$tags eq $ctags} {
7056            set cached_dtags($origid) $tags
7057        } else {
7058            set tags $ctags
7059        }
7060    } else {
7061        set cached_dtags($origid) $tags
7062    }
7063    set t3 [clock clicks -milliseconds]
7064    if {0 && $t3 - $t1 >= 100} {
7065        puts "iterating descendents ($loopix/[llength $todo] nodes) took\
7066            [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
7067    }
7068    return $tags
7069}
7070
7071proc anctags {id} {
7072    global arcnos arcids arcout arcend arctags idtags allparents
7073    global growing cached_atags
7074
7075    if {![info exists allparents($id)]} {
7076        return {}
7077    }
7078    set t1 [clock clicks -milliseconds]
7079    set argid $id
7080    if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
7081        # part-way along an arc; check that arc first
7082        set a [lindex $arcnos($id) 0]
7083        if {$arctags($a) ne {}} {
7084            validate_arctags $a
7085            set i [lsearch -exact $arcids($a) $id]
7086            foreach t $arctags($a) {
7087                set j [lsearch -exact $arcids($a) $t]
7088                if {$j > $i} {
7089                    return $t
7090                }
7091            }
7092        }
7093        if {![info exists arcend($a)]} {
7094            return {}
7095        }
7096        set id $arcend($a)
7097        if {[info exists idtags($id)]} {
7098            return $id
7099        }
7100    }
7101    if {[info exists cached_atags($id)]} {
7102        return $cached_atags($id)
7103    }
7104
7105    set origid $id
7106    set todo [list $id]
7107    set queued($id) 1
7108    set taglist {}
7109    set nc 1
7110    for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
7111        set id [lindex $todo $i]
7112        set done($id) 1
7113        set td [info exists hastaggeddescendent($id)]
7114        if {!$td} {
7115            incr nc -1
7116        }
7117        # ignore tags on starting node
7118        if {!$td && $i > 0} {
7119            if {[info exists idtags($id)]} {
7120                set tagloc($id) $id
7121                set td 1
7122            } elseif {[info exists cached_atags($id)]} {
7123                set tagloc($id) $cached_atags($id)
7124                set td 1
7125            }
7126        }
7127        foreach a $arcout($id) {
7128            if {!$td && $arctags($a) ne {}} {
7129                validate_arctags $a
7130                if {$arctags($a) ne {}} {
7131                    lappend tagloc($id) [lindex $arctags($a) 0]
7132                }
7133            }
7134            if {![info exists arcend($a)]} continue
7135            set d $arcend($a)
7136            if {$td || $arctags($a) ne {}} {
7137                set tomark [list $d]
7138                for {set j 0} {$j < [llength $tomark]} {incr j} {
7139                    set dd [lindex $tomark $j]
7140                    if {![info exists hastaggeddescendent($dd)]} {
7141                        if {[info exists done($dd)]} {
7142                            foreach b $arcout($dd) {
7143                                if {[info exists arcend($b)]} {
7144                                    lappend tomark $arcend($b)
7145                                }
7146                            }
7147                            if {[info exists tagloc($dd)]} {
7148                                unset tagloc($dd)
7149                            }
7150                        } elseif {[info exists queued($dd)]} {
7151                            incr nc -1
7152                        }
7153                        set hastaggeddescendent($dd) 1
7154                    }
7155                }
7156            }
7157            if {![info exists queued($d)]} {
7158                lappend todo $d
7159                set queued($d) 1
7160                if {![info exists hastaggeddescendent($d)]} {
7161                    incr nc
7162                }
7163            }
7164        }
7165    }
7166    set t2 [clock clicks -milliseconds]
7167    set loopix $i
7168    set tags {}
7169    foreach id [array names tagloc] {
7170        if {![info exists hastaggeddescendent($id)]} {
7171            foreach t $tagloc($id) {
7172                if {[lsearch -exact $tags $t] < 0} {
7173                    lappend tags $t
7174                }
7175            }
7176        }
7177    }
7178
7179    # remove tags that are ancestors of other tags
7180    for {set i 0} {$i < [llength $tags]} {incr i} {
7181        set a [lindex $tags $i]
7182        for {set j 0} {$j < $i} {incr j} {
7183            set b [lindex $tags $j]
7184            set r [anc_or_desc $a $b]
7185            if {$r == -1} {
7186                set tags [lreplace $tags $j $j]
7187                incr j -1
7188                incr i -1
7189            } elseif {$r == 1} {
7190                set tags [lreplace $tags $i $i]
7191                incr i -1
7192                break
7193            }
7194        }
7195    }
7196
7197    if {[array names growing] ne {}} {
7198        # graph isn't finished, need to check if any tag could get
7199        # eclipsed by another tag coming later.  Simply ignore any
7200        # tags that could later get eclipsed.
7201        set ctags {}
7202        foreach t $tags {
7203            if {[is_certain $origid $t]} {
7204                lappend ctags $t
7205            }
7206        }
7207        if {$tags eq $ctags} {
7208            set cached_atags($origid) $tags
7209        } else {
7210            set tags $ctags
7211        }
7212    } else {
7213        set cached_atags($origid) $tags
7214    }
7215    set t3 [clock clicks -milliseconds]
7216    if {0 && $t3 - $t1 >= 100} {
7217        puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
7218            [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
7219    }
7220    return $tags
7221}
7222
7223# Return the list of IDs that have heads that are descendents of id,
7224# including id itself if it has a head.
7225proc descheads {id} {
7226    global arcnos arcstart arcids archeads idheads cached_dheads
7227    global allparents
7228
7229    if {![info exists allparents($id)]} {
7230        return {}
7231    }
7232    set aret {}
7233    if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
7234        # part-way along an arc; check it first
7235        set a [lindex $arcnos($id) 0]
7236        if {$archeads($a) ne {}} {
7237            validate_archeads $a
7238            set i [lsearch -exact $arcids($a) $id]
7239            foreach t $archeads($a) {
7240                set j [lsearch -exact $arcids($a) $t]
7241                if {$j > $i} break
7242                lappend aret $t
7243            }
7244        }
7245        set id $arcstart($a)
7246    }
7247    set origid $id
7248    set todo [list $id]
7249    set seen($id) 1
7250    set ret {}
7251    for {set i 0} {$i < [llength $todo]} {incr i} {
7252        set id [lindex $todo $i]
7253        if {[info exists cached_dheads($id)]} {
7254            set ret [concat $ret $cached_dheads($id)]
7255        } else {
7256            if {[info exists idheads($id)]} {
7257                lappend ret $id
7258            }
7259            foreach a $arcnos($id) {
7260                if {$archeads($a) ne {}} {
7261                    validate_archeads $a
7262                    if {$archeads($a) ne {}} {
7263                        set ret [concat $ret $archeads($a)]
7264                    }
7265                }
7266                set d $arcstart($a)
7267                if {![info exists seen($d)]} {
7268                    lappend todo $d
7269                    set seen($d) 1
7270                }
7271            }
7272        }
7273    }
7274    set ret [lsort -unique $ret]
7275    set cached_dheads($origid) $ret
7276    return [concat $ret $aret]
7277}
7278
7279proc addedtag {id} {
7280    global arcnos arcout cached_dtags cached_atags
7281
7282    if {![info exists arcnos($id)]} return
7283    if {![info exists arcout($id)]} {
7284        recalcarc [lindex $arcnos($id) 0]
7285    }
7286    catch {unset cached_dtags}
7287    catch {unset cached_atags}
7288}
7289
7290proc addedhead {hid head} {
7291    global arcnos arcout cached_dheads
7292
7293    if {![info exists arcnos($hid)]} return
7294    if {![info exists arcout($hid)]} {
7295        recalcarc [lindex $arcnos($hid) 0]
7296    }
7297    catch {unset cached_dheads}
7298}
7299
7300proc removedhead {hid head} {
7301    global cached_dheads
7302
7303    catch {unset cached_dheads}
7304}
7305
7306proc movedhead {hid head} {
7307    global arcnos arcout cached_dheads
7308
7309    if {![info exists arcnos($hid)]} return
7310    if {![info exists arcout($hid)]} {
7311        recalcarc [lindex $arcnos($hid) 0]
7312    }
7313    catch {unset cached_dheads}
7314}
7315
7316proc changedrefs {} {
7317    global cached_dheads cached_dtags cached_atags
7318    global arctags archeads arcnos arcout idheads idtags
7319
7320    foreach id [concat [array names idheads] [array names idtags]] {
7321        if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
7322            set a [lindex $arcnos($id) 0]
7323            if {![info exists donearc($a)]} {
7324                recalcarc $a
7325                set donearc($a) 1
7326            }
7327        }
7328    }
7329    catch {unset cached_dtags}
7330    catch {unset cached_atags}
7331    catch {unset cached_dheads}
7332}
7333
7334proc rereadrefs {} {
7335    global idtags idheads idotherrefs mainhead
7336
7337    set refids [concat [array names idtags] \
7338                    [array names idheads] [array names idotherrefs]]
7339    foreach id $refids {
7340        if {![info exists ref($id)]} {
7341            set ref($id) [listrefs $id]
7342        }
7343    }
7344    set oldmainhead $mainhead
7345    readrefs
7346    changedrefs
7347    set refids [lsort -unique [concat $refids [array names idtags] \
7348                        [array names idheads] [array names idotherrefs]]]
7349    foreach id $refids {
7350        set v [listrefs $id]
7351        if {![info exists ref($id)] || $ref($id) != $v ||
7352            ($id eq $oldmainhead && $id ne $mainhead) ||
7353            ($id eq $mainhead && $id ne $oldmainhead)} {
7354            redrawtags $id
7355        }
7356    }
7357    run refill_reflist
7358}
7359
7360proc listrefs {id} {
7361    global idtags idheads idotherrefs
7362
7363    set x {}
7364    if {[info exists idtags($id)]} {
7365        set x $idtags($id)
7366    }
7367    set y {}
7368    if {[info exists idheads($id)]} {
7369        set y $idheads($id)
7370    }
7371    set z {}
7372    if {[info exists idotherrefs($id)]} {
7373        set z $idotherrefs($id)
7374    }
7375    return [list $x $y $z]
7376}
7377
7378proc showtag {tag isnew} {
7379    global ctext tagcontents tagids linknum tagobjid
7380
7381    if {$isnew} {
7382        addtohistory [list showtag $tag 0]
7383    }
7384    $ctext conf -state normal
7385    clear_ctext
7386    set linknum 0
7387    if {![info exists tagcontents($tag)]} {
7388        catch {
7389            set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
7390        }
7391    }
7392    if {[info exists tagcontents($tag)]} {
7393        set text $tagcontents($tag)
7394    } else {
7395        set text "Tag: $tag\nId:  $tagids($tag)"
7396    }
7397    appendwithlinks $text {}
7398    $ctext conf -state disabled
7399    init_flist {}
7400}
7401
7402proc doquit {} {
7403    global stopped
7404    set stopped 100
7405    savestuff .
7406    destroy .
7407}
7408
7409proc doprefs {} {
7410    global maxwidth maxgraphpct diffopts
7411    global oldprefs prefstop showneartags showlocalchanges
7412    global bgcolor fgcolor ctext diffcolors selectbgcolor
7413    global uifont tabstop limitdiffs
7414
7415    set top .gitkprefs
7416    set prefstop $top
7417    if {[winfo exists $top]} {
7418        raise $top
7419        return
7420    }
7421    foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges \
7422                   limitdiffs} {
7423        set oldprefs($v) [set $v]
7424    }
7425    toplevel $top
7426    wm title $top "Gitk preferences"
7427    label $top.ldisp -text "Commit list display options"
7428    $top.ldisp configure -font $uifont
7429    grid $top.ldisp - -sticky w -pady 10
7430    label $top.spacer -text " "
7431    label $top.maxwidthl -text "Maximum graph width (lines)" \
7432        -font optionfont
7433    spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
7434    grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
7435    label $top.maxpctl -text "Maximum graph width (% of pane)" \
7436        -font optionfont
7437    spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
7438    grid x $top.maxpctl $top.maxpct -sticky w
7439    frame $top.showlocal
7440    label $top.showlocal.l -text "Show local changes" -font optionfont
7441    checkbutton $top.showlocal.b -variable showlocalchanges
7442    pack $top.showlocal.b $top.showlocal.l -side left
7443    grid x $top.showlocal -sticky w
7444
7445    label $top.ddisp -text "Diff display options"
7446    $top.ddisp configure -font $uifont
7447    grid $top.ddisp - -sticky w -pady 10
7448    label $top.diffoptl -text "Options for diff program" \
7449        -font optionfont
7450    entry $top.diffopt -width 20 -textvariable diffopts
7451    grid x $top.diffoptl $top.diffopt -sticky w
7452    frame $top.ntag
7453    label $top.ntag.l -text "Display nearby tags" -font optionfont
7454    checkbutton $top.ntag.b -variable showneartags
7455    pack $top.ntag.b $top.ntag.l -side left
7456    grid x $top.ntag -sticky w
7457    label $top.tabstopl -text "tabstop" -font optionfont
7458    spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
7459    grid x $top.tabstopl $top.tabstop -sticky w
7460    frame $top.ldiff
7461    label $top.ldiff.l -text "Limit diffs to listed paths" -font optionfont
7462    checkbutton $top.ldiff.b -variable limitdiffs
7463    pack $top.ldiff.b $top.ldiff.l -side left
7464    grid x $top.ldiff -sticky w
7465
7466    label $top.cdisp -text "Colors: press to choose"
7467    $top.cdisp configure -font $uifont
7468    grid $top.cdisp - -sticky w -pady 10
7469    label $top.bg -padx 40 -relief sunk -background $bgcolor
7470    button $top.bgbut -text "Background" -font optionfont \
7471        -command [list choosecolor bgcolor 0 $top.bg background setbg]
7472    grid x $top.bgbut $top.bg -sticky w
7473    label $top.fg -padx 40 -relief sunk -background $fgcolor
7474    button $top.fgbut -text "Foreground" -font optionfont \
7475        -command [list choosecolor fgcolor 0 $top.fg foreground setfg]
7476    grid x $top.fgbut $top.fg -sticky w
7477    label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
7478    button $top.diffoldbut -text "Diff: old lines" -font optionfont \
7479        -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
7480                      [list $ctext tag conf d0 -foreground]]
7481    grid x $top.diffoldbut $top.diffold -sticky w
7482    label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
7483    button $top.diffnewbut -text "Diff: new lines" -font optionfont \
7484        -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
7485                      [list $ctext tag conf d1 -foreground]]
7486    grid x $top.diffnewbut $top.diffnew -sticky w
7487    label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
7488    button $top.hunksepbut -text "Diff: hunk header" -font optionfont \
7489        -command [list choosecolor diffcolors 2 $top.hunksep \
7490                      "diff hunk header" \
7491                      [list $ctext tag conf hunksep -foreground]]
7492    grid x $top.hunksepbut $top.hunksep -sticky w
7493    label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
7494    button $top.selbgbut -text "Select bg" -font optionfont \
7495        -command [list choosecolor selectbgcolor 0 $top.selbgsep background setselbg]
7496    grid x $top.selbgbut $top.selbgsep -sticky w
7497
7498    frame $top.buts
7499    button $top.buts.ok -text "OK" -command prefsok -default active
7500    $top.buts.ok configure -font $uifont
7501    button $top.buts.can -text "Cancel" -command prefscan -default normal
7502    $top.buts.can configure -font $uifont
7503    grid $top.buts.ok $top.buts.can
7504    grid columnconfigure $top.buts 0 -weight 1 -uniform a
7505    grid columnconfigure $top.buts 1 -weight 1 -uniform a
7506    grid $top.buts - - -pady 10 -sticky ew
7507    bind $top <Visibility> "focus $top.buts.ok"
7508}
7509
7510proc choosecolor {v vi w x cmd} {
7511    global $v
7512
7513    set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
7514               -title "Gitk: choose color for $x"]
7515    if {$c eq {}} return
7516    $w conf -background $c
7517    lset $v $vi $c
7518    eval $cmd $c
7519}
7520
7521proc setselbg {c} {
7522    global bglist cflist
7523    foreach w $bglist {
7524        $w configure -selectbackground $c
7525    }
7526    $cflist tag configure highlight \
7527        -background [$cflist cget -selectbackground]
7528    allcanvs itemconf secsel -fill $c
7529}
7530
7531proc setbg {c} {
7532    global bglist
7533
7534    foreach w $bglist {
7535        $w conf -background $c
7536    }
7537}
7538
7539proc setfg {c} {
7540    global fglist canv
7541
7542    foreach w $fglist {
7543        $w conf -foreground $c
7544    }
7545    allcanvs itemconf text -fill $c
7546    $canv itemconf circle -outline $c
7547}
7548
7549proc prefscan {} {
7550    global maxwidth maxgraphpct diffopts
7551    global oldprefs prefstop showneartags showlocalchanges limitdiffs
7552
7553    foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges \
7554                   limitdiffs} {
7555        set $v $oldprefs($v)
7556    }
7557    catch {destroy $prefstop}
7558    unset prefstop
7559}
7560
7561proc prefsok {} {
7562    global maxwidth maxgraphpct
7563    global oldprefs prefstop showneartags showlocalchanges
7564    global charspc ctext tabstop limitdiffs
7565
7566    catch {destroy $prefstop}
7567    unset prefstop
7568    $ctext configure -tabs "[expr {$tabstop * $charspc}]"
7569    if {$showlocalchanges != $oldprefs(showlocalchanges)} {
7570        if {$showlocalchanges} {
7571            doshowlocalchanges
7572        } else {
7573            dohidelocalchanges
7574        }
7575    }
7576    if {$maxwidth != $oldprefs(maxwidth)
7577        || $maxgraphpct != $oldprefs(maxgraphpct)} {
7578        redisplay
7579    } elseif {$showneartags != $oldprefs(showneartags) ||
7580          $limitdiffs != $oldprefs(limitdiffs)} {
7581        reselectline
7582    }
7583}
7584
7585proc formatdate {d} {
7586    global datetimeformat
7587    if {$d ne {}} {
7588        set d [clock format $d -format $datetimeformat]
7589    }
7590    return $d
7591}
7592
7593# This list of encoding names and aliases is distilled from
7594# http://www.iana.org/assignments/character-sets.
7595# Not all of them are supported by Tcl.
7596set encoding_aliases {
7597    { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
7598      ISO646-US US-ASCII us IBM367 cp367 csASCII }
7599    { ISO-10646-UTF-1 csISO10646UTF1 }
7600    { ISO_646.basic:1983 ref csISO646basic1983 }
7601    { INVARIANT csINVARIANT }
7602    { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
7603    { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
7604    { NATS-SEFI iso-ir-8-1 csNATSSEFI }
7605    { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
7606    { NATS-DANO iso-ir-9-1 csNATSDANO }
7607    { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
7608    { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
7609    { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
7610    { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
7611    { ISO-2022-KR csISO2022KR }
7612    { EUC-KR csEUCKR }
7613    { ISO-2022-JP csISO2022JP }
7614    { ISO-2022-JP-2 csISO2022JP2 }
7615    { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
7616      csISO13JISC6220jp }
7617    { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
7618    { IT iso-ir-15 ISO646-IT csISO15Italian }
7619    { PT iso-ir-16 ISO646-PT csISO16Portuguese }
7620    { ES iso-ir-17 ISO646-ES csISO17Spanish }
7621    { greek7-old iso-ir-18 csISO18Greek7Old }
7622    { latin-greek iso-ir-19 csISO19LatinGreek }
7623    { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
7624    { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
7625    { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
7626    { ISO_5427 iso-ir-37 csISO5427Cyrillic }
7627    { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
7628    { BS_viewdata iso-ir-47 csISO47BSViewdata }
7629    { INIS iso-ir-49 csISO49INIS }
7630    { INIS-8 iso-ir-50 csISO50INIS8 }
7631    { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
7632    { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
7633    { ISO_5428:1980 iso-ir-55 csISO5428Greek }
7634    { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
7635    { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
7636    { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
7637      csISO60Norwegian1 }
7638    { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
7639    { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
7640    { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
7641    { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
7642    { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
7643    { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
7644    { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
7645    { greek7 iso-ir-88 csISO88Greek7 }
7646    { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
7647    { iso-ir-90 csISO90 }
7648    { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
7649    { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
7650      csISO92JISC62991984b }
7651    { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
7652    { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
7653    { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
7654      csISO95JIS62291984handadd }
7655    { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
7656    { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
7657    { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
7658    { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
7659      CP819 csISOLatin1 }
7660    { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
7661    { T.61-7bit iso-ir-102 csISO102T617bit }
7662    { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
7663    { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
7664    { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
7665    { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
7666    { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
7667    { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
7668    { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
7669    { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
7670      arabic csISOLatinArabic }
7671    { ISO_8859-6-E csISO88596E ISO-8859-6-E }
7672    { ISO_8859-6-I csISO88596I ISO-8859-6-I }
7673    { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
7674      greek greek8 csISOLatinGreek }
7675    { T.101-G2 iso-ir-128 csISO128T101G2 }
7676    { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
7677      csISOLatinHebrew }
7678    { ISO_8859-8-E csISO88598E ISO-8859-8-E }
7679    { ISO_8859-8-I csISO88598I ISO-8859-8-I }
7680    { CSN_369103 iso-ir-139 csISO139CSN369103 }
7681    { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
7682    { ISO_6937-2-add iso-ir-142 csISOTextComm }
7683    { IEC_P27-1 iso-ir-143 csISO143IECP271 }
7684    { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
7685      csISOLatinCyrillic }
7686    { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
7687    { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
7688    { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
7689    { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
7690    { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
7691    { ISO_6937-2-25 iso-ir-152 csISO6937Add }
7692    { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
7693    { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
7694    { ISO_10367-box iso-ir-155 csISO10367Box }
7695    { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
7696    { latin-lap lap iso-ir-158 csISO158Lap }
7697    { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
7698    { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
7699    { us-dk csUSDK }
7700    { dk-us csDKUS }
7701    { JIS_X0201 X0201 csHalfWidthKatakana }
7702    { KSC5636 ISO646-KR csKSC5636 }
7703    { ISO-10646-UCS-2 csUnicode }
7704    { ISO-10646-UCS-4 csUCS4 }
7705    { DEC-MCS dec csDECMCS }
7706    { hp-roman8 roman8 r8 csHPRoman8 }
7707    { macintosh mac csMacintosh }
7708    { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
7709      csIBM037 }
7710    { IBM038 EBCDIC-INT cp038 csIBM038 }
7711    { IBM273 CP273 csIBM273 }
7712    { IBM274 EBCDIC-BE CP274 csIBM274 }
7713    { IBM275 EBCDIC-BR cp275 csIBM275 }
7714    { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
7715    { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
7716    { IBM280 CP280 ebcdic-cp-it csIBM280 }
7717    { IBM281 EBCDIC-JP-E cp281 csIBM281 }
7718    { IBM284 CP284 ebcdic-cp-es csIBM284 }
7719    { IBM285 CP285 ebcdic-cp-gb csIBM285 }
7720    { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
7721    { IBM297 cp297 ebcdic-cp-fr csIBM297 }
7722    { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
7723    { IBM423 cp423 ebcdic-cp-gr csIBM423 }
7724    { IBM424 cp424 ebcdic-cp-he csIBM424 }
7725    { IBM437 cp437 437 csPC8CodePage437 }
7726    { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
7727    { IBM775 cp775 csPC775Baltic }
7728    { IBM850 cp850 850 csPC850Multilingual }
7729    { IBM851 cp851 851 csIBM851 }
7730    { IBM852 cp852 852 csPCp852 }
7731    { IBM855 cp855 855 csIBM855 }
7732    { IBM857 cp857 857 csIBM857 }
7733    { IBM860 cp860 860 csIBM860 }
7734    { IBM861 cp861 861 cp-is csIBM861 }
7735    { IBM862 cp862 862 csPC862LatinHebrew }
7736    { IBM863 cp863 863 csIBM863 }
7737    { IBM864 cp864 csIBM864 }
7738    { IBM865 cp865 865 csIBM865 }
7739    { IBM866 cp866 866 csIBM866 }
7740    { IBM868 CP868 cp-ar csIBM868 }
7741    { IBM869 cp869 869 cp-gr csIBM869 }
7742    { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
7743    { IBM871 CP871 ebcdic-cp-is csIBM871 }
7744    { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
7745    { IBM891 cp891 csIBM891 }
7746    { IBM903 cp903 csIBM903 }
7747    { IBM904 cp904 904 csIBBM904 }
7748    { IBM905 CP905 ebcdic-cp-tr csIBM905 }
7749    { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
7750    { IBM1026 CP1026 csIBM1026 }
7751    { EBCDIC-AT-DE csIBMEBCDICATDE }
7752    { EBCDIC-AT-DE-A csEBCDICATDEA }
7753    { EBCDIC-CA-FR csEBCDICCAFR }
7754    { EBCDIC-DK-NO csEBCDICDKNO }
7755    { EBCDIC-DK-NO-A csEBCDICDKNOA }
7756    { EBCDIC-FI-SE csEBCDICFISE }
7757    { EBCDIC-FI-SE-A csEBCDICFISEA }
7758    { EBCDIC-FR csEBCDICFR }
7759    { EBCDIC-IT csEBCDICIT }
7760    { EBCDIC-PT csEBCDICPT }
7761    { EBCDIC-ES csEBCDICES }
7762    { EBCDIC-ES-A csEBCDICESA }
7763    { EBCDIC-ES-S csEBCDICESS }
7764    { EBCDIC-UK csEBCDICUK }
7765    { EBCDIC-US csEBCDICUS }
7766    { UNKNOWN-8BIT csUnknown8BiT }
7767    { MNEMONIC csMnemonic }
7768    { MNEM csMnem }
7769    { VISCII csVISCII }
7770    { VIQR csVIQR }
7771    { KOI8-R csKOI8R }
7772    { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
7773    { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
7774    { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
7775    { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
7776    { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
7777    { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
7778    { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
7779    { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
7780    { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
7781    { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
7782    { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
7783    { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
7784    { IBM1047 IBM-1047 }
7785    { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
7786    { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
7787    { UNICODE-1-1 csUnicode11 }
7788    { CESU-8 csCESU-8 }
7789    { BOCU-1 csBOCU-1 }
7790    { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
7791    { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
7792      l8 }
7793    { ISO-8859-15 ISO_8859-15 Latin-9 }
7794    { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
7795    { GBK CP936 MS936 windows-936 }
7796    { JIS_Encoding csJISEncoding }
7797    { Shift_JIS MS_Kanji csShiftJIS }
7798    { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
7799      EUC-JP }
7800    { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
7801    { ISO-10646-UCS-Basic csUnicodeASCII }
7802    { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
7803    { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
7804    { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
7805    { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
7806    { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
7807    { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
7808    { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
7809    { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
7810    { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
7811    { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
7812    { Adobe-Standard-Encoding csAdobeStandardEncoding }
7813    { Ventura-US csVenturaUS }
7814    { Ventura-International csVenturaInternational }
7815    { PC8-Danish-Norwegian csPC8DanishNorwegian }
7816    { PC8-Turkish csPC8Turkish }
7817    { IBM-Symbols csIBMSymbols }
7818    { IBM-Thai csIBMThai }
7819    { HP-Legal csHPLegal }
7820    { HP-Pi-font csHPPiFont }
7821    { HP-Math8 csHPMath8 }
7822    { Adobe-Symbol-Encoding csHPPSMath }
7823    { HP-DeskTop csHPDesktop }
7824    { Ventura-Math csVenturaMath }
7825    { Microsoft-Publishing csMicrosoftPublishing }
7826    { Windows-31J csWindows31J }
7827    { GB2312 csGB2312 }
7828    { Big5 csBig5 }
7829}
7830
7831proc tcl_encoding {enc} {
7832    global encoding_aliases
7833    set names [encoding names]
7834    set lcnames [string tolower $names]
7835    set enc [string tolower $enc]
7836    set i [lsearch -exact $lcnames $enc]
7837    if {$i < 0} {
7838        # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
7839        if {[regsub {^iso[-_]} $enc iso encx]} {
7840            set i [lsearch -exact $lcnames $encx]
7841        }
7842    }
7843    if {$i < 0} {
7844        foreach l $encoding_aliases {
7845            set ll [string tolower $l]
7846            if {[lsearch -exact $ll $enc] < 0} continue
7847            # look through the aliases for one that tcl knows about
7848            foreach e $ll {
7849                set i [lsearch -exact $lcnames $e]
7850                if {$i < 0} {
7851                    if {[regsub {^iso[-_]} $e iso ex]} {
7852                        set i [lsearch -exact $lcnames $ex]
7853                    }
7854                }
7855                if {$i >= 0} break
7856            }
7857            break
7858        }
7859    }
7860    if {$i >= 0} {
7861        return [lindex $names $i]
7862    }
7863    return {}
7864}
7865
7866# First check that Tcl/Tk is recent enough
7867if {[catch {package require Tk 8.4} err]} {
7868    show_error {} . "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
7869                     Gitk requires at least Tcl/Tk 8.4."
7870    exit 1
7871}
7872
7873# defaults...
7874set datemode 0
7875set diffopts "-U 5 -p"
7876set wrcomcmd "git diff-tree --stdin -p --pretty"
7877
7878set gitencoding {}
7879catch {
7880    set gitencoding [exec git config --get i18n.commitencoding]
7881}
7882if {$gitencoding == ""} {
7883    set gitencoding "utf-8"
7884}
7885set tclencoding [tcl_encoding $gitencoding]
7886if {$tclencoding == {}} {
7887    puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
7888}
7889
7890set mainfont {Helvetica 9}
7891set textfont {Courier 9}
7892set uifont {Helvetica 9 bold}
7893set tabstop 8
7894set findmergefiles 0
7895set maxgraphpct 50
7896set maxwidth 16
7897set revlistorder 0
7898set fastdate 0
7899set uparrowlen 7
7900set downarrowlen 7
7901set mingaplen 30
7902set cmitmode "patch"
7903set wrapcomment "none"
7904set showneartags 1
7905set maxrefs 20
7906set maxlinelen 200
7907set showlocalchanges 1
7908set limitdiffs 1
7909set datetimeformat "%Y-%m-%d %H:%M:%S"
7910
7911set colors {green red blue magenta darkgrey brown orange}
7912set bgcolor white
7913set fgcolor black
7914set diffcolors {red "#00a000" blue}
7915set diffcontext 3
7916set selectbgcolor gray85
7917
7918catch {source ~/.gitk}
7919
7920font create optionfont -family sans-serif -size -12
7921
7922# check that we can find a .git directory somewhere...
7923if {[catch {set gitdir [gitdir]}]} {
7924    show_error {} . "Cannot find a git repository here."
7925    exit 1
7926}
7927if {![file isdirectory $gitdir]} {
7928    show_error {} . "Cannot find the git directory \"$gitdir\"."
7929    exit 1
7930}
7931
7932set mergeonly 0
7933set revtreeargs {}
7934set cmdline_files {}
7935set i 0
7936foreach arg $argv {
7937    switch -- $arg {
7938        "" { }
7939        "-d" { set datemode 1 }
7940        "--merge" {
7941            set mergeonly 1
7942            lappend revtreeargs $arg
7943        }
7944        "--" {
7945            set cmdline_files [lrange $argv [expr {$i + 1}] end]
7946            break
7947        }
7948        default {
7949            lappend revtreeargs $arg
7950        }
7951    }
7952    incr i
7953}
7954
7955if {$i >= [llength $argv] && $revtreeargs ne {}} {
7956    # no -- on command line, but some arguments (other than -d)
7957    if {[catch {
7958        set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
7959        set cmdline_files [split $f "\n"]
7960        set n [llength $cmdline_files]
7961        set revtreeargs [lrange $revtreeargs 0 end-$n]
7962        # Unfortunately git rev-parse doesn't produce an error when
7963        # something is both a revision and a filename.  To be consistent
7964        # with git log and git rev-list, check revtreeargs for filenames.
7965        foreach arg $revtreeargs {
7966            if {[file exists $arg]} {
7967                show_error {} . "Ambiguous argument '$arg': both revision\
7968                                 and filename"
7969                exit 1
7970            }
7971        }
7972    } err]} {
7973        # unfortunately we get both stdout and stderr in $err,
7974        # so look for "fatal:".
7975        set i [string first "fatal:" $err]
7976        if {$i > 0} {
7977            set err [string range $err [expr {$i + 6}] end]
7978        }
7979        show_error {} . "Bad arguments to gitk:\n$err"
7980        exit 1
7981    }
7982}
7983
7984if {$mergeonly} {
7985    # find the list of unmerged files
7986    set mlist {}
7987    set nr_unmerged 0
7988    if {[catch {
7989        set fd [open "| git ls-files -u" r]
7990    } err]} {
7991        show_error {} . "Couldn't get list of unmerged files: $err"
7992        exit 1
7993    }
7994    while {[gets $fd line] >= 0} {
7995        set i [string first "\t" $line]
7996        if {$i < 0} continue
7997        set fname [string range $line [expr {$i+1}] end]
7998        if {[lsearch -exact $mlist $fname] >= 0} continue
7999        incr nr_unmerged
8000        if {$cmdline_files eq {} || [path_filter $cmdline_files $fname]} {
8001            lappend mlist $fname
8002        }
8003    }
8004    catch {close $fd}
8005    if {$mlist eq {}} {
8006        if {$nr_unmerged == 0} {
8007            show_error {} . "No files selected: --merge specified but\
8008                             no files are unmerged."
8009        } else {
8010            show_error {} . "No files selected: --merge specified but\
8011                             no unmerged files are within file limit."
8012        }
8013        exit 1
8014    }
8015    set cmdline_files $mlist
8016}
8017
8018set nullid "0000000000000000000000000000000000000000"
8019set nullid2 "0000000000000000000000000000000000000001"
8020
8021
8022set runq {}
8023set history {}
8024set historyindex 0
8025set fh_serial 0
8026set nhl_names {}
8027set highlight_paths {}
8028set searchdirn -forwards
8029set boldrows {}
8030set boldnamerows {}
8031set diffelide {0 0}
8032set markingmatches 0
8033
8034set optim_delay 16
8035
8036set nextviewnum 1
8037set curview 0
8038set selectedview 0
8039set selectedhlview None
8040set viewfiles(0) {}
8041set viewperm(0) 0
8042set viewargs(0) {}
8043
8044set cmdlineok 0
8045set stopped 0
8046set stuffsaved 0
8047set patchnum 0
8048set lookingforhead 0
8049set localirow -1
8050set localfrow -1
8051set lserial 0
8052setcoords
8053makewindow
8054# wait for the window to become visible
8055tkwait visibility .
8056wm title . "[file tail $argv0]: [file tail [pwd]]"
8057readrefs
8058
8059if {$cmdline_files ne {} || $revtreeargs ne {}} {
8060    # create a view for the files/dirs specified on the command line
8061    set curview 1
8062    set selectedview 1
8063    set nextviewnum 2
8064    set viewname(1) "Command line"
8065    set viewfiles(1) $cmdline_files
8066    set viewargs(1) $revtreeargs
8067    set viewperm(1) 0
8068    addviewmenu 1
8069    .bar.view entryconf Edit* -state normal
8070    .bar.view entryconf Delete* -state normal
8071}
8072
8073if {[info exists permviews]} {
8074    foreach v $permviews {
8075        set n $nextviewnum
8076        incr nextviewnum
8077        set viewname($n) [lindex $v 0]
8078        set viewfiles($n) [lindex $v 1]
8079        set viewargs($n) [lindex $v 2]
8080        set viewperm($n) 1
8081        addviewmenu $n
8082    }
8083}
8084getcommits