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