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