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