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