5948ec37c5c08883ebfed88af53731fffea521e1
   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
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    set tags [lsort -index 0 -decreasing $tags]
3848    set sep {}
3849    foreach ti $tags {
3850        set id [lindex $ti 1]
3851        set lk link$linknum
3852        incr linknum
3853        $ctext tag delete $lk
3854        $ctext insert $pos $sep
3855        $ctext insert $pos [lindex $ti 0] $lk
3856        if {[info exists commitrow($curview,$id)]} {
3857            $ctext tag conf $lk -foreground blue
3858            $ctext tag bind $lk <1> \
3859                [list selectline $commitrow($curview,$id) 1]
3860            $ctext tag conf $lk -underline 1
3861            $ctext tag bind $lk <Enter> { %W configure -cursor hand2 }
3862            $ctext tag bind $lk <Leave> { %W configure -cursor $curtextcursor }
3863        }
3864        set sep ", "
3865    }
3866    $ctext conf -state disabled
3867    return [llength $tags]
3868}
3869
3870# called when we have finished computing the nearby tags
3871proc dispneartags {delay} {
3872    global selectedline currentid showneartags tagphase
3873
3874    if {![info exists selectedline] || !$showneartags} return
3875    after cancel dispnexttag
3876    if {$delay} {
3877        after 200 dispnexttag
3878        set tagphase -1
3879    } else {
3880        after idle dispnexttag
3881        set tagphase 0
3882    }
3883}
3884
3885proc dispnexttag {} {
3886    global selectedline currentid showneartags tagphase ctext
3887
3888    if {![info exists selectedline] || !$showneartags} return
3889    switch -- $tagphase {
3890        0 {
3891            set dtags [desctags $currentid]
3892            if {$dtags ne {}} {
3893                appendrefs precedes $dtags idtags
3894            }
3895        }
3896        1 {
3897            set atags [anctags $currentid]
3898            if {$atags ne {}} {
3899                appendrefs follows $atags idtags
3900            }
3901        }
3902        2 {
3903            set dheads [descheads $currentid]
3904            if {$dheads ne {}} {
3905                if {[appendrefs branch $dheads idheads] > 1
3906                    && [$ctext get "branch -3c"] eq "h"} {
3907                    # turn "Branch" into "Branches"
3908                    $ctext conf -state normal
3909                    $ctext insert "branch -2c" "es"
3910                    $ctext conf -state disabled
3911                }
3912            }
3913        }
3914    }
3915    if {[incr tagphase] <= 2} {
3916        after idle dispnexttag
3917    }
3918}
3919
3920proc selectline {l isnew} {
3921    global canv canv2 canv3 ctext commitinfo selectedline
3922    global displayorder linehtag linentag linedtag
3923    global canvy0 linespc parentlist childlist
3924    global currentid sha1entry
3925    global commentend idtags linknum
3926    global mergemax numcommits pending_select
3927    global cmitmode showneartags allcommits
3928
3929    catch {unset pending_select}
3930    $canv delete hover
3931    normalline
3932    cancel_next_highlight
3933    if {$l < 0 || $l >= $numcommits} return
3934    set y [expr {$canvy0 + $l * $linespc}]
3935    set ymax [lindex [$canv cget -scrollregion] 3]
3936    set ytop [expr {$y - $linespc - 1}]
3937    set ybot [expr {$y + $linespc + 1}]
3938    set wnow [$canv yview]
3939    set wtop [expr {[lindex $wnow 0] * $ymax}]
3940    set wbot [expr {[lindex $wnow 1] * $ymax}]
3941    set wh [expr {$wbot - $wtop}]
3942    set newtop $wtop
3943    if {$ytop < $wtop} {
3944        if {$ybot < $wtop} {
3945            set newtop [expr {$y - $wh / 2.0}]
3946        } else {
3947            set newtop $ytop
3948            if {$newtop > $wtop - $linespc} {
3949                set newtop [expr {$wtop - $linespc}]
3950            }
3951        }
3952    } elseif {$ybot > $wbot} {
3953        if {$ytop > $wbot} {
3954            set newtop [expr {$y - $wh / 2.0}]
3955        } else {
3956            set newtop [expr {$ybot - $wh}]
3957            if {$newtop < $wtop + $linespc} {
3958                set newtop [expr {$wtop + $linespc}]
3959            }
3960        }
3961    }
3962    if {$newtop != $wtop} {
3963        if {$newtop < 0} {
3964            set newtop 0
3965        }
3966        allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
3967        drawvisible
3968    }
3969
3970    if {![info exists linehtag($l)]} return
3971    $canv delete secsel
3972    set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
3973               -tags secsel -fill [$canv cget -selectbackground]]
3974    $canv lower $t
3975    $canv2 delete secsel
3976    set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
3977               -tags secsel -fill [$canv2 cget -selectbackground]]
3978    $canv2 lower $t
3979    $canv3 delete secsel
3980    set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
3981               -tags secsel -fill [$canv3 cget -selectbackground]]
3982    $canv3 lower $t
3983
3984    if {$isnew} {
3985        addtohistory [list selectline $l 0]
3986    }
3987
3988    set selectedline $l
3989
3990    set id [lindex $displayorder $l]
3991    set currentid $id
3992    $sha1entry delete 0 end
3993    $sha1entry insert 0 $id
3994    $sha1entry selection from 0
3995    $sha1entry selection to end
3996    rhighlight_sel $id
3997
3998    $ctext conf -state normal
3999    clear_ctext
4000    set linknum 0
4001    set info $commitinfo($id)
4002    set date [formatdate [lindex $info 2]]
4003    $ctext insert end "Author: [lindex $info 1]  $date\n"
4004    set date [formatdate [lindex $info 4]]
4005    $ctext insert end "Committer: [lindex $info 3]  $date\n"
4006    if {[info exists idtags($id)]} {
4007        $ctext insert end "Tags:"
4008        foreach tag $idtags($id) {
4009            $ctext insert end " $tag"
4010        }
4011        $ctext insert end "\n"
4012    }
4013
4014    set headers {}
4015    set olds [lindex $parentlist $l]
4016    if {[llength $olds] > 1} {
4017        set np 0
4018        foreach p $olds {
4019            if {$np >= $mergemax} {
4020                set tag mmax
4021            } else {
4022                set tag m$np
4023            }
4024            $ctext insert end "Parent: " $tag
4025            appendwithlinks [commit_descriptor $p] {}
4026            incr np
4027        }
4028    } else {
4029        foreach p $olds {
4030            append headers "Parent: [commit_descriptor $p]"
4031        }
4032    }
4033
4034    foreach c [lindex $childlist $l] {
4035        append headers "Child:  [commit_descriptor $c]"
4036    }
4037
4038    # make anything that looks like a SHA1 ID be a clickable link
4039    appendwithlinks $headers {}
4040    if {$showneartags} {
4041        if {![info exists allcommits]} {
4042            getallcommits
4043        }
4044        $ctext insert end "Branch: "
4045        $ctext mark set branch "end -1c"
4046        $ctext mark gravity branch left
4047        $ctext insert end "\nFollows: "
4048        $ctext mark set follows "end -1c"
4049        $ctext mark gravity follows left
4050        $ctext insert end "\nPrecedes: "
4051        $ctext mark set precedes "end -1c"
4052        $ctext mark gravity precedes left
4053        $ctext insert end "\n"
4054        dispneartags 1
4055    }
4056    $ctext insert end "\n"
4057    appendwithlinks [lindex $info 5] {comment}
4058
4059    $ctext tag delete Comments
4060    $ctext tag remove found 1.0 end
4061    $ctext conf -state disabled
4062    set commentend [$ctext index "end - 1c"]
4063
4064    init_flist "Comments"
4065    if {$cmitmode eq "tree"} {
4066        gettree $id
4067    } elseif {[llength $olds] <= 1} {
4068        startdiff $id
4069    } else {
4070        mergediff $id $l
4071    }
4072}
4073
4074proc selfirstline {} {
4075    unmarkmatches
4076    selectline 0 1
4077}
4078
4079proc sellastline {} {
4080    global numcommits
4081    unmarkmatches
4082    set l [expr {$numcommits - 1}]
4083    selectline $l 1
4084}
4085
4086proc selnextline {dir} {
4087    global selectedline
4088    if {![info exists selectedline]} return
4089    set l [expr {$selectedline + $dir}]
4090    unmarkmatches
4091    selectline $l 1
4092}
4093
4094proc selnextpage {dir} {
4095    global canv linespc selectedline numcommits
4096
4097    set lpp [expr {([winfo height $canv] - 2) / $linespc}]
4098    if {$lpp < 1} {
4099        set lpp 1
4100    }
4101    allcanvs yview scroll [expr {$dir * $lpp}] units
4102    drawvisible
4103    if {![info exists selectedline]} return
4104    set l [expr {$selectedline + $dir * $lpp}]
4105    if {$l < 0} {
4106        set l 0
4107    } elseif {$l >= $numcommits} {
4108        set l [expr $numcommits - 1]
4109    }
4110    unmarkmatches
4111    selectline $l 1
4112}
4113
4114proc unselectline {} {
4115    global selectedline currentid
4116
4117    catch {unset selectedline}
4118    catch {unset currentid}
4119    allcanvs delete secsel
4120    rhighlight_none
4121    cancel_next_highlight
4122}
4123
4124proc reselectline {} {
4125    global selectedline
4126
4127    if {[info exists selectedline]} {
4128        selectline $selectedline 0
4129    }
4130}
4131
4132proc addtohistory {cmd} {
4133    global history historyindex curview
4134
4135    set elt [list $curview $cmd]
4136    if {$historyindex > 0
4137        && [lindex $history [expr {$historyindex - 1}]] == $elt} {
4138        return
4139    }
4140
4141    if {$historyindex < [llength $history]} {
4142        set history [lreplace $history $historyindex end $elt]
4143    } else {
4144        lappend history $elt
4145    }
4146    incr historyindex
4147    if {$historyindex > 1} {
4148        .tf.bar.leftbut conf -state normal
4149    } else {
4150        .tf.bar.leftbut conf -state disabled
4151    }
4152    .tf.bar.rightbut conf -state disabled
4153}
4154
4155proc godo {elt} {
4156    global curview
4157
4158    set view [lindex $elt 0]
4159    set cmd [lindex $elt 1]
4160    if {$curview != $view} {
4161        showview $view
4162    }
4163    eval $cmd
4164}
4165
4166proc goback {} {
4167    global history historyindex
4168
4169    if {$historyindex > 1} {
4170        incr historyindex -1
4171        godo [lindex $history [expr {$historyindex - 1}]]
4172        .tf.bar.rightbut conf -state normal
4173    }
4174    if {$historyindex <= 1} {
4175        .tf.bar.leftbut conf -state disabled
4176    }
4177}
4178
4179proc goforw {} {
4180    global history historyindex
4181
4182    if {$historyindex < [llength $history]} {
4183        set cmd [lindex $history $historyindex]
4184        incr historyindex
4185        godo $cmd
4186        .tf.bar.leftbut conf -state normal
4187    }
4188    if {$historyindex >= [llength $history]} {
4189        .tf.bar.rightbut conf -state disabled
4190    }
4191}
4192
4193proc gettree {id} {
4194    global treefilelist treeidlist diffids diffmergeid treepending
4195
4196    set diffids $id
4197    catch {unset diffmergeid}
4198    if {![info exists treefilelist($id)]} {
4199        if {![info exists treepending]} {
4200            if {[catch {set gtf [open [concat | git ls-tree -r $id] r]}]} {
4201                return
4202            }
4203            set treepending $id
4204            set treefilelist($id) {}
4205            set treeidlist($id) {}
4206            fconfigure $gtf -blocking 0
4207            fileevent $gtf readable [list gettreeline $gtf $id]
4208        }
4209    } else {
4210        setfilelist $id
4211    }
4212}
4213
4214proc gettreeline {gtf id} {
4215    global treefilelist treeidlist treepending cmitmode diffids
4216
4217    while {[gets $gtf line] >= 0} {
4218        if {[lindex $line 1] ne "blob"} continue
4219        set sha1 [lindex $line 2]
4220        set fname [lindex $line 3]
4221        lappend treefilelist($id) $fname
4222        lappend treeidlist($id) $sha1
4223    }
4224    if {![eof $gtf]} return
4225    close $gtf
4226    unset treepending
4227    if {$cmitmode ne "tree"} {
4228        if {![info exists diffmergeid]} {
4229            gettreediffs $diffids
4230        }
4231    } elseif {$id ne $diffids} {
4232        gettree $diffids
4233    } else {
4234        setfilelist $id
4235    }
4236}
4237
4238proc showfile {f} {
4239    global treefilelist treeidlist diffids
4240    global ctext commentend
4241
4242    set i [lsearch -exact $treefilelist($diffids) $f]
4243    if {$i < 0} {
4244        puts "oops, $f not in list for id $diffids"
4245        return
4246    }
4247    set blob [lindex $treeidlist($diffids) $i]
4248    if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
4249        puts "oops, error reading blob $blob: $err"
4250        return
4251    }
4252    fconfigure $bf -blocking 0
4253    fileevent $bf readable [list getblobline $bf $diffids]
4254    $ctext config -state normal
4255    clear_ctext $commentend
4256    $ctext insert end "\n"
4257    $ctext insert end "$f\n" filesep
4258    $ctext config -state disabled
4259    $ctext yview $commentend
4260}
4261
4262proc getblobline {bf id} {
4263    global diffids cmitmode ctext
4264
4265    if {$id ne $diffids || $cmitmode ne "tree"} {
4266        catch {close $bf}
4267        return
4268    }
4269    $ctext config -state normal
4270    while {[gets $bf line] >= 0} {
4271        $ctext insert end "$line\n"
4272    }
4273    if {[eof $bf]} {
4274        # delete last newline
4275        $ctext delete "end - 2c" "end - 1c"
4276        close $bf
4277    }
4278    $ctext config -state disabled
4279}
4280
4281proc mergediff {id l} {
4282    global diffmergeid diffopts mdifffd
4283    global diffids
4284    global parentlist
4285
4286    set diffmergeid $id
4287    set diffids $id
4288    # this doesn't seem to actually affect anything...
4289    set env(GIT_DIFF_OPTS) $diffopts
4290    set cmd [concat | git diff-tree --no-commit-id --cc $id]
4291    if {[catch {set mdf [open $cmd r]} err]} {
4292        error_popup "Error getting merge diffs: $err"
4293        return
4294    }
4295    fconfigure $mdf -blocking 0
4296    set mdifffd($id) $mdf
4297    set np [llength [lindex $parentlist $l]]
4298    fileevent $mdf readable [list getmergediffline $mdf $id $np]
4299    set nextupdate [expr {[clock clicks -milliseconds] + 100}]
4300}
4301
4302proc getmergediffline {mdf id np} {
4303    global diffmergeid ctext cflist nextupdate mergemax
4304    global difffilestart mdifffd
4305
4306    set n [gets $mdf line]
4307    if {$n < 0} {
4308        if {[eof $mdf]} {
4309            close $mdf
4310        }
4311        return
4312    }
4313    if {![info exists diffmergeid] || $id != $diffmergeid
4314        || $mdf != $mdifffd($id)} {
4315        return
4316    }
4317    $ctext conf -state normal
4318    if {[regexp {^diff --cc (.*)} $line match fname]} {
4319        # start of a new file
4320        $ctext insert end "\n"
4321        set here [$ctext index "end - 1c"]
4322        lappend difffilestart $here
4323        add_flist [list $fname]
4324        set l [expr {(78 - [string length $fname]) / 2}]
4325        set pad [string range "----------------------------------------" 1 $l]
4326        $ctext insert end "$pad $fname $pad\n" filesep
4327    } elseif {[regexp {^@@} $line]} {
4328        $ctext insert end "$line\n" hunksep
4329    } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
4330        # do nothing
4331    } else {
4332        # parse the prefix - one ' ', '-' or '+' for each parent
4333        set spaces {}
4334        set minuses {}
4335        set pluses {}
4336        set isbad 0
4337        for {set j 0} {$j < $np} {incr j} {
4338            set c [string range $line $j $j]
4339            if {$c == " "} {
4340                lappend spaces $j
4341            } elseif {$c == "-"} {
4342                lappend minuses $j
4343            } elseif {$c == "+"} {
4344                lappend pluses $j
4345            } else {
4346                set isbad 1
4347                break
4348            }
4349        }
4350        set tags {}
4351        set num {}
4352        if {!$isbad && $minuses ne {} && $pluses eq {}} {
4353            # line doesn't appear in result, parents in $minuses have the line
4354            set num [lindex $minuses 0]
4355        } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
4356            # line appears in result, parents in $pluses don't have the line
4357            lappend tags mresult
4358            set num [lindex $spaces 0]
4359        }
4360        if {$num ne {}} {
4361            if {$num >= $mergemax} {
4362                set num "max"
4363            }
4364            lappend tags m$num
4365        }
4366        $ctext insert end "$line\n" $tags
4367    }
4368    $ctext conf -state disabled
4369    if {[clock clicks -milliseconds] >= $nextupdate} {
4370        incr nextupdate 100
4371        fileevent $mdf readable {}
4372        update
4373        fileevent $mdf readable [list getmergediffline $mdf $id $np]
4374    }
4375}
4376
4377proc startdiff {ids} {
4378    global treediffs diffids treepending diffmergeid
4379
4380    set diffids $ids
4381    catch {unset diffmergeid}
4382    if {![info exists treediffs($ids)]} {
4383        if {![info exists treepending]} {
4384            gettreediffs $ids
4385        }
4386    } else {
4387        addtocflist $ids
4388    }
4389}
4390
4391proc addtocflist {ids} {
4392    global treediffs cflist
4393    add_flist $treediffs($ids)
4394    getblobdiffs $ids
4395}
4396
4397proc gettreediffs {ids} {
4398    global treediff treepending
4399    set treepending $ids
4400    set treediff {}
4401    if {[catch \
4402         {set gdtf [open [concat | git diff-tree --no-commit-id -r $ids] r]} \
4403        ]} return
4404    fconfigure $gdtf -blocking 0
4405    fileevent $gdtf readable [list gettreediffline $gdtf $ids]
4406}
4407
4408proc gettreediffline {gdtf ids} {
4409    global treediff treediffs treepending diffids diffmergeid
4410    global cmitmode
4411
4412    set n [gets $gdtf line]
4413    if {$n < 0} {
4414        if {![eof $gdtf]} return
4415        close $gdtf
4416        set treediffs($ids) $treediff
4417        unset treepending
4418        if {$cmitmode eq "tree"} {
4419            gettree $diffids
4420        } elseif {$ids != $diffids} {
4421            if {![info exists diffmergeid]} {
4422                gettreediffs $diffids
4423            }
4424        } else {
4425            addtocflist $ids
4426        }
4427        return
4428    }
4429    set file [lindex $line 5]
4430    lappend treediff $file
4431}
4432
4433proc getblobdiffs {ids} {
4434    global diffopts blobdifffd diffids env curdifftag curtagstart
4435    global nextupdate diffinhdr treediffs
4436
4437    set env(GIT_DIFF_OPTS) $diffopts
4438    set cmd [concat | git diff-tree --no-commit-id -r -p -C $ids]
4439    if {[catch {set bdf [open $cmd r]} err]} {
4440        puts "error getting diffs: $err"
4441        return
4442    }
4443    set diffinhdr 0
4444    fconfigure $bdf -blocking 0
4445    set blobdifffd($ids) $bdf
4446    set curdifftag Comments
4447    set curtagstart 0.0
4448    fileevent $bdf readable [list getblobdiffline $bdf $diffids]
4449    set nextupdate [expr {[clock clicks -milliseconds] + 100}]
4450}
4451
4452proc setinlist {var i val} {
4453    global $var
4454
4455    while {[llength [set $var]] < $i} {
4456        lappend $var {}
4457    }
4458    if {[llength [set $var]] == $i} {
4459        lappend $var $val
4460    } else {
4461        lset $var $i $val
4462    }
4463}
4464
4465proc getblobdiffline {bdf ids} {
4466    global diffids blobdifffd ctext curdifftag curtagstart
4467    global diffnexthead diffnextnote difffilestart
4468    global nextupdate diffinhdr treediffs
4469
4470    set n [gets $bdf line]
4471    if {$n < 0} {
4472        if {[eof $bdf]} {
4473            close $bdf
4474            if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
4475                $ctext tag add $curdifftag $curtagstart end
4476            }
4477        }
4478        return
4479    }
4480    if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
4481        return
4482    }
4483    $ctext conf -state normal
4484    if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} {
4485        # start of a new file
4486        $ctext insert end "\n"
4487        $ctext tag add $curdifftag $curtagstart end
4488        set here [$ctext index "end - 1c"]
4489        set curtagstart $here
4490        set header $newname
4491        set i [lsearch -exact $treediffs($ids) $fname]
4492        if {$i >= 0} {
4493            setinlist difffilestart $i $here
4494        }
4495        if {$newname ne $fname} {
4496            set i [lsearch -exact $treediffs($ids) $newname]
4497            if {$i >= 0} {
4498                setinlist difffilestart $i $here
4499            }
4500        }
4501        set curdifftag "f:$fname"
4502        $ctext tag delete $curdifftag
4503        set l [expr {(78 - [string length $header]) / 2}]
4504        set pad [string range "----------------------------------------" 1 $l]
4505        $ctext insert end "$pad $header $pad\n" filesep
4506        set diffinhdr 1
4507    } elseif {$diffinhdr && [string compare -length 3 $line "---"] == 0} {
4508        # do nothing
4509    } elseif {$diffinhdr && [string compare -length 3 $line "+++"] == 0} {
4510        set diffinhdr 0
4511    } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
4512                   $line match f1l f1c f2l f2c rest]} {
4513        $ctext insert end "$line\n" hunksep
4514        set diffinhdr 0
4515    } else {
4516        set x [string range $line 0 0]
4517        if {$x == "-" || $x == "+"} {
4518            set tag [expr {$x == "+"}]
4519            $ctext insert end "$line\n" d$tag
4520        } elseif {$x == " "} {
4521            $ctext insert end "$line\n"
4522        } elseif {$diffinhdr || $x == "\\"} {
4523            # e.g. "\ No newline at end of file"
4524            $ctext insert end "$line\n" filesep
4525        } else {
4526            # Something else we don't recognize
4527            if {$curdifftag != "Comments"} {
4528                $ctext insert end "\n"
4529                $ctext tag add $curdifftag $curtagstart end
4530                set curtagstart [$ctext index "end - 1c"]
4531                set curdifftag Comments
4532            }
4533            $ctext insert end "$line\n" filesep
4534        }
4535    }
4536    $ctext conf -state disabled
4537    if {[clock clicks -milliseconds] >= $nextupdate} {
4538        incr nextupdate 100
4539        fileevent $bdf readable {}
4540        update
4541        fileevent $bdf readable "getblobdiffline $bdf {$ids}"
4542    }
4543}
4544
4545proc changediffdisp {} {
4546    global ctext diffelide
4547
4548    $ctext tag conf d0 -elide [lindex $diffelide 0]
4549    $ctext tag conf d1 -elide [lindex $diffelide 1]
4550}
4551
4552proc prevfile {} {
4553    global difffilestart ctext
4554    set prev [lindex $difffilestart 0]
4555    set here [$ctext index @0,0]
4556    foreach loc $difffilestart {
4557        if {[$ctext compare $loc >= $here]} {
4558            $ctext yview $prev
4559            return
4560        }
4561        set prev $loc
4562    }
4563    $ctext yview $prev
4564}
4565
4566proc nextfile {} {
4567    global difffilestart ctext
4568    set here [$ctext index @0,0]
4569    foreach loc $difffilestart {
4570        if {[$ctext compare $loc > $here]} {
4571            $ctext yview $loc
4572            return
4573        }
4574    }
4575}
4576
4577proc clear_ctext {{first 1.0}} {
4578    global ctext smarktop smarkbot
4579
4580    set l [lindex [split $first .] 0]
4581    if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
4582        set smarktop $l
4583    }
4584    if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
4585        set smarkbot $l
4586    }
4587    $ctext delete $first end
4588}
4589
4590proc incrsearch {name ix op} {
4591    global ctext searchstring searchdirn
4592
4593    $ctext tag remove found 1.0 end
4594    if {[catch {$ctext index anchor}]} {
4595        # no anchor set, use start of selection, or of visible area
4596        set sel [$ctext tag ranges sel]
4597        if {$sel ne {}} {
4598            $ctext mark set anchor [lindex $sel 0]
4599        } elseif {$searchdirn eq "-forwards"} {
4600            $ctext mark set anchor @0,0
4601        } else {
4602            $ctext mark set anchor @0,[winfo height $ctext]
4603        }
4604    }
4605    if {$searchstring ne {}} {
4606        set here [$ctext search $searchdirn -- $searchstring anchor]
4607        if {$here ne {}} {
4608            $ctext see $here
4609        }
4610        searchmarkvisible 1
4611    }
4612}
4613
4614proc dosearch {} {
4615    global sstring ctext searchstring searchdirn
4616
4617    focus $sstring
4618    $sstring icursor end
4619    set searchdirn -forwards
4620    if {$searchstring ne {}} {
4621        set sel [$ctext tag ranges sel]
4622        if {$sel ne {}} {
4623            set start "[lindex $sel 0] + 1c"
4624        } elseif {[catch {set start [$ctext index anchor]}]} {
4625            set start "@0,0"
4626        }
4627        set match [$ctext search -count mlen -- $searchstring $start]
4628        $ctext tag remove sel 1.0 end
4629        if {$match eq {}} {
4630            bell
4631            return
4632        }
4633        $ctext see $match
4634        set mend "$match + $mlen c"
4635        $ctext tag add sel $match $mend
4636        $ctext mark unset anchor
4637    }
4638}
4639
4640proc dosearchback {} {
4641    global sstring ctext searchstring searchdirn
4642
4643    focus $sstring
4644    $sstring icursor end
4645    set searchdirn -backwards
4646    if {$searchstring ne {}} {
4647        set sel [$ctext tag ranges sel]
4648        if {$sel ne {}} {
4649            set start [lindex $sel 0]
4650        } elseif {[catch {set start [$ctext index anchor]}]} {
4651            set start @0,[winfo height $ctext]
4652        }
4653        set match [$ctext search -backwards -count ml -- $searchstring $start]
4654        $ctext tag remove sel 1.0 end
4655        if {$match eq {}} {
4656            bell
4657            return
4658        }
4659        $ctext see $match
4660        set mend "$match + $ml c"
4661        $ctext tag add sel $match $mend
4662        $ctext mark unset anchor
4663    }
4664}
4665
4666proc searchmark {first last} {
4667    global ctext searchstring
4668
4669    set mend $first.0
4670    while {1} {
4671        set match [$ctext search -count mlen -- $searchstring $mend $last.end]
4672        if {$match eq {}} break
4673        set mend "$match + $mlen c"
4674        $ctext tag add found $match $mend
4675    }
4676}
4677
4678proc searchmarkvisible {doall} {
4679    global ctext smarktop smarkbot
4680
4681    set topline [lindex [split [$ctext index @0,0] .] 0]
4682    set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
4683    if {$doall || $botline < $smarktop || $topline > $smarkbot} {
4684        # no overlap with previous
4685        searchmark $topline $botline
4686        set smarktop $topline
4687        set smarkbot $botline
4688    } else {
4689        if {$topline < $smarktop} {
4690            searchmark $topline [expr {$smarktop-1}]
4691            set smarktop $topline
4692        }
4693        if {$botline > $smarkbot} {
4694            searchmark [expr {$smarkbot+1}] $botline
4695            set smarkbot $botline
4696        }
4697    }
4698}
4699
4700proc scrolltext {f0 f1} {
4701    global searchstring
4702
4703    .bleft.sb set $f0 $f1
4704    if {$searchstring ne {}} {
4705        searchmarkvisible 0
4706    }
4707}
4708
4709proc setcoords {} {
4710    global linespc charspc canvx0 canvy0 mainfont
4711    global xspc1 xspc2 lthickness
4712
4713    set linespc [font metrics $mainfont -linespace]
4714    set charspc [font measure $mainfont "m"]
4715    set canvy0 [expr {int(3 + 0.5 * $linespc)}]
4716    set canvx0 [expr {int(3 + 0.5 * $linespc)}]
4717    set lthickness [expr {int($linespc / 9) + 1}]
4718    set xspc1(0) $linespc
4719    set xspc2 $linespc
4720}
4721
4722proc redisplay {} {
4723    global canv
4724    global selectedline
4725
4726    set ymax [lindex [$canv cget -scrollregion] 3]
4727    if {$ymax eq {} || $ymax == 0} return
4728    set span [$canv yview]
4729    clear_display
4730    setcanvscroll
4731    allcanvs yview moveto [lindex $span 0]
4732    drawvisible
4733    if {[info exists selectedline]} {
4734        selectline $selectedline 0
4735        allcanvs yview moveto [lindex $span 0]
4736    }
4737}
4738
4739proc incrfont {inc} {
4740    global mainfont textfont ctext canv phase cflist
4741    global charspc tabstop
4742    global stopped entries
4743    unmarkmatches
4744    set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
4745    set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
4746    setcoords
4747    $ctext conf -font $textfont -tabs "[expr {$tabstop * $charspc}]"
4748    $cflist conf -font $textfont
4749    $ctext tag conf filesep -font [concat $textfont bold]
4750    foreach e $entries {
4751        $e conf -font $mainfont
4752    }
4753    if {$phase eq "getcommits"} {
4754        $canv itemconf textitems -font $mainfont
4755    }
4756    redisplay
4757}
4758
4759proc clearsha1 {} {
4760    global sha1entry sha1string
4761    if {[string length $sha1string] == 40} {
4762        $sha1entry delete 0 end
4763    }
4764}
4765
4766proc sha1change {n1 n2 op} {
4767    global sha1string currentid sha1but
4768    if {$sha1string == {}
4769        || ([info exists currentid] && $sha1string == $currentid)} {
4770        set state disabled
4771    } else {
4772        set state normal
4773    }
4774    if {[$sha1but cget -state] == $state} return
4775    if {$state == "normal"} {
4776        $sha1but conf -state normal -relief raised -text "Goto: "
4777    } else {
4778        $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
4779    }
4780}
4781
4782proc gotocommit {} {
4783    global sha1string currentid commitrow tagids headids
4784    global displayorder numcommits curview
4785
4786    if {$sha1string == {}
4787        || ([info exists currentid] && $sha1string == $currentid)} return
4788    if {[info exists tagids($sha1string)]} {
4789        set id $tagids($sha1string)
4790    } elseif {[info exists headids($sha1string)]} {
4791        set id $headids($sha1string)
4792    } else {
4793        set id [string tolower $sha1string]
4794        if {[regexp {^[0-9a-f]{4,39}$} $id]} {
4795            set matches {}
4796            foreach i $displayorder {
4797                if {[string match $id* $i]} {
4798                    lappend matches $i
4799                }
4800            }
4801            if {$matches ne {}} {
4802                if {[llength $matches] > 1} {
4803                    error_popup "Short SHA1 id $id is ambiguous"
4804                    return
4805                }
4806                set id [lindex $matches 0]
4807            }
4808        }
4809    }
4810    if {[info exists commitrow($curview,$id)]} {
4811        selectline $commitrow($curview,$id) 1
4812        return
4813    }
4814    if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
4815        set type "SHA1 id"
4816    } else {
4817        set type "Tag/Head"
4818    }
4819    error_popup "$type $sha1string is not known"
4820}
4821
4822proc lineenter {x y id} {
4823    global hoverx hovery hoverid hovertimer
4824    global commitinfo canv
4825
4826    if {![info exists commitinfo($id)] && ![getcommit $id]} return
4827    set hoverx $x
4828    set hovery $y
4829    set hoverid $id
4830    if {[info exists hovertimer]} {
4831        after cancel $hovertimer
4832    }
4833    set hovertimer [after 500 linehover]
4834    $canv delete hover
4835}
4836
4837proc linemotion {x y id} {
4838    global hoverx hovery hoverid hovertimer
4839
4840    if {[info exists hoverid] && $id == $hoverid} {
4841        set hoverx $x
4842        set hovery $y
4843        if {[info exists hovertimer]} {
4844            after cancel $hovertimer
4845        }
4846        set hovertimer [after 500 linehover]
4847    }
4848}
4849
4850proc lineleave {id} {
4851    global hoverid hovertimer canv
4852
4853    if {[info exists hoverid] && $id == $hoverid} {
4854        $canv delete hover
4855        if {[info exists hovertimer]} {
4856            after cancel $hovertimer
4857            unset hovertimer
4858        }
4859        unset hoverid
4860    }
4861}
4862
4863proc linehover {} {
4864    global hoverx hovery hoverid hovertimer
4865    global canv linespc lthickness
4866    global commitinfo mainfont
4867
4868    set text [lindex $commitinfo($hoverid) 0]
4869    set ymax [lindex [$canv cget -scrollregion] 3]
4870    if {$ymax == {}} return
4871    set yfrac [lindex [$canv yview] 0]
4872    set x [expr {$hoverx + 2 * $linespc}]
4873    set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
4874    set x0 [expr {$x - 2 * $lthickness}]
4875    set y0 [expr {$y - 2 * $lthickness}]
4876    set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
4877    set y1 [expr {$y + $linespc + 2 * $lthickness}]
4878    set t [$canv create rectangle $x0 $y0 $x1 $y1 \
4879               -fill \#ffff80 -outline black -width 1 -tags hover]
4880    $canv raise $t
4881    set t [$canv create text $x $y -anchor nw -text $text -tags hover \
4882               -font $mainfont]
4883    $canv raise $t
4884}
4885
4886proc clickisonarrow {id y} {
4887    global lthickness
4888
4889    set ranges [rowranges $id]
4890    set thresh [expr {2 * $lthickness + 6}]
4891    set n [expr {[llength $ranges] - 1}]
4892    for {set i 1} {$i < $n} {incr i} {
4893        set row [lindex $ranges $i]
4894        if {abs([yc $row] - $y) < $thresh} {
4895            return $i
4896        }
4897    }
4898    return {}
4899}
4900
4901proc arrowjump {id n y} {
4902    global canv
4903
4904    # 1 <-> 2, 3 <-> 4, etc...
4905    set n [expr {(($n - 1) ^ 1) + 1}]
4906    set row [lindex [rowranges $id] $n]
4907    set yt [yc $row]
4908    set ymax [lindex [$canv cget -scrollregion] 3]
4909    if {$ymax eq {} || $ymax <= 0} return
4910    set view [$canv yview]
4911    set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
4912    set yfrac [expr {$yt / $ymax - $yspan / 2}]
4913    if {$yfrac < 0} {
4914        set yfrac 0
4915    }
4916    allcanvs yview moveto $yfrac
4917}
4918
4919proc lineclick {x y id isnew} {
4920    global ctext commitinfo children canv thickerline curview
4921
4922    if {![info exists commitinfo($id)] && ![getcommit $id]} return
4923    unmarkmatches
4924    unselectline
4925    normalline
4926    $canv delete hover
4927    # draw this line thicker than normal
4928    set thickerline $id
4929    drawlines $id
4930    if {$isnew} {
4931        set ymax [lindex [$canv cget -scrollregion] 3]
4932        if {$ymax eq {}} return
4933        set yfrac [lindex [$canv yview] 0]
4934        set y [expr {$y + $yfrac * $ymax}]
4935    }
4936    set dirn [clickisonarrow $id $y]
4937    if {$dirn ne {}} {
4938        arrowjump $id $dirn $y
4939        return
4940    }
4941
4942    if {$isnew} {
4943        addtohistory [list lineclick $x $y $id 0]
4944    }
4945    # fill the details pane with info about this line
4946    $ctext conf -state normal
4947    clear_ctext
4948    $ctext tag conf link -foreground blue -underline 1
4949    $ctext tag bind link <Enter> { %W configure -cursor hand2 }
4950    $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
4951    $ctext insert end "Parent:\t"
4952    $ctext insert end $id [list link link0]
4953    $ctext tag bind link0 <1> [list selbyid $id]
4954    set info $commitinfo($id)
4955    $ctext insert end "\n\t[lindex $info 0]\n"
4956    $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
4957    set date [formatdate [lindex $info 2]]
4958    $ctext insert end "\tDate:\t$date\n"
4959    set kids $children($curview,$id)
4960    if {$kids ne {}} {
4961        $ctext insert end "\nChildren:"
4962        set i 0
4963        foreach child $kids {
4964            incr i
4965            if {![info exists commitinfo($child)] && ![getcommit $child]} continue
4966            set info $commitinfo($child)
4967            $ctext insert end "\n\t"
4968            $ctext insert end $child [list link link$i]
4969            $ctext tag bind link$i <1> [list selbyid $child]
4970            $ctext insert end "\n\t[lindex $info 0]"
4971            $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
4972            set date [formatdate [lindex $info 2]]
4973            $ctext insert end "\n\tDate:\t$date\n"
4974        }
4975    }
4976    $ctext conf -state disabled
4977    init_flist {}
4978}
4979
4980proc normalline {} {
4981    global thickerline
4982    if {[info exists thickerline]} {
4983        set id $thickerline
4984        unset thickerline
4985        drawlines $id
4986    }
4987}
4988
4989proc selbyid {id} {
4990    global commitrow curview
4991    if {[info exists commitrow($curview,$id)]} {
4992        selectline $commitrow($curview,$id) 1
4993    }
4994}
4995
4996proc mstime {} {
4997    global startmstime
4998    if {![info exists startmstime]} {
4999        set startmstime [clock clicks -milliseconds]
5000    }
5001    return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
5002}
5003
5004proc rowmenu {x y id} {
5005    global rowctxmenu commitrow selectedline rowmenuid curview
5006
5007    if {![info exists selectedline]
5008        || $commitrow($curview,$id) eq $selectedline} {
5009        set state disabled
5010    } else {
5011        set state normal
5012    }
5013    $rowctxmenu entryconfigure "Diff this*" -state $state
5014    $rowctxmenu entryconfigure "Diff selected*" -state $state
5015    $rowctxmenu entryconfigure "Make patch" -state $state
5016    set rowmenuid $id
5017    tk_popup $rowctxmenu $x $y
5018}
5019
5020proc diffvssel {dirn} {
5021    global rowmenuid selectedline displayorder
5022
5023    if {![info exists selectedline]} return
5024    if {$dirn} {
5025        set oldid [lindex $displayorder $selectedline]
5026        set newid $rowmenuid
5027    } else {
5028        set oldid $rowmenuid
5029        set newid [lindex $displayorder $selectedline]
5030    }
5031    addtohistory [list doseldiff $oldid $newid]
5032    doseldiff $oldid $newid
5033}
5034
5035proc doseldiff {oldid newid} {
5036    global ctext
5037    global commitinfo
5038
5039    $ctext conf -state normal
5040    clear_ctext
5041    init_flist "Top"
5042    $ctext insert end "From "
5043    $ctext tag conf link -foreground blue -underline 1
5044    $ctext tag bind link <Enter> { %W configure -cursor hand2 }
5045    $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
5046    $ctext tag bind link0 <1> [list selbyid $oldid]
5047    $ctext insert end $oldid [list link link0]
5048    $ctext insert end "\n     "
5049    $ctext insert end [lindex $commitinfo($oldid) 0]
5050    $ctext insert end "\n\nTo   "
5051    $ctext tag bind link1 <1> [list selbyid $newid]
5052    $ctext insert end $newid [list link link1]
5053    $ctext insert end "\n     "
5054    $ctext insert end [lindex $commitinfo($newid) 0]
5055    $ctext insert end "\n"
5056    $ctext conf -state disabled
5057    $ctext tag delete Comments
5058    $ctext tag remove found 1.0 end
5059    startdiff [list $oldid $newid]
5060}
5061
5062proc mkpatch {} {
5063    global rowmenuid currentid commitinfo patchtop patchnum
5064
5065    if {![info exists currentid]} return
5066    set oldid $currentid
5067    set oldhead [lindex $commitinfo($oldid) 0]
5068    set newid $rowmenuid
5069    set newhead [lindex $commitinfo($newid) 0]
5070    set top .patch
5071    set patchtop $top
5072    catch {destroy $top}
5073    toplevel $top
5074    label $top.title -text "Generate patch"
5075    grid $top.title - -pady 10
5076    label $top.from -text "From:"
5077    entry $top.fromsha1 -width 40 -relief flat
5078    $top.fromsha1 insert 0 $oldid
5079    $top.fromsha1 conf -state readonly
5080    grid $top.from $top.fromsha1 -sticky w
5081    entry $top.fromhead -width 60 -relief flat
5082    $top.fromhead insert 0 $oldhead
5083    $top.fromhead conf -state readonly
5084    grid x $top.fromhead -sticky w
5085    label $top.to -text "To:"
5086    entry $top.tosha1 -width 40 -relief flat
5087    $top.tosha1 insert 0 $newid
5088    $top.tosha1 conf -state readonly
5089    grid $top.to $top.tosha1 -sticky w
5090    entry $top.tohead -width 60 -relief flat
5091    $top.tohead insert 0 $newhead
5092    $top.tohead conf -state readonly
5093    grid x $top.tohead -sticky w
5094    button $top.rev -text "Reverse" -command mkpatchrev -padx 5
5095    grid $top.rev x -pady 10
5096    label $top.flab -text "Output file:"
5097    entry $top.fname -width 60
5098    $top.fname insert 0 [file normalize "patch$patchnum.patch"]
5099    incr patchnum
5100    grid $top.flab $top.fname -sticky w
5101    frame $top.buts
5102    button $top.buts.gen -text "Generate" -command mkpatchgo
5103    button $top.buts.can -text "Cancel" -command mkpatchcan
5104    grid $top.buts.gen $top.buts.can
5105    grid columnconfigure $top.buts 0 -weight 1 -uniform a
5106    grid columnconfigure $top.buts 1 -weight 1 -uniform a
5107    grid $top.buts - -pady 10 -sticky ew
5108    focus $top.fname
5109}
5110
5111proc mkpatchrev {} {
5112    global patchtop
5113
5114    set oldid [$patchtop.fromsha1 get]
5115    set oldhead [$patchtop.fromhead get]
5116    set newid [$patchtop.tosha1 get]
5117    set newhead [$patchtop.tohead get]
5118    foreach e [list fromsha1 fromhead tosha1 tohead] \
5119            v [list $newid $newhead $oldid $oldhead] {
5120        $patchtop.$e conf -state normal
5121        $patchtop.$e delete 0 end
5122        $patchtop.$e insert 0 $v
5123        $patchtop.$e conf -state readonly
5124    }
5125}
5126
5127proc mkpatchgo {} {
5128    global patchtop
5129
5130    set oldid [$patchtop.fromsha1 get]
5131    set newid [$patchtop.tosha1 get]
5132    set fname [$patchtop.fname get]
5133    if {[catch {exec git diff-tree -p $oldid $newid >$fname &} err]} {
5134        error_popup "Error creating patch: $err"
5135    }
5136    catch {destroy $patchtop}
5137    unset patchtop
5138}
5139
5140proc mkpatchcan {} {
5141    global patchtop
5142
5143    catch {destroy $patchtop}
5144    unset patchtop
5145}
5146
5147proc mktag {} {
5148    global rowmenuid mktagtop commitinfo
5149
5150    set top .maketag
5151    set mktagtop $top
5152    catch {destroy $top}
5153    toplevel $top
5154    label $top.title -text "Create tag"
5155    grid $top.title - -pady 10
5156    label $top.id -text "ID:"
5157    entry $top.sha1 -width 40 -relief flat
5158    $top.sha1 insert 0 $rowmenuid
5159    $top.sha1 conf -state readonly
5160    grid $top.id $top.sha1 -sticky w
5161    entry $top.head -width 60 -relief flat
5162    $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
5163    $top.head conf -state readonly
5164    grid x $top.head -sticky w
5165    label $top.tlab -text "Tag name:"
5166    entry $top.tag -width 60
5167    grid $top.tlab $top.tag -sticky w
5168    frame $top.buts
5169    button $top.buts.gen -text "Create" -command mktaggo
5170    button $top.buts.can -text "Cancel" -command mktagcan
5171    grid $top.buts.gen $top.buts.can
5172    grid columnconfigure $top.buts 0 -weight 1 -uniform a
5173    grid columnconfigure $top.buts 1 -weight 1 -uniform a
5174    grid $top.buts - -pady 10 -sticky ew
5175    focus $top.tag
5176}
5177
5178proc domktag {} {
5179    global mktagtop env tagids idtags
5180
5181    set id [$mktagtop.sha1 get]
5182    set tag [$mktagtop.tag get]
5183    if {$tag == {}} {
5184        error_popup "No tag name specified"
5185        return
5186    }
5187    if {[info exists tagids($tag)]} {
5188        error_popup "Tag \"$tag\" already exists"
5189        return
5190    }
5191    if {[catch {
5192        set dir [gitdir]
5193        set fname [file join $dir "refs/tags" $tag]
5194        set f [open $fname w]
5195        puts $f $id
5196        close $f
5197    } err]} {
5198        error_popup "Error creating tag: $err"
5199        return
5200    }
5201
5202    set tagids($tag) $id
5203    lappend idtags($id) $tag
5204    redrawtags $id
5205    addedtag $id
5206}
5207
5208proc redrawtags {id} {
5209    global canv linehtag commitrow idpos selectedline curview
5210    global mainfont canvxmax
5211
5212    if {![info exists commitrow($curview,$id)]} return
5213    drawcmitrow $commitrow($curview,$id)
5214    $canv delete tag.$id
5215    set xt [eval drawtags $id $idpos($id)]
5216    $canv coords $linehtag($commitrow($curview,$id)) $xt [lindex $idpos($id) 2]
5217    set text [$canv itemcget $linehtag($commitrow($curview,$id)) -text]
5218    set xr [expr {$xt + [font measure $mainfont $text]}]
5219    if {$xr > $canvxmax} {
5220        set canvxmax $xr
5221        setcanvscroll
5222    }
5223    if {[info exists selectedline]
5224        && $selectedline == $commitrow($curview,$id)} {
5225        selectline $selectedline 0
5226    }
5227}
5228
5229proc mktagcan {} {
5230    global mktagtop
5231
5232    catch {destroy $mktagtop}
5233    unset mktagtop
5234}
5235
5236proc mktaggo {} {
5237    domktag
5238    mktagcan
5239}
5240
5241proc writecommit {} {
5242    global rowmenuid wrcomtop commitinfo wrcomcmd
5243
5244    set top .writecommit
5245    set wrcomtop $top
5246    catch {destroy $top}
5247    toplevel $top
5248    label $top.title -text "Write commit to file"
5249    grid $top.title - -pady 10
5250    label $top.id -text "ID:"
5251    entry $top.sha1 -width 40 -relief flat
5252    $top.sha1 insert 0 $rowmenuid
5253    $top.sha1 conf -state readonly
5254    grid $top.id $top.sha1 -sticky w
5255    entry $top.head -width 60 -relief flat
5256    $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
5257    $top.head conf -state readonly
5258    grid x $top.head -sticky w
5259    label $top.clab -text "Command:"
5260    entry $top.cmd -width 60 -textvariable wrcomcmd
5261    grid $top.clab $top.cmd -sticky w -pady 10
5262    label $top.flab -text "Output file:"
5263    entry $top.fname -width 60
5264    $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
5265    grid $top.flab $top.fname -sticky w
5266    frame $top.buts
5267    button $top.buts.gen -text "Write" -command wrcomgo
5268    button $top.buts.can -text "Cancel" -command wrcomcan
5269    grid $top.buts.gen $top.buts.can
5270    grid columnconfigure $top.buts 0 -weight 1 -uniform a
5271    grid columnconfigure $top.buts 1 -weight 1 -uniform a
5272    grid $top.buts - -pady 10 -sticky ew
5273    focus $top.fname
5274}
5275
5276proc wrcomgo {} {
5277    global wrcomtop
5278
5279    set id [$wrcomtop.sha1 get]
5280    set cmd "echo $id | [$wrcomtop.cmd get]"
5281    set fname [$wrcomtop.fname get]
5282    if {[catch {exec sh -c $cmd >$fname &} err]} {
5283        error_popup "Error writing commit: $err"
5284    }
5285    catch {destroy $wrcomtop}
5286    unset wrcomtop
5287}
5288
5289proc wrcomcan {} {
5290    global wrcomtop
5291
5292    catch {destroy $wrcomtop}
5293    unset wrcomtop
5294}
5295
5296proc mkbranch {} {
5297    global rowmenuid mkbrtop
5298
5299    set top .makebranch
5300    catch {destroy $top}
5301    toplevel $top
5302    label $top.title -text "Create new branch"
5303    grid $top.title - -pady 10
5304    label $top.id -text "ID:"
5305    entry $top.sha1 -width 40 -relief flat
5306    $top.sha1 insert 0 $rowmenuid
5307    $top.sha1 conf -state readonly
5308    grid $top.id $top.sha1 -sticky w
5309    label $top.nlab -text "Name:"
5310    entry $top.name -width 40
5311    grid $top.nlab $top.name -sticky w
5312    frame $top.buts
5313    button $top.buts.go -text "Create" -command [list mkbrgo $top]
5314    button $top.buts.can -text "Cancel" -command "catch {destroy $top}"
5315    grid $top.buts.go $top.buts.can
5316    grid columnconfigure $top.buts 0 -weight 1 -uniform a
5317    grid columnconfigure $top.buts 1 -weight 1 -uniform a
5318    grid $top.buts - -pady 10 -sticky ew
5319    focus $top.name
5320}
5321
5322proc mkbrgo {top} {
5323    global headids idheads
5324
5325    set name [$top.name get]
5326    set id [$top.sha1 get]
5327    if {$name eq {}} {
5328        error_popup "Please specify a name for the new branch"
5329        return
5330    }
5331    catch {destroy $top}
5332    nowbusy newbranch
5333    update
5334    if {[catch {
5335        exec git branch $name $id
5336    } err]} {
5337        notbusy newbranch
5338        error_popup $err
5339    } else {
5340        set headids($name) $id
5341        lappend idheads($id) $name
5342        addedhead $id $name
5343        notbusy newbranch
5344        redrawtags $id
5345        dispneartags 0
5346    }
5347}
5348
5349proc cherrypick {} {
5350    global rowmenuid curview commitrow
5351    global mainhead
5352
5353    set oldhead [exec git rev-parse HEAD]
5354    set dheads [descheads $rowmenuid]
5355    if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
5356        set ok [confirm_popup "Commit [string range $rowmenuid 0 7] is already\
5357                        included in branch $mainhead -- really re-apply it?"]
5358        if {!$ok} return
5359    }
5360    nowbusy cherrypick
5361    update
5362    # Unfortunately git-cherry-pick writes stuff to stderr even when
5363    # no error occurs, and exec takes that as an indication of error...
5364    if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
5365        notbusy cherrypick
5366        error_popup $err
5367        return
5368    }
5369    set newhead [exec git rev-parse HEAD]
5370    if {$newhead eq $oldhead} {
5371        notbusy cherrypick
5372        error_popup "No changes committed"
5373        return
5374    }
5375    addnewchild $newhead $oldhead
5376    if {[info exists commitrow($curview,$oldhead)]} {
5377        insertrow $commitrow($curview,$oldhead) $newhead
5378        if {$mainhead ne {}} {
5379            movehead $newhead $mainhead
5380            movedhead $newhead $mainhead
5381        }
5382        redrawtags $oldhead
5383        redrawtags $newhead
5384    }
5385    notbusy cherrypick
5386}
5387
5388# context menu for a head
5389proc headmenu {x y id head} {
5390    global headmenuid headmenuhead headctxmenu
5391
5392    set headmenuid $id
5393    set headmenuhead $head
5394    tk_popup $headctxmenu $x $y
5395}
5396
5397proc cobranch {} {
5398    global headmenuid headmenuhead mainhead headids
5399
5400    # check the tree is clean first??
5401    set oldmainhead $mainhead
5402    nowbusy checkout
5403    update
5404    if {[catch {
5405        exec git checkout -q $headmenuhead
5406    } err]} {
5407        notbusy checkout
5408        error_popup $err
5409    } else {
5410        notbusy checkout
5411        set mainhead $headmenuhead
5412        if {[info exists headids($oldmainhead)]} {
5413            redrawtags $headids($oldmainhead)
5414        }
5415        redrawtags $headmenuid
5416    }
5417}
5418
5419proc rmbranch {} {
5420    global headmenuid headmenuhead mainhead
5421    global headids idheads
5422
5423    set head $headmenuhead
5424    set id $headmenuid
5425    if {$head eq $mainhead} {
5426        error_popup "Cannot delete the currently checked-out branch"
5427        return
5428    }
5429    set dheads [descheads $id]
5430    if {$dheads eq $headids($head)} {
5431        # the stuff on this branch isn't on any other branch
5432        if {![confirm_popup "The commits on branch $head aren't on any other\
5433                        branch.\nReally delete branch $head?"]} return
5434    }
5435    nowbusy rmbranch
5436    update
5437    if {[catch {exec git branch -D $head} err]} {
5438        notbusy rmbranch
5439        error_popup $err
5440        return
5441    }
5442    removehead $id $head
5443    removedhead $id $head
5444    redrawtags $id
5445    notbusy rmbranch
5446    dispneartags 0
5447}
5448
5449# Stuff for finding nearby tags
5450proc getallcommits {} {
5451    global allcommits allids nbmp nextarc seeds
5452
5453    set allids {}
5454    set nbmp 0
5455    set nextarc 0
5456    set allcommits 0
5457    set seeds {}
5458    regetallcommits
5459}
5460
5461# Called when the graph might have changed
5462proc regetallcommits {} {
5463    global allcommits seeds
5464
5465    set cmd [concat | git rev-list --all --parents]
5466    foreach id $seeds {
5467        lappend cmd "^$id"
5468    }
5469    set fd [open $cmd r]
5470    fconfigure $fd -blocking 0
5471    incr allcommits
5472    nowbusy allcommits
5473    restartgetall $fd
5474}
5475
5476proc restartgetall {fd} {
5477    fileevent $fd readable [list getallclines $fd]
5478}
5479
5480# Since most commits have 1 parent and 1 child, we group strings of
5481# such commits into "arcs" joining branch/merge points (BMPs), which
5482# are commits that either don't have 1 parent or don't have 1 child.
5483#
5484# arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
5485# arcout(id) - outgoing arcs for BMP
5486# arcids(a) - list of IDs on arc including end but not start
5487# arcstart(a) - BMP ID at start of arc
5488# arcend(a) - BMP ID at end of arc
5489# growing(a) - arc a is still growing
5490# arctags(a) - IDs out of arcids (excluding end) that have tags
5491# archeads(a) - IDs out of arcids (excluding end) that have heads
5492# The start of an arc is at the descendent end, so "incoming" means
5493# coming from descendents, and "outgoing" means going towards ancestors.
5494
5495proc getallclines {fd} {
5496    global allids allparents allchildren idtags nextarc nbmp
5497    global arcnos arcids arctags arcout arcend arcstart archeads growing
5498    global seeds allcommits allcstart
5499
5500    if {![info exists allcstart]} {
5501        set allcstart [clock clicks -milliseconds]
5502    }
5503    set nid 0
5504    while {[gets $fd line] >= 0} {
5505        set id [lindex $line 0]
5506        if {[info exists allparents($id)]} {
5507            # seen it already
5508            continue
5509        }
5510        lappend allids $id
5511        set olds [lrange $line 1 end]
5512        set allparents($id) $olds
5513        if {![info exists allchildren($id)]} {
5514            set allchildren($id) {}
5515            set arcnos($id) {}
5516            lappend seeds $id
5517        } else {
5518            set a $arcnos($id)
5519            if {[llength $olds] == 1 && [llength $a] == 1} {
5520                lappend arcids($a) $id
5521                if {[info exists idtags($id)]} {
5522                    lappend arctags($a) $id
5523                }
5524                if {[info exists idheads($id)]} {
5525                    lappend archeads($a) $id
5526                }
5527                if {[info exists allparents($olds)]} {
5528                    # seen parent already
5529                    if {![info exists arcout($olds)]} {
5530                        splitarc $olds
5531                    }
5532                    lappend arcids($a) $olds
5533                    set arcend($a) $olds
5534                    unset growing($a)
5535                }
5536                lappend allchildren($olds) $id
5537                lappend arcnos($olds) $a
5538                continue
5539            }
5540        }
5541        incr nbmp
5542        foreach a $arcnos($id) {
5543            lappend arcids($a) $id
5544            set arcend($a) $id
5545            unset growing($a)
5546        }
5547
5548        set ao {}
5549        foreach p $olds {
5550            lappend allchildren($p) $id
5551            set a [incr nextarc]
5552            set arcstart($a) $id
5553            set archeads($a) {}
5554            set arctags($a) {}
5555            set archeads($a) {}
5556            set arcids($a) {}
5557            lappend ao $a
5558            set growing($a) 1
5559            if {[info exists allparents($p)]} {
5560                # seen it already, may need to make a new branch
5561                if {![info exists arcout($p)]} {
5562                    splitarc $p
5563                }
5564                lappend arcids($a) $p
5565                set arcend($a) $p
5566                unset growing($a)
5567            }
5568            lappend arcnos($p) $a
5569        }
5570        set arcout($id) $ao
5571        if {[incr nid] >= 50} {
5572            set nid 0
5573            if {[clock clicks -milliseconds] - $allcstart >= 50} {
5574                fileevent $fd readable {}
5575                after idle restartgetall $fd
5576                unset allcstart
5577                return
5578            }
5579        }
5580    }
5581    if {![eof $fd]} return
5582    close $fd
5583    if {[incr allcommits -1] == 0} {
5584        notbusy allcommits
5585    }
5586    dispneartags 0
5587}
5588
5589proc recalcarc {a} {
5590    global arctags archeads arcids idtags idheads
5591
5592    set at {}
5593    set ah {}
5594    foreach id [lrange $arcids($a) 0 end-1] {
5595        if {[info exists idtags($id)]} {
5596            lappend at $id
5597        }
5598        if {[info exists idheads($id)]} {
5599            lappend ah $id
5600        }
5601    }
5602    set arctags($a) $at
5603    set archeads($a) $ah
5604}
5605
5606proc splitarc {p} {
5607    global arcnos arcids nextarc nbmp arctags archeads idtags idheads
5608    global arcstart arcend arcout allparents growing
5609
5610    set a $arcnos($p)
5611    if {[llength $a] != 1} {
5612        puts "oops splitarc called but [llength $a] arcs already"
5613        return
5614    }
5615    set a [lindex $a 0]
5616    set i [lsearch -exact $arcids($a) $p]
5617    if {$i < 0} {
5618        puts "oops splitarc $p not in arc $a"
5619        return
5620    }
5621    set na [incr nextarc]
5622    if {[info exists arcend($a)]} {
5623        set arcend($na) $arcend($a)
5624    } else {
5625        set l [lindex $allparents([lindex $arcids($a) end]) 0]
5626        set j [lsearch -exact $arcnos($l) $a]
5627        set arcnos($l) [lreplace $arcnos($l) $j $j $na]
5628    }
5629    set tail [lrange $arcids($a) [expr {$i+1}] end]
5630    set arcids($a) [lrange $arcids($a) 0 $i]
5631    set arcend($a) $p
5632    set arcstart($na) $p
5633    set arcout($p) $na
5634    set arcids($na) $tail
5635    if {[info exists growing($a)]} {
5636        set growing($na) 1
5637        unset growing($a)
5638    }
5639    incr nbmp
5640
5641    foreach id $tail {
5642        if {[llength $arcnos($id)] == 1} {
5643            set arcnos($id) $na
5644        } else {
5645            set j [lsearch -exact $arcnos($id) $a]
5646            set arcnos($id) [lreplace $arcnos($id) $j $j $na]
5647        }
5648    }
5649
5650    # reconstruct tags and heads lists
5651    if {$arctags($a) ne {} || $archeads($a) ne {}} {
5652        recalcarc $a
5653        recalcarc $na
5654    } else {
5655        set arctags($na) {}
5656        set archeads($na) {}
5657    }
5658}
5659
5660# Update things for a new commit added that is a child of one
5661# existing commit.  Used when cherry-picking.
5662proc addnewchild {id p} {
5663    global allids allparents allchildren idtags nextarc nbmp
5664    global arcnos arcids arctags arcout arcend arcstart archeads growing
5665    global seeds
5666
5667    lappend allids $id
5668    set allparents($id) [list $p]
5669    set allchildren($id) {}
5670    set arcnos($id) {}
5671    lappend seeds $id
5672    incr nbmp
5673    lappend allchildren($p) $id
5674    set a [incr nextarc]
5675    set arcstart($a) $id
5676    set archeads($a) {}
5677    set arctags($a) {}
5678    set arcids($a) [list $p]
5679    set arcend($a) $p
5680    if {![info exists arcout($p)]} {
5681        splitarc $p
5682    }
5683    lappend arcnos($p) $a
5684    set arcout($id) [list $a]
5685}
5686
5687# Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
5688# or 0 if neither is true.
5689proc anc_or_desc {a b} {
5690    global arcout arcstart arcend arcnos cached_isanc
5691
5692    if {$arcnos($a) eq $arcnos($b)} {
5693        # Both are on the same arc(s); either both are the same BMP,
5694        # or if one is not a BMP, the other is also not a BMP or is
5695        # the BMP at end of the arc (and it only has 1 incoming arc).
5696        if {$a eq $b} {
5697            return 0
5698        }
5699        # assert {[llength $arcnos($a)] == 1}
5700        set arc [lindex $arcnos($a) 0]
5701        set i [lsearch -exact $arcids($arc) $a]
5702        set j [lsearch -exact $arcids($arc) $b]
5703        if {$i < 0 || $i > $j} {
5704            return 1
5705        } else {
5706            return -1
5707        }
5708    }
5709
5710    if {![info exists arcout($a)]} {
5711        set arc [lindex $arcnos($a) 0]
5712        if {[info exists arcend($arc)]} {
5713            set aend $arcend($arc)
5714        } else {
5715            set aend {}
5716        }
5717        set a $arcstart($arc)
5718    } else {
5719        set aend $a
5720    }
5721    if {![info exists arcout($b)]} {
5722        set arc [lindex $arcnos($b) 0]
5723        if {[info exists arcend($arc)]} {
5724            set bend $arcend($arc)
5725        } else {
5726            set bend {}
5727        }
5728        set b $arcstart($arc)
5729    } else {
5730        set bend $b
5731    }
5732    if {$a eq $bend} {
5733        return 1
5734    }
5735    if {$b eq $aend} {
5736        return -1
5737    }
5738    if {[info exists cached_isanc($a,$bend)]} {
5739        if {$cached_isanc($a,$bend)} {
5740            return 1
5741        }
5742    }
5743    if {[info exists cached_isanc($b,$aend)]} {
5744        if {$cached_isanc($b,$aend)} {
5745            return -1
5746        }
5747        if {[info exists cached_isanc($a,$bend)]} {
5748            return 0
5749        }
5750    }
5751
5752    set todo [list $a $b]
5753    set anc($a) a
5754    set anc($b) b
5755    for {set i 0} {$i < [llength $todo]} {incr i} {
5756        set x [lindex $todo $i]
5757        if {$anc($x) eq {}} {
5758            continue
5759        }
5760        foreach arc $arcnos($x) {
5761            set xd $arcstart($arc)
5762            if {$xd eq $bend} {
5763                set cached_isanc($a,$bend) 1
5764                set cached_isanc($b,$aend) 0
5765                return 1
5766            } elseif {$xd eq $aend} {
5767                set cached_isanc($b,$aend) 1
5768                set cached_isanc($a,$bend) 0
5769                return -1
5770            }
5771            if {![info exists anc($xd)]} {
5772                set anc($xd) $anc($x)
5773                lappend todo $xd
5774            } elseif {$anc($xd) ne $anc($x)} {
5775                set anc($xd) {}
5776            }
5777        }
5778    }
5779    set cached_isanc($a,$bend) 0
5780    set cached_isanc($b,$aend) 0
5781    return 0
5782}
5783
5784# This identifies whether $desc has an ancestor that is
5785# a growing tip of the graph and which is not an ancestor of $anc
5786# and returns 0 if so and 1 if not.
5787# If we subsequently discover a tag on such a growing tip, and that
5788# turns out to be a descendent of $anc (which it could, since we
5789# don't necessarily see children before parents), then $desc
5790# isn't a good choice to display as a descendent tag of
5791# $anc (since it is the descendent of another tag which is
5792# a descendent of $anc).  Similarly, $anc isn't a good choice to
5793# display as a ancestor tag of $desc.
5794#
5795proc is_certain {desc anc} {
5796    global arcnos arcout arcstart arcend growing problems
5797
5798    set certain {}
5799    if {[llength $arcnos($anc)] == 1} {
5800        # tags on the same arc are certain
5801        if {$arcnos($desc) eq $arcnos($anc)} {
5802            return 1
5803        }
5804        if {![info exists arcout($anc)]} {
5805            # if $anc is partway along an arc, use the start of the arc instead
5806            set a [lindex $arcnos($anc) 0]
5807            set anc $arcstart($a)
5808        }
5809    }
5810    if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
5811        set x $desc
5812    } else {
5813        set a [lindex $arcnos($desc) 0]
5814        set x $arcend($a)
5815    }
5816    if {$x == $anc} {
5817        return 1
5818    }
5819    set anclist [list $x]
5820    set dl($x) 1
5821    set nnh 1
5822    set ngrowanc 0
5823    for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
5824        set x [lindex $anclist $i]
5825        if {$dl($x)} {
5826            incr nnh -1
5827        }
5828        set done($x) 1
5829        foreach a $arcout($x) {
5830            if {[info exists growing($a)]} {
5831                if {![info exists growanc($x)] && $dl($x)} {
5832                    set growanc($x) 1
5833                    incr ngrowanc
5834                }
5835            } else {
5836                set y $arcend($a)
5837                if {[info exists dl($y)]} {
5838                    if {$dl($y)} {
5839                        if {!$dl($x)} {
5840                            set dl($y) 0
5841                            if {![info exists done($y)]} {
5842                                incr nnh -1
5843                            }
5844                            if {[info exists growanc($x)]} {
5845                                incr ngrowanc -1
5846                            }
5847                            set xl [list $y]
5848                            for {set k 0} {$k < [llength $xl]} {incr k} {
5849                                set z [lindex $xl $k]
5850                                foreach c $arcout($z) {
5851                                    if {[info exists arcend($c)]} {
5852                                        set v $arcend($c)
5853                                        if {[info exists dl($v)] && $dl($v)} {
5854                                            set dl($v) 0
5855                                            if {![info exists done($v)]} {
5856                                                incr nnh -1
5857                                            }
5858                                            if {[info exists growanc($v)]} {
5859                                                incr ngrowanc -1
5860                                            }
5861                                            lappend xl $v
5862                                        }
5863                                    }
5864                                }
5865                            }
5866                        }
5867                    }
5868                } elseif {$y eq $anc || !$dl($x)} {
5869                    set dl($y) 0
5870                    lappend anclist $y
5871                } else {
5872                    set dl($y) 1
5873                    lappend anclist $y
5874                    incr nnh
5875                }
5876            }
5877        }
5878    }
5879    foreach x [array names growanc] {
5880        if {$dl($x)} {
5881            return 0
5882        }
5883    }
5884    return 1
5885}
5886
5887proc validate_arctags {a} {
5888    global arctags idtags
5889
5890    set i -1
5891    set na $arctags($a)
5892    foreach id $arctags($a) {
5893        incr i
5894        if {![info exists idtags($id)]} {
5895            set na [lreplace $na $i $i]
5896            incr i -1
5897        }
5898    }
5899    set arctags($a) $na
5900}
5901
5902proc validate_archeads {a} {
5903    global archeads idheads
5904
5905    set i -1
5906    set na $archeads($a)
5907    foreach id $archeads($a) {
5908        incr i
5909        if {![info exists idheads($id)]} {
5910            set na [lreplace $na $i $i]
5911            incr i -1
5912        }
5913    }
5914    set archeads($a) $na
5915}
5916
5917# Return the list of IDs that have tags that are descendents of id,
5918# ignoring IDs that are descendents of IDs already reported.
5919proc desctags {id} {
5920    global arcnos arcstart arcids arctags idtags allparents
5921    global growing cached_dtags
5922
5923    if {![info exists allparents($id)]} {
5924        return {}
5925    }
5926    set t1 [clock clicks -milliseconds]
5927    set argid $id
5928    if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
5929        # part-way along an arc; check that arc first
5930        set a [lindex $arcnos($id) 0]
5931        if {$arctags($a) ne {}} {
5932            validate_arctags $a
5933            set i [lsearch -exact $arcids($a) $id]
5934            set tid {}
5935            foreach t $arctags($a) {
5936                set j [lsearch -exact $arcids($a) $t]
5937                if {$j >= $i} break
5938                set tid $t
5939            }
5940            if {$tid ne {}} {
5941                return $tid
5942            }
5943        }
5944        set id $arcstart($a)
5945        if {[info exists idtags($id)]} {
5946            return $id
5947        }
5948    }
5949    if {[info exists cached_dtags($id)]} {
5950        return $cached_dtags($id)
5951    }
5952
5953    set origid $id
5954    set todo [list $id]
5955    set queued($id) 1
5956    set nc 1
5957    for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
5958        set id [lindex $todo $i]
5959        set done($id) 1
5960        set ta [info exists hastaggedancestor($id)]
5961        if {!$ta} {
5962            incr nc -1
5963        }
5964        # ignore tags on starting node
5965        if {!$ta && $i > 0} {
5966            if {[info exists idtags($id)]} {
5967                set tagloc($id) $id
5968                set ta 1
5969            } elseif {[info exists cached_dtags($id)]} {
5970                set tagloc($id) $cached_dtags($id)
5971                set ta 1
5972            }
5973        }
5974        foreach a $arcnos($id) {
5975            set d $arcstart($a)
5976            if {!$ta && $arctags($a) ne {}} {
5977                validate_arctags $a
5978                if {$arctags($a) ne {}} {
5979                    lappend tagloc($id) [lindex $arctags($a) end]
5980                }
5981            }
5982            if {$ta || $arctags($a) ne {}} {
5983                set tomark [list $d]
5984                for {set j 0} {$j < [llength $tomark]} {incr j} {
5985                    set dd [lindex $tomark $j]
5986                    if {![info exists hastaggedancestor($dd)]} {
5987                        if {[info exists done($dd)]} {
5988                            foreach b $arcnos($dd) {
5989                                lappend tomark $arcstart($b)
5990                            }
5991                            if {[info exists tagloc($dd)]} {
5992                                unset tagloc($dd)
5993                            }
5994                        } elseif {[info exists queued($dd)]} {
5995                            incr nc -1
5996                        }
5997                        set hastaggedancestor($dd) 1
5998                    }
5999                }
6000            }
6001            if {![info exists queued($d)]} {
6002                lappend todo $d
6003                set queued($d) 1
6004                if {![info exists hastaggedancestor($d)]} {
6005                    incr nc
6006                }
6007            }
6008        }
6009    }
6010    set tags {}
6011    foreach id [array names tagloc] {
6012        if {![info exists hastaggedancestor($id)]} {
6013            foreach t $tagloc($id) {
6014                if {[lsearch -exact $tags $t] < 0} {
6015                    lappend tags $t
6016                }
6017            }
6018        }
6019    }
6020    set t2 [clock clicks -milliseconds]
6021    set loopix $i
6022
6023    # remove tags that are descendents of other tags
6024    for {set i 0} {$i < [llength $tags]} {incr i} {
6025        set a [lindex $tags $i]
6026        for {set j 0} {$j < $i} {incr j} {
6027            set b [lindex $tags $j]
6028            set r [anc_or_desc $a $b]
6029            if {$r == 1} {
6030                set tags [lreplace $tags $j $j]
6031                incr j -1
6032                incr i -1
6033            } elseif {$r == -1} {
6034                set tags [lreplace $tags $i $i]
6035                incr i -1
6036                break
6037            }
6038        }
6039    }
6040
6041    if {[array names growing] ne {}} {
6042        # graph isn't finished, need to check if any tag could get
6043        # eclipsed by another tag coming later.  Simply ignore any
6044        # tags that could later get eclipsed.
6045        set ctags {}
6046        foreach t $tags {
6047            if {[is_certain $t $origid]} {
6048                lappend ctags $t
6049            }
6050        }
6051        if {$tags eq $ctags} {
6052            set cached_dtags($origid) $tags
6053        } else {
6054            set tags $ctags
6055        }
6056    } else {
6057        set cached_dtags($origid) $tags
6058    }
6059    set t3 [clock clicks -milliseconds]
6060    if {0 && $t3 - $t1 >= 100} {
6061        puts "iterating descendents ($loopix/[llength $todo] nodes) took\
6062            [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
6063    }
6064    return $tags
6065}
6066
6067proc anctags {id} {
6068    global arcnos arcids arcout arcend arctags idtags allparents
6069    global growing cached_atags
6070
6071    if {![info exists allparents($id)]} {
6072        return {}
6073    }
6074    set t1 [clock clicks -milliseconds]
6075    set argid $id
6076    if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
6077        # part-way along an arc; check that arc first
6078        set a [lindex $arcnos($id) 0]
6079        if {$arctags($a) ne {}} {
6080            validate_arctags $a
6081            set i [lsearch -exact $arcids($a) $id]
6082            foreach t $arctags($a) {
6083                set j [lsearch -exact $arcids($a) $t]
6084                if {$j > $i} {
6085                    return $t
6086                }
6087            }
6088        }
6089        if {![info exists arcend($a)]} {
6090            return {}
6091        }
6092        set id $arcend($a)
6093        if {[info exists idtags($id)]} {
6094            return $id
6095        }
6096    }
6097    if {[info exists cached_atags($id)]} {
6098        return $cached_atags($id)
6099    }
6100
6101    set origid $id
6102    set todo [list $id]
6103    set queued($id) 1
6104    set taglist {}
6105    set nc 1
6106    for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
6107        set id [lindex $todo $i]
6108        set done($id) 1
6109        set td [info exists hastaggeddescendent($id)]
6110        if {!$td} {
6111            incr nc -1
6112        }
6113        # ignore tags on starting node
6114        if {!$td && $i > 0} {
6115            if {[info exists idtags($id)]} {
6116                set tagloc($id) $id
6117                set td 1
6118            } elseif {[info exists cached_atags($id)]} {
6119                set tagloc($id) $cached_atags($id)
6120                set td 1
6121            }
6122        }
6123        foreach a $arcout($id) {
6124            if {!$td && $arctags($a) ne {}} {
6125                validate_arctags $a
6126                if {$arctags($a) ne {}} {
6127                    lappend tagloc($id) [lindex $arctags($a) 0]
6128                }
6129            }
6130            if {![info exists arcend($a)]} continue
6131            set d $arcend($a)
6132            if {$td || $arctags($a) ne {}} {
6133                set tomark [list $d]
6134                for {set j 0} {$j < [llength $tomark]} {incr j} {
6135                    set dd [lindex $tomark $j]
6136                    if {![info exists hastaggeddescendent($dd)]} {
6137                        if {[info exists done($dd)]} {
6138                            foreach b $arcout($dd) {
6139                                if {[info exists arcend($b)]} {
6140                                    lappend tomark $arcend($b)
6141                                }
6142                            }
6143                            if {[info exists tagloc($dd)]} {
6144                                unset tagloc($dd)
6145                            }
6146                        } elseif {[info exists queued($dd)]} {
6147                            incr nc -1
6148                        }
6149                        set hastaggeddescendent($dd) 1
6150                    }
6151                }
6152            }
6153            if {![info exists queued($d)]} {
6154                lappend todo $d
6155                set queued($d) 1
6156                if {![info exists hastaggeddescendent($d)]} {
6157                    incr nc
6158                }
6159            }
6160        }
6161    }
6162    set t2 [clock clicks -milliseconds]
6163    set loopix $i
6164    set tags {}
6165    foreach id [array names tagloc] {
6166        if {![info exists hastaggeddescendent($id)]} {
6167            foreach t $tagloc($id) {
6168                if {[lsearch -exact $tags $t] < 0} {
6169                    lappend tags $t
6170                }
6171            }
6172        }
6173    }
6174
6175    # remove tags that are ancestors of other tags
6176    for {set i 0} {$i < [llength $tags]} {incr i} {
6177        set a [lindex $tags $i]
6178        for {set j 0} {$j < $i} {incr j} {
6179            set b [lindex $tags $j]
6180            set r [anc_or_desc $a $b]
6181            if {$r == -1} {
6182                set tags [lreplace $tags $j $j]
6183                incr j -1
6184                incr i -1
6185            } elseif {$r == 1} {
6186                set tags [lreplace $tags $i $i]
6187                incr i -1
6188                break
6189            }
6190        }
6191    }
6192
6193    if {[array names growing] ne {}} {
6194        # graph isn't finished, need to check if any tag could get
6195        # eclipsed by another tag coming later.  Simply ignore any
6196        # tags that could later get eclipsed.
6197        set ctags {}
6198        foreach t $tags {
6199            if {[is_certain $origid $t]} {
6200                lappend ctags $t
6201            }
6202        }
6203        if {$tags eq $ctags} {
6204            set cached_atags($origid) $tags
6205        } else {
6206            set tags $ctags
6207        }
6208    } else {
6209        set cached_atags($origid) $tags
6210    }
6211    set t3 [clock clicks -milliseconds]
6212    if {0 && $t3 - $t1 >= 100} {
6213        puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
6214            [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
6215    }
6216    return $tags
6217}
6218
6219# Return the list of IDs that have heads that are descendents of id,
6220# including id itself if it has a head.
6221proc descheads {id} {
6222    global arcnos arcstart arcids archeads idheads cached_dheads
6223    global allparents
6224
6225    if {![info exists allparents($id)]} {
6226        return {}
6227    }
6228    set ret {}
6229    if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
6230        # part-way along an arc; check it first
6231        set a [lindex $arcnos($id) 0]
6232        if {$archeads($a) ne {}} {
6233            validate_archeads $a
6234            set i [lsearch -exact $arcids($a) $id]
6235            foreach t $archeads($a) {
6236                set j [lsearch -exact $arcids($a) $t]
6237                if {$j > $i} break
6238                lappend $ret $t
6239            }
6240        }
6241        set id $arcstart($a)
6242    }
6243    set origid $id
6244    set todo [list $id]
6245    set seen($id) 1
6246    for {set i 0} {$i < [llength $todo]} {incr i} {
6247        set id [lindex $todo $i]
6248        if {[info exists cached_dheads($id)]} {
6249            set ret [concat $ret $cached_dheads($id)]
6250        } else {
6251            if {[info exists idheads($id)]} {
6252                lappend ret $id
6253            }
6254            foreach a $arcnos($id) {
6255                if {$archeads($a) ne {}} {
6256                    set ret [concat $ret $archeads($a)]
6257                }
6258                set d $arcstart($a)
6259                if {![info exists seen($d)]} {
6260                    lappend todo $d
6261                    set seen($d) 1
6262                }
6263            }
6264        }
6265    }
6266    set ret [lsort -unique $ret]
6267    set cached_dheads($origid) $ret
6268}
6269
6270proc addedtag {id} {
6271    global arcnos arcout cached_dtags cached_atags
6272
6273    if {![info exists arcnos($id)]} return
6274    if {![info exists arcout($id)]} {
6275        recalcarc [lindex $arcnos($id) 0]
6276    }
6277    catch {unset cached_dtags}
6278    catch {unset cached_atags}
6279}
6280
6281proc addedhead {hid head} {
6282    global arcnos arcout cached_dheads
6283
6284    if {![info exists arcnos($hid)]} return
6285    if {![info exists arcout($hid)]} {
6286        recalcarc [lindex $arcnos($hid) 0]
6287    }
6288    catch {unset cached_dheads}
6289}
6290
6291proc removedhead {hid head} {
6292    global cached_dheads
6293
6294    catch {unset cached_dheads}
6295}
6296
6297proc movedhead {hid head} {
6298    global arcnos arcout cached_dheads
6299
6300    if {![info exists arcnos($hid)]} return
6301    if {![info exists arcout($hid)]} {
6302        recalcarc [lindex $arcnos($hid) 0]
6303    }
6304    catch {unset cached_dheads}
6305}
6306
6307proc changedrefs {} {
6308    global cached_dheads cached_dtags cached_atags
6309    global arctags archeads arcnos arcout idheads idtags
6310
6311    foreach id [concat [array names idheads] [array names idtags]] {
6312        if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
6313            set a [lindex $arcnos($id) 0]
6314            if {![info exists donearc($a)]} {
6315                recalcarc $a
6316                set donearc($a) 1
6317            }
6318        }
6319    }
6320    catch {unset cached_dtags}
6321    catch {unset cached_atags}
6322    catch {unset cached_dheads}
6323}
6324
6325proc rereadrefs {} {
6326    global idtags idheads idotherrefs mainhead
6327
6328    set refids [concat [array names idtags] \
6329                    [array names idheads] [array names idotherrefs]]
6330    foreach id $refids {
6331        if {![info exists ref($id)]} {
6332            set ref($id) [listrefs $id]
6333        }
6334    }
6335    set oldmainhead $mainhead
6336    readrefs
6337    changedrefs
6338    set refids [lsort -unique [concat $refids [array names idtags] \
6339                        [array names idheads] [array names idotherrefs]]]
6340    foreach id $refids {
6341        set v [listrefs $id]
6342        if {![info exists ref($id)] || $ref($id) != $v ||
6343            ($id eq $oldmainhead && $id ne $mainhead) ||
6344            ($id eq $mainhead && $id ne $oldmainhead)} {
6345            redrawtags $id
6346        }
6347    }
6348}
6349
6350proc listrefs {id} {
6351    global idtags idheads idotherrefs
6352
6353    set x {}
6354    if {[info exists idtags($id)]} {
6355        set x $idtags($id)
6356    }
6357    set y {}
6358    if {[info exists idheads($id)]} {
6359        set y $idheads($id)
6360    }
6361    set z {}
6362    if {[info exists idotherrefs($id)]} {
6363        set z $idotherrefs($id)
6364    }
6365    return [list $x $y $z]
6366}
6367
6368proc showtag {tag isnew} {
6369    global ctext tagcontents tagids linknum
6370
6371    if {$isnew} {
6372        addtohistory [list showtag $tag 0]
6373    }
6374    $ctext conf -state normal
6375    clear_ctext
6376    set linknum 0
6377    if {[info exists tagcontents($tag)]} {
6378        set text $tagcontents($tag)
6379    } else {
6380        set text "Tag: $tag\nId:  $tagids($tag)"
6381    }
6382    appendwithlinks $text {}
6383    $ctext conf -state disabled
6384    init_flist {}
6385}
6386
6387proc doquit {} {
6388    global stopped
6389    set stopped 100
6390    savestuff .
6391    destroy .
6392}
6393
6394proc doprefs {} {
6395    global maxwidth maxgraphpct diffopts
6396    global oldprefs prefstop showneartags
6397    global bgcolor fgcolor ctext diffcolors selectbgcolor
6398    global uifont tabstop
6399
6400    set top .gitkprefs
6401    set prefstop $top
6402    if {[winfo exists $top]} {
6403        raise $top
6404        return
6405    }
6406    foreach v {maxwidth maxgraphpct diffopts showneartags} {
6407        set oldprefs($v) [set $v]
6408    }
6409    toplevel $top
6410    wm title $top "Gitk preferences"
6411    label $top.ldisp -text "Commit list display options"
6412    $top.ldisp configure -font $uifont
6413    grid $top.ldisp - -sticky w -pady 10
6414    label $top.spacer -text " "
6415    label $top.maxwidthl -text "Maximum graph width (lines)" \
6416        -font optionfont
6417    spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
6418    grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
6419    label $top.maxpctl -text "Maximum graph width (% of pane)" \
6420        -font optionfont
6421    spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
6422    grid x $top.maxpctl $top.maxpct -sticky w
6423
6424    label $top.ddisp -text "Diff display options"
6425    $top.ddisp configure -font $uifont
6426    grid $top.ddisp - -sticky w -pady 10
6427    label $top.diffoptl -text "Options for diff program" \
6428        -font optionfont
6429    entry $top.diffopt -width 20 -textvariable diffopts
6430    grid x $top.diffoptl $top.diffopt -sticky w
6431    frame $top.ntag
6432    label $top.ntag.l -text "Display nearby tags" -font optionfont
6433    checkbutton $top.ntag.b -variable showneartags
6434    pack $top.ntag.b $top.ntag.l -side left
6435    grid x $top.ntag -sticky w
6436    label $top.tabstopl -text "tabstop" -font optionfont
6437    entry $top.tabstop -width 10 -textvariable tabstop
6438    grid x $top.tabstopl $top.tabstop -sticky w
6439
6440    label $top.cdisp -text "Colors: press to choose"
6441    $top.cdisp configure -font $uifont
6442    grid $top.cdisp - -sticky w -pady 10
6443    label $top.bg -padx 40 -relief sunk -background $bgcolor
6444    button $top.bgbut -text "Background" -font optionfont \
6445        -command [list choosecolor bgcolor 0 $top.bg background setbg]
6446    grid x $top.bgbut $top.bg -sticky w
6447    label $top.fg -padx 40 -relief sunk -background $fgcolor
6448    button $top.fgbut -text "Foreground" -font optionfont \
6449        -command [list choosecolor fgcolor 0 $top.fg foreground setfg]
6450    grid x $top.fgbut $top.fg -sticky w
6451    label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
6452    button $top.diffoldbut -text "Diff: old lines" -font optionfont \
6453        -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
6454                      [list $ctext tag conf d0 -foreground]]
6455    grid x $top.diffoldbut $top.diffold -sticky w
6456    label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
6457    button $top.diffnewbut -text "Diff: new lines" -font optionfont \
6458        -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
6459                      [list $ctext tag conf d1 -foreground]]
6460    grid x $top.diffnewbut $top.diffnew -sticky w
6461    label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
6462    button $top.hunksepbut -text "Diff: hunk header" -font optionfont \
6463        -command [list choosecolor diffcolors 2 $top.hunksep \
6464                      "diff hunk header" \
6465                      [list $ctext tag conf hunksep -foreground]]
6466    grid x $top.hunksepbut $top.hunksep -sticky w
6467    label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
6468    button $top.selbgbut -text "Select bg" -font optionfont \
6469        -command [list choosecolor selectbgcolor 0 $top.bg background setselbg]
6470    grid x $top.selbgbut $top.selbgsep -sticky w
6471
6472    frame $top.buts
6473    button $top.buts.ok -text "OK" -command prefsok -default active
6474    $top.buts.ok configure -font $uifont
6475    button $top.buts.can -text "Cancel" -command prefscan -default normal
6476    $top.buts.can configure -font $uifont
6477    grid $top.buts.ok $top.buts.can
6478    grid columnconfigure $top.buts 0 -weight 1 -uniform a
6479    grid columnconfigure $top.buts 1 -weight 1 -uniform a
6480    grid $top.buts - - -pady 10 -sticky ew
6481    bind $top <Visibility> "focus $top.buts.ok"
6482}
6483
6484proc choosecolor {v vi w x cmd} {
6485    global $v
6486
6487    set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
6488               -title "Gitk: choose color for $x"]
6489    if {$c eq {}} return
6490    $w conf -background $c
6491    lset $v $vi $c
6492    eval $cmd $c
6493}
6494
6495proc setselbg {c} {
6496    global bglist cflist
6497    foreach w $bglist {
6498        $w configure -selectbackground $c
6499    }
6500    $cflist tag configure highlight \
6501        -background [$cflist cget -selectbackground]
6502    allcanvs itemconf secsel -fill $c
6503}
6504
6505proc setbg {c} {
6506    global bglist
6507
6508    foreach w $bglist {
6509        $w conf -background $c
6510    }
6511}
6512
6513proc setfg {c} {
6514    global fglist canv
6515
6516    foreach w $fglist {
6517        $w conf -foreground $c
6518    }
6519    allcanvs itemconf text -fill $c
6520    $canv itemconf circle -outline $c
6521}
6522
6523proc prefscan {} {
6524    global maxwidth maxgraphpct diffopts
6525    global oldprefs prefstop showneartags
6526
6527    foreach v {maxwidth maxgraphpct diffopts showneartags} {
6528        set $v $oldprefs($v)
6529    }
6530    catch {destroy $prefstop}
6531    unset prefstop
6532}
6533
6534proc prefsok {} {
6535    global maxwidth maxgraphpct
6536    global oldprefs prefstop showneartags
6537    global charspc ctext tabstop
6538
6539    catch {destroy $prefstop}
6540    unset prefstop
6541    $ctext configure -tabs "[expr {$tabstop * $charspc}]"
6542    if {$maxwidth != $oldprefs(maxwidth)
6543        || $maxgraphpct != $oldprefs(maxgraphpct)} {
6544        redisplay
6545    } elseif {$showneartags != $oldprefs(showneartags)} {
6546        reselectline
6547    }
6548}
6549
6550proc formatdate {d} {
6551    return [clock format $d -format "%Y-%m-%d %H:%M:%S"]
6552}
6553
6554# This list of encoding names and aliases is distilled from
6555# http://www.iana.org/assignments/character-sets.
6556# Not all of them are supported by Tcl.
6557set encoding_aliases {
6558    { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
6559      ISO646-US US-ASCII us IBM367 cp367 csASCII }
6560    { ISO-10646-UTF-1 csISO10646UTF1 }
6561    { ISO_646.basic:1983 ref csISO646basic1983 }
6562    { INVARIANT csINVARIANT }
6563    { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
6564    { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
6565    { NATS-SEFI iso-ir-8-1 csNATSSEFI }
6566    { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
6567    { NATS-DANO iso-ir-9-1 csNATSDANO }
6568    { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
6569    { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
6570    { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
6571    { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
6572    { ISO-2022-KR csISO2022KR }
6573    { EUC-KR csEUCKR }
6574    { ISO-2022-JP csISO2022JP }
6575    { ISO-2022-JP-2 csISO2022JP2 }
6576    { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
6577      csISO13JISC6220jp }
6578    { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
6579    { IT iso-ir-15 ISO646-IT csISO15Italian }
6580    { PT iso-ir-16 ISO646-PT csISO16Portuguese }
6581    { ES iso-ir-17 ISO646-ES csISO17Spanish }
6582    { greek7-old iso-ir-18 csISO18Greek7Old }
6583    { latin-greek iso-ir-19 csISO19LatinGreek }
6584    { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
6585    { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
6586    { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
6587    { ISO_5427 iso-ir-37 csISO5427Cyrillic }
6588    { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
6589    { BS_viewdata iso-ir-47 csISO47BSViewdata }
6590    { INIS iso-ir-49 csISO49INIS }
6591    { INIS-8 iso-ir-50 csISO50INIS8 }
6592    { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
6593    { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
6594    { ISO_5428:1980 iso-ir-55 csISO5428Greek }
6595    { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
6596    { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
6597    { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
6598      csISO60Norwegian1 }
6599    { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
6600    { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
6601    { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
6602    { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
6603    { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
6604    { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
6605    { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
6606    { greek7 iso-ir-88 csISO88Greek7 }
6607    { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
6608    { iso-ir-90 csISO90 }
6609    { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
6610    { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
6611      csISO92JISC62991984b }
6612    { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
6613    { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
6614    { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
6615      csISO95JIS62291984handadd }
6616    { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
6617    { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
6618    { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
6619    { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
6620      CP819 csISOLatin1 }
6621    { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
6622    { T.61-7bit iso-ir-102 csISO102T617bit }
6623    { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
6624    { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
6625    { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
6626    { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
6627    { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
6628    { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
6629    { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
6630    { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
6631      arabic csISOLatinArabic }
6632    { ISO_8859-6-E csISO88596E ISO-8859-6-E }
6633    { ISO_8859-6-I csISO88596I ISO-8859-6-I }
6634    { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
6635      greek greek8 csISOLatinGreek }
6636    { T.101-G2 iso-ir-128 csISO128T101G2 }
6637    { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
6638      csISOLatinHebrew }
6639    { ISO_8859-8-E csISO88598E ISO-8859-8-E }
6640    { ISO_8859-8-I csISO88598I ISO-8859-8-I }
6641    { CSN_369103 iso-ir-139 csISO139CSN369103 }
6642    { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
6643    { ISO_6937-2-add iso-ir-142 csISOTextComm }
6644    { IEC_P27-1 iso-ir-143 csISO143IECP271 }
6645    { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
6646      csISOLatinCyrillic }
6647    { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
6648    { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
6649    { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
6650    { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
6651    { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
6652    { ISO_6937-2-25 iso-ir-152 csISO6937Add }
6653    { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
6654    { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
6655    { ISO_10367-box iso-ir-155 csISO10367Box }
6656    { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
6657    { latin-lap lap iso-ir-158 csISO158Lap }
6658    { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
6659    { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
6660    { us-dk csUSDK }
6661    { dk-us csDKUS }
6662    { JIS_X0201 X0201 csHalfWidthKatakana }
6663    { KSC5636 ISO646-KR csKSC5636 }
6664    { ISO-10646-UCS-2 csUnicode }
6665    { ISO-10646-UCS-4 csUCS4 }
6666    { DEC-MCS dec csDECMCS }
6667    { hp-roman8 roman8 r8 csHPRoman8 }
6668    { macintosh mac csMacintosh }
6669    { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
6670      csIBM037 }
6671    { IBM038 EBCDIC-INT cp038 csIBM038 }
6672    { IBM273 CP273 csIBM273 }
6673    { IBM274 EBCDIC-BE CP274 csIBM274 }
6674    { IBM275 EBCDIC-BR cp275 csIBM275 }
6675    { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
6676    { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
6677    { IBM280 CP280 ebcdic-cp-it csIBM280 }
6678    { IBM281 EBCDIC-JP-E cp281 csIBM281 }
6679    { IBM284 CP284 ebcdic-cp-es csIBM284 }
6680    { IBM285 CP285 ebcdic-cp-gb csIBM285 }
6681    { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
6682    { IBM297 cp297 ebcdic-cp-fr csIBM297 }
6683    { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
6684    { IBM423 cp423 ebcdic-cp-gr csIBM423 }
6685    { IBM424 cp424 ebcdic-cp-he csIBM424 }
6686    { IBM437 cp437 437 csPC8CodePage437 }
6687    { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
6688    { IBM775 cp775 csPC775Baltic }
6689    { IBM850 cp850 850 csPC850Multilingual }
6690    { IBM851 cp851 851 csIBM851 }
6691    { IBM852 cp852 852 csPCp852 }
6692    { IBM855 cp855 855 csIBM855 }
6693    { IBM857 cp857 857 csIBM857 }
6694    { IBM860 cp860 860 csIBM860 }
6695    { IBM861 cp861 861 cp-is csIBM861 }
6696    { IBM862 cp862 862 csPC862LatinHebrew }
6697    { IBM863 cp863 863 csIBM863 }
6698    { IBM864 cp864 csIBM864 }
6699    { IBM865 cp865 865 csIBM865 }
6700    { IBM866 cp866 866 csIBM866 }
6701    { IBM868 CP868 cp-ar csIBM868 }
6702    { IBM869 cp869 869 cp-gr csIBM869 }
6703    { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
6704    { IBM871 CP871 ebcdic-cp-is csIBM871 }
6705    { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
6706    { IBM891 cp891 csIBM891 }
6707    { IBM903 cp903 csIBM903 }
6708    { IBM904 cp904 904 csIBBM904 }
6709    { IBM905 CP905 ebcdic-cp-tr csIBM905 }
6710    { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
6711    { IBM1026 CP1026 csIBM1026 }
6712    { EBCDIC-AT-DE csIBMEBCDICATDE }
6713    { EBCDIC-AT-DE-A csEBCDICATDEA }
6714    { EBCDIC-CA-FR csEBCDICCAFR }
6715    { EBCDIC-DK-NO csEBCDICDKNO }
6716    { EBCDIC-DK-NO-A csEBCDICDKNOA }
6717    { EBCDIC-FI-SE csEBCDICFISE }
6718    { EBCDIC-FI-SE-A csEBCDICFISEA }
6719    { EBCDIC-FR csEBCDICFR }
6720    { EBCDIC-IT csEBCDICIT }
6721    { EBCDIC-PT csEBCDICPT }
6722    { EBCDIC-ES csEBCDICES }
6723    { EBCDIC-ES-A csEBCDICESA }
6724    { EBCDIC-ES-S csEBCDICESS }
6725    { EBCDIC-UK csEBCDICUK }
6726    { EBCDIC-US csEBCDICUS }
6727    { UNKNOWN-8BIT csUnknown8BiT }
6728    { MNEMONIC csMnemonic }
6729    { MNEM csMnem }
6730    { VISCII csVISCII }
6731    { VIQR csVIQR }
6732    { KOI8-R csKOI8R }
6733    { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
6734    { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
6735    { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
6736    { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
6737    { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
6738    { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
6739    { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
6740    { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
6741    { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
6742    { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
6743    { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
6744    { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
6745    { IBM1047 IBM-1047 }
6746    { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
6747    { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
6748    { UNICODE-1-1 csUnicode11 }
6749    { CESU-8 csCESU-8 }
6750    { BOCU-1 csBOCU-1 }
6751    { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
6752    { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
6753      l8 }
6754    { ISO-8859-15 ISO_8859-15 Latin-9 }
6755    { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
6756    { GBK CP936 MS936 windows-936 }
6757    { JIS_Encoding csJISEncoding }
6758    { Shift_JIS MS_Kanji csShiftJIS }
6759    { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
6760      EUC-JP }
6761    { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
6762    { ISO-10646-UCS-Basic csUnicodeASCII }
6763    { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
6764    { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
6765    { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
6766    { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
6767    { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
6768    { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
6769    { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
6770    { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
6771    { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
6772    { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
6773    { Adobe-Standard-Encoding csAdobeStandardEncoding }
6774    { Ventura-US csVenturaUS }
6775    { Ventura-International csVenturaInternational }
6776    { PC8-Danish-Norwegian csPC8DanishNorwegian }
6777    { PC8-Turkish csPC8Turkish }
6778    { IBM-Symbols csIBMSymbols }
6779    { IBM-Thai csIBMThai }
6780    { HP-Legal csHPLegal }
6781    { HP-Pi-font csHPPiFont }
6782    { HP-Math8 csHPMath8 }
6783    { Adobe-Symbol-Encoding csHPPSMath }
6784    { HP-DeskTop csHPDesktop }
6785    { Ventura-Math csVenturaMath }
6786    { Microsoft-Publishing csMicrosoftPublishing }
6787    { Windows-31J csWindows31J }
6788    { GB2312 csGB2312 }
6789    { Big5 csBig5 }
6790}
6791
6792proc tcl_encoding {enc} {
6793    global encoding_aliases
6794    set names [encoding names]
6795    set lcnames [string tolower $names]
6796    set enc [string tolower $enc]
6797    set i [lsearch -exact $lcnames $enc]
6798    if {$i < 0} {
6799        # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
6800        if {[regsub {^iso[-_]} $enc iso encx]} {
6801            set i [lsearch -exact $lcnames $encx]
6802        }
6803    }
6804    if {$i < 0} {
6805        foreach l $encoding_aliases {
6806            set ll [string tolower $l]
6807            if {[lsearch -exact $ll $enc] < 0} continue
6808            # look through the aliases for one that tcl knows about
6809            foreach e $ll {
6810                set i [lsearch -exact $lcnames $e]
6811                if {$i < 0} {
6812                    if {[regsub {^iso[-_]} $e iso ex]} {
6813                        set i [lsearch -exact $lcnames $ex]
6814                    }
6815                }
6816                if {$i >= 0} break
6817            }
6818            break
6819        }
6820    }
6821    if {$i >= 0} {
6822        return [lindex $names $i]
6823    }
6824    return {}
6825}
6826
6827# defaults...
6828set datemode 0
6829set diffopts "-U 5 -p"
6830set wrcomcmd "git diff-tree --stdin -p --pretty"
6831
6832set gitencoding {}
6833catch {
6834    set gitencoding [exec git config --get i18n.commitencoding]
6835}
6836if {$gitencoding == ""} {
6837    set gitencoding "utf-8"
6838}
6839set tclencoding [tcl_encoding $gitencoding]
6840if {$tclencoding == {}} {
6841    puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
6842}
6843
6844set mainfont {Helvetica 9}
6845set textfont {Courier 9}
6846set uifont {Helvetica 9 bold}
6847set tabstop 8
6848set findmergefiles 0
6849set maxgraphpct 50
6850set maxwidth 16
6851set revlistorder 0
6852set fastdate 0
6853set uparrowlen 7
6854set downarrowlen 7
6855set mingaplen 30
6856set cmitmode "patch"
6857set wrapcomment "none"
6858set showneartags 1
6859
6860set colors {green red blue magenta darkgrey brown orange}
6861set bgcolor white
6862set fgcolor black
6863set diffcolors {red "#00a000" blue}
6864set selectbgcolor gray85
6865
6866catch {source ~/.gitk}
6867
6868font create optionfont -family sans-serif -size -12
6869
6870set revtreeargs {}
6871foreach arg $argv {
6872    switch -regexp -- $arg {
6873        "^$" { }
6874        "^-d" { set datemode 1 }
6875        default {
6876            lappend revtreeargs $arg
6877        }
6878    }
6879}
6880
6881# check that we can find a .git directory somewhere...
6882set gitdir [gitdir]
6883if {![file isdirectory $gitdir]} {
6884    show_error {} . "Cannot find the git directory \"$gitdir\"."
6885    exit 1
6886}
6887
6888set cmdline_files {}
6889set i [lsearch -exact $revtreeargs "--"]
6890if {$i >= 0} {
6891    set cmdline_files [lrange $revtreeargs [expr {$i + 1}] end]
6892    set revtreeargs [lrange $revtreeargs 0 [expr {$i - 1}]]
6893} elseif {$revtreeargs ne {}} {
6894    if {[catch {
6895        set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
6896        set cmdline_files [split $f "\n"]
6897        set n [llength $cmdline_files]
6898        set revtreeargs [lrange $revtreeargs 0 end-$n]
6899    } err]} {
6900        # unfortunately we get both stdout and stderr in $err,
6901        # so look for "fatal:".
6902        set i [string first "fatal:" $err]
6903        if {$i > 0} {
6904            set err [string range $err [expr {$i + 6}] end]
6905        }
6906        show_error {} . "Bad arguments to gitk:\n$err"
6907        exit 1
6908    }
6909}
6910
6911set history {}
6912set historyindex 0
6913set fh_serial 0
6914set nhl_names {}
6915set highlight_paths {}
6916set searchdirn -forwards
6917set boldrows {}
6918set boldnamerows {}
6919set diffelide {0 0}
6920
6921set optim_delay 16
6922
6923set nextviewnum 1
6924set curview 0
6925set selectedview 0
6926set selectedhlview None
6927set viewfiles(0) {}
6928set viewperm(0) 0
6929set viewargs(0) {}
6930
6931set cmdlineok 0
6932set stopped 0
6933set stuffsaved 0
6934set patchnum 0
6935setcoords
6936makewindow
6937wm title . "[file tail $argv0]: [file tail [pwd]]"
6938readrefs
6939
6940if {$cmdline_files ne {} || $revtreeargs ne {}} {
6941    # create a view for the files/dirs specified on the command line
6942    set curview 1
6943    set selectedview 1
6944    set nextviewnum 2
6945    set viewname(1) "Command line"
6946    set viewfiles(1) $cmdline_files
6947    set viewargs(1) $revtreeargs
6948    set viewperm(1) 0
6949    addviewmenu 1
6950    .bar.view entryconf Edit* -state normal
6951    .bar.view entryconf Delete* -state normal
6952}
6953
6954if {[info exists permviews]} {
6955    foreach v $permviews {
6956        set n $nextviewnum
6957        incr nextviewnum
6958        set viewname($n) [lindex $v 0]
6959        set viewfiles($n) [lindex $v 1]
6960        set viewargs($n) [lindex $v 2]
6961        set viewperm($n) 1
6962        addviewmenu $n
6963    }
6964}
6965getcommits