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