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