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