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