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