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