gitkon commit gitk: Various speed improvements (8f7d0ce)
   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 parse_args {rargs} {
  20    global parsed_args
  21
  22    if {[catch {
  23        set parse_args [concat --default HEAD $rargs]
  24        set parsed_args [split [eval exec git-rev-parse $parse_args] "\n"]
  25    }]} {
  26        # if git-rev-parse failed for some reason...
  27        if {$rargs == {}} {
  28            set rargs HEAD
  29        }
  30        set parsed_args $rargs
  31    }
  32    return $parsed_args
  33}
  34
  35proc start_rev_list {rlargs} {
  36    global startmsecs nextupdate ncmupdate
  37    global commfd leftover tclencoding datemode
  38
  39    set startmsecs [clock clicks -milliseconds]
  40    set nextupdate [expr {$startmsecs + 100}]
  41    set ncmupdate 1
  42    initlayout
  43    set order "--topo-order"
  44    if {$datemode} {
  45        set order "--date-order"
  46    }
  47    if {[catch {
  48        set commfd [open [concat | git-rev-list --header $order \
  49                              --parents $rlargs] r]
  50    } err]} {
  51        puts stderr "Error executing git-rev-list: $err"
  52        exit 1
  53    }
  54    set leftover {}
  55    fconfigure $commfd -blocking 0 -translation lf
  56    if {$tclencoding != {}} {
  57        fconfigure $commfd -encoding $tclencoding
  58    }
  59    fileevent $commfd readable [list getcommitlines $commfd]
  60    . config -cursor watch
  61    settextcursor watch
  62}
  63
  64proc getcommits {rargs} {
  65    global phase canv mainfont
  66
  67    set phase getcommits
  68    start_rev_list [parse_args $rargs]
  69    $canv delete all
  70    $canv create text 3 3 -anchor nw -text "Reading commits..." \
  71        -font $mainfont -tags textitems
  72}
  73
  74proc getcommitlines {commfd}  {
  75    global parents cdate children nchildren
  76    global commitlisted commitinfo phase nextupdate
  77    global stopped leftover
  78    global canv
  79    global displayorder commitidx commitrow
  80
  81    set stuff [read $commfd]
  82    if {$stuff == {}} {
  83        if {![eof $commfd]} return
  84        # set it blocking so we wait for the process to terminate
  85        fconfigure $commfd -blocking 1
  86        if {![catch {close $commfd} err]} {
  87            after idle finishcommits
  88            return
  89        }
  90        if {[string range $err 0 4] == "usage"} {
  91            set err \
  92                "Gitk: error reading commits: bad arguments to git-rev-list.\
  93                (Note: arguments to gitk are passed to git-rev-list\
  94                to allow selection of commits to be displayed.)"
  95        } else {
  96            set err "Error reading commits: $err"
  97        }
  98        error_popup $err
  99        exit 1
 100    }
 101    set start 0
 102    set gotsome 0
 103    while 1 {
 104        set i [string first "\0" $stuff $start]
 105        if {$i < 0} {
 106            append leftover [string range $stuff $start end]
 107            break
 108        }
 109        if {$start == 0} {
 110            set cmit $leftover
 111            append cmit [string range $stuff 0 [expr {$i - 1}]]
 112            set leftover {}
 113        } else {
 114            set cmit [string range $stuff $start [expr {$i - 1}]]
 115        }
 116        set start [expr {$i + 1}]
 117        set j [string first "\n" $cmit]
 118        set ok 0
 119        if {$j >= 0} {
 120            set ids [string range $cmit 0 [expr {$j - 1}]]
 121            set ok 1
 122            foreach id $ids {
 123                if {[string length $id] != 40} {
 124                    set ok 0
 125                    break
 126                }
 127            }
 128        }
 129        if {!$ok} {
 130            set shortcmit $cmit
 131            if {[string length $shortcmit] > 80} {
 132                set shortcmit "[string range $shortcmit 0 80]..."
 133            }
 134            error_popup "Can't parse git-rev-list output: {$shortcmit}"
 135            exit 1
 136        }
 137        set id [lindex $ids 0]
 138        set olds [lrange $ids 1 end]
 139        set cmit [string range $cmit [expr {$j + 1}] end]
 140        set commitlisted($id) 1
 141        updatechildren $id [lrange $ids 1 end]
 142        if {![info exists commitinfo($id)]} {
 143            parsecommit $id $cmit 1
 144        }
 145        set commitrow($id) $commitidx
 146        incr commitidx
 147        lappend displayorder $id
 148        set gotsome 1
 149    }
 150    if {$gotsome} {
 151        layoutmore
 152    }
 153    if {[clock clicks -milliseconds] >= $nextupdate} {
 154        doupdate 1
 155    }
 156}
 157
 158proc doupdate {reading} {
 159    global commfd nextupdate numcommits ncmupdate
 160
 161    if {$reading} {
 162        fileevent $commfd readable {}
 163    }
 164    update
 165    set nextupdate [expr {[clock clicks -milliseconds] + 100}]
 166    if {$numcommits < 100} {
 167        set ncmupdate [expr {$numcommits + 1}]
 168    } elseif {$numcommits < 10000} {
 169        set ncmupdate [expr {$numcommits + 10}]
 170    } else {
 171        set ncmupdate [expr {$numcommits + 100}]
 172    }
 173    if {$reading} {
 174        fileevent $commfd readable [list getcommitlines $commfd]
 175    }
 176}
 177
 178proc readcommit {id} {
 179    if {[catch {set contents [exec git-cat-file commit $id]}]} return
 180    updatechildren $id {}
 181    parsecommit $id $contents 0
 182}
 183
 184proc updatecommits {rargs} {
 185    stopfindproc
 186    foreach v {children nchildren parents nparents commitlisted
 187        colormap selectedline matchinglines treediffs
 188        mergefilelist currentid rowtextx commitrow
 189        rowidlist rowoffsets idrowranges idrangedrawn iddrawn
 190        linesegends crossings cornercrossings} {
 191        global $v
 192        catch {unset $v}
 193    }
 194    allcanvs delete all
 195    readrefs
 196    getcommits $rargs
 197}
 198
 199proc updatechildren {id olds} {
 200    global children nchildren parents nparents
 201
 202    if {![info exists nchildren($id)]} {
 203        set children($id) {}
 204        set nchildren($id) 0
 205    }
 206    set parents($id) $olds
 207    set nparents($id) [llength $olds]
 208    foreach p $olds {
 209        if {![info exists nchildren($p)]} {
 210            set children($p) [list $id]
 211            set nchildren($p) 1
 212        } elseif {[lsearch -exact $children($p) $id] < 0} {
 213            lappend children($p) $id
 214            incr nchildren($p)
 215        }
 216    }
 217}
 218
 219proc parsecommit {id contents listed} {
 220    global commitinfo cdate
 221
 222    set inhdr 1
 223    set comment {}
 224    set headline {}
 225    set auname {}
 226    set audate {}
 227    set comname {}
 228    set comdate {}
 229    set hdrend [string first "\n\n" $contents]
 230    if {$hdrend < 0} {
 231        # should never happen...
 232        set hdrend [string length $contents]
 233    }
 234    set header [string range $contents 0 [expr {$hdrend - 1}]]
 235    set comment [string range $contents [expr {$hdrend + 2}] end]
 236    foreach line [split $header "\n"] {
 237        set tag [lindex $line 0]
 238        if {$tag == "author"} {
 239            set audate [lindex $line end-1]
 240            set auname [lrange $line 1 end-2]
 241        } elseif {$tag == "committer"} {
 242            set comdate [lindex $line end-1]
 243            set comname [lrange $line 1 end-2]
 244        }
 245    }
 246    set headline {}
 247    # take the first line of the comment as the headline
 248    set i [string first "\n" $comment]
 249    if {$i >= 0} {
 250        set headline [string trim [string range $comment 0 $i]]
 251    } else {
 252        set headline $comment
 253    }
 254    if {!$listed} {
 255        # git-rev-list indents the comment by 4 spaces;
 256        # if we got this via git-cat-file, add the indentation
 257        set newcomment {}
 258        foreach line [split $comment "\n"] {
 259            append newcomment "    "
 260            append newcomment $line
 261            append newcomment "\n"
 262        }
 263        set comment $newcomment
 264    }
 265    if {$comdate != {}} {
 266        set cdate($id) $comdate
 267    }
 268    set commitinfo($id) [list $headline $auname $audate \
 269                             $comname $comdate $comment]
 270}
 271
 272proc readrefs {} {
 273    global tagids idtags headids idheads tagcontents
 274    global otherrefids idotherrefs
 275
 276    foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
 277        catch {unset $v}
 278    }
 279    set refd [open [list | git-ls-remote [gitdir]] r]
 280    while {0 <= [set n [gets $refd line]]} {
 281        if {![regexp {^([0-9a-f]{40})   refs/([^^]*)$} $line \
 282            match id path]} {
 283            continue
 284        }
 285        if {![regexp {^(tags|heads)/(.*)$} $path match type name]} {
 286            set type others
 287            set name $path
 288        }
 289        if {$type == "tags"} {
 290            set tagids($name) $id
 291            lappend idtags($id) $name
 292            set obj {}
 293            set type {}
 294            set tag {}
 295            catch {
 296                set commit [exec git-rev-parse "$id^0"]
 297                if {"$commit" != "$id"} {
 298                    set tagids($name) $commit
 299                    lappend idtags($commit) $name
 300                }
 301            }           
 302            catch {
 303                set tagcontents($name) [exec git-cat-file tag "$id"]
 304            }
 305        } elseif { $type == "heads" } {
 306            set headids($name) $id
 307            lappend idheads($id) $name
 308        } else {
 309            set otherrefids($name) $id
 310            lappend idotherrefs($id) $name
 311        }
 312    }
 313    close $refd
 314}
 315
 316proc error_popup msg {
 317    set w .error
 318    toplevel $w
 319    wm transient $w .
 320    message $w.m -text $msg -justify center -aspect 400
 321    pack $w.m -side top -fill x -padx 20 -pady 20
 322    button $w.ok -text OK -command "destroy $w"
 323    pack $w.ok -side bottom -fill x
 324    bind $w <Visibility> "grab $w; focus $w"
 325    tkwait window $w
 326}
 327
 328proc makewindow {rargs} {
 329    global canv canv2 canv3 linespc charspc ctext cflist textfont
 330    global findtype findtypemenu findloc findstring fstring geometry
 331    global entries sha1entry sha1string sha1but
 332    global maincursor textcursor curtextcursor
 333    global rowctxmenu mergemax
 334
 335    menu .bar
 336    .bar add cascade -label "File" -menu .bar.file
 337    menu .bar.file
 338    .bar.file add command -label "Update" -command [list updatecommits $rargs]
 339    .bar.file add command -label "Reread references" -command rereadrefs
 340    .bar.file add command -label "Quit" -command doquit
 341    menu .bar.edit
 342    .bar add cascade -label "Edit" -menu .bar.edit
 343    .bar.edit add command -label "Preferences" -command doprefs
 344    menu .bar.help
 345    .bar add cascade -label "Help" -menu .bar.help
 346    .bar.help add command -label "About gitk" -command about
 347    . configure -menu .bar
 348
 349    if {![info exists geometry(canv1)]} {
 350        set geometry(canv1) [expr {45 * $charspc}]
 351        set geometry(canv2) [expr {30 * $charspc}]
 352        set geometry(canv3) [expr {15 * $charspc}]
 353        set geometry(canvh) [expr {25 * $linespc + 4}]
 354        set geometry(ctextw) 80
 355        set geometry(ctexth) 30
 356        set geometry(cflistw) 30
 357    }
 358    panedwindow .ctop -orient vertical
 359    if {[info exists geometry(width)]} {
 360        .ctop conf -width $geometry(width) -height $geometry(height)
 361        set texth [expr {$geometry(height) - $geometry(canvh) - 56}]
 362        set geometry(ctexth) [expr {($texth - 8) /
 363                                    [font metrics $textfont -linespace]}]
 364    }
 365    frame .ctop.top
 366    frame .ctop.top.bar
 367    pack .ctop.top.bar -side bottom -fill x
 368    set cscroll .ctop.top.csb
 369    scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
 370    pack $cscroll -side right -fill y
 371    panedwindow .ctop.top.clist -orient horizontal -sashpad 0 -handlesize 4
 372    pack .ctop.top.clist -side top -fill both -expand 1
 373    .ctop add .ctop.top
 374    set canv .ctop.top.clist.canv
 375    canvas $canv -height $geometry(canvh) -width $geometry(canv1) \
 376        -bg white -bd 0 \
 377        -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
 378    .ctop.top.clist add $canv
 379    set canv2 .ctop.top.clist.canv2
 380    canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \
 381        -bg white -bd 0 -yscrollincr $linespc
 382    .ctop.top.clist add $canv2
 383    set canv3 .ctop.top.clist.canv3
 384    canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \
 385        -bg white -bd 0 -yscrollincr $linespc
 386    .ctop.top.clist add $canv3
 387    bind .ctop.top.clist <Configure> {resizeclistpanes %W %w}
 388
 389    set sha1entry .ctop.top.bar.sha1
 390    set entries $sha1entry
 391    set sha1but .ctop.top.bar.sha1label
 392    button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
 393        -command gotocommit -width 8
 394    $sha1but conf -disabledforeground [$sha1but cget -foreground]
 395    pack .ctop.top.bar.sha1label -side left
 396    entry $sha1entry -width 40 -font $textfont -textvariable sha1string
 397    trace add variable sha1string write sha1change
 398    pack $sha1entry -side left -pady 2
 399
 400    image create bitmap bm-left -data {
 401        #define left_width 16
 402        #define left_height 16
 403        static unsigned char left_bits[] = {
 404        0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
 405        0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
 406        0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
 407    }
 408    image create bitmap bm-right -data {
 409        #define right_width 16
 410        #define right_height 16
 411        static unsigned char right_bits[] = {
 412        0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
 413        0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
 414        0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
 415    }
 416    button .ctop.top.bar.leftbut -image bm-left -command goback \
 417        -state disabled -width 26
 418    pack .ctop.top.bar.leftbut -side left -fill y
 419    button .ctop.top.bar.rightbut -image bm-right -command goforw \
 420        -state disabled -width 26
 421    pack .ctop.top.bar.rightbut -side left -fill y
 422
 423    button .ctop.top.bar.findbut -text "Find" -command dofind
 424    pack .ctop.top.bar.findbut -side left
 425    set findstring {}
 426    set fstring .ctop.top.bar.findstring
 427    lappend entries $fstring
 428    entry $fstring -width 30 -font $textfont -textvariable findstring
 429    pack $fstring -side left -expand 1 -fill x
 430    set findtype Exact
 431    set findtypemenu [tk_optionMenu .ctop.top.bar.findtype \
 432                          findtype Exact IgnCase Regexp]
 433    set findloc "All fields"
 434    tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \
 435        Comments Author Committer Files Pickaxe
 436    pack .ctop.top.bar.findloc -side right
 437    pack .ctop.top.bar.findtype -side right
 438    # for making sure type==Exact whenever loc==Pickaxe
 439    trace add variable findloc write findlocchange
 440
 441    panedwindow .ctop.cdet -orient horizontal
 442    .ctop add .ctop.cdet
 443    frame .ctop.cdet.left
 444    set ctext .ctop.cdet.left.ctext
 445    text $ctext -bg white -state disabled -font $textfont \
 446        -width $geometry(ctextw) -height $geometry(ctexth) \
 447        -yscrollcommand ".ctop.cdet.left.sb set" -wrap none
 448    scrollbar .ctop.cdet.left.sb -command "$ctext yview"
 449    pack .ctop.cdet.left.sb -side right -fill y
 450    pack $ctext -side left -fill both -expand 1
 451    .ctop.cdet add .ctop.cdet.left
 452
 453    $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
 454    $ctext tag conf hunksep -fore blue
 455    $ctext tag conf d0 -fore red
 456    $ctext tag conf d1 -fore "#00a000"
 457    $ctext tag conf m0 -fore red
 458    $ctext tag conf m1 -fore blue
 459    $ctext tag conf m2 -fore green
 460    $ctext tag conf m3 -fore purple
 461    $ctext tag conf m4 -fore brown
 462    $ctext tag conf m5 -fore "#009090"
 463    $ctext tag conf m6 -fore magenta
 464    $ctext tag conf m7 -fore "#808000"
 465    $ctext tag conf m8 -fore "#009000"
 466    $ctext tag conf m9 -fore "#ff0080"
 467    $ctext tag conf m10 -fore cyan
 468    $ctext tag conf m11 -fore "#b07070"
 469    $ctext tag conf m12 -fore "#70b0f0"
 470    $ctext tag conf m13 -fore "#70f0b0"
 471    $ctext tag conf m14 -fore "#f0b070"
 472    $ctext tag conf m15 -fore "#ff70b0"
 473    $ctext tag conf mmax -fore darkgrey
 474    set mergemax 16
 475    $ctext tag conf mresult -font [concat $textfont bold]
 476    $ctext tag conf msep -font [concat $textfont bold]
 477    $ctext tag conf found -back yellow
 478
 479    frame .ctop.cdet.right
 480    set cflist .ctop.cdet.right.cfiles
 481    listbox $cflist -bg white -selectmode extended -width $geometry(cflistw) \
 482        -yscrollcommand ".ctop.cdet.right.sb set"
 483    scrollbar .ctop.cdet.right.sb -command "$cflist yview"
 484    pack .ctop.cdet.right.sb -side right -fill y
 485    pack $cflist -side left -fill both -expand 1
 486    .ctop.cdet add .ctop.cdet.right
 487    bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
 488
 489    pack .ctop -side top -fill both -expand 1
 490
 491    bindall <1> {selcanvline %W %x %y}
 492    #bindall <B1-Motion> {selcanvline %W %x %y}
 493    bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
 494    bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
 495    bindall <2> "allcanvs scan mark 0 %y"
 496    bindall <B2-Motion> "allcanvs scan dragto 0 %y"
 497    bind . <Key-Up> "selnextline -1"
 498    bind . <Key-Down> "selnextline 1"
 499    bind . <Key-Right> "goforw"
 500    bind . <Key-Left> "goback"
 501    bind . <Key-Prior> "allcanvs yview scroll -1 pages"
 502    bind . <Key-Next> "allcanvs yview scroll 1 pages"
 503    bindkey <Key-Delete> "$ctext yview scroll -1 pages"
 504    bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
 505    bindkey <Key-space> "$ctext yview scroll 1 pages"
 506    bindkey p "selnextline -1"
 507    bindkey n "selnextline 1"
 508    bindkey z "goback"
 509    bindkey x "goforw"
 510    bindkey i "selnextline -1"
 511    bindkey k "selnextline 1"
 512    bindkey j "goback"
 513    bindkey l "goforw"
 514    bindkey b "$ctext yview scroll -1 pages"
 515    bindkey d "$ctext yview scroll 18 units"
 516    bindkey u "$ctext yview scroll -18 units"
 517    bindkey / {findnext 1}
 518    bindkey <Key-Return> {findnext 0}
 519    bindkey ? findprev
 520    bindkey f nextfile
 521    bind . <Control-q> doquit
 522    bind . <Control-f> dofind
 523    bind . <Control-g> {findnext 0}
 524    bind . <Control-r> findprev
 525    bind . <Control-equal> {incrfont 1}
 526    bind . <Control-KP_Add> {incrfont 1}
 527    bind . <Control-minus> {incrfont -1}
 528    bind . <Control-KP_Subtract> {incrfont -1}
 529    bind $cflist <<ListboxSelect>> listboxsel
 530    bind . <Destroy> {savestuff %W}
 531    bind . <Button-1> "click %W"
 532    bind $fstring <Key-Return> dofind
 533    bind $sha1entry <Key-Return> gotocommit
 534    bind $sha1entry <<PasteSelection>> clearsha1
 535
 536    set maincursor [. cget -cursor]
 537    set textcursor [$ctext cget -cursor]
 538    set curtextcursor $textcursor
 539
 540    set rowctxmenu .rowctxmenu
 541    menu $rowctxmenu -tearoff 0
 542    $rowctxmenu add command -label "Diff this -> selected" \
 543        -command {diffvssel 0}
 544    $rowctxmenu add command -label "Diff selected -> this" \
 545        -command {diffvssel 1}
 546    $rowctxmenu add command -label "Make patch" -command mkpatch
 547    $rowctxmenu add command -label "Create tag" -command mktag
 548    $rowctxmenu add command -label "Write commit to file" -command writecommit
 549}
 550
 551proc scrollcanv {cscroll f0 f1} {
 552    $cscroll set $f0 $f1
 553    drawfrac $f0 $f1
 554}
 555
 556# when we make a key binding for the toplevel, make sure
 557# it doesn't get triggered when that key is pressed in the
 558# find string entry widget.
 559proc bindkey {ev script} {
 560    global entries
 561    bind . $ev $script
 562    set escript [bind Entry $ev]
 563    if {$escript == {}} {
 564        set escript [bind Entry <Key>]
 565    }
 566    foreach e $entries {
 567        bind $e $ev "$escript; break"
 568    }
 569}
 570
 571# set the focus back to the toplevel for any click outside
 572# the entry widgets
 573proc click {w} {
 574    global entries
 575    foreach e $entries {
 576        if {$w == $e} return
 577    }
 578    focus .
 579}
 580
 581proc savestuff {w} {
 582    global canv canv2 canv3 ctext cflist mainfont textfont
 583    global stuffsaved findmergefiles maxgraphpct
 584    global maxwidth
 585
 586    if {$stuffsaved} return
 587    if {![winfo viewable .]} return
 588    catch {
 589        set f [open "~/.gitk-new" w]
 590        puts $f [list set mainfont $mainfont]
 591        puts $f [list set textfont $textfont]
 592        puts $f [list set findmergefiles $findmergefiles]
 593        puts $f [list set maxgraphpct $maxgraphpct]
 594        puts $f [list set maxwidth $maxwidth]
 595        puts $f "set geometry(width) [winfo width .ctop]"
 596        puts $f "set geometry(height) [winfo height .ctop]"
 597        puts $f "set geometry(canv1) [expr {[winfo width $canv]-2}]"
 598        puts $f "set geometry(canv2) [expr {[winfo width $canv2]-2}]"
 599        puts $f "set geometry(canv3) [expr {[winfo width $canv3]-2}]"
 600        puts $f "set geometry(canvh) [expr {[winfo height $canv]-2}]"
 601        set wid [expr {([winfo width $ctext] - 8) \
 602                           / [font measure $textfont "0"]}]
 603        puts $f "set geometry(ctextw) $wid"
 604        set wid [expr {([winfo width $cflist] - 11) \
 605                           / [font measure [$cflist cget -font] "0"]}]
 606        puts $f "set geometry(cflistw) $wid"
 607        close $f
 608        file rename -force "~/.gitk-new" "~/.gitk"
 609    }
 610    set stuffsaved 1
 611}
 612
 613proc resizeclistpanes {win w} {
 614    global oldwidth
 615    if {[info exists oldwidth($win)]} {
 616        set s0 [$win sash coord 0]
 617        set s1 [$win sash coord 1]
 618        if {$w < 60} {
 619            set sash0 [expr {int($w/2 - 2)}]
 620            set sash1 [expr {int($w*5/6 - 2)}]
 621        } else {
 622            set factor [expr {1.0 * $w / $oldwidth($win)}]
 623            set sash0 [expr {int($factor * [lindex $s0 0])}]
 624            set sash1 [expr {int($factor * [lindex $s1 0])}]
 625            if {$sash0 < 30} {
 626                set sash0 30
 627            }
 628            if {$sash1 < $sash0 + 20} {
 629                set sash1 [expr {$sash0 + 20}]
 630            }
 631            if {$sash1 > $w - 10} {
 632                set sash1 [expr {$w - 10}]
 633                if {$sash0 > $sash1 - 20} {
 634                    set sash0 [expr {$sash1 - 20}]
 635                }
 636            }
 637        }
 638        $win sash place 0 $sash0 [lindex $s0 1]
 639        $win sash place 1 $sash1 [lindex $s1 1]
 640    }
 641    set oldwidth($win) $w
 642}
 643
 644proc resizecdetpanes {win w} {
 645    global oldwidth
 646    if {[info exists oldwidth($win)]} {
 647        set s0 [$win sash coord 0]
 648        if {$w < 60} {
 649            set sash0 [expr {int($w*3/4 - 2)}]
 650        } else {
 651            set factor [expr {1.0 * $w / $oldwidth($win)}]
 652            set sash0 [expr {int($factor * [lindex $s0 0])}]
 653            if {$sash0 < 45} {
 654                set sash0 45
 655            }
 656            if {$sash0 > $w - 15} {
 657                set sash0 [expr {$w - 15}]
 658            }
 659        }
 660        $win sash place 0 $sash0 [lindex $s0 1]
 661    }
 662    set oldwidth($win) $w
 663}
 664
 665proc allcanvs args {
 666    global canv canv2 canv3
 667    eval $canv $args
 668    eval $canv2 $args
 669    eval $canv3 $args
 670}
 671
 672proc bindall {event action} {
 673    global canv canv2 canv3
 674    bind $canv $event $action
 675    bind $canv2 $event $action
 676    bind $canv3 $event $action
 677}
 678
 679proc about {} {
 680    set w .about
 681    if {[winfo exists $w]} {
 682        raise $w
 683        return
 684    }
 685    toplevel $w
 686    wm title $w "About gitk"
 687    message $w.m -text {
 688Gitk - a commit viewer for git
 689
 690Copyright © 2005-2006 Paul Mackerras
 691
 692Use and redistribute under the terms of the GNU General Public License} \
 693            -justify center -aspect 400
 694    pack $w.m -side top -fill x -padx 20 -pady 20
 695    button $w.ok -text Close -command "destroy $w"
 696    pack $w.ok -side bottom
 697}
 698
 699proc shortids {ids} {
 700    set res {}
 701    foreach id $ids {
 702        if {[llength $id] > 1} {
 703            lappend res [shortids $id]
 704        } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
 705            lappend res [string range $id 0 7]
 706        } else {
 707            lappend res $id
 708        }
 709    }
 710    return $res
 711}
 712
 713proc incrange {l x o} {
 714    set n [llength $l]
 715    while {$x < $n} {
 716        set e [lindex $l $x]
 717        if {$e ne {}} {
 718            lset l $x [expr {$e + $o}]
 719        }
 720        incr x
 721    }
 722    return $l
 723}
 724
 725proc ntimes {n o} {
 726    set ret {}
 727    for {} {$n > 0} {incr n -1} {
 728        lappend ret $o
 729    }
 730    return $ret
 731}
 732
 733proc usedinrange {id l1 l2} {
 734    global children commitrow
 735
 736    if {[info exists commitrow($id)]} {
 737        set r $commitrow($id)
 738        if {$l1 <= $r && $r <= $l2} {
 739            return [expr {$r - $l1 + 1}]
 740        }
 741    }
 742    foreach c $children($id) {
 743        if {[info exists commitrow($c)]} {
 744            set r $commitrow($c)
 745            if {$l1 <= $r && $r <= $l2} {
 746                return [expr {$r - $l1 + 1}]
 747            }
 748        }
 749    }
 750    return 0
 751}
 752
 753proc sanity {row {full 0}} {
 754    global rowidlist rowoffsets
 755
 756    set col -1
 757    set ids [lindex $rowidlist $row]
 758    foreach id $ids {
 759        incr col
 760        if {$id eq {}} continue
 761        if {$col < [llength $ids] - 1 &&
 762            [lsearch -exact -start [expr {$col+1}] $ids $id] >= 0} {
 763            puts "oops: [shortids $id] repeated in row $row col $col: {[shortids [lindex $rowidlist $row]]}"
 764        }
 765        set o [lindex $rowoffsets $row $col]
 766        set y $row
 767        set x $col
 768        while {$o ne {}} {
 769            incr y -1
 770            incr x $o
 771            if {[lindex $rowidlist $y $x] != $id} {
 772                puts "oops: rowoffsets wrong at row [expr {$y+1}] col [expr {$x-$o}]"
 773                puts "  id=[shortids $id] check started at row $row"
 774                for {set i $row} {$i >= $y} {incr i -1} {
 775                    puts "  row $i ids={[shortids [lindex $rowidlist $i]]} offs={[lindex $rowoffsets $i]}"
 776                }
 777                break
 778            }
 779            if {!$full} break
 780            set o [lindex $rowoffsets $y $x]
 781        }
 782    }
 783}
 784
 785proc makeuparrow {oid x y z} {
 786    global rowidlist rowoffsets uparrowlen idrowranges
 787
 788    for {set i 1} {$i < $uparrowlen && $y > 1} {incr i} {
 789        incr y -1
 790        incr x $z
 791        set off0 [lindex $rowoffsets $y]
 792        for {set x0 $x} {1} {incr x0} {
 793            if {$x0 >= [llength $off0]} {
 794                set x0 [llength [lindex $rowoffsets [expr {$y-1}]]]
 795                break
 796            }
 797            set z [lindex $off0 $x0]
 798            if {$z ne {}} {
 799                incr x0 $z
 800                break
 801            }
 802        }
 803        set z [expr {$x0 - $x}]
 804        lset rowidlist $y [linsert [lindex $rowidlist $y] $x $oid]
 805        lset rowoffsets $y [linsert [lindex $rowoffsets $y] $x $z]
 806    }
 807    set tmp [lreplace [lindex $rowoffsets $y] $x $x {}]
 808    lset rowoffsets $y [incrange $tmp [expr {$x+1}] -1]
 809    lappend idrowranges($oid) $y
 810}
 811
 812proc initlayout {} {
 813    global rowidlist rowoffsets displayorder
 814    global rowlaidout rowoptim
 815    global idinlist rowchk
 816    global commitidx numcommits
 817    global nextcolor
 818
 819    set commitidx 0
 820    set numcommits 0
 821    set displayorder {}
 822    set nextcolor 0
 823    set rowidlist {{}}
 824    set rowoffsets {{}}
 825    catch {unset idinlist}
 826    catch {unset rowchk}
 827    set rowlaidout 0
 828    set rowoptim 0
 829}
 830
 831proc visiblerows {} {
 832    global canv numcommits linespc
 833
 834    set ymax [lindex [$canv cget -scrollregion] 3]
 835    if {$ymax eq {} || $ymax == 0} return
 836    set f [$canv yview]
 837    set y0 [expr {int([lindex $f 0] * $ymax)}]
 838    set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
 839    if {$r0 < 0} {
 840        set r0 0
 841    }
 842    set y1 [expr {int([lindex $f 1] * $ymax)}]
 843    set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
 844    if {$r1 >= $numcommits} {
 845        set r1 [expr {$numcommits - 1}]
 846    }
 847    return [list $r0 $r1]
 848}
 849
 850proc layoutmore {} {
 851    global rowlaidout rowoptim commitidx numcommits optim_delay
 852    global uparrowlen
 853
 854    set row $rowlaidout
 855    set rowlaidout [layoutrows $row $commitidx 0]
 856    set orow [expr {$rowlaidout - $uparrowlen - 1}]
 857    if {$orow > $rowoptim} {
 858        checkcrossings $rowoptim $orow
 859        optimize_rows $rowoptim 0 $orow
 860        set rowoptim $orow
 861    }
 862    set canshow [expr {$rowoptim - $optim_delay}]
 863    if {$canshow > $numcommits} {
 864        showstuff $canshow
 865    }
 866}
 867
 868proc showstuff {canshow} {
 869    global numcommits
 870    global canvy0 linespc
 871    global linesegends idrowranges idrangedrawn
 872
 873    if {$numcommits == 0} {
 874        global phase
 875        set phase "incrdraw"
 876        allcanvs delete all
 877    }
 878    set row $numcommits
 879    set numcommits $canshow
 880    allcanvs conf -scrollregion \
 881        [list 0 0 0 [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]]
 882    set rows [visiblerows]
 883    set r0 [lindex $rows 0]
 884    set r1 [lindex $rows 1]
 885    for {set r $row} {$r < $canshow} {incr r} {
 886        if {[info exists linesegends($r)]} {
 887            foreach id $linesegends($r) {
 888                set i -1
 889                foreach {s e} $idrowranges($id) {
 890                    incr i
 891                    if {$e ne {} && $e < $numcommits && $s <= $r1 && $e >= $r0
 892                        && ![info exists idrangedrawn($id,$i)]} {
 893                        drawlineseg $id $i 1
 894                        set idrangedrawn($id,$i) 1
 895                    }
 896                }
 897            }
 898        }
 899    }
 900    if {$canshow > $r1} {
 901        set canshow $r1
 902    }
 903    while {$row < $canshow} {
 904        drawcmitrow $row
 905        incr row
 906    }
 907}
 908
 909proc layoutrows {row endrow last} {
 910    global rowidlist rowoffsets displayorder
 911    global uparrowlen downarrowlen maxwidth mingaplen
 912    global nchildren parents nparents
 913    global idrowranges linesegends
 914    global commitidx
 915    global idinlist rowchk
 916
 917    set idlist [lindex $rowidlist $row]
 918    set offs [lindex $rowoffsets $row]
 919    while {$row < $endrow} {
 920        set id [lindex $displayorder $row]
 921        set oldolds {}
 922        set newolds {}
 923        foreach p $parents($id) {
 924            if {![info exists idinlist($p)]} {
 925                lappend newolds $p
 926            } elseif {!$idinlist($p)} {
 927                lappend oldolds $p
 928            }
 929        }
 930        set nev [expr {[llength $idlist] + [llength $newolds]
 931                       + [llength $oldolds] - $maxwidth + 1}]
 932        if {$nev > 0} {
 933            if {!$last && $row + $uparrowlen + $mingaplen >= $commitidx} break
 934            for {set x [llength $idlist]} {[incr x -1] >= 0} {} {
 935                set i [lindex $idlist $x]
 936                if {![info exists rowchk($i)] || $row >= $rowchk($i)} {
 937                    set r [usedinrange $i [expr {$row - $downarrowlen}] \
 938                               [expr {$row + $uparrowlen + $mingaplen}]]
 939                    if {$r == 0} {
 940                        set idlist [lreplace $idlist $x $x]
 941                        set offs [lreplace $offs $x $x]
 942                        set offs [incrange $offs $x 1]
 943                        set idinlist($i) 0
 944                        lappend linesegends($row) $i
 945                        lappend idrowranges($i) [expr {$row-1}]
 946                        if {[incr nev -1] <= 0} break
 947                        continue
 948                    }
 949                    set rowchk($id) [expr {$row + $r}]
 950                }
 951            }
 952            lset rowidlist $row $idlist
 953            lset rowoffsets $row $offs
 954        }
 955        set col [lsearch -exact $idlist $id]
 956        if {$col < 0} {
 957            set col [llength $idlist]
 958            lappend idlist $id
 959            lset rowidlist $row $idlist
 960            set z {}
 961            if {$nchildren($id) > 0} {
 962                set z [expr {[llength [lindex $rowidlist [expr {$row-1}]]] - $col}]
 963                unset idinlist($id)
 964            }
 965            lappend offs $z
 966            lset rowoffsets $row $offs
 967            if {$z ne {}} {
 968                makeuparrow $id $col $row $z
 969            }
 970        } else {
 971            unset idinlist($id)
 972        }
 973        if {[info exists idrowranges($id)]} {
 974            lappend linesegends($row) $id
 975            lappend idrowranges($id) $row
 976        }
 977        incr row
 978        set offs [ntimes [llength $idlist] 0]
 979        set l [llength $newolds]
 980        set idlist [eval lreplace \$idlist $col $col $newolds]
 981        set o 0
 982        if {$l != 1} {
 983            set offs [lrange $offs 0 [expr {$col - 1}]]
 984            foreach x $newolds {
 985                lappend offs {}
 986                incr o -1
 987            }
 988            incr o
 989            set tmp [expr {[llength $idlist] - [llength $offs]}]
 990            if {$tmp > 0} {
 991                set offs [concat $offs [ntimes $tmp $o]]
 992            }
 993        } else {
 994            lset offs $col {}
 995        }
 996        foreach i $newolds {
 997            set idinlist($i) 1
 998            set idrowranges($i) $row
 999        }
1000        incr col $l
1001        foreach oid $oldolds {
1002            set idinlist($oid) 1
1003            set idlist [linsert $idlist $col $oid]
1004            set offs [linsert $offs $col $o]
1005            makeuparrow $oid $col $row $o
1006            incr col
1007        }
1008        lappend rowidlist $idlist
1009        lappend rowoffsets $offs
1010    }
1011    return $row
1012}
1013
1014proc addextraid {id row} {
1015    global displayorder commitrow commitinfo nparents
1016    global commitidx
1017
1018    incr commitidx
1019    lappend displayorder $id
1020    set commitrow($id) $row
1021    readcommit $id
1022    if {![info exists commitinfo($id)]} {
1023        set commitinfo($id) {"No commit information available"}
1024        set nparents($id) 0
1025    }
1026}
1027
1028proc layouttail {} {
1029    global rowidlist rowoffsets idinlist commitidx
1030    global idrowranges linesegends
1031
1032    set row $commitidx
1033    set idlist [lindex $rowidlist $row]
1034    while {$idlist ne {}} {
1035        set col [expr {[llength $idlist] - 1}]
1036        set id [lindex $idlist $col]
1037        addextraid $id $row
1038        unset idinlist($id)
1039        lappend linesegends($row) $id
1040        lappend idrowranges($id) $row
1041        incr row
1042        set offs [ntimes $col 0]
1043        set idlist [lreplace $idlist $col $col]
1044        lappend rowidlist $idlist
1045        lappend rowoffsets $offs
1046    }
1047
1048    foreach id [array names idinlist] {
1049        addextraid $id $row
1050        lset rowidlist $row [list $id]
1051        lset rowoffsets $row 0
1052        makeuparrow $id 0 $row 0
1053        lappend linesegends($row) $id
1054        lappend idrowranges($id) $row
1055        incr row
1056        lappend rowidlist {}
1057        lappend rowoffsets {}
1058    }
1059}
1060
1061proc insert_pad {row col npad} {
1062    global rowidlist rowoffsets
1063
1064    set pad [ntimes $npad {}]
1065    lset rowidlist $row [eval linsert [list [lindex $rowidlist $row]] $col $pad]
1066    set tmp [eval linsert [list [lindex $rowoffsets $row]] $col $pad]
1067    lset rowoffsets $row [incrange $tmp [expr {$col + $npad}] [expr {-$npad}]]
1068}
1069
1070proc optimize_rows {row col endrow} {
1071    global rowidlist rowoffsets idrowranges
1072
1073    for {} {$row < $endrow} {incr row} {
1074        set idlist [lindex $rowidlist $row]
1075        set offs [lindex $rowoffsets $row]
1076        set haspad 0
1077        for {} {$col < [llength $offs]} {incr col} {
1078            if {[lindex $idlist $col] eq {}} {
1079                set haspad 1
1080                continue
1081            }
1082            set z [lindex $offs $col]
1083            if {$z eq {}} continue
1084            set isarrow 0
1085            set x0 [expr {$col + $z}]
1086            set y0 [expr {$row - 1}]
1087            set z0 [lindex $rowoffsets $y0 $x0]
1088            if {$z0 eq {}} {
1089                set id [lindex $idlist $col]
1090                if {[info exists idrowranges($id)] &&
1091                    $y0 > [lindex $idrowranges($id) 0]} {
1092                    set isarrow 1
1093                }
1094            }
1095            if {$z < -1 || ($z < 0 && $isarrow)} {
1096                set npad [expr {-1 - $z + $isarrow}]
1097                set offs [incrange $offs $col $npad]
1098                insert_pad $y0 $x0 $npad
1099                if {$y0 > 0} {
1100                    optimize_rows $y0 $x0 $row
1101                }
1102                set z [lindex $offs $col]
1103                set x0 [expr {$col + $z}]
1104                set z0 [lindex $rowoffsets $y0 $x0]
1105            } elseif {$z > 1 || ($z > 0 && $isarrow)} {
1106                set npad [expr {$z - 1 + $isarrow}]
1107                set y1 [expr {$row + 1}]
1108                set offs2 [lindex $rowoffsets $y1]
1109                set x1 -1
1110                foreach z $offs2 {
1111                    incr x1
1112                    if {$z eq {} || $x1 + $z < $col} continue
1113                    if {$x1 + $z > $col} {
1114                        incr npad
1115                    }
1116                    lset rowoffsets $y1 [incrange $offs2 $x1 $npad]
1117                    break
1118                }
1119                set pad [ntimes $npad {}]
1120                set idlist [eval linsert \$idlist $col $pad]
1121                set tmp [eval linsert \$offs $col $pad]
1122                incr col $npad
1123                set offs [incrange $tmp $col [expr {-$npad}]]
1124                set z [lindex $offs $col]
1125                set haspad 1
1126            }
1127            if {$z0 ne {} && $z < 0 && $z0 > 0} {
1128                insert_pad $y0 $x0 1
1129                set offs [incrange $offs $col 1]
1130                optimize_rows $y0 [expr {$x0 + 1}] $row
1131            }
1132        }
1133        if {!$haspad} {
1134            for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
1135                set o [lindex $offs $col]
1136                if {$o eq {} || $o <= 0} break
1137            }
1138            if {[incr col] < [llength $idlist]} {
1139                set y1 [expr {$row + 1}]
1140                set offs2 [lindex $rowoffsets $y1]
1141                set x1 -1
1142                foreach z $offs2 {
1143                    incr x1
1144                    if {$z eq {} || $x1 + $z < $col} continue
1145                    lset rowoffsets $y1 [incrange $offs2 $x1 1]
1146                    break
1147                }
1148                set idlist [linsert $idlist $col {}]
1149                set tmp [linsert $offs $col {}]
1150                incr col
1151                set offs [incrange $tmp $col -1]
1152            }
1153        }
1154        lset rowidlist $row $idlist
1155        lset rowoffsets $row $offs
1156        set col 0
1157    }
1158}
1159
1160proc xc {row col} {
1161    global canvx0 linespc
1162    return [expr {$canvx0 + $col * $linespc}]
1163}
1164
1165proc yc {row} {
1166    global canvy0 linespc
1167    return [expr {$canvy0 + $row * $linespc}]
1168}
1169
1170proc drawlineseg {id i wid} {
1171    global rowoffsets rowidlist idrowranges
1172    global canv colormap lthickness
1173
1174    set startrow [lindex $idrowranges($id) [expr {2 * $i}]]
1175    set row [lindex $idrowranges($id) [expr {2 * $i + 1}]]
1176    if {$startrow == $row} return
1177    assigncolor $id
1178    set coords {}
1179    set col [lsearch -exact [lindex $rowidlist $row] $id]
1180    if {$col < 0} {
1181        puts "oops: drawline: id $id not on row $row"
1182        return
1183    }
1184    set lasto {}
1185    set ns 0
1186    while {1} {
1187        set o [lindex $rowoffsets $row $col]
1188        if {$o eq {}} break
1189        if {$o ne $lasto} {
1190            # changing direction
1191            set x [xc $row $col]
1192            set y [yc $row]
1193            lappend coords $x $y
1194            set lasto $o
1195        }
1196        incr col $o
1197        incr row -1
1198    }
1199    if {$coords eq {}} return
1200    set last [expr {[llength $idrowranges($id)] / 2 - 1}]
1201    set arrow [expr {2 * ($i > 0) + ($i < $last)}]
1202    set arrow [lindex {none first last both} $arrow]
1203    set wid [expr {$wid * $lthickness}]
1204    set x [xc $row $col]
1205    set y [yc $row]
1206    lappend coords $x $y
1207    set t [$canv create line $coords -width $wid \
1208               -fill $colormap($id) -tags lines.$id -arrow $arrow]
1209    $canv lower $t
1210    bindline $t $id
1211}
1212
1213proc drawparentlinks {id row col olds wid} {
1214    global rowidlist canv colormap lthickness
1215
1216    set row2 [expr {$row + 1}]
1217    set x [xc $row $col]
1218    set y [yc $row]
1219    set y2 [yc $row2]
1220    set ids [lindex $rowidlist $row2]
1221    # rmx = right-most X coord used
1222    set rmx 0
1223    set wid [expr {$wid * $lthickness}]
1224    foreach p $olds {
1225        set i [lsearch -exact $ids $p]
1226        if {$i < 0} {
1227            puts "oops, parent $p of $id not in list"
1228            continue
1229        }
1230        assigncolor $p
1231        # should handle duplicated parents here...
1232        set coords [list $x $y]
1233        if {$i < $col - 1} {
1234            lappend coords [xc $row [expr {$i + 1}]] $y
1235        } elseif {$i > $col + 1} {
1236            lappend coords [xc $row [expr {$i - 1}]] $y
1237        }
1238        set x2 [xc $row2 $i]
1239        if {$x2 > $rmx} {
1240            set rmx $x2
1241        }
1242        lappend coords $x2 $y2
1243        set t [$canv create line $coords -width $wid \
1244                   -fill $colormap($p) -tags lines.$p]
1245        $canv lower $t
1246        bindline $t $p
1247    }
1248    return $rmx
1249}
1250
1251proc drawlines {id xtra} {
1252    global colormap canv
1253    global idrowranges idrangedrawn
1254    global children iddrawn commitrow rowidlist
1255
1256    $canv delete lines.$id
1257    set wid [expr {$xtra + 1}]
1258    set nr [expr {[llength $idrowranges($id)] / 2}]
1259    for {set i 0} {$i < $nr} {incr i} {
1260        if {[info exists idrangedrawn($id,$i)]} {
1261            drawlineseg $id $i $wid
1262        }
1263    }
1264    if {[info exists children($id)]} {
1265        foreach child $children($id) {
1266            if {[info exists iddrawn($child)]} {
1267                set row $commitrow($child)
1268                set col [lsearch -exact [lindex $rowidlist $row] $child]
1269                if {$col >= 0} {
1270                    drawparentlinks $child $row $col [list $id] $wid
1271                }
1272            }
1273        }
1274    }
1275}
1276
1277proc drawcmittext {id row col rmx} {
1278    global linespc canv canv2 canv3 canvy0
1279    global commitlisted commitinfo rowidlist
1280    global rowtextx idpos idtags idheads idotherrefs
1281    global linehtag linentag linedtag
1282    global mainfont namefont
1283
1284    set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}]
1285    set x [xc $row $col]
1286    set y [yc $row]
1287    set orad [expr {$linespc / 3}]
1288    set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
1289               [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
1290               -fill $ofill -outline black -width 1]
1291    $canv raise $t
1292    $canv bind $t <1> {selcanvline {} %x %y}
1293    set xt [xc $row [llength [lindex $rowidlist $row]]]
1294    if {$xt < $rmx} {
1295        set xt $rmx
1296    }
1297    set rowtextx($row) $xt
1298    set idpos($id) [list $x $xt $y]
1299    if {[info exists idtags($id)] || [info exists idheads($id)]
1300        || [info exists idotherrefs($id)]} {
1301        set xt [drawtags $id $x $xt $y]
1302    }
1303    set headline [lindex $commitinfo($id) 0]
1304    set name [lindex $commitinfo($id) 1]
1305    set date [lindex $commitinfo($id) 2]
1306    set date [formatdate $date]
1307    set linehtag($row) [$canv create text $xt $y -anchor w \
1308                            -text $headline -font $mainfont ]
1309    $canv bind $linehtag($row) <Button-3> "rowmenu %X %Y $id"
1310    set linentag($row) [$canv2 create text 3 $y -anchor w \
1311                            -text $name -font $namefont]
1312    set linedtag($row) [$canv3 create text 3 $y -anchor w \
1313                            -text $date -font $mainfont]
1314}
1315
1316proc drawcmitrow {row} {
1317    global displayorder rowidlist
1318    global idrowranges idrangedrawn iddrawn
1319    global commitinfo commitlisted parents numcommits
1320
1321    if {$row >= $numcommits} return
1322    foreach id [lindex $rowidlist $row] {
1323        if {![info exists idrowranges($id)]} continue
1324        set i -1
1325        foreach {s e} $idrowranges($id) {
1326            incr i
1327            if {$row < $s} continue
1328            if {$e eq {}} break
1329            if {$row <= $e} {
1330                if {$e < $numcommits && ![info exists idrangedrawn($id,$i)]} {
1331                    drawlineseg $id $i 1
1332                    set idrangedrawn($id,$i) 1
1333                }
1334                break
1335            }
1336        }
1337    }
1338
1339    set id [lindex $displayorder $row]
1340    if {[info exists iddrawn($id)]} return
1341    set col [lsearch -exact [lindex $rowidlist $row] $id]
1342    if {$col < 0} {
1343        puts "oops, row $row id $id not in list"
1344        return
1345    }
1346    if {![info exists commitinfo($id)]} {
1347        readcommit $id
1348        if {![info exists commitinfo($id)]} {
1349            set commitinfo($id) {"No commit information available"}
1350            set nparents($id) 0
1351        }
1352    }
1353    assigncolor $id
1354    if {[info exists commitlisted($id)] && [info exists parents($id)]
1355        && $parents($id) ne {}} {
1356        set rmx [drawparentlinks $id $row $col $parents($id) 1]
1357    } else {
1358        set rmx 0
1359    }
1360    drawcmittext $id $row $col $rmx
1361    set iddrawn($id) 1
1362}
1363
1364proc drawfrac {f0 f1} {
1365    global numcommits canv
1366    global linespc
1367
1368    set ymax [lindex [$canv cget -scrollregion] 3]
1369    if {$ymax eq {} || $ymax == 0} return
1370    set y0 [expr {int($f0 * $ymax)}]
1371    set row [expr {int(($y0 - 3) / $linespc) - 1}]
1372    if {$row < 0} {
1373        set row 0
1374    }
1375    set y1 [expr {int($f1 * $ymax)}]
1376    set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
1377    if {$endrow >= $numcommits} {
1378        set endrow [expr {$numcommits - 1}]
1379    }
1380    for {} {$row <= $endrow} {incr row} {
1381        drawcmitrow $row
1382    }
1383}
1384
1385proc drawvisible {} {
1386    global canv
1387    eval drawfrac [$canv yview]
1388}
1389
1390proc clear_display {} {
1391    global iddrawn idrangedrawn
1392
1393    allcanvs delete all
1394    catch {unset iddrawn}
1395    catch {unset idrangedrawn}
1396}
1397
1398proc assigncolor {id} {
1399    global colormap colors nextcolor
1400    global parents nparents children nchildren
1401    global cornercrossings crossings
1402
1403    if {[info exists colormap($id)]} return
1404    set ncolors [llength $colors]
1405    if {$nchildren($id) == 1} {
1406        set child [lindex $children($id) 0]
1407        if {[info exists colormap($child)]
1408            && $nparents($child) == 1} {
1409            set colormap($id) $colormap($child)
1410            return
1411        }
1412    }
1413    set badcolors {}
1414    if {[info exists cornercrossings($id)]} {
1415        foreach x $cornercrossings($id) {
1416            if {[info exists colormap($x)]
1417                && [lsearch -exact $badcolors $colormap($x)] < 0} {
1418                lappend badcolors $colormap($x)
1419            }
1420        }
1421        if {[llength $badcolors] >= $ncolors} {
1422            set badcolors {}
1423        }
1424    }
1425    set origbad $badcolors
1426    if {[llength $badcolors] < $ncolors - 1} {
1427        if {[info exists crossings($id)]} {
1428            foreach x $crossings($id) {
1429                if {[info exists colormap($x)]
1430                    && [lsearch -exact $badcolors $colormap($x)] < 0} {
1431                    lappend badcolors $colormap($x)
1432                }
1433            }
1434            if {[llength $badcolors] >= $ncolors} {
1435                set badcolors $origbad
1436            }
1437        }
1438        set origbad $badcolors
1439    }
1440    if {[llength $badcolors] < $ncolors - 1} {
1441        foreach child $children($id) {
1442            if {[info exists colormap($child)]
1443                && [lsearch -exact $badcolors $colormap($child)] < 0} {
1444                lappend badcolors $colormap($child)
1445            }
1446            if {[info exists parents($child)]} {
1447                foreach p $parents($child) {
1448                    if {[info exists colormap($p)]
1449                        && [lsearch -exact $badcolors $colormap($p)] < 0} {
1450                        lappend badcolors $colormap($p)
1451                    }
1452                }
1453            }
1454        }
1455        if {[llength $badcolors] >= $ncolors} {
1456            set badcolors $origbad
1457        }
1458    }
1459    for {set i 0} {$i <= $ncolors} {incr i} {
1460        set c [lindex $colors $nextcolor]
1461        if {[incr nextcolor] >= $ncolors} {
1462            set nextcolor 0
1463        }
1464        if {[lsearch -exact $badcolors $c]} break
1465    }
1466    set colormap($id) $c
1467}
1468
1469proc bindline {t id} {
1470    global canv
1471
1472    $canv bind $t <Enter> "lineenter %x %y $id"
1473    $canv bind $t <Motion> "linemotion %x %y $id"
1474    $canv bind $t <Leave> "lineleave $id"
1475    $canv bind $t <Button-1> "lineclick %x %y $id 1"
1476}
1477
1478proc drawtags {id x xt y1} {
1479    global idtags idheads idotherrefs
1480    global linespc lthickness
1481    global canv mainfont commitrow rowtextx
1482
1483    set marks {}
1484    set ntags 0
1485    set nheads 0
1486    if {[info exists idtags($id)]} {
1487        set marks $idtags($id)
1488        set ntags [llength $marks]
1489    }
1490    if {[info exists idheads($id)]} {
1491        set marks [concat $marks $idheads($id)]
1492        set nheads [llength $idheads($id)]
1493    }
1494    if {[info exists idotherrefs($id)]} {
1495        set marks [concat $marks $idotherrefs($id)]
1496    }
1497    if {$marks eq {}} {
1498        return $xt
1499    }
1500
1501    set delta [expr {int(0.5 * ($linespc - $lthickness))}]
1502    set yt [expr {$y1 - 0.5 * $linespc}]
1503    set yb [expr {$yt + $linespc - 1}]
1504    set xvals {}
1505    set wvals {}
1506    foreach tag $marks {
1507        set wid [font measure $mainfont $tag]
1508        lappend xvals $xt
1509        lappend wvals $wid
1510        set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
1511    }
1512    set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
1513               -width $lthickness -fill black -tags tag.$id]
1514    $canv lower $t
1515    foreach tag $marks x $xvals wid $wvals {
1516        set xl [expr {$x + $delta}]
1517        set xr [expr {$x + $delta + $wid + $lthickness}]
1518        if {[incr ntags -1] >= 0} {
1519            # draw a tag
1520            set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
1521                       $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
1522                       -width 1 -outline black -fill yellow -tags tag.$id]
1523            $canv bind $t <1> [list showtag $tag 1]
1524            set rowtextx($commitrow($id)) [expr {$xr + $linespc}]
1525        } else {
1526            # draw a head or other ref
1527            if {[incr nheads -1] >= 0} {
1528                set col green
1529            } else {
1530                set col "#ddddff"
1531            }
1532            set xl [expr {$xl - $delta/2}]
1533            $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
1534                -width 1 -outline black -fill $col -tags tag.$id
1535        }
1536        set t [$canv create text $xl $y1 -anchor w -text $tag \
1537                   -font $mainfont -tags tag.$id]
1538        if {$ntags >= 0} {
1539            $canv bind $t <1> [list showtag $tag 1]
1540        }
1541    }
1542    return $xt
1543}
1544
1545proc checkcrossings {row endrow} {
1546    global displayorder parents rowidlist
1547
1548    for {} {$row < $endrow} {incr row} {
1549        set id [lindex $displayorder $row]
1550        set i [lsearch -exact [lindex $rowidlist $row] $id]
1551        if {$i < 0} continue
1552        set idlist [lindex $rowidlist [expr {$row+1}]]
1553        foreach p $parents($id) {
1554            set j [lsearch -exact $idlist $p]
1555            if {$j > 0} {
1556                if {$j < $i - 1} {
1557                    notecrossings $row $p $j $i [expr {$j+1}]
1558                } elseif {$j > $i + 1} {
1559                    notecrossings $row $p $i $j [expr {$j-1}]
1560                }
1561            }
1562        }
1563    }
1564}
1565
1566proc notecrossings {row id lo hi corner} {
1567    global rowidlist crossings cornercrossings
1568
1569    for {set i $lo} {[incr i] < $hi} {} {
1570        set p [lindex [lindex $rowidlist $row] $i]
1571        if {$p == {}} continue
1572        if {$i == $corner} {
1573            if {![info exists cornercrossings($id)]
1574                || [lsearch -exact $cornercrossings($id) $p] < 0} {
1575                lappend cornercrossings($id) $p
1576            }
1577            if {![info exists cornercrossings($p)]
1578                || [lsearch -exact $cornercrossings($p) $id] < 0} {
1579                lappend cornercrossings($p) $id
1580            }
1581        } else {
1582            if {![info exists crossings($id)]
1583                || [lsearch -exact $crossings($id) $p] < 0} {
1584                lappend crossings($id) $p
1585            }
1586            if {![info exists crossings($p)]
1587                || [lsearch -exact $crossings($p) $id] < 0} {
1588                lappend crossings($p) $id
1589            }
1590        }
1591    }
1592}
1593
1594proc xcoord {i level ln} {
1595    global canvx0 xspc1 xspc2
1596
1597    set x [expr {$canvx0 + $i * $xspc1($ln)}]
1598    if {$i > 0 && $i == $level} {
1599        set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
1600    } elseif {$i > $level} {
1601        set x [expr {$x + $xspc2 - $xspc1($ln)}]
1602    }
1603    return $x
1604}
1605
1606proc finishcommits {} {
1607    global phase
1608    global canv mainfont ctext maincursor textcursor
1609
1610    if {$phase == "incrdraw"} {
1611        drawrest
1612    } else {
1613        $canv delete all
1614        $canv create text 3 3 -anchor nw -text "No commits selected" \
1615            -font $mainfont -tags textitems
1616        set phase {}
1617    }
1618    . config -cursor $maincursor
1619    settextcursor $textcursor
1620}
1621
1622# Don't change the text pane cursor if it is currently the hand cursor,
1623# showing that we are over a sha1 ID link.
1624proc settextcursor {c} {
1625    global ctext curtextcursor
1626
1627    if {[$ctext cget -cursor] == $curtextcursor} {
1628        $ctext config -cursor $c
1629    }
1630    set curtextcursor $c
1631}
1632
1633proc drawrest {} {
1634    global phase
1635    global numcommits
1636    global startmsecs
1637    global canvy0 numcommits linespc
1638    global rowlaidout commitidx
1639
1640    set row $rowlaidout
1641    layoutrows $rowlaidout $commitidx 1
1642    layouttail
1643    optimize_rows $row 0 $commitidx
1644    showstuff $commitidx
1645
1646    set phase {}
1647    set drawmsecs [expr {[clock clicks -milliseconds] - $startmsecs}]
1648    #puts "overall $drawmsecs ms for $numcommits commits"
1649}
1650
1651proc findmatches {f} {
1652    global findtype foundstring foundstrlen
1653    if {$findtype == "Regexp"} {
1654        set matches [regexp -indices -all -inline $foundstring $f]
1655    } else {
1656        if {$findtype == "IgnCase"} {
1657            set str [string tolower $f]
1658        } else {
1659            set str $f
1660        }
1661        set matches {}
1662        set i 0
1663        while {[set j [string first $foundstring $str $i]] >= 0} {
1664            lappend matches [list $j [expr {$j+$foundstrlen-1}]]
1665            set i [expr {$j + $foundstrlen}]
1666        }
1667    }
1668    return $matches
1669}
1670
1671proc dofind {} {
1672    global findtype findloc findstring markedmatches commitinfo
1673    global numcommits displayorder linehtag linentag linedtag
1674    global mainfont namefont canv canv2 canv3 selectedline
1675    global matchinglines foundstring foundstrlen
1676
1677    stopfindproc
1678    unmarkmatches
1679    focus .
1680    set matchinglines {}
1681    if {$findloc == "Pickaxe"} {
1682        findpatches
1683        return
1684    }
1685    if {$findtype == "IgnCase"} {
1686        set foundstring [string tolower $findstring]
1687    } else {
1688        set foundstring $findstring
1689    }
1690    set foundstrlen [string length $findstring]
1691    if {$foundstrlen == 0} return
1692    if {$findloc == "Files"} {
1693        findfiles
1694        return
1695    }
1696    if {![info exists selectedline]} {
1697        set oldsel -1
1698    } else {
1699        set oldsel $selectedline
1700    }
1701    set didsel 0
1702    set fldtypes {Headline Author Date Committer CDate Comment}
1703    for {set l 0} {$l < $numcommits} {incr l} {
1704        set id [lindex $displayorder $l]
1705        set info $commitinfo($id)
1706        set doesmatch 0
1707        foreach f $info ty $fldtypes {
1708            if {$findloc != "All fields" && $findloc != $ty} {
1709                continue
1710            }
1711            set matches [findmatches $f]
1712            if {$matches == {}} continue
1713            set doesmatch 1
1714            if {$ty == "Headline"} {
1715                drawcmitrow $l
1716                markmatches $canv $l $f $linehtag($l) $matches $mainfont
1717            } elseif {$ty == "Author"} {
1718                drawcmitrow $l
1719                markmatches $canv2 $l $f $linentag($l) $matches $namefont
1720            } elseif {$ty == "Date"} {
1721                drawcmitrow $l
1722                markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
1723            }
1724        }
1725        if {$doesmatch} {
1726            lappend matchinglines $l
1727            if {!$didsel && $l > $oldsel} {
1728                findselectline $l
1729                set didsel 1
1730            }
1731        }
1732    }
1733    if {$matchinglines == {}} {
1734        bell
1735    } elseif {!$didsel} {
1736        findselectline [lindex $matchinglines 0]
1737    }
1738}
1739
1740proc findselectline {l} {
1741    global findloc commentend ctext
1742    selectline $l 1
1743    if {$findloc == "All fields" || $findloc == "Comments"} {
1744        # highlight the matches in the comments
1745        set f [$ctext get 1.0 $commentend]
1746        set matches [findmatches $f]
1747        foreach match $matches {
1748            set start [lindex $match 0]
1749            set end [expr {[lindex $match 1] + 1}]
1750            $ctext tag add found "1.0 + $start c" "1.0 + $end c"
1751        }
1752    }
1753}
1754
1755proc findnext {restart} {
1756    global matchinglines selectedline
1757    if {![info exists matchinglines]} {
1758        if {$restart} {
1759            dofind
1760        }
1761        return
1762    }
1763    if {![info exists selectedline]} return
1764    foreach l $matchinglines {
1765        if {$l > $selectedline} {
1766            findselectline $l
1767            return
1768        }
1769    }
1770    bell
1771}
1772
1773proc findprev {} {
1774    global matchinglines selectedline
1775    if {![info exists matchinglines]} {
1776        dofind
1777        return
1778    }
1779    if {![info exists selectedline]} return
1780    set prev {}
1781    foreach l $matchinglines {
1782        if {$l >= $selectedline} break
1783        set prev $l
1784    }
1785    if {$prev != {}} {
1786        findselectline $prev
1787    } else {
1788        bell
1789    }
1790}
1791
1792proc findlocchange {name ix op} {
1793    global findloc findtype findtypemenu
1794    if {$findloc == "Pickaxe"} {
1795        set findtype Exact
1796        set state disabled
1797    } else {
1798        set state normal
1799    }
1800    $findtypemenu entryconf 1 -state $state
1801    $findtypemenu entryconf 2 -state $state
1802}
1803
1804proc stopfindproc {{done 0}} {
1805    global findprocpid findprocfile findids
1806    global ctext findoldcursor phase maincursor textcursor
1807    global findinprogress
1808
1809    catch {unset findids}
1810    if {[info exists findprocpid]} {
1811        if {!$done} {
1812            catch {exec kill $findprocpid}
1813        }
1814        catch {close $findprocfile}
1815        unset findprocpid
1816    }
1817    if {[info exists findinprogress]} {
1818        unset findinprogress
1819        if {$phase != "incrdraw"} {
1820            . config -cursor $maincursor
1821            settextcursor $textcursor
1822        }
1823    }
1824}
1825
1826proc findpatches {} {
1827    global findstring selectedline numcommits
1828    global findprocpid findprocfile
1829    global finddidsel ctext displayorder findinprogress
1830    global findinsertpos
1831
1832    if {$numcommits == 0} return
1833
1834    # make a list of all the ids to search, starting at the one
1835    # after the selected line (if any)
1836    if {[info exists selectedline]} {
1837        set l $selectedline
1838    } else {
1839        set l -1
1840    }
1841    set inputids {}
1842    for {set i 0} {$i < $numcommits} {incr i} {
1843        if {[incr l] >= $numcommits} {
1844            set l 0
1845        }
1846        append inputids [lindex $displayorder $l] "\n"
1847    }
1848
1849    if {[catch {
1850        set f [open [list | git-diff-tree --stdin -s -r -S$findstring \
1851                         << $inputids] r]
1852    } err]} {
1853        error_popup "Error starting search process: $err"
1854        return
1855    }
1856
1857    set findinsertpos end
1858    set findprocfile $f
1859    set findprocpid [pid $f]
1860    fconfigure $f -blocking 0
1861    fileevent $f readable readfindproc
1862    set finddidsel 0
1863    . config -cursor watch
1864    settextcursor watch
1865    set findinprogress 1
1866}
1867
1868proc readfindproc {} {
1869    global findprocfile finddidsel
1870    global commitrow matchinglines findinsertpos
1871
1872    set n [gets $findprocfile line]
1873    if {$n < 0} {
1874        if {[eof $findprocfile]} {
1875            stopfindproc 1
1876            if {!$finddidsel} {
1877                bell
1878            }
1879        }
1880        return
1881    }
1882    if {![regexp {^[0-9a-f]{40}} $line id]} {
1883        error_popup "Can't parse git-diff-tree output: $line"
1884        stopfindproc
1885        return
1886    }
1887    if {![info exists commitrow($id)]} {
1888        puts stderr "spurious id: $id"
1889        return
1890    }
1891    set l $commitrow($id)
1892    insertmatch $l $id
1893}
1894
1895proc insertmatch {l id} {
1896    global matchinglines findinsertpos finddidsel
1897
1898    if {$findinsertpos == "end"} {
1899        if {$matchinglines != {} && $l < [lindex $matchinglines 0]} {
1900            set matchinglines [linsert $matchinglines 0 $l]
1901            set findinsertpos 1
1902        } else {
1903            lappend matchinglines $l
1904        }
1905    } else {
1906        set matchinglines [linsert $matchinglines $findinsertpos $l]
1907        incr findinsertpos
1908    }
1909    markheadline $l $id
1910    if {!$finddidsel} {
1911        findselectline $l
1912        set finddidsel 1
1913    }
1914}
1915
1916proc findfiles {} {
1917    global selectedline numcommits displayorder ctext
1918    global ffileline finddidsel parents nparents
1919    global findinprogress findstartline findinsertpos
1920    global treediffs fdiffid fdiffsneeded fdiffpos
1921    global findmergefiles
1922
1923    if {$numcommits == 0} return
1924
1925    if {[info exists selectedline]} {
1926        set l [expr {$selectedline + 1}]
1927    } else {
1928        set l 0
1929    }
1930    set ffileline $l
1931    set findstartline $l
1932    set diffsneeded {}
1933    set fdiffsneeded {}
1934    while 1 {
1935        set id [lindex $displayorder $l]
1936        if {$findmergefiles || $nparents($id) == 1} {
1937            if {![info exists treediffs($id)]} {
1938                append diffsneeded "$id\n"
1939                lappend fdiffsneeded $id
1940            }
1941        }
1942        if {[incr l] >= $numcommits} {
1943            set l 0
1944        }
1945        if {$l == $findstartline} break
1946    }
1947
1948    # start off a git-diff-tree process if needed
1949    if {$diffsneeded ne {}} {
1950        if {[catch {
1951            set df [open [list | git-diff-tree -r --stdin << $diffsneeded] r]
1952        } err ]} {
1953            error_popup "Error starting search process: $err"
1954            return
1955        }
1956        catch {unset fdiffid}
1957        set fdiffpos 0
1958        fconfigure $df -blocking 0
1959        fileevent $df readable [list readfilediffs $df]
1960    }
1961
1962    set finddidsel 0
1963    set findinsertpos end
1964    set id [lindex $displayorder $l]
1965    . config -cursor watch
1966    settextcursor watch
1967    set findinprogress 1
1968    findcont $id
1969    update
1970}
1971
1972proc readfilediffs {df} {
1973    global findid fdiffid fdiffs
1974
1975    set n [gets $df line]
1976    if {$n < 0} {
1977        if {[eof $df]} {
1978            donefilediff
1979            if {[catch {close $df} err]} {
1980                stopfindproc
1981                bell
1982                error_popup "Error in git-diff-tree: $err"
1983            } elseif {[info exists findid]} {
1984                set id $findid
1985                stopfindproc
1986                bell
1987                error_popup "Couldn't find diffs for $id"
1988            }
1989        }
1990        return
1991    }
1992    if {[regexp {^([0-9a-f]{40})$} $line match id]} {
1993        # start of a new string of diffs
1994        donefilediff
1995        set fdiffid $id
1996        set fdiffs {}
1997    } elseif {[string match ":*" $line]} {
1998        lappend fdiffs [lindex $line 5]
1999    }
2000}
2001
2002proc donefilediff {} {
2003    global fdiffid fdiffs treediffs findid
2004    global fdiffsneeded fdiffpos
2005
2006    if {[info exists fdiffid]} {
2007        while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffid
2008               && $fdiffpos < [llength $fdiffsneeded]} {
2009            # git-diff-tree doesn't output anything for a commit
2010            # which doesn't change anything
2011            set nullid [lindex $fdiffsneeded $fdiffpos]
2012            set treediffs($nullid) {}
2013            if {[info exists findid] && $nullid eq $findid} {
2014                unset findid
2015                findcont $nullid
2016            }
2017            incr fdiffpos
2018        }
2019        incr fdiffpos
2020
2021        if {![info exists treediffs($fdiffid)]} {
2022            set treediffs($fdiffid) $fdiffs
2023        }
2024        if {[info exists findid] && $fdiffid eq $findid} {
2025            unset findid
2026            findcont $fdiffid
2027        }
2028    }
2029}
2030
2031proc findcont {id} {
2032    global findid treediffs parents nparents
2033    global ffileline findstartline finddidsel
2034    global displayorder numcommits matchinglines findinprogress
2035    global findmergefiles
2036
2037    set l $ffileline
2038    while 1 {
2039        if {$findmergefiles || $nparents($id) == 1} {
2040            if {![info exists treediffs($id)]} {
2041                set findid $id
2042                set ffileline $l
2043                return
2044            }
2045            set doesmatch 0
2046            foreach f $treediffs($id) {
2047                set x [findmatches $f]
2048                if {$x != {}} {
2049                    set doesmatch 1
2050                    break
2051                }
2052            }
2053            if {$doesmatch} {
2054                insertmatch $l $id
2055            }
2056        }
2057        if {[incr l] >= $numcommits} {
2058            set l 0
2059        }
2060        if {$l == $findstartline} break
2061        set id [lindex $displayorder $l]
2062    }
2063    stopfindproc
2064    if {!$finddidsel} {
2065        bell
2066    }
2067}
2068
2069# mark a commit as matching by putting a yellow background
2070# behind the headline
2071proc markheadline {l id} {
2072    global canv mainfont linehtag commitinfo
2073
2074    drawcmitrow $l
2075    set bbox [$canv bbox $linehtag($l)]
2076    set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
2077    $canv lower $t
2078}
2079
2080# mark the bits of a headline, author or date that match a find string
2081proc markmatches {canv l str tag matches font} {
2082    set bbox [$canv bbox $tag]
2083    set x0 [lindex $bbox 0]
2084    set y0 [lindex $bbox 1]
2085    set y1 [lindex $bbox 3]
2086    foreach match $matches {
2087        set start [lindex $match 0]
2088        set end [lindex $match 1]
2089        if {$start > $end} continue
2090        set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
2091        set xlen [font measure $font [string range $str 0 [expr {$end}]]]
2092        set t [$canv create rect [expr {$x0+$xoff}] $y0 \
2093                   [expr {$x0+$xlen+2}] $y1 \
2094                   -outline {} -tags matches -fill yellow]
2095        $canv lower $t
2096    }
2097}
2098
2099proc unmarkmatches {} {
2100    global matchinglines findids
2101    allcanvs delete matches
2102    catch {unset matchinglines}
2103    catch {unset findids}
2104}
2105
2106proc selcanvline {w x y} {
2107    global canv canvy0 ctext linespc
2108    global rowtextx
2109    set ymax [lindex [$canv cget -scrollregion] 3]
2110    if {$ymax == {}} return
2111    set yfrac [lindex [$canv yview] 0]
2112    set y [expr {$y + $yfrac * $ymax}]
2113    set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
2114    if {$l < 0} {
2115        set l 0
2116    }
2117    if {$w eq $canv} {
2118        if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
2119    }
2120    unmarkmatches
2121    selectline $l 1
2122}
2123
2124proc commit_descriptor {p} {
2125    global commitinfo
2126    set l "..."
2127    if {[info exists commitinfo($p)]} {
2128        set l [lindex $commitinfo($p) 0]
2129    }
2130    return "$p ($l)"
2131}
2132
2133# append some text to the ctext widget, and make any SHA1 ID
2134# that we know about be a clickable link.
2135proc appendwithlinks {text} {
2136    global ctext commitrow linknum
2137
2138    set start [$ctext index "end - 1c"]
2139    $ctext insert end $text
2140    $ctext insert end "\n"
2141    set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
2142    foreach l $links {
2143        set s [lindex $l 0]
2144        set e [lindex $l 1]
2145        set linkid [string range $text $s $e]
2146        if {![info exists commitrow($linkid)]} continue
2147        incr e
2148        $ctext tag add link "$start + $s c" "$start + $e c"
2149        $ctext tag add link$linknum "$start + $s c" "$start + $e c"
2150        $ctext tag bind link$linknum <1> [list selectline $commitrow($linkid) 1]
2151        incr linknum
2152    }
2153    $ctext tag conf link -foreground blue -underline 1
2154    $ctext tag bind link <Enter> { %W configure -cursor hand2 }
2155    $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
2156}
2157
2158proc selectline {l isnew} {
2159    global canv canv2 canv3 ctext commitinfo selectedline
2160    global displayorder linehtag linentag linedtag
2161    global canvy0 linespc parents nparents children
2162    global cflist currentid sha1entry
2163    global commentend idtags linknum
2164    global mergemax numcommits
2165
2166    $canv delete hover
2167    normalline
2168    if {$l < 0 || $l >= $numcommits} return
2169    set y [expr {$canvy0 + $l * $linespc}]
2170    set ymax [lindex [$canv cget -scrollregion] 3]
2171    set ytop [expr {$y - $linespc - 1}]
2172    set ybot [expr {$y + $linespc + 1}]
2173    set wnow [$canv yview]
2174    set wtop [expr {[lindex $wnow 0] * $ymax}]
2175    set wbot [expr {[lindex $wnow 1] * $ymax}]
2176    set wh [expr {$wbot - $wtop}]
2177    set newtop $wtop
2178    if {$ytop < $wtop} {
2179        if {$ybot < $wtop} {
2180            set newtop [expr {$y - $wh / 2.0}]
2181        } else {
2182            set newtop $ytop
2183            if {$newtop > $wtop - $linespc} {
2184                set newtop [expr {$wtop - $linespc}]
2185            }
2186        }
2187    } elseif {$ybot > $wbot} {
2188        if {$ytop > $wbot} {
2189            set newtop [expr {$y - $wh / 2.0}]
2190        } else {
2191            set newtop [expr {$ybot - $wh}]
2192            if {$newtop < $wtop + $linespc} {
2193                set newtop [expr {$wtop + $linespc}]
2194            }
2195        }
2196    }
2197    if {$newtop != $wtop} {
2198        if {$newtop < 0} {
2199            set newtop 0
2200        }
2201        allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
2202        drawvisible
2203    }
2204
2205    if {![info exists linehtag($l)]} return
2206    $canv delete secsel
2207    set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
2208               -tags secsel -fill [$canv cget -selectbackground]]
2209    $canv lower $t
2210    $canv2 delete secsel
2211    set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
2212               -tags secsel -fill [$canv2 cget -selectbackground]]
2213    $canv2 lower $t
2214    $canv3 delete secsel
2215    set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
2216               -tags secsel -fill [$canv3 cget -selectbackground]]
2217    $canv3 lower $t
2218
2219    if {$isnew} {
2220        addtohistory [list selectline $l 0]
2221    }
2222
2223    set selectedline $l
2224
2225    set id [lindex $displayorder $l]
2226    set currentid $id
2227    $sha1entry delete 0 end
2228    $sha1entry insert 0 $id
2229    $sha1entry selection from 0
2230    $sha1entry selection to end
2231
2232    $ctext conf -state normal
2233    $ctext delete 0.0 end
2234    set linknum 0
2235    $ctext mark set fmark.0 0.0
2236    $ctext mark gravity fmark.0 left
2237    set info $commitinfo($id)
2238    set date [formatdate [lindex $info 2]]
2239    $ctext insert end "Author: [lindex $info 1]  $date\n"
2240    set date [formatdate [lindex $info 4]]
2241    $ctext insert end "Committer: [lindex $info 3]  $date\n"
2242    if {[info exists idtags($id)]} {
2243        $ctext insert end "Tags:"
2244        foreach tag $idtags($id) {
2245            $ctext insert end " $tag"
2246        }
2247        $ctext insert end "\n"
2248    }
2249 
2250    set comment {}
2251    if {$nparents($id) > 1} {
2252        set np 0
2253        foreach p $parents($id) {
2254            if {$np >= $mergemax} {
2255                set tag mmax
2256            } else {
2257                set tag m$np
2258            }
2259            $ctext insert end "Parent: " $tag
2260            appendwithlinks [commit_descriptor $p]
2261            incr np
2262        }
2263    } else {
2264        if {[info exists parents($id)]} {
2265            foreach p $parents($id) {
2266                append comment "Parent: [commit_descriptor $p]\n"
2267            }
2268        }
2269    }
2270
2271    if {[info exists children($id)]} {
2272        foreach c $children($id) {
2273            append comment "Child:  [commit_descriptor $c]\n"
2274        }
2275    }
2276    append comment "\n"
2277    append comment [lindex $info 5]
2278
2279    # make anything that looks like a SHA1 ID be a clickable link
2280    appendwithlinks $comment
2281
2282    $ctext tag delete Comments
2283    $ctext tag remove found 1.0 end
2284    $ctext conf -state disabled
2285    set commentend [$ctext index "end - 1c"]
2286
2287    $cflist delete 0 end
2288    $cflist insert end "Comments"
2289    if {$nparents($id) == 1} {
2290        startdiff $id
2291    } elseif {$nparents($id) > 1} {
2292        mergediff $id
2293    }
2294}
2295
2296proc selnextline {dir} {
2297    global selectedline
2298    if {![info exists selectedline]} return
2299    set l [expr {$selectedline + $dir}]
2300    unmarkmatches
2301    selectline $l 1
2302}
2303
2304proc unselectline {} {
2305    global selectedline
2306
2307    catch {unset selectedline}
2308    allcanvs delete secsel
2309}
2310
2311proc addtohistory {cmd} {
2312    global history historyindex
2313
2314    if {$historyindex > 0
2315        && [lindex $history [expr {$historyindex - 1}]] == $cmd} {
2316        return
2317    }
2318
2319    if {$historyindex < [llength $history]} {
2320        set history [lreplace $history $historyindex end $cmd]
2321    } else {
2322        lappend history $cmd
2323    }
2324    incr historyindex
2325    if {$historyindex > 1} {
2326        .ctop.top.bar.leftbut conf -state normal
2327    } else {
2328        .ctop.top.bar.leftbut conf -state disabled
2329    }
2330    .ctop.top.bar.rightbut conf -state disabled
2331}
2332
2333proc goback {} {
2334    global history historyindex
2335
2336    if {$historyindex > 1} {
2337        incr historyindex -1
2338        set cmd [lindex $history [expr {$historyindex - 1}]]
2339        eval $cmd
2340        .ctop.top.bar.rightbut conf -state normal
2341    }
2342    if {$historyindex <= 1} {
2343        .ctop.top.bar.leftbut conf -state disabled
2344    }
2345}
2346
2347proc goforw {} {
2348    global history historyindex
2349
2350    if {$historyindex < [llength $history]} {
2351        set cmd [lindex $history $historyindex]
2352        incr historyindex
2353        eval $cmd
2354        .ctop.top.bar.leftbut conf -state normal
2355    }
2356    if {$historyindex >= [llength $history]} {
2357        .ctop.top.bar.rightbut conf -state disabled
2358    }
2359}
2360
2361proc mergediff {id} {
2362    global parents diffmergeid diffopts mdifffd
2363    global difffilestart
2364
2365    set diffmergeid $id
2366    catch {unset difffilestart}
2367    # this doesn't seem to actually affect anything...
2368    set env(GIT_DIFF_OPTS) $diffopts
2369    set cmd [concat | git-diff-tree --no-commit-id --cc $id]
2370    if {[catch {set mdf [open $cmd r]} err]} {
2371        error_popup "Error getting merge diffs: $err"
2372        return
2373    }
2374    fconfigure $mdf -blocking 0
2375    set mdifffd($id) $mdf
2376    fileevent $mdf readable [list getmergediffline $mdf $id]
2377    set nextupdate [expr {[clock clicks -milliseconds] + 100}]
2378}
2379
2380proc getmergediffline {mdf id} {
2381    global diffmergeid ctext cflist nextupdate nparents mergemax
2382    global difffilestart
2383
2384    set n [gets $mdf line]
2385    if {$n < 0} {
2386        if {[eof $mdf]} {
2387            close $mdf
2388        }
2389        return
2390    }
2391    if {![info exists diffmergeid] || $id != $diffmergeid} {
2392        return
2393    }
2394    $ctext conf -state normal
2395    if {[regexp {^diff --cc (.*)} $line match fname]} {
2396        # start of a new file
2397        $ctext insert end "\n"
2398        set here [$ctext index "end - 1c"]
2399        set i [$cflist index end]
2400        $ctext mark set fmark.$i $here
2401        $ctext mark gravity fmark.$i left
2402        set difffilestart([expr {$i-1}]) $here
2403        $cflist insert end $fname
2404        set l [expr {(78 - [string length $fname]) / 2}]
2405        set pad [string range "----------------------------------------" 1 $l]
2406        $ctext insert end "$pad $fname $pad\n" filesep
2407    } elseif {[regexp {^@@} $line]} {
2408        $ctext insert end "$line\n" hunksep
2409    } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
2410        # do nothing
2411    } else {
2412        # parse the prefix - one ' ', '-' or '+' for each parent
2413        set np $nparents($id)
2414        set spaces {}
2415        set minuses {}
2416        set pluses {}
2417        set isbad 0
2418        for {set j 0} {$j < $np} {incr j} {
2419            set c [string range $line $j $j]
2420            if {$c == " "} {
2421                lappend spaces $j
2422            } elseif {$c == "-"} {
2423                lappend minuses $j
2424            } elseif {$c == "+"} {
2425                lappend pluses $j
2426            } else {
2427                set isbad 1
2428                break
2429            }
2430        }
2431        set tags {}
2432        set num {}
2433        if {!$isbad && $minuses ne {} && $pluses eq {}} {
2434            # line doesn't appear in result, parents in $minuses have the line
2435            set num [lindex $minuses 0]
2436        } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
2437            # line appears in result, parents in $pluses don't have the line
2438            lappend tags mresult
2439            set num [lindex $spaces 0]
2440        }
2441        if {$num ne {}} {
2442            if {$num >= $mergemax} {
2443                set num "max"
2444            }
2445            lappend tags m$num
2446        }
2447        $ctext insert end "$line\n" $tags
2448    }
2449    $ctext conf -state disabled
2450    if {[clock clicks -milliseconds] >= $nextupdate} {
2451        incr nextupdate 100
2452        fileevent $mdf readable {}
2453        update
2454        fileevent $mdf readable [list getmergediffline $mdf $id]
2455    }
2456}
2457
2458proc startdiff {ids} {
2459    global treediffs diffids treepending diffmergeid
2460
2461    set diffids $ids
2462    catch {unset diffmergeid}
2463    if {![info exists treediffs($ids)]} {
2464        if {![info exists treepending]} {
2465            gettreediffs $ids
2466        }
2467    } else {
2468        addtocflist $ids
2469    }
2470}
2471
2472proc addtocflist {ids} {
2473    global treediffs cflist
2474    foreach f $treediffs($ids) {
2475        $cflist insert end $f
2476    }
2477    getblobdiffs $ids
2478}
2479
2480proc gettreediffs {ids} {
2481    global treediff parents treepending
2482    set treepending $ids
2483    set treediff {}
2484    if {[catch \
2485         {set gdtf [open [concat | git-diff-tree --no-commit-id -r $ids] r]} \
2486        ]} return
2487    fconfigure $gdtf -blocking 0
2488    fileevent $gdtf readable [list gettreediffline $gdtf $ids]
2489}
2490
2491proc gettreediffline {gdtf ids} {
2492    global treediff treediffs treepending diffids diffmergeid
2493
2494    set n [gets $gdtf line]
2495    if {$n < 0} {
2496        if {![eof $gdtf]} return
2497        close $gdtf
2498        set treediffs($ids) $treediff
2499        unset treepending
2500        if {$ids != $diffids} {
2501            gettreediffs $diffids
2502        } else {
2503            if {[info exists diffmergeid]} {
2504                contmergediff $ids
2505            } else {
2506                addtocflist $ids
2507            }
2508        }
2509        return
2510    }
2511    set file [lindex $line 5]
2512    lappend treediff $file
2513}
2514
2515proc getblobdiffs {ids} {
2516    global diffopts blobdifffd diffids env curdifftag curtagstart
2517    global difffilestart nextupdate diffinhdr treediffs
2518
2519    set env(GIT_DIFF_OPTS) $diffopts
2520    set cmd [concat | git-diff-tree --no-commit-id -r -p -C $ids]
2521    if {[catch {set bdf [open $cmd r]} err]} {
2522        puts "error getting diffs: $err"
2523        return
2524    }
2525    set diffinhdr 0
2526    fconfigure $bdf -blocking 0
2527    set blobdifffd($ids) $bdf
2528    set curdifftag Comments
2529    set curtagstart 0.0
2530    catch {unset difffilestart}
2531    fileevent $bdf readable [list getblobdiffline $bdf $diffids]
2532    set nextupdate [expr {[clock clicks -milliseconds] + 100}]
2533}
2534
2535proc getblobdiffline {bdf ids} {
2536    global diffids blobdifffd ctext curdifftag curtagstart
2537    global diffnexthead diffnextnote difffilestart
2538    global nextupdate diffinhdr treediffs
2539
2540    set n [gets $bdf line]
2541    if {$n < 0} {
2542        if {[eof $bdf]} {
2543            close $bdf
2544            if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
2545                $ctext tag add $curdifftag $curtagstart end
2546            }
2547        }
2548        return
2549    }
2550    if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
2551        return
2552    }
2553    $ctext conf -state normal
2554    if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} {
2555        # start of a new file
2556        $ctext insert end "\n"
2557        $ctext tag add $curdifftag $curtagstart end
2558        set curtagstart [$ctext index "end - 1c"]
2559        set header $newname
2560        set here [$ctext index "end - 1c"]
2561        set i [lsearch -exact $treediffs($diffids) $fname]
2562        if {$i >= 0} {
2563            set difffilestart($i) $here
2564            incr i
2565            $ctext mark set fmark.$i $here
2566            $ctext mark gravity fmark.$i left
2567        }
2568        if {$newname != $fname} {
2569            set i [lsearch -exact $treediffs($diffids) $newname]
2570            if {$i >= 0} {
2571                set difffilestart($i) $here
2572                incr i
2573                $ctext mark set fmark.$i $here
2574                $ctext mark gravity fmark.$i left
2575            }
2576        }
2577        set curdifftag "f:$fname"
2578        $ctext tag delete $curdifftag
2579        set l [expr {(78 - [string length $header]) / 2}]
2580        set pad [string range "----------------------------------------" 1 $l]
2581        $ctext insert end "$pad $header $pad\n" filesep
2582        set diffinhdr 1
2583    } elseif {[regexp {^(---|\+\+\+)} $line]} {
2584        set diffinhdr 0
2585    } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2586                   $line match f1l f1c f2l f2c rest]} {
2587        $ctext insert end "$line\n" hunksep
2588        set diffinhdr 0
2589    } else {
2590        set x [string range $line 0 0]
2591        if {$x == "-" || $x == "+"} {
2592            set tag [expr {$x == "+"}]
2593            $ctext insert end "$line\n" d$tag
2594        } elseif {$x == " "} {
2595            $ctext insert end "$line\n"
2596        } elseif {$diffinhdr || $x == "\\"} {
2597            # e.g. "\ No newline at end of file"
2598            $ctext insert end "$line\n" filesep
2599        } else {
2600            # Something else we don't recognize
2601            if {$curdifftag != "Comments"} {
2602                $ctext insert end "\n"
2603                $ctext tag add $curdifftag $curtagstart end
2604                set curtagstart [$ctext index "end - 1c"]
2605                set curdifftag Comments
2606            }
2607            $ctext insert end "$line\n" filesep
2608        }
2609    }
2610    $ctext conf -state disabled
2611    if {[clock clicks -milliseconds] >= $nextupdate} {
2612        incr nextupdate 100
2613        fileevent $bdf readable {}
2614        update
2615        fileevent $bdf readable "getblobdiffline $bdf {$ids}"
2616    }
2617}
2618
2619proc nextfile {} {
2620    global difffilestart ctext
2621    set here [$ctext index @0,0]
2622    for {set i 0} {[info exists difffilestart($i)]} {incr i} {
2623        if {[$ctext compare $difffilestart($i) > $here]} {
2624            if {![info exists pos]
2625                || [$ctext compare $difffilestart($i) < $pos]} {
2626                set pos $difffilestart($i)
2627            }
2628        }
2629    }
2630    if {[info exists pos]} {
2631        $ctext yview $pos
2632    }
2633}
2634
2635proc listboxsel {} {
2636    global ctext cflist currentid
2637    if {![info exists currentid]} return
2638    set sel [lsort [$cflist curselection]]
2639    if {$sel eq {}} return
2640    set first [lindex $sel 0]
2641    catch {$ctext yview fmark.$first}
2642}
2643
2644proc setcoords {} {
2645    global linespc charspc canvx0 canvy0 mainfont
2646    global xspc1 xspc2 lthickness
2647
2648    set linespc [font metrics $mainfont -linespace]
2649    set charspc [font measure $mainfont "m"]
2650    set canvy0 [expr {int(3 + 0.5 * $linespc)}]
2651    set canvx0 [expr {int(3 + 0.5 * $linespc)}]
2652    set lthickness [expr {int($linespc / 9) + 1}]
2653    set xspc1(0) $linespc
2654    set xspc2 $linespc
2655}
2656
2657proc redisplay {} {
2658    global canv canvy0 linespc numcommits
2659    global selectedline
2660
2661    set ymax [lindex [$canv cget -scrollregion] 3]
2662    if {$ymax eq {} || $ymax == 0} return
2663    set span [$canv yview]
2664    clear_display
2665    allcanvs conf -scrollregion \
2666        [list 0 0 0 [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]]
2667    allcanvs yview moveto [lindex $span 0]
2668    drawvisible
2669    if {[info exists selectedline]} {
2670        selectline $selectedline 0
2671    }
2672}
2673
2674proc incrfont {inc} {
2675    global mainfont namefont textfont ctext canv phase
2676    global stopped entries
2677    unmarkmatches
2678    set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
2679    set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
2680    set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
2681    setcoords
2682    $ctext conf -font $textfont
2683    $ctext tag conf filesep -font [concat $textfont bold]
2684    foreach e $entries {
2685        $e conf -font $mainfont
2686    }
2687    if {$phase == "getcommits"} {
2688        $canv itemconf textitems -font $mainfont
2689    }
2690    redisplay
2691}
2692
2693proc clearsha1 {} {
2694    global sha1entry sha1string
2695    if {[string length $sha1string] == 40} {
2696        $sha1entry delete 0 end
2697    }
2698}
2699
2700proc sha1change {n1 n2 op} {
2701    global sha1string currentid sha1but
2702    if {$sha1string == {}
2703        || ([info exists currentid] && $sha1string == $currentid)} {
2704        set state disabled
2705    } else {
2706        set state normal
2707    }
2708    if {[$sha1but cget -state] == $state} return
2709    if {$state == "normal"} {
2710        $sha1but conf -state normal -relief raised -text "Goto: "
2711    } else {
2712        $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
2713    }
2714}
2715
2716proc gotocommit {} {
2717    global sha1string currentid commitrow tagids
2718    global displayorder numcommits
2719
2720    if {$sha1string == {}
2721        || ([info exists currentid] && $sha1string == $currentid)} return
2722    if {[info exists tagids($sha1string)]} {
2723        set id $tagids($sha1string)
2724    } else {
2725        set id [string tolower $sha1string]
2726        if {[regexp {^[0-9a-f]{4,39}$} $id]} {
2727            set matches {}
2728            for {set l 0} {$l < $numcommits} {incr l} {
2729                if {[string match $id* [lindex $displayorder $l]]} {
2730                    lappend matches [lindex $displayorder $l]
2731                }
2732            }
2733            if {$matches ne {}} {
2734                if {[llength $matches] > 1} {
2735                    error_popup "Short SHA1 id $id is ambiguous"
2736                    return
2737                }
2738                set id [lindex $matches 0]
2739            }
2740        }
2741    }
2742    if {[info exists commitrow($id)]} {
2743        selectline $commitrow($id) 1
2744        return
2745    }
2746    if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
2747        set type "SHA1 id"
2748    } else {
2749        set type "Tag"
2750    }
2751    error_popup "$type $sha1string is not known"
2752}
2753
2754proc lineenter {x y id} {
2755    global hoverx hovery hoverid hovertimer
2756    global commitinfo canv
2757
2758    if {![info exists commitinfo($id)]} return
2759    set hoverx $x
2760    set hovery $y
2761    set hoverid $id
2762    if {[info exists hovertimer]} {
2763        after cancel $hovertimer
2764    }
2765    set hovertimer [after 500 linehover]
2766    $canv delete hover
2767}
2768
2769proc linemotion {x y id} {
2770    global hoverx hovery hoverid hovertimer
2771
2772    if {[info exists hoverid] && $id == $hoverid} {
2773        set hoverx $x
2774        set hovery $y
2775        if {[info exists hovertimer]} {
2776            after cancel $hovertimer
2777        }
2778        set hovertimer [after 500 linehover]
2779    }
2780}
2781
2782proc lineleave {id} {
2783    global hoverid hovertimer canv
2784
2785    if {[info exists hoverid] && $id == $hoverid} {
2786        $canv delete hover
2787        if {[info exists hovertimer]} {
2788            after cancel $hovertimer
2789            unset hovertimer
2790        }
2791        unset hoverid
2792    }
2793}
2794
2795proc linehover {} {
2796    global hoverx hovery hoverid hovertimer
2797    global canv linespc lthickness
2798    global commitinfo mainfont
2799
2800    set text [lindex $commitinfo($hoverid) 0]
2801    set ymax [lindex [$canv cget -scrollregion] 3]
2802    if {$ymax == {}} return
2803    set yfrac [lindex [$canv yview] 0]
2804    set x [expr {$hoverx + 2 * $linespc}]
2805    set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
2806    set x0 [expr {$x - 2 * $lthickness}]
2807    set y0 [expr {$y - 2 * $lthickness}]
2808    set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
2809    set y1 [expr {$y + $linespc + 2 * $lthickness}]
2810    set t [$canv create rectangle $x0 $y0 $x1 $y1 \
2811               -fill \#ffff80 -outline black -width 1 -tags hover]
2812    $canv raise $t
2813    set t [$canv create text $x $y -anchor nw -text $text -tags hover -font $mainfont]
2814    $canv raise $t
2815}
2816
2817proc clickisonarrow {id y} {
2818    global lthickness idrowranges
2819
2820    set thresh [expr {2 * $lthickness + 6}]
2821    set n [expr {[llength $idrowranges($id)] - 1}]
2822    for {set i 1} {$i < $n} {incr i} {
2823        set row [lindex $idrowranges($id) $i]
2824        if {abs([yc $row] - $y) < $thresh} {
2825            return $i
2826        }
2827    }
2828    return {}
2829}
2830
2831proc arrowjump {id n y} {
2832    global idrowranges canv
2833
2834    # 1 <-> 2, 3 <-> 4, etc...
2835    set n [expr {(($n - 1) ^ 1) + 1}]
2836    set row [lindex $idrowranges($id) $n]
2837    set yt [yc $row]
2838    set ymax [lindex [$canv cget -scrollregion] 3]
2839    if {$ymax eq {} || $ymax <= 0} return
2840    set view [$canv yview]
2841    set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
2842    set yfrac [expr {$yt / $ymax - $yspan / 2}]
2843    if {$yfrac < 0} {
2844        set yfrac 0
2845    }
2846    allcanvs yview moveto $yfrac
2847}
2848
2849proc lineclick {x y id isnew} {
2850    global ctext commitinfo children cflist canv thickerline
2851
2852    unmarkmatches
2853    unselectline
2854    normalline
2855    $canv delete hover
2856    # draw this line thicker than normal
2857    drawlines $id 1
2858    set thickerline $id
2859    if {$isnew} {
2860        set ymax [lindex [$canv cget -scrollregion] 3]
2861        if {$ymax eq {}} return
2862        set yfrac [lindex [$canv yview] 0]
2863        set y [expr {$y + $yfrac * $ymax}]
2864    }
2865    set dirn [clickisonarrow $id $y]
2866    if {$dirn ne {}} {
2867        arrowjump $id $dirn $y
2868        return
2869    }
2870
2871    if {$isnew} {
2872        addtohistory [list lineclick $x $y $id 0]
2873    }
2874    # fill the details pane with info about this line
2875    $ctext conf -state normal
2876    $ctext delete 0.0 end
2877    $ctext tag conf link -foreground blue -underline 1
2878    $ctext tag bind link <Enter> { %W configure -cursor hand2 }
2879    $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
2880    $ctext insert end "Parent:\t"
2881    $ctext insert end $id [list link link0]
2882    $ctext tag bind link0 <1> [list selbyid $id]
2883    set info $commitinfo($id)
2884    $ctext insert end "\n\t[lindex $info 0]\n"
2885    $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
2886    set date [formatdate [lindex $info 2]]
2887    $ctext insert end "\tDate:\t$date\n"
2888    if {[info exists children($id)]} {
2889        $ctext insert end "\nChildren:"
2890        set i 0
2891        foreach child $children($id) {
2892            incr i
2893            set info $commitinfo($child)
2894            $ctext insert end "\n\t"
2895            $ctext insert end $child [list link link$i]
2896            $ctext tag bind link$i <1> [list selbyid $child]
2897            $ctext insert end "\n\t[lindex $info 0]"
2898            $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
2899            set date [formatdate [lindex $info 2]]
2900            $ctext insert end "\n\tDate:\t$date\n"
2901        }
2902    }
2903    $ctext conf -state disabled
2904
2905    $cflist delete 0 end
2906}
2907
2908proc normalline {} {
2909    global thickerline
2910    if {[info exists thickerline]} {
2911        drawlines $thickerline 0
2912        unset thickerline
2913    }
2914}
2915
2916proc selbyid {id} {
2917    global commitrow
2918    if {[info exists commitrow($id)]} {
2919        selectline $commitrow($id) 1
2920    }
2921}
2922
2923proc mstime {} {
2924    global startmstime
2925    if {![info exists startmstime]} {
2926        set startmstime [clock clicks -milliseconds]
2927    }
2928    return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
2929}
2930
2931proc rowmenu {x y id} {
2932    global rowctxmenu commitrow selectedline rowmenuid
2933
2934    if {![info exists selectedline] || $commitrow($id) eq $selectedline} {
2935        set state disabled
2936    } else {
2937        set state normal
2938    }
2939    $rowctxmenu entryconfigure 0 -state $state
2940    $rowctxmenu entryconfigure 1 -state $state
2941    $rowctxmenu entryconfigure 2 -state $state
2942    set rowmenuid $id
2943    tk_popup $rowctxmenu $x $y
2944}
2945
2946proc diffvssel {dirn} {
2947    global rowmenuid selectedline displayorder
2948
2949    if {![info exists selectedline]} return
2950    if {$dirn} {
2951        set oldid [lindex $displayorder $selectedline]
2952        set newid $rowmenuid
2953    } else {
2954        set oldid $rowmenuid
2955        set newid [lindex $displayorder $selectedline]
2956    }
2957    addtohistory [list doseldiff $oldid $newid]
2958    doseldiff $oldid $newid
2959}
2960
2961proc doseldiff {oldid newid} {
2962    global ctext cflist
2963    global commitinfo
2964
2965    $ctext conf -state normal
2966    $ctext delete 0.0 end
2967    $ctext mark set fmark.0 0.0
2968    $ctext mark gravity fmark.0 left
2969    $cflist delete 0 end
2970    $cflist insert end "Top"
2971    $ctext insert end "From "
2972    $ctext tag conf link -foreground blue -underline 1
2973    $ctext tag bind link <Enter> { %W configure -cursor hand2 }
2974    $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
2975    $ctext tag bind link0 <1> [list selbyid $oldid]
2976    $ctext insert end $oldid [list link link0]
2977    $ctext insert end "\n     "
2978    $ctext insert end [lindex $commitinfo($oldid) 0]
2979    $ctext insert end "\n\nTo   "
2980    $ctext tag bind link1 <1> [list selbyid $newid]
2981    $ctext insert end $newid [list link link1]
2982    $ctext insert end "\n     "
2983    $ctext insert end [lindex $commitinfo($newid) 0]
2984    $ctext insert end "\n"
2985    $ctext conf -state disabled
2986    $ctext tag delete Comments
2987    $ctext tag remove found 1.0 end
2988    startdiff [list $oldid $newid]
2989}
2990
2991proc mkpatch {} {
2992    global rowmenuid currentid commitinfo patchtop patchnum
2993
2994    if {![info exists currentid]} return
2995    set oldid $currentid
2996    set oldhead [lindex $commitinfo($oldid) 0]
2997    set newid $rowmenuid
2998    set newhead [lindex $commitinfo($newid) 0]
2999    set top .patch
3000    set patchtop $top
3001    catch {destroy $top}
3002    toplevel $top
3003    label $top.title -text "Generate patch"
3004    grid $top.title - -pady 10
3005    label $top.from -text "From:"
3006    entry $top.fromsha1 -width 40 -relief flat
3007    $top.fromsha1 insert 0 $oldid
3008    $top.fromsha1 conf -state readonly
3009    grid $top.from $top.fromsha1 -sticky w
3010    entry $top.fromhead -width 60 -relief flat
3011    $top.fromhead insert 0 $oldhead
3012    $top.fromhead conf -state readonly
3013    grid x $top.fromhead -sticky w
3014    label $top.to -text "To:"
3015    entry $top.tosha1 -width 40 -relief flat
3016    $top.tosha1 insert 0 $newid
3017    $top.tosha1 conf -state readonly
3018    grid $top.to $top.tosha1 -sticky w
3019    entry $top.tohead -width 60 -relief flat
3020    $top.tohead insert 0 $newhead
3021    $top.tohead conf -state readonly
3022    grid x $top.tohead -sticky w
3023    button $top.rev -text "Reverse" -command mkpatchrev -padx 5
3024    grid $top.rev x -pady 10
3025    label $top.flab -text "Output file:"
3026    entry $top.fname -width 60
3027    $top.fname insert 0 [file normalize "patch$patchnum.patch"]
3028    incr patchnum
3029    grid $top.flab $top.fname -sticky w
3030    frame $top.buts
3031    button $top.buts.gen -text "Generate" -command mkpatchgo
3032    button $top.buts.can -text "Cancel" -command mkpatchcan
3033    grid $top.buts.gen $top.buts.can
3034    grid columnconfigure $top.buts 0 -weight 1 -uniform a
3035    grid columnconfigure $top.buts 1 -weight 1 -uniform a
3036    grid $top.buts - -pady 10 -sticky ew
3037    focus $top.fname
3038}
3039
3040proc mkpatchrev {} {
3041    global patchtop
3042
3043    set oldid [$patchtop.fromsha1 get]
3044    set oldhead [$patchtop.fromhead get]
3045    set newid [$patchtop.tosha1 get]
3046    set newhead [$patchtop.tohead get]
3047    foreach e [list fromsha1 fromhead tosha1 tohead] \
3048            v [list $newid $newhead $oldid $oldhead] {
3049        $patchtop.$e conf -state normal
3050        $patchtop.$e delete 0 end
3051        $patchtop.$e insert 0 $v
3052        $patchtop.$e conf -state readonly
3053    }
3054}
3055
3056proc mkpatchgo {} {
3057    global patchtop
3058
3059    set oldid [$patchtop.fromsha1 get]
3060    set newid [$patchtop.tosha1 get]
3061    set fname [$patchtop.fname get]
3062    if {[catch {exec git-diff-tree -p $oldid $newid >$fname &} err]} {
3063        error_popup "Error creating patch: $err"
3064    }
3065    catch {destroy $patchtop}
3066    unset patchtop
3067}
3068
3069proc mkpatchcan {} {
3070    global patchtop
3071
3072    catch {destroy $patchtop}
3073    unset patchtop
3074}
3075
3076proc mktag {} {
3077    global rowmenuid mktagtop commitinfo
3078
3079    set top .maketag
3080    set mktagtop $top
3081    catch {destroy $top}
3082    toplevel $top
3083    label $top.title -text "Create tag"
3084    grid $top.title - -pady 10
3085    label $top.id -text "ID:"
3086    entry $top.sha1 -width 40 -relief flat
3087    $top.sha1 insert 0 $rowmenuid
3088    $top.sha1 conf -state readonly
3089    grid $top.id $top.sha1 -sticky w
3090    entry $top.head -width 60 -relief flat
3091    $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3092    $top.head conf -state readonly
3093    grid x $top.head -sticky w
3094    label $top.tlab -text "Tag name:"
3095    entry $top.tag -width 60
3096    grid $top.tlab $top.tag -sticky w
3097    frame $top.buts
3098    button $top.buts.gen -text "Create" -command mktaggo
3099    button $top.buts.can -text "Cancel" -command mktagcan
3100    grid $top.buts.gen $top.buts.can
3101    grid columnconfigure $top.buts 0 -weight 1 -uniform a
3102    grid columnconfigure $top.buts 1 -weight 1 -uniform a
3103    grid $top.buts - -pady 10 -sticky ew
3104    focus $top.tag
3105}
3106
3107proc domktag {} {
3108    global mktagtop env tagids idtags
3109
3110    set id [$mktagtop.sha1 get]
3111    set tag [$mktagtop.tag get]
3112    if {$tag == {}} {
3113        error_popup "No tag name specified"
3114        return
3115    }
3116    if {[info exists tagids($tag)]} {
3117        error_popup "Tag \"$tag\" already exists"
3118        return
3119    }
3120    if {[catch {
3121        set dir [gitdir]
3122        set fname [file join $dir "refs/tags" $tag]
3123        set f [open $fname w]
3124        puts $f $id
3125        close $f
3126    } err]} {
3127        error_popup "Error creating tag: $err"
3128        return
3129    }
3130
3131    set tagids($tag) $id
3132    lappend idtags($id) $tag
3133    redrawtags $id
3134}
3135
3136proc redrawtags {id} {
3137    global canv linehtag commitrow idpos selectedline
3138
3139    if {![info exists commitrow($id)]} return
3140    drawcmitrow $commitrow($id)
3141    $canv delete tag.$id
3142    set xt [eval drawtags $id $idpos($id)]
3143    $canv coords $linehtag($commitrow($id)) $xt [lindex $idpos($id) 2]
3144    if {[info exists selectedline] && $selectedline == $commitrow($id)} {
3145        selectline $selectedline 0
3146    }
3147}
3148
3149proc mktagcan {} {
3150    global mktagtop
3151
3152    catch {destroy $mktagtop}
3153    unset mktagtop
3154}
3155
3156proc mktaggo {} {
3157    domktag
3158    mktagcan
3159}
3160
3161proc writecommit {} {
3162    global rowmenuid wrcomtop commitinfo wrcomcmd
3163
3164    set top .writecommit
3165    set wrcomtop $top
3166    catch {destroy $top}
3167    toplevel $top
3168    label $top.title -text "Write commit to file"
3169    grid $top.title - -pady 10
3170    label $top.id -text "ID:"
3171    entry $top.sha1 -width 40 -relief flat
3172    $top.sha1 insert 0 $rowmenuid
3173    $top.sha1 conf -state readonly
3174    grid $top.id $top.sha1 -sticky w
3175    entry $top.head -width 60 -relief flat
3176    $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3177    $top.head conf -state readonly
3178    grid x $top.head -sticky w
3179    label $top.clab -text "Command:"
3180    entry $top.cmd -width 60 -textvariable wrcomcmd
3181    grid $top.clab $top.cmd -sticky w -pady 10
3182    label $top.flab -text "Output file:"
3183    entry $top.fname -width 60
3184    $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
3185    grid $top.flab $top.fname -sticky w
3186    frame $top.buts
3187    button $top.buts.gen -text "Write" -command wrcomgo
3188    button $top.buts.can -text "Cancel" -command wrcomcan
3189    grid $top.buts.gen $top.buts.can
3190    grid columnconfigure $top.buts 0 -weight 1 -uniform a
3191    grid columnconfigure $top.buts 1 -weight 1 -uniform a
3192    grid $top.buts - -pady 10 -sticky ew
3193    focus $top.fname
3194}
3195
3196proc wrcomgo {} {
3197    global wrcomtop
3198
3199    set id [$wrcomtop.sha1 get]
3200    set cmd "echo $id | [$wrcomtop.cmd get]"
3201    set fname [$wrcomtop.fname get]
3202    if {[catch {exec sh -c $cmd >$fname &} err]} {
3203        error_popup "Error writing commit: $err"
3204    }
3205    catch {destroy $wrcomtop}
3206    unset wrcomtop
3207}
3208
3209proc wrcomcan {} {
3210    global wrcomtop
3211
3212    catch {destroy $wrcomtop}
3213    unset wrcomtop
3214}
3215
3216proc listrefs {id} {
3217    global idtags idheads idotherrefs
3218
3219    set x {}
3220    if {[info exists idtags($id)]} {
3221        set x $idtags($id)
3222    }
3223    set y {}
3224    if {[info exists idheads($id)]} {
3225        set y $idheads($id)
3226    }
3227    set z {}
3228    if {[info exists idotherrefs($id)]} {
3229        set z $idotherrefs($id)
3230    }
3231    return [list $x $y $z]
3232}
3233
3234proc rereadrefs {} {
3235    global idtags idheads idotherrefs
3236    global tagids headids otherrefids
3237
3238    set refids [concat [array names idtags] \
3239                    [array names idheads] [array names idotherrefs]]
3240    foreach id $refids {
3241        if {![info exists ref($id)]} {
3242            set ref($id) [listrefs $id]
3243        }
3244    }
3245    readrefs
3246    set refids [lsort -unique [concat $refids [array names idtags] \
3247                        [array names idheads] [array names idotherrefs]]]
3248    foreach id $refids {
3249        set v [listrefs $id]
3250        if {![info exists ref($id)] || $ref($id) != $v} {
3251            redrawtags $id
3252        }
3253    }
3254}
3255
3256proc showtag {tag isnew} {
3257    global ctext cflist tagcontents tagids linknum
3258
3259    if {$isnew} {
3260        addtohistory [list showtag $tag 0]
3261    }
3262    $ctext conf -state normal
3263    $ctext delete 0.0 end
3264    set linknum 0
3265    if {[info exists tagcontents($tag)]} {
3266        set text $tagcontents($tag)
3267    } else {
3268        set text "Tag: $tag\nId:  $tagids($tag)"
3269    }
3270    appendwithlinks $text
3271    $ctext conf -state disabled
3272    $cflist delete 0 end
3273}
3274
3275proc doquit {} {
3276    global stopped
3277    set stopped 100
3278    destroy .
3279}
3280
3281proc doprefs {} {
3282    global maxwidth maxgraphpct diffopts findmergefiles
3283    global oldprefs prefstop
3284
3285    set top .gitkprefs
3286    set prefstop $top
3287    if {[winfo exists $top]} {
3288        raise $top
3289        return
3290    }
3291    foreach v {maxwidth maxgraphpct diffopts findmergefiles} {
3292        set oldprefs($v) [set $v]
3293    }
3294    toplevel $top
3295    wm title $top "Gitk preferences"
3296    label $top.ldisp -text "Commit list display options"
3297    grid $top.ldisp - -sticky w -pady 10
3298    label $top.spacer -text " "
3299    label $top.maxwidthl -text "Maximum graph width (lines)" \
3300        -font optionfont
3301    spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
3302    grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
3303    label $top.maxpctl -text "Maximum graph width (% of pane)" \
3304        -font optionfont
3305    spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
3306    grid x $top.maxpctl $top.maxpct -sticky w
3307    checkbutton $top.findm -variable findmergefiles
3308    label $top.findml -text "Include merges for \"Find\" in \"Files\"" \
3309        -font optionfont
3310    grid $top.findm $top.findml - -sticky w
3311    label $top.ddisp -text "Diff display options"
3312    grid $top.ddisp - -sticky w -pady 10
3313    label $top.diffoptl -text "Options for diff program" \
3314        -font optionfont
3315    entry $top.diffopt -width 20 -textvariable diffopts
3316    grid x $top.diffoptl $top.diffopt -sticky w
3317    frame $top.buts
3318    button $top.buts.ok -text "OK" -command prefsok
3319    button $top.buts.can -text "Cancel" -command prefscan
3320    grid $top.buts.ok $top.buts.can
3321    grid columnconfigure $top.buts 0 -weight 1 -uniform a
3322    grid columnconfigure $top.buts 1 -weight 1 -uniform a
3323    grid $top.buts - - -pady 10 -sticky ew
3324}
3325
3326proc prefscan {} {
3327    global maxwidth maxgraphpct diffopts findmergefiles
3328    global oldprefs prefstop
3329
3330    foreach v {maxwidth maxgraphpct diffopts findmergefiles} {
3331        set $v $oldprefs($v)
3332    }
3333    catch {destroy $prefstop}
3334    unset prefstop
3335}
3336
3337proc prefsok {} {
3338    global maxwidth maxgraphpct
3339    global oldprefs prefstop
3340
3341    catch {destroy $prefstop}
3342    unset prefstop
3343    if {$maxwidth != $oldprefs(maxwidth)
3344        || $maxgraphpct != $oldprefs(maxgraphpct)} {
3345        redisplay
3346    }
3347}
3348
3349proc formatdate {d} {
3350    return [clock format $d -format "%Y-%m-%d %H:%M:%S"]
3351}
3352
3353# This list of encoding names and aliases is distilled from
3354# http://www.iana.org/assignments/character-sets.
3355# Not all of them are supported by Tcl.
3356set encoding_aliases {
3357    { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
3358      ISO646-US US-ASCII us IBM367 cp367 csASCII }
3359    { ISO-10646-UTF-1 csISO10646UTF1 }
3360    { ISO_646.basic:1983 ref csISO646basic1983 }
3361    { INVARIANT csINVARIANT }
3362    { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
3363    { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
3364    { NATS-SEFI iso-ir-8-1 csNATSSEFI }
3365    { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
3366    { NATS-DANO iso-ir-9-1 csNATSDANO }
3367    { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
3368    { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
3369    { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
3370    { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
3371    { ISO-2022-KR csISO2022KR }
3372    { EUC-KR csEUCKR }
3373    { ISO-2022-JP csISO2022JP }
3374    { ISO-2022-JP-2 csISO2022JP2 }
3375    { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
3376      csISO13JISC6220jp }
3377    { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
3378    { IT iso-ir-15 ISO646-IT csISO15Italian }
3379    { PT iso-ir-16 ISO646-PT csISO16Portuguese }
3380    { ES iso-ir-17 ISO646-ES csISO17Spanish }
3381    { greek7-old iso-ir-18 csISO18Greek7Old }
3382    { latin-greek iso-ir-19 csISO19LatinGreek }
3383    { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
3384    { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
3385    { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
3386    { ISO_5427 iso-ir-37 csISO5427Cyrillic }
3387    { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
3388    { BS_viewdata iso-ir-47 csISO47BSViewdata }
3389    { INIS iso-ir-49 csISO49INIS }
3390    { INIS-8 iso-ir-50 csISO50INIS8 }
3391    { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
3392    { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
3393    { ISO_5428:1980 iso-ir-55 csISO5428Greek }
3394    { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
3395    { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
3396    { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
3397      csISO60Norwegian1 }
3398    { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
3399    { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
3400    { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
3401    { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
3402    { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
3403    { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
3404    { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
3405    { greek7 iso-ir-88 csISO88Greek7 }
3406    { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
3407    { iso-ir-90 csISO90 }
3408    { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
3409    { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
3410      csISO92JISC62991984b }
3411    { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
3412    { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
3413    { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
3414      csISO95JIS62291984handadd }
3415    { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
3416    { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
3417    { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
3418    { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
3419      CP819 csISOLatin1 }
3420    { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
3421    { T.61-7bit iso-ir-102 csISO102T617bit }
3422    { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
3423    { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
3424    { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
3425    { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
3426    { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
3427    { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
3428    { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
3429    { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
3430      arabic csISOLatinArabic }
3431    { ISO_8859-6-E csISO88596E ISO-8859-6-E }
3432    { ISO_8859-6-I csISO88596I ISO-8859-6-I }
3433    { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
3434      greek greek8 csISOLatinGreek }
3435    { T.101-G2 iso-ir-128 csISO128T101G2 }
3436    { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
3437      csISOLatinHebrew }
3438    { ISO_8859-8-E csISO88598E ISO-8859-8-E }
3439    { ISO_8859-8-I csISO88598I ISO-8859-8-I }
3440    { CSN_369103 iso-ir-139 csISO139CSN369103 }
3441    { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
3442    { ISO_6937-2-add iso-ir-142 csISOTextComm }
3443    { IEC_P27-1 iso-ir-143 csISO143IECP271 }
3444    { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
3445      csISOLatinCyrillic }
3446    { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
3447    { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
3448    { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
3449    { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
3450    { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
3451    { ISO_6937-2-25 iso-ir-152 csISO6937Add }
3452    { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
3453    { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
3454    { ISO_10367-box iso-ir-155 csISO10367Box }
3455    { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
3456    { latin-lap lap iso-ir-158 csISO158Lap }
3457    { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
3458    { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
3459    { us-dk csUSDK }
3460    { dk-us csDKUS }
3461    { JIS_X0201 X0201 csHalfWidthKatakana }
3462    { KSC5636 ISO646-KR csKSC5636 }
3463    { ISO-10646-UCS-2 csUnicode }
3464    { ISO-10646-UCS-4 csUCS4 }
3465    { DEC-MCS dec csDECMCS }
3466    { hp-roman8 roman8 r8 csHPRoman8 }
3467    { macintosh mac csMacintosh }
3468    { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
3469      csIBM037 }
3470    { IBM038 EBCDIC-INT cp038 csIBM038 }
3471    { IBM273 CP273 csIBM273 }
3472    { IBM274 EBCDIC-BE CP274 csIBM274 }
3473    { IBM275 EBCDIC-BR cp275 csIBM275 }
3474    { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
3475    { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
3476    { IBM280 CP280 ebcdic-cp-it csIBM280 }
3477    { IBM281 EBCDIC-JP-E cp281 csIBM281 }
3478    { IBM284 CP284 ebcdic-cp-es csIBM284 }
3479    { IBM285 CP285 ebcdic-cp-gb csIBM285 }
3480    { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
3481    { IBM297 cp297 ebcdic-cp-fr csIBM297 }
3482    { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
3483    { IBM423 cp423 ebcdic-cp-gr csIBM423 }
3484    { IBM424 cp424 ebcdic-cp-he csIBM424 }
3485    { IBM437 cp437 437 csPC8CodePage437 }
3486    { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
3487    { IBM775 cp775 csPC775Baltic }
3488    { IBM850 cp850 850 csPC850Multilingual }
3489    { IBM851 cp851 851 csIBM851 }
3490    { IBM852 cp852 852 csPCp852 }
3491    { IBM855 cp855 855 csIBM855 }
3492    { IBM857 cp857 857 csIBM857 }
3493    { IBM860 cp860 860 csIBM860 }
3494    { IBM861 cp861 861 cp-is csIBM861 }
3495    { IBM862 cp862 862 csPC862LatinHebrew }
3496    { IBM863 cp863 863 csIBM863 }
3497    { IBM864 cp864 csIBM864 }
3498    { IBM865 cp865 865 csIBM865 }
3499    { IBM866 cp866 866 csIBM866 }
3500    { IBM868 CP868 cp-ar csIBM868 }
3501    { IBM869 cp869 869 cp-gr csIBM869 }
3502    { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
3503    { IBM871 CP871 ebcdic-cp-is csIBM871 }
3504    { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
3505    { IBM891 cp891 csIBM891 }
3506    { IBM903 cp903 csIBM903 }
3507    { IBM904 cp904 904 csIBBM904 }
3508    { IBM905 CP905 ebcdic-cp-tr csIBM905 }
3509    { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
3510    { IBM1026 CP1026 csIBM1026 }
3511    { EBCDIC-AT-DE csIBMEBCDICATDE }
3512    { EBCDIC-AT-DE-A csEBCDICATDEA }
3513    { EBCDIC-CA-FR csEBCDICCAFR }
3514    { EBCDIC-DK-NO csEBCDICDKNO }
3515    { EBCDIC-DK-NO-A csEBCDICDKNOA }
3516    { EBCDIC-FI-SE csEBCDICFISE }
3517    { EBCDIC-FI-SE-A csEBCDICFISEA }
3518    { EBCDIC-FR csEBCDICFR }
3519    { EBCDIC-IT csEBCDICIT }
3520    { EBCDIC-PT csEBCDICPT }
3521    { EBCDIC-ES csEBCDICES }
3522    { EBCDIC-ES-A csEBCDICESA }
3523    { EBCDIC-ES-S csEBCDICESS }
3524    { EBCDIC-UK csEBCDICUK }
3525    { EBCDIC-US csEBCDICUS }
3526    { UNKNOWN-8BIT csUnknown8BiT }
3527    { MNEMONIC csMnemonic }
3528    { MNEM csMnem }
3529    { VISCII csVISCII }
3530    { VIQR csVIQR }
3531    { KOI8-R csKOI8R }
3532    { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
3533    { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
3534    { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
3535    { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
3536    { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
3537    { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
3538    { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
3539    { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
3540    { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
3541    { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
3542    { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
3543    { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
3544    { IBM1047 IBM-1047 }
3545    { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
3546    { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
3547    { UNICODE-1-1 csUnicode11 }
3548    { CESU-8 csCESU-8 }
3549    { BOCU-1 csBOCU-1 }
3550    { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
3551    { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
3552      l8 }
3553    { ISO-8859-15 ISO_8859-15 Latin-9 }
3554    { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
3555    { GBK CP936 MS936 windows-936 }
3556    { JIS_Encoding csJISEncoding }
3557    { Shift_JIS MS_Kanji csShiftJIS }
3558    { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
3559      EUC-JP }
3560    { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
3561    { ISO-10646-UCS-Basic csUnicodeASCII }
3562    { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
3563    { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
3564    { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
3565    { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
3566    { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
3567    { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
3568    { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
3569    { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
3570    { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
3571    { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
3572    { Adobe-Standard-Encoding csAdobeStandardEncoding }
3573    { Ventura-US csVenturaUS }
3574    { Ventura-International csVenturaInternational }
3575    { PC8-Danish-Norwegian csPC8DanishNorwegian }
3576    { PC8-Turkish csPC8Turkish }
3577    { IBM-Symbols csIBMSymbols }
3578    { IBM-Thai csIBMThai }
3579    { HP-Legal csHPLegal }
3580    { HP-Pi-font csHPPiFont }
3581    { HP-Math8 csHPMath8 }
3582    { Adobe-Symbol-Encoding csHPPSMath }
3583    { HP-DeskTop csHPDesktop }
3584    { Ventura-Math csVenturaMath }
3585    { Microsoft-Publishing csMicrosoftPublishing }
3586    { Windows-31J csWindows31J }
3587    { GB2312 csGB2312 }
3588    { Big5 csBig5 }
3589}
3590
3591proc tcl_encoding {enc} {
3592    global encoding_aliases
3593    set names [encoding names]
3594    set lcnames [string tolower $names]
3595    set enc [string tolower $enc]
3596    set i [lsearch -exact $lcnames $enc]
3597    if {$i < 0} {
3598        # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
3599        if {[regsub {^iso[-_]} $enc iso encx]} {
3600            set i [lsearch -exact $lcnames $encx]
3601        }
3602    }
3603    if {$i < 0} {
3604        foreach l $encoding_aliases {
3605            set ll [string tolower $l]
3606            if {[lsearch -exact $ll $enc] < 0} continue
3607            # look through the aliases for one that tcl knows about
3608            foreach e $ll {
3609                set i [lsearch -exact $lcnames $e]
3610                if {$i < 0} {
3611                    if {[regsub {^iso[-_]} $e iso ex]} {
3612                        set i [lsearch -exact $lcnames $ex]
3613                    }
3614                }
3615                if {$i >= 0} break
3616            }
3617            break
3618        }
3619    }
3620    if {$i >= 0} {
3621        return [lindex $names $i]
3622    }
3623    return {}
3624}
3625
3626# defaults...
3627set datemode 0
3628set diffopts "-U 5 -p"
3629set wrcomcmd "git-diff-tree --stdin -p --pretty"
3630
3631set gitencoding {}
3632catch {
3633    set gitencoding [exec git-repo-config --get i18n.commitencoding]
3634}
3635if {$gitencoding == ""} {
3636    set gitencoding "utf-8"
3637}
3638set tclencoding [tcl_encoding $gitencoding]
3639if {$tclencoding == {}} {
3640    puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
3641}
3642
3643set mainfont {Helvetica 9}
3644set textfont {Courier 9}
3645set findmergefiles 0
3646set maxgraphpct 50
3647set maxwidth 16
3648set revlistorder 0
3649set fastdate 0
3650set uparrowlen 7
3651set downarrowlen 7
3652set mingaplen 30
3653
3654set colors {green red blue magenta darkgrey brown orange}
3655
3656catch {source ~/.gitk}
3657
3658set namefont $mainfont
3659
3660font create optionfont -family sans-serif -size -12
3661
3662set revtreeargs {}
3663foreach arg $argv {
3664    switch -regexp -- $arg {
3665        "^$" { }
3666        "^-d" { set datemode 1 }
3667        default {
3668            lappend revtreeargs $arg
3669        }
3670    }
3671}
3672
3673# check that we can find a .git directory somewhere...
3674set gitdir [gitdir]
3675if {![file isdirectory $gitdir]} {
3676    error_popup "Cannot find the git directory \"$gitdir\"."
3677    exit 1
3678}
3679
3680set history {}
3681set historyindex 0
3682
3683set optim_delay 16
3684
3685set stopped 0
3686set stuffsaved 0
3687set patchnum 0
3688setcoords
3689makewindow $revtreeargs
3690readrefs
3691getcommits $revtreeargs