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