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