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