git-gui / git-gui.shon commit Merge branch 'ew/maint-svn-cert-fileprovider' (7385a42)
   1#!/bin/sh
   2# Tcl ignores the next line -*- tcl -*- \
   3 if test "z$*" = zversion \
   4 || test "z$*" = z--version; \
   5 then \
   6        echo 'git-gui version @@GITGUI_VERSION@@'; \
   7        exit; \
   8 fi; \
   9 argv0=$0; \
  10 exec wish "$argv0" -- "$@"
  11
  12set appvers {@@GITGUI_VERSION@@}
  13set copyright [encoding convertfrom utf-8 {
  14Copyright © 2006, 2007 Shawn Pearce, et. al.
  15
  16This program is free software; you can redistribute it and/or modify
  17it under the terms of the GNU General Public License as published by
  18the Free Software Foundation; either version 2 of the License, or
  19(at your option) any later version.
  20
  21This program is distributed in the hope that it will be useful,
  22but WITHOUT ANY WARRANTY; without even the implied warranty of
  23MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  24GNU General Public License for more details.
  25
  26You should have received a copy of the GNU General Public License
  27along with this program; if not, write to the Free Software
  28Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA}]
  29
  30######################################################################
  31##
  32## Tcl/Tk sanity check
  33
  34if {[catch {package require Tcl 8.4} err]
  35 || [catch {package require Tk  8.4} err]
  36} {
  37        catch {wm withdraw .}
  38        tk_messageBox \
  39                -icon error \
  40                -type ok \
  41                -title [mc "git-gui: fatal error"] \
  42                -message $err
  43        exit 1
  44}
  45
  46catch {rename send {}} ; # What an evil concept...
  47
  48######################################################################
  49##
  50## locate our library
  51
  52set oguilib {@@GITGUI_LIBDIR@@}
  53set oguirel {@@GITGUI_RELATIVE@@}
  54if {$oguirel eq {1}} {
  55        set oguilib [file dirname [file dirname [file normalize $argv0]]]
  56        set oguilib [file join $oguilib share git-gui lib]
  57        set oguimsg [file join $oguilib msgs]
  58} elseif {[string match @@* $oguirel]} {
  59        set oguilib [file join [file dirname [file normalize $argv0]] lib]
  60        set oguimsg [file join [file dirname [file normalize $argv0]] po]
  61} else {
  62        set oguimsg [file join $oguilib msgs]
  63}
  64unset oguirel
  65
  66######################################################################
  67##
  68## enable verbose loading?
  69
  70if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} {
  71        unset _verbose
  72        rename auto_load real__auto_load
  73        proc auto_load {name args} {
  74                puts stderr "auto_load $name"
  75                return [uplevel 1 real__auto_load $name $args]
  76        }
  77        rename source real__source
  78        proc source {name} {
  79                puts stderr "source    $name"
  80                uplevel 1 real__source $name
  81        }
  82}
  83
  84######################################################################
  85##
  86## Internationalization (i18n) through msgcat and gettext. See
  87## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
  88
  89package require msgcat
  90
  91proc _mc_trim {fmt} {
  92        set cmk [string first @@ $fmt]
  93        if {$cmk > 0} {
  94                return [string range $fmt 0 [expr {$cmk - 1}]]
  95        }
  96        return $fmt
  97}
  98
  99proc mc {en_fmt args} {
 100        set fmt [_mc_trim [::msgcat::mc $en_fmt]]
 101        if {[catch {set msg [eval [list format $fmt] $args]} err]} {
 102                set msg [eval [list format [_mc_trim $en_fmt]] $args]
 103        }
 104        return $msg
 105}
 106
 107proc strcat {args} {
 108        return [join $args {}]
 109}
 110
 111::msgcat::mcload $oguimsg
 112unset oguimsg
 113
 114######################################################################
 115##
 116## read only globals
 117
 118set _appname {Git Gui}
 119set _gitdir {}
 120set _gitexec {}
 121set _reponame {}
 122set _iscygwin {}
 123set _search_path {}
 124
 125proc appname {} {
 126        global _appname
 127        return $_appname
 128}
 129
 130proc gitdir {args} {
 131        global _gitdir
 132        if {$args eq {}} {
 133                return $_gitdir
 134        }
 135        return [eval [list file join $_gitdir] $args]
 136}
 137
 138proc gitexec {args} {
 139        global _gitexec
 140        if {$_gitexec eq {}} {
 141                if {[catch {set _gitexec [git --exec-path]} err]} {
 142                        error "Git not installed?\n\n$err"
 143                }
 144                if {[is_Cygwin]} {
 145                        set _gitexec [exec cygpath \
 146                                --windows \
 147                                --absolute \
 148                                $_gitexec]
 149                } else {
 150                        set _gitexec [file normalize $_gitexec]
 151                }
 152        }
 153        if {$args eq {}} {
 154                return $_gitexec
 155        }
 156        return [eval [list file join $_gitexec] $args]
 157}
 158
 159proc reponame {} {
 160        return $::_reponame
 161}
 162
 163proc is_MacOSX {} {
 164        if {[tk windowingsystem] eq {aqua}} {
 165                return 1
 166        }
 167        return 0
 168}
 169
 170proc is_Windows {} {
 171        if {$::tcl_platform(platform) eq {windows}} {
 172                return 1
 173        }
 174        return 0
 175}
 176
 177proc is_Cygwin {} {
 178        global _iscygwin
 179        if {$_iscygwin eq {}} {
 180                if {$::tcl_platform(platform) eq {windows}} {
 181                        if {[catch {set p [exec cygpath --windir]} err]} {
 182                                set _iscygwin 0
 183                        } else {
 184                                set _iscygwin 1
 185                        }
 186                } else {
 187                        set _iscygwin 0
 188                }
 189        }
 190        return $_iscygwin
 191}
 192
 193proc is_enabled {option} {
 194        global enabled_options
 195        if {[catch {set on $enabled_options($option)}]} {return 0}
 196        return $on
 197}
 198
 199proc enable_option {option} {
 200        global enabled_options
 201        set enabled_options($option) 1
 202}
 203
 204proc disable_option {option} {
 205        global enabled_options
 206        set enabled_options($option) 0
 207}
 208
 209######################################################################
 210##
 211## config
 212
 213proc is_many_config {name} {
 214        switch -glob -- $name {
 215        gui.recentrepo -
 216        remote.*.fetch -
 217        remote.*.push
 218                {return 1}
 219        *
 220                {return 0}
 221        }
 222}
 223
 224proc is_config_true {name} {
 225        global repo_config
 226        if {[catch {set v $repo_config($name)}]} {
 227                return 0
 228        } elseif {$v eq {true} || $v eq {1} || $v eq {yes}} {
 229                return 1
 230        } else {
 231                return 0
 232        }
 233}
 234
 235proc get_config {name} {
 236        global repo_config
 237        if {[catch {set v $repo_config($name)}]} {
 238                return {}
 239        } else {
 240                return $v
 241        }
 242}
 243
 244######################################################################
 245##
 246## handy utils
 247
 248proc _git_cmd {name} {
 249        global _git_cmd_path
 250
 251        if {[catch {set v $_git_cmd_path($name)}]} {
 252                switch -- $name {
 253                  version   -
 254                --version   -
 255                --exec-path { return [list $::_git $name] }
 256                }
 257
 258                set p [gitexec git-$name$::_search_exe]
 259                if {[file exists $p]} {
 260                        set v [list $p]
 261                } elseif {[is_Windows] && [file exists [gitexec git-$name]]} {
 262                        # Try to determine what sort of magic will make
 263                        # git-$name go and do its thing, because native
 264                        # Tcl on Windows doesn't know it.
 265                        #
 266                        set p [gitexec git-$name]
 267                        set f [open $p r]
 268                        set s [gets $f]
 269                        close $f
 270
 271                        switch -glob -- [lindex $s 0] {
 272                        #!*sh     { set i sh     }
 273                        #!*perl   { set i perl   }
 274                        #!*python { set i python }
 275                        default   { error "git-$name is not supported: $s" }
 276                        }
 277
 278                        upvar #0 _$i interp
 279                        if {![info exists interp]} {
 280                                set interp [_which $i]
 281                        }
 282                        if {$interp eq {}} {
 283                                error "git-$name requires $i (not in PATH)"
 284                        }
 285                        set v [concat [list $interp] [lrange $s 1 end] [list $p]]
 286                } else {
 287                        # Assume it is builtin to git somehow and we
 288                        # aren't actually able to see a file for it.
 289                        #
 290                        set v [list $::_git $name]
 291                }
 292                set _git_cmd_path($name) $v
 293        }
 294        return $v
 295}
 296
 297proc _which {what} {
 298        global env _search_exe _search_path
 299
 300        if {$_search_path eq {}} {
 301                if {[is_Cygwin] && [regexp {^(/|\.:)} $env(PATH)]} {
 302                        set _search_path [split [exec cygpath \
 303                                --windows \
 304                                --path \
 305                                --absolute \
 306                                $env(PATH)] {;}]
 307                        set _search_exe .exe
 308                } elseif {[is_Windows]} {
 309                        set gitguidir [file dirname [info script]]
 310                        regsub -all ";" $gitguidir "\\;" gitguidir
 311                        set env(PATH) "$gitguidir;$env(PATH)"
 312                        set _search_path [split $env(PATH) {;}]
 313                        set _search_exe .exe
 314                } else {
 315                        set _search_path [split $env(PATH) :]
 316                        set _search_exe {}
 317                }
 318        }
 319
 320        foreach p $_search_path {
 321                set p [file join $p $what$_search_exe]
 322                if {[file exists $p]} {
 323                        return [file normalize $p]
 324                }
 325        }
 326        return {}
 327}
 328
 329proc _lappend_nice {cmd_var} {
 330        global _nice
 331        upvar $cmd_var cmd
 332
 333        if {![info exists _nice]} {
 334                set _nice [_which nice]
 335        }
 336        if {$_nice ne {}} {
 337                lappend cmd $_nice
 338        }
 339}
 340
 341proc git {args} {
 342        set opt [list exec]
 343
 344        while {1} {
 345                switch -- [lindex $args 0] {
 346                --nice {
 347                        _lappend_nice opt
 348                }
 349
 350                default {
 351                        break
 352                }
 353
 354                }
 355
 356                set args [lrange $args 1 end]
 357        }
 358
 359        set cmdp [_git_cmd [lindex $args 0]]
 360        set args [lrange $args 1 end]
 361
 362        return [eval $opt $cmdp $args]
 363}
 364
 365proc _open_stdout_stderr {cmd} {
 366        if {[catch {
 367                        set fd [open $cmd r]
 368                } err]} {
 369                if {   [lindex $cmd end] eq {2>@1}
 370                    && $err eq {can not find channel named "1"}
 371                        } {
 372                        # Older versions of Tcl 8.4 don't have this 2>@1 IO
 373                        # redirect operator.  Fallback to |& cat for those.
 374                        # The command was not actually started, so its safe
 375                        # to try to start it a second time.
 376                        #
 377                        set fd [open [concat \
 378                                [lrange $cmd 0 end-1] \
 379                                [list |& cat] \
 380                                ] r]
 381                } else {
 382                        error $err
 383                }
 384        }
 385        fconfigure $fd -eofchar {}
 386        return $fd
 387}
 388
 389proc git_read {args} {
 390        set opt [list |]
 391
 392        while {1} {
 393                switch -- [lindex $args 0] {
 394                --nice {
 395                        _lappend_nice opt
 396                }
 397
 398                --stderr {
 399                        lappend args 2>@1
 400                }
 401
 402                default {
 403                        break
 404                }
 405
 406                }
 407
 408                set args [lrange $args 1 end]
 409        }
 410
 411        set cmdp [_git_cmd [lindex $args 0]]
 412        set args [lrange $args 1 end]
 413
 414        return [_open_stdout_stderr [concat $opt $cmdp $args]]
 415}
 416
 417proc git_write {args} {
 418        set opt [list |]
 419
 420        while {1} {
 421                switch -- [lindex $args 0] {
 422                --nice {
 423                        _lappend_nice opt
 424                }
 425
 426                default {
 427                        break
 428                }
 429
 430                }
 431
 432                set args [lrange $args 1 end]
 433        }
 434
 435        set cmdp [_git_cmd [lindex $args 0]]
 436        set args [lrange $args 1 end]
 437
 438        return [open [concat $opt $cmdp $args] w]
 439}
 440
 441proc githook_read {hook_name args} {
 442        set pchook [gitdir hooks $hook_name]
 443        lappend args 2>@1
 444
 445        # On Cygwin [file executable] might lie so we need to ask
 446        # the shell if the hook is executable.  Yes that's annoying.
 447        #
 448        if {[is_Cygwin]} {
 449                upvar #0 _sh interp
 450                if {![info exists interp]} {
 451                        set interp [_which sh]
 452                }
 453                if {$interp eq {}} {
 454                        error "hook execution requires sh (not in PATH)"
 455                }
 456
 457                set scr {if test -x "$1";then exec "$@";fi}
 458                set sh_c [list | $interp -c $scr $interp $pchook]
 459                return [_open_stdout_stderr [concat $sh_c $args]]
 460        }
 461
 462        if {[file executable $pchook]} {
 463                return [_open_stdout_stderr [concat [list | $pchook] $args]]
 464        }
 465
 466        return {}
 467}
 468
 469proc sq {value} {
 470        regsub -all ' $value "'\\''" value
 471        return "'$value'"
 472}
 473
 474proc load_current_branch {} {
 475        global current_branch is_detached
 476
 477        set fd [open [gitdir HEAD] r]
 478        if {[gets $fd ref] < 1} {
 479                set ref {}
 480        }
 481        close $fd
 482
 483        set pfx {ref: refs/heads/}
 484        set len [string length $pfx]
 485        if {[string equal -length $len $pfx $ref]} {
 486                # We're on a branch.  It might not exist.  But
 487                # HEAD looks good enough to be a branch.
 488                #
 489                set current_branch [string range $ref $len end]
 490                set is_detached 0
 491        } else {
 492                # Assume this is a detached head.
 493                #
 494                set current_branch HEAD
 495                set is_detached 1
 496        }
 497}
 498
 499auto_load tk_optionMenu
 500rename tk_optionMenu real__tkOptionMenu
 501proc tk_optionMenu {w varName args} {
 502        set m [eval real__tkOptionMenu $w $varName $args]
 503        $m configure -font font_ui
 504        $w configure -font font_ui
 505        return $m
 506}
 507
 508proc rmsel_tag {text} {
 509        $text tag conf sel \
 510                -background [$text cget -background] \
 511                -foreground [$text cget -foreground] \
 512                -borderwidth 0
 513        $text tag conf in_sel -background lightgray
 514        bind $text <Motion> break
 515        return $text
 516}
 517
 518set root_exists 0
 519bind . <Visibility> {
 520        bind . <Visibility> {}
 521        set root_exists 1
 522}
 523
 524if {[is_Windows]} {
 525        wm iconbitmap . -default $oguilib/git-gui.ico
 526}
 527
 528######################################################################
 529##
 530## config defaults
 531
 532set cursor_ptr arrow
 533font create font_diff -family Courier -size 10
 534font create font_ui
 535catch {
 536        label .dummy
 537        eval font configure font_ui [font actual [.dummy cget -font]]
 538        destroy .dummy
 539}
 540
 541font create font_uiitalic
 542font create font_uibold
 543font create font_diffbold
 544font create font_diffitalic
 545
 546foreach class {Button Checkbutton Entry Label
 547                Labelframe Listbox Menu Message
 548                Radiobutton Spinbox Text} {
 549        option add *$class.font font_ui
 550}
 551unset class
 552
 553if {[is_Windows] || [is_MacOSX]} {
 554        option add *Menu.tearOff 0
 555}
 556
 557if {[is_MacOSX]} {
 558        set M1B M1
 559        set M1T Cmd
 560} else {
 561        set M1B Control
 562        set M1T Ctrl
 563}
 564
 565proc bind_button3 {w cmd} {
 566        bind $w <Any-Button-3> $cmd
 567        if {[is_MacOSX]} {
 568                # Mac OS X sends Button-2 on right click through three-button mouse,
 569                # or through trackpad right-clicking (two-finger touch + click).
 570                bind $w <Any-Button-2> $cmd
 571                bind $w <Control-Button-1> $cmd
 572        }
 573}
 574
 575proc apply_config {} {
 576        global repo_config font_descs
 577
 578        foreach option $font_descs {
 579                set name [lindex $option 0]
 580                set font [lindex $option 1]
 581                if {[catch {
 582                        set need_weight 1
 583                        foreach {cn cv} $repo_config(gui.$name) {
 584                                if {$cn eq {-weight}} {
 585                                        set need_weight 0
 586                                }
 587                                font configure $font $cn $cv
 588                        }
 589                        if {$need_weight} {
 590                                font configure $font -weight normal
 591                        }
 592                        } err]} {
 593                        error_popup [strcat [mc "Invalid font specified in %s:" "gui.$name"] "\n\n$err"]
 594                }
 595                foreach {cn cv} [font configure $font] {
 596                        font configure ${font}bold $cn $cv
 597                        font configure ${font}italic $cn $cv
 598                }
 599                font configure ${font}bold -weight bold
 600                font configure ${font}italic -slant italic
 601        }
 602}
 603
 604set default_config(merge.diffstat) true
 605set default_config(merge.summary) false
 606set default_config(merge.verbosity) 2
 607set default_config(user.name) {}
 608set default_config(user.email) {}
 609
 610set default_config(gui.matchtrackingbranch) false
 611set default_config(gui.pruneduringfetch) false
 612set default_config(gui.trustmtime) false
 613set default_config(gui.diffcontext) 5
 614set default_config(gui.newbranchtemplate) {}
 615set default_config(gui.spellingdictionary) {}
 616set default_config(gui.fontui) [font configure font_ui]
 617set default_config(gui.fontdiff) [font configure font_diff]
 618set font_descs {
 619        {fontui   font_ui   {mc "Main Font"}}
 620        {fontdiff font_diff {mc "Diff/Console Font"}}
 621}
 622
 623######################################################################
 624##
 625## find git
 626
 627set _git  [_which git]
 628if {$_git eq {}} {
 629        catch {wm withdraw .}
 630        tk_messageBox \
 631                -icon error \
 632                -type ok \
 633                -title [mc "git-gui: fatal error"] \
 634                -message [mc "Cannot find git in PATH."]
 635        exit 1
 636}
 637
 638######################################################################
 639##
 640## version check
 641
 642if {[catch {set _git_version [git --version]} err]} {
 643        catch {wm withdraw .}
 644        tk_messageBox \
 645                -icon error \
 646                -type ok \
 647                -title [mc "git-gui: fatal error"] \
 648                -message "Cannot determine Git version:
 649
 650$err
 651
 652[appname] requires Git 1.5.0 or later."
 653        exit 1
 654}
 655if {![regsub {^git version } $_git_version {} _git_version]} {
 656        catch {wm withdraw .}
 657        tk_messageBox \
 658                -icon error \
 659                -type ok \
 660                -title [mc "git-gui: fatal error"] \
 661                -message [strcat [mc "Cannot parse Git version string:"] "\n\n$_git_version"]
 662        exit 1
 663}
 664
 665set _real_git_version $_git_version
 666regsub -- {[\-\.]dirty$} $_git_version {} _git_version
 667regsub {\.[0-9]+\.g[0-9a-f]+$} $_git_version {} _git_version
 668regsub {\.rc[0-9]+$} $_git_version {} _git_version
 669regsub {\.GIT$} $_git_version {} _git_version
 670regsub {\.[a-zA-Z]+\.[0-9]+$} $_git_version {} _git_version
 671
 672if {![regexp {^[1-9]+(\.[0-9]+)+$} $_git_version]} {
 673        catch {wm withdraw .}
 674        if {[tk_messageBox \
 675                -icon warning \
 676                -type yesno \
 677                -default no \
 678                -title "[appname]: warning" \
 679                 -message [mc "Git version cannot be determined.
 680
 681%s claims it is version '%s'.
 682
 683%s requires at least Git 1.5.0 or later.
 684
 685Assume '%s' is version 1.5.0?
 686" $_git $_real_git_version [appname] $_real_git_version]] eq {yes}} {
 687                set _git_version 1.5.0
 688        } else {
 689                exit 1
 690        }
 691}
 692unset _real_git_version
 693
 694proc git-version {args} {
 695        global _git_version
 696
 697        switch [llength $args] {
 698        0 {
 699                return $_git_version
 700        }
 701
 702        2 {
 703                set op [lindex $args 0]
 704                set vr [lindex $args 1]
 705                set cm [package vcompare $_git_version $vr]
 706                return [expr $cm $op 0]
 707        }
 708
 709        4 {
 710                set type [lindex $args 0]
 711                set name [lindex $args 1]
 712                set parm [lindex $args 2]
 713                set body [lindex $args 3]
 714
 715                if {($type ne {proc} && $type ne {method})} {
 716                        error "Invalid arguments to git-version"
 717                }
 718                if {[llength $body] < 2 || [lindex $body end-1] ne {default}} {
 719                        error "Last arm of $type $name must be default"
 720                }
 721
 722                foreach {op vr cb} [lrange $body 0 end-2] {
 723                        if {[git-version $op $vr]} {
 724                                return [uplevel [list $type $name $parm $cb]]
 725                        }
 726                }
 727
 728                return [uplevel [list $type $name $parm [lindex $body end]]]
 729        }
 730
 731        default {
 732                error "git-version >= x"
 733        }
 734
 735        }
 736}
 737
 738if {[git-version < 1.5]} {
 739        catch {wm withdraw .}
 740        tk_messageBox \
 741                -icon error \
 742                -type ok \
 743                -title [mc "git-gui: fatal error"] \
 744                -message "[appname] requires Git 1.5.0 or later.
 745
 746You are using [git-version]:
 747
 748[git --version]"
 749        exit 1
 750}
 751
 752######################################################################
 753##
 754## configure our library
 755
 756set idx [file join $oguilib tclIndex]
 757if {[catch {set fd [open $idx r]} err]} {
 758        catch {wm withdraw .}
 759        tk_messageBox \
 760                -icon error \
 761                -type ok \
 762                -title [mc "git-gui: fatal error"] \
 763                -message $err
 764        exit 1
 765}
 766if {[gets $fd] eq {# Autogenerated by git-gui Makefile}} {
 767        set idx [list]
 768        while {[gets $fd n] >= 0} {
 769                if {$n ne {} && ![string match #* $n]} {
 770                        lappend idx $n
 771                }
 772        }
 773} else {
 774        set idx {}
 775}
 776close $fd
 777
 778if {$idx ne {}} {
 779        set loaded [list]
 780        foreach p $idx {
 781                if {[lsearch -exact $loaded $p] >= 0} continue
 782                source [file join $oguilib $p]
 783                lappend loaded $p
 784        }
 785        unset loaded p
 786} else {
 787        set auto_path [concat [list $oguilib] $auto_path]
 788}
 789unset -nocomplain idx fd
 790
 791######################################################################
 792##
 793## config file parsing
 794
 795git-version proc _parse_config {arr_name args} {
 796        >= 1.5.3 {
 797                upvar $arr_name arr
 798                array unset arr
 799                set buf {}
 800                catch {
 801                        set fd_rc [eval \
 802                                [list git_read config] \
 803                                $args \
 804                                [list --null --list]]
 805                        fconfigure $fd_rc -translation binary
 806                        set buf [read $fd_rc]
 807                        close $fd_rc
 808                }
 809                foreach line [split $buf "\0"] {
 810                        if {[regexp {^([^\n]+)\n(.*)$} $line line name value]} {
 811                                if {[is_many_config $name]} {
 812                                        lappend arr($name) $value
 813                                } else {
 814                                        set arr($name) $value
 815                                }
 816                        }
 817                }
 818        }
 819        default {
 820                upvar $arr_name arr
 821                array unset arr
 822                catch {
 823                        set fd_rc [eval [list git_read config --list] $args]
 824                        while {[gets $fd_rc line] >= 0} {
 825                                if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
 826                                        if {[is_many_config $name]} {
 827                                                lappend arr($name) $value
 828                                        } else {
 829                                                set arr($name) $value
 830                                        }
 831                                }
 832                        }
 833                        close $fd_rc
 834                }
 835        }
 836}
 837
 838proc load_config {include_global} {
 839        global repo_config global_config default_config
 840
 841        if {$include_global} {
 842                _parse_config global_config --global
 843        }
 844        _parse_config repo_config
 845
 846        foreach name [array names default_config] {
 847                if {[catch {set v $global_config($name)}]} {
 848                        set global_config($name) $default_config($name)
 849                }
 850                if {[catch {set v $repo_config($name)}]} {
 851                        set repo_config($name) $default_config($name)
 852                }
 853        }
 854}
 855
 856######################################################################
 857##
 858## feature option selection
 859
 860if {[regexp {^git-(.+)$} [file tail $argv0] _junk subcommand]} {
 861        unset _junk
 862} else {
 863        set subcommand gui
 864}
 865if {$subcommand eq {gui.sh}} {
 866        set subcommand gui
 867}
 868if {$subcommand eq {gui} && [llength $argv] > 0} {
 869        set subcommand [lindex $argv 0]
 870        set argv [lrange $argv 1 end]
 871}
 872
 873enable_option multicommit
 874enable_option branch
 875enable_option transport
 876disable_option bare
 877
 878switch -- $subcommand {
 879browser -
 880blame {
 881        enable_option bare
 882
 883        disable_option multicommit
 884        disable_option branch
 885        disable_option transport
 886}
 887citool {
 888        enable_option singlecommit
 889
 890        disable_option multicommit
 891        disable_option branch
 892        disable_option transport
 893}
 894}
 895
 896######################################################################
 897##
 898## repository setup
 899
 900if {[catch {
 901                set _gitdir $env(GIT_DIR)
 902                set _prefix {}
 903                }]
 904        && [catch {
 905                set _gitdir [git rev-parse --git-dir]
 906                set _prefix [git rev-parse --show-prefix]
 907        } err]} {
 908        load_config 1
 909        apply_config
 910        choose_repository::pick
 911}
 912if {![file isdirectory $_gitdir] && [is_Cygwin]} {
 913        catch {set _gitdir [exec cygpath --windows $_gitdir]}
 914}
 915if {![file isdirectory $_gitdir]} {
 916        catch {wm withdraw .}
 917        error_popup [strcat [mc "Git directory not found:"] "\n\n$_gitdir"]
 918        exit 1
 919}
 920if {$_prefix ne {}} {
 921        regsub -all {[^/]+/} $_prefix ../ cdup
 922        if {[catch {cd $cdup} err]} {
 923                catch {wm withdraw .}
 924                error_popup [strcat [mc "Cannot move to top of working directory:"] "\n\n$err"]
 925                exit 1
 926        }
 927        unset cdup
 928} elseif {![is_enabled bare]} {
 929        if {[lindex [file split $_gitdir] end] ne {.git}} {
 930                catch {wm withdraw .}
 931                error_popup [strcat [mc "Cannot use funny .git directory:"] "\n\n$_gitdir"]
 932                exit 1
 933        }
 934        if {[catch {cd [file dirname $_gitdir]} err]} {
 935                catch {wm withdraw .}
 936                error_popup [strcat [mc "No working directory"] " [file dirname $_gitdir]:\n\n$err"]
 937                exit 1
 938        }
 939}
 940set _reponame [file split [file normalize $_gitdir]]
 941if {[lindex $_reponame end] eq {.git}} {
 942        set _reponame [lindex $_reponame end-1]
 943} else {
 944        set _reponame [lindex $_reponame end]
 945}
 946
 947######################################################################
 948##
 949## global init
 950
 951set current_diff_path {}
 952set current_diff_side {}
 953set diff_actions [list]
 954
 955set HEAD {}
 956set PARENT {}
 957set MERGE_HEAD [list]
 958set commit_type {}
 959set empty_tree {}
 960set current_branch {}
 961set is_detached 0
 962set current_diff_path {}
 963set is_3way_diff 0
 964set selected_commit_type new
 965
 966######################################################################
 967##
 968## task management
 969
 970set rescan_active 0
 971set diff_active 0
 972set last_clicked {}
 973
 974set disable_on_lock [list]
 975set index_lock_type none
 976
 977proc lock_index {type} {
 978        global index_lock_type disable_on_lock
 979
 980        if {$index_lock_type eq {none}} {
 981                set index_lock_type $type
 982                foreach w $disable_on_lock {
 983                        uplevel #0 $w disabled
 984                }
 985                return 1
 986        } elseif {$index_lock_type eq "begin-$type"} {
 987                set index_lock_type $type
 988                return 1
 989        }
 990        return 0
 991}
 992
 993proc unlock_index {} {
 994        global index_lock_type disable_on_lock
 995
 996        set index_lock_type none
 997        foreach w $disable_on_lock {
 998                uplevel #0 $w normal
 999        }
1000}
1001
1002######################################################################
1003##
1004## status
1005
1006proc repository_state {ctvar hdvar mhvar} {
1007        global current_branch
1008        upvar $ctvar ct $hdvar hd $mhvar mh
1009
1010        set mh [list]
1011
1012        load_current_branch
1013        if {[catch {set hd [git rev-parse --verify HEAD]}]} {
1014                set hd {}
1015                set ct initial
1016                return
1017        }
1018
1019        set merge_head [gitdir MERGE_HEAD]
1020        if {[file exists $merge_head]} {
1021                set ct merge
1022                set fd_mh [open $merge_head r]
1023                while {[gets $fd_mh line] >= 0} {
1024                        lappend mh $line
1025                }
1026                close $fd_mh
1027                return
1028        }
1029
1030        set ct normal
1031}
1032
1033proc PARENT {} {
1034        global PARENT empty_tree
1035
1036        set p [lindex $PARENT 0]
1037        if {$p ne {}} {
1038                return $p
1039        }
1040        if {$empty_tree eq {}} {
1041                set empty_tree [git mktree << {}]
1042        }
1043        return $empty_tree
1044}
1045
1046proc rescan {after {honor_trustmtime 1}} {
1047        global HEAD PARENT MERGE_HEAD commit_type
1048        global ui_index ui_workdir ui_comm
1049        global rescan_active file_states
1050        global repo_config
1051
1052        if {$rescan_active > 0 || ![lock_index read]} return
1053
1054        repository_state newType newHEAD newMERGE_HEAD
1055        if {[string match amend* $commit_type]
1056                && $newType eq {normal}
1057                && $newHEAD eq $HEAD} {
1058        } else {
1059                set HEAD $newHEAD
1060                set PARENT $newHEAD
1061                set MERGE_HEAD $newMERGE_HEAD
1062                set commit_type $newType
1063        }
1064
1065        array unset file_states
1066
1067        if {!$::GITGUI_BCK_exists &&
1068                (![$ui_comm edit modified]
1069                || [string trim [$ui_comm get 0.0 end]] eq {})} {
1070                if {[string match amend* $commit_type]} {
1071                } elseif {[load_message GITGUI_MSG]} {
1072                } elseif {[load_message MERGE_MSG]} {
1073                } elseif {[load_message SQUASH_MSG]} {
1074                }
1075                $ui_comm edit reset
1076                $ui_comm edit modified false
1077        }
1078
1079        if {$honor_trustmtime && $repo_config(gui.trustmtime) eq {true}} {
1080                rescan_stage2 {} $after
1081        } else {
1082                set rescan_active 1
1083                ui_status [mc "Refreshing file status..."]
1084                set fd_rf [git_read update-index \
1085                        -q \
1086                        --unmerged \
1087                        --ignore-missing \
1088                        --refresh \
1089                        ]
1090                fconfigure $fd_rf -blocking 0 -translation binary
1091                fileevent $fd_rf readable \
1092                        [list rescan_stage2 $fd_rf $after]
1093        }
1094}
1095
1096if {[is_Cygwin]} {
1097        set is_git_info_link {}
1098        set is_git_info_exclude {}
1099        proc have_info_exclude {} {
1100                global is_git_info_link is_git_info_exclude
1101
1102                if {$is_git_info_link eq {}} {
1103                        set is_git_info_link [file isfile [gitdir info.lnk]]
1104                }
1105
1106                if {$is_git_info_link} {
1107                        if {$is_git_info_exclude eq {}} {
1108                                if {[catch {exec test -f [gitdir info exclude]}]} {
1109                                        set is_git_info_exclude 0
1110                                } else {
1111                                        set is_git_info_exclude 1
1112                                }
1113                        }
1114                        return $is_git_info_exclude
1115                } else {
1116                        return [file readable [gitdir info exclude]]
1117                }
1118        }
1119} else {
1120        proc have_info_exclude {} {
1121                return [file readable [gitdir info exclude]]
1122        }
1123}
1124
1125proc rescan_stage2 {fd after} {
1126        global rescan_active buf_rdi buf_rdf buf_rlo
1127
1128        if {$fd ne {}} {
1129                read $fd
1130                if {![eof $fd]} return
1131                close $fd
1132        }
1133
1134        set ls_others [list --exclude-per-directory=.gitignore]
1135        if {[have_info_exclude]} {
1136                lappend ls_others "--exclude-from=[gitdir info exclude]"
1137        }
1138        set user_exclude [get_config core.excludesfile]
1139        if {$user_exclude ne {} && [file readable $user_exclude]} {
1140                lappend ls_others "--exclude-from=$user_exclude"
1141        }
1142
1143        set buf_rdi {}
1144        set buf_rdf {}
1145        set buf_rlo {}
1146
1147        set rescan_active 3
1148        ui_status [mc "Scanning for modified files ..."]
1149        set fd_di [git_read diff-index --cached -z [PARENT]]
1150        set fd_df [git_read diff-files -z]
1151        set fd_lo [eval git_read ls-files --others -z $ls_others]
1152
1153        fconfigure $fd_di -blocking 0 -translation binary -encoding binary
1154        fconfigure $fd_df -blocking 0 -translation binary -encoding binary
1155        fconfigure $fd_lo -blocking 0 -translation binary -encoding binary
1156        fileevent $fd_di readable [list read_diff_index $fd_di $after]
1157        fileevent $fd_df readable [list read_diff_files $fd_df $after]
1158        fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
1159}
1160
1161proc load_message {file} {
1162        global ui_comm
1163
1164        set f [gitdir $file]
1165        if {[file isfile $f]} {
1166                if {[catch {set fd [open $f r]}]} {
1167                        return 0
1168                }
1169                fconfigure $fd -eofchar {}
1170                set content [string trim [read $fd]]
1171                close $fd
1172                regsub -all -line {[ \r\t]+$} $content {} content
1173                $ui_comm delete 0.0 end
1174                $ui_comm insert end $content
1175                return 1
1176        }
1177        return 0
1178}
1179
1180proc read_diff_index {fd after} {
1181        global buf_rdi
1182
1183        append buf_rdi [read $fd]
1184        set c 0
1185        set n [string length $buf_rdi]
1186        while {$c < $n} {
1187                set z1 [string first "\0" $buf_rdi $c]
1188                if {$z1 == -1} break
1189                incr z1
1190                set z2 [string first "\0" $buf_rdi $z1]
1191                if {$z2 == -1} break
1192
1193                incr c
1194                set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }]
1195                set p [string range $buf_rdi $z1 [expr {$z2 - 1}]]
1196                merge_state \
1197                        [encoding convertfrom $p] \
1198                        [lindex $i 4]? \
1199                        [list [lindex $i 0] [lindex $i 2]] \
1200                        [list]
1201                set c $z2
1202                incr c
1203        }
1204        if {$c < $n} {
1205                set buf_rdi [string range $buf_rdi $c end]
1206        } else {
1207                set buf_rdi {}
1208        }
1209
1210        rescan_done $fd buf_rdi $after
1211}
1212
1213proc read_diff_files {fd after} {
1214        global buf_rdf
1215
1216        append buf_rdf [read $fd]
1217        set c 0
1218        set n [string length $buf_rdf]
1219        while {$c < $n} {
1220                set z1 [string first "\0" $buf_rdf $c]
1221                if {$z1 == -1} break
1222                incr z1
1223                set z2 [string first "\0" $buf_rdf $z1]
1224                if {$z2 == -1} break
1225
1226                incr c
1227                set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }]
1228                set p [string range $buf_rdf $z1 [expr {$z2 - 1}]]
1229                merge_state \
1230                        [encoding convertfrom $p] \
1231                        ?[lindex $i 4] \
1232                        [list] \
1233                        [list [lindex $i 0] [lindex $i 2]]
1234                set c $z2
1235                incr c
1236        }
1237        if {$c < $n} {
1238                set buf_rdf [string range $buf_rdf $c end]
1239        } else {
1240                set buf_rdf {}
1241        }
1242
1243        rescan_done $fd buf_rdf $after
1244}
1245
1246proc read_ls_others {fd after} {
1247        global buf_rlo
1248
1249        append buf_rlo [read $fd]
1250        set pck [split $buf_rlo "\0"]
1251        set buf_rlo [lindex $pck end]
1252        foreach p [lrange $pck 0 end-1] {
1253                set p [encoding convertfrom $p]
1254                if {[string index $p end] eq {/}} {
1255                        set p [string range $p 0 end-1]
1256                }
1257                merge_state $p ?O
1258        }
1259        rescan_done $fd buf_rlo $after
1260}
1261
1262proc rescan_done {fd buf after} {
1263        global rescan_active current_diff_path
1264        global file_states repo_config
1265        upvar $buf to_clear
1266
1267        if {![eof $fd]} return
1268        set to_clear {}
1269        close $fd
1270        if {[incr rescan_active -1] > 0} return
1271
1272        prune_selection
1273        unlock_index
1274        display_all_files
1275        if {$current_diff_path ne {}} reshow_diff
1276        uplevel #0 $after
1277}
1278
1279proc prune_selection {} {
1280        global file_states selected_paths
1281
1282        foreach path [array names selected_paths] {
1283                if {[catch {set still_here $file_states($path)}]} {
1284                        unset selected_paths($path)
1285                }
1286        }
1287}
1288
1289######################################################################
1290##
1291## ui helpers
1292
1293proc mapicon {w state path} {
1294        global all_icons
1295
1296        if {[catch {set r $all_icons($state$w)}]} {
1297                puts "error: no icon for $w state={$state} $path"
1298                return file_plain
1299        }
1300        return $r
1301}
1302
1303proc mapdesc {state path} {
1304        global all_descs
1305
1306        if {[catch {set r $all_descs($state)}]} {
1307                puts "error: no desc for state={$state} $path"
1308                return $state
1309        }
1310        return $r
1311}
1312
1313proc ui_status {msg} {
1314        global main_status
1315        if {[info exists main_status]} {
1316                $main_status show $msg
1317        }
1318}
1319
1320proc ui_ready {{test {}}} {
1321        global main_status
1322        if {[info exists main_status]} {
1323                $main_status show [mc "Ready."] $test
1324        }
1325}
1326
1327proc escape_path {path} {
1328        regsub -all {\\} $path "\\\\" path
1329        regsub -all "\n" $path "\\n" path
1330        return $path
1331}
1332
1333proc short_path {path} {
1334        return [escape_path [lindex [file split $path] end]]
1335}
1336
1337set next_icon_id 0
1338set null_sha1 [string repeat 0 40]
1339
1340proc merge_state {path new_state {head_info {}} {index_info {}}} {
1341        global file_states next_icon_id null_sha1
1342
1343        set s0 [string index $new_state 0]
1344        set s1 [string index $new_state 1]
1345
1346        if {[catch {set info $file_states($path)}]} {
1347                set state __
1348                set icon n[incr next_icon_id]
1349        } else {
1350                set state [lindex $info 0]
1351                set icon [lindex $info 1]
1352                if {$head_info eq {}}  {set head_info  [lindex $info 2]}
1353                if {$index_info eq {}} {set index_info [lindex $info 3]}
1354        }
1355
1356        if     {$s0 eq {?}} {set s0 [string index $state 0]} \
1357        elseif {$s0 eq {_}} {set s0 _}
1358
1359        if     {$s1 eq {?}} {set s1 [string index $state 1]} \
1360        elseif {$s1 eq {_}} {set s1 _}
1361
1362        if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} {
1363                set head_info [list 0 $null_sha1]
1364        } elseif {$s0 ne {_} && [string index $state 0] eq {_}
1365                && $head_info eq {}} {
1366                set head_info $index_info
1367        }
1368
1369        set file_states($path) [list $s0$s1 $icon \
1370                $head_info $index_info \
1371                ]
1372        return $state
1373}
1374
1375proc display_file_helper {w path icon_name old_m new_m} {
1376        global file_lists
1377
1378        if {$new_m eq {_}} {
1379                set lno [lsearch -sorted -exact $file_lists($w) $path]
1380                if {$lno >= 0} {
1381                        set file_lists($w) [lreplace $file_lists($w) $lno $lno]
1382                        incr lno
1383                        $w conf -state normal
1384                        $w delete $lno.0 [expr {$lno + 1}].0
1385                        $w conf -state disabled
1386                }
1387        } elseif {$old_m eq {_} && $new_m ne {_}} {
1388                lappend file_lists($w) $path
1389                set file_lists($w) [lsort -unique $file_lists($w)]
1390                set lno [lsearch -sorted -exact $file_lists($w) $path]
1391                incr lno
1392                $w conf -state normal
1393                $w image create $lno.0 \
1394                        -align center -padx 5 -pady 1 \
1395                        -name $icon_name \
1396                        -image [mapicon $w $new_m $path]
1397                $w insert $lno.1 "[escape_path $path]\n"
1398                $w conf -state disabled
1399        } elseif {$old_m ne $new_m} {
1400                $w conf -state normal
1401                $w image conf $icon_name -image [mapicon $w $new_m $path]
1402                $w conf -state disabled
1403        }
1404}
1405
1406proc display_file {path state} {
1407        global file_states selected_paths
1408        global ui_index ui_workdir
1409
1410        set old_m [merge_state $path $state]
1411        set s $file_states($path)
1412        set new_m [lindex $s 0]
1413        set icon_name [lindex $s 1]
1414
1415        set o [string index $old_m 0]
1416        set n [string index $new_m 0]
1417        if {$o eq {U}} {
1418                set o _
1419        }
1420        if {$n eq {U}} {
1421                set n _
1422        }
1423        display_file_helper     $ui_index $path $icon_name $o $n
1424
1425        if {[string index $old_m 0] eq {U}} {
1426                set o U
1427        } else {
1428                set o [string index $old_m 1]
1429        }
1430        if {[string index $new_m 0] eq {U}} {
1431                set n U
1432        } else {
1433                set n [string index $new_m 1]
1434        }
1435        display_file_helper     $ui_workdir $path $icon_name $o $n
1436
1437        if {$new_m eq {__}} {
1438                unset file_states($path)
1439                catch {unset selected_paths($path)}
1440        }
1441}
1442
1443proc display_all_files_helper {w path icon_name m} {
1444        global file_lists
1445
1446        lappend file_lists($w) $path
1447        set lno [expr {[lindex [split [$w index end] .] 0] - 1}]
1448        $w image create end \
1449                -align center -padx 5 -pady 1 \
1450                -name $icon_name \
1451                -image [mapicon $w $m $path]
1452        $w insert end "[escape_path $path]\n"
1453}
1454
1455proc display_all_files {} {
1456        global ui_index ui_workdir
1457        global file_states file_lists
1458        global last_clicked
1459
1460        $ui_index conf -state normal
1461        $ui_workdir conf -state normal
1462
1463        $ui_index delete 0.0 end
1464        $ui_workdir delete 0.0 end
1465        set last_clicked {}
1466
1467        set file_lists($ui_index) [list]
1468        set file_lists($ui_workdir) [list]
1469
1470        foreach path [lsort [array names file_states]] {
1471                set s $file_states($path)
1472                set m [lindex $s 0]
1473                set icon_name [lindex $s 1]
1474
1475                set s [string index $m 0]
1476                if {$s ne {U} && $s ne {_}} {
1477                        display_all_files_helper $ui_index $path \
1478                                $icon_name $s
1479                }
1480
1481                if {[string index $m 0] eq {U}} {
1482                        set s U
1483                } else {
1484                        set s [string index $m 1]
1485                }
1486                if {$s ne {_}} {
1487                        display_all_files_helper $ui_workdir $path \
1488                                $icon_name $s
1489                }
1490        }
1491
1492        $ui_index conf -state disabled
1493        $ui_workdir conf -state disabled
1494}
1495
1496######################################################################
1497##
1498## icons
1499
1500set filemask {
1501#define mask_width 14
1502#define mask_height 15
1503static unsigned char mask_bits[] = {
1504   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1505   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1506   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
1507}
1508
1509image create bitmap file_plain -background white -foreground black -data {
1510#define plain_width 14
1511#define plain_height 15
1512static unsigned char plain_bits[] = {
1513   0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1514   0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
1515   0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1516} -maskdata $filemask
1517
1518image create bitmap file_mod -background white -foreground blue -data {
1519#define mod_width 14
1520#define mod_height 15
1521static unsigned char mod_bits[] = {
1522   0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1523   0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1524   0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1525} -maskdata $filemask
1526
1527image create bitmap file_fulltick -background white -foreground "#007000" -data {
1528#define file_fulltick_width 14
1529#define file_fulltick_height 15
1530static unsigned char file_fulltick_bits[] = {
1531   0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
1532   0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
1533   0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1534} -maskdata $filemask
1535
1536image create bitmap file_parttick -background white -foreground "#005050" -data {
1537#define parttick_width 14
1538#define parttick_height 15
1539static unsigned char parttick_bits[] = {
1540   0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1541   0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
1542   0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1543} -maskdata $filemask
1544
1545image create bitmap file_question -background white -foreground black -data {
1546#define file_question_width 14
1547#define file_question_height 15
1548static unsigned char file_question_bits[] = {
1549   0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
1550   0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
1551   0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1552} -maskdata $filemask
1553
1554image create bitmap file_removed -background white -foreground red -data {
1555#define file_removed_width 14
1556#define file_removed_height 15
1557static unsigned char file_removed_bits[] = {
1558   0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1559   0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
1560   0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
1561} -maskdata $filemask
1562
1563image create bitmap file_merge -background white -foreground blue -data {
1564#define file_merge_width 14
1565#define file_merge_height 15
1566static unsigned char file_merge_bits[] = {
1567   0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
1568   0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1569   0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1570} -maskdata $filemask
1571
1572set ui_index .vpane.files.index.list
1573set ui_workdir .vpane.files.workdir.list
1574
1575set all_icons(_$ui_index)   file_plain
1576set all_icons(A$ui_index)   file_fulltick
1577set all_icons(M$ui_index)   file_fulltick
1578set all_icons(D$ui_index)   file_removed
1579set all_icons(U$ui_index)   file_merge
1580
1581set all_icons(_$ui_workdir) file_plain
1582set all_icons(M$ui_workdir) file_mod
1583set all_icons(D$ui_workdir) file_question
1584set all_icons(U$ui_workdir) file_merge
1585set all_icons(O$ui_workdir) file_plain
1586
1587set max_status_desc 0
1588foreach i {
1589                {__ {mc "Unmodified"}}
1590
1591                {_M {mc "Modified, not staged"}}
1592                {M_ {mc "Staged for commit"}}
1593                {MM {mc "Portions staged for commit"}}
1594                {MD {mc "Staged for commit, missing"}}
1595
1596                {_O {mc "Untracked, not staged"}}
1597                {A_ {mc "Staged for commit"}}
1598                {AM {mc "Portions staged for commit"}}
1599                {AD {mc "Staged for commit, missing"}}
1600
1601                {_D {mc "Missing"}}
1602                {D_ {mc "Staged for removal"}}
1603                {DO {mc "Staged for removal, still present"}}
1604
1605                {U_ {mc "Requires merge resolution"}}
1606                {UU {mc "Requires merge resolution"}}
1607                {UM {mc "Requires merge resolution"}}
1608                {UD {mc "Requires merge resolution"}}
1609        } {
1610        set text [eval [lindex $i 1]]
1611        if {$max_status_desc < [string length $text]} {
1612                set max_status_desc [string length $text]
1613        }
1614        set all_descs([lindex $i 0]) $text
1615}
1616unset i
1617
1618######################################################################
1619##
1620## util
1621
1622proc scrollbar2many {list mode args} {
1623        foreach w $list {eval $w $mode $args}
1624}
1625
1626proc many2scrollbar {list mode sb top bottom} {
1627        $sb set $top $bottom
1628        foreach w $list {$w $mode moveto $top}
1629}
1630
1631proc incr_font_size {font {amt 1}} {
1632        set sz [font configure $font -size]
1633        incr sz $amt
1634        font configure $font -size $sz
1635        font configure ${font}bold -size $sz
1636        font configure ${font}italic -size $sz
1637}
1638
1639######################################################################
1640##
1641## ui commands
1642
1643set starting_gitk_msg [mc "Starting gitk... please wait..."]
1644
1645proc do_gitk {revs} {
1646        # -- Always start gitk through whatever we were loaded with.  This
1647        #    lets us bypass using shell process on Windows systems.
1648        #
1649        set exe [file join [file dirname $::_git] gitk]
1650        set cmd [list [info nameofexecutable] $exe]
1651        if {! [file exists $exe]} {
1652                error_popup [mc "Unable to start gitk:\n\n%s does not exist" $exe]
1653        } else {
1654                global env
1655
1656                if {[info exists env(GIT_DIR)]} {
1657                        set old_GIT_DIR $env(GIT_DIR)
1658                } else {
1659                        set old_GIT_DIR {}
1660                }
1661
1662                set pwd [pwd]
1663                cd [file dirname [gitdir]]
1664                set env(GIT_DIR) [file tail [gitdir]]
1665
1666                eval exec $cmd $revs &
1667
1668                if {$old_GIT_DIR eq {}} {
1669                        unset env(GIT_DIR)
1670                } else {
1671                        set env(GIT_DIR) $old_GIT_DIR
1672                }
1673                cd $pwd
1674
1675                ui_status $::starting_gitk_msg
1676                after 10000 {
1677                        ui_ready $starting_gitk_msg
1678                }
1679        }
1680}
1681
1682set is_quitting 0
1683
1684proc do_quit {} {
1685        global ui_comm is_quitting repo_config commit_type
1686        global GITGUI_BCK_exists GITGUI_BCK_i
1687        global ui_comm_spell
1688
1689        if {$is_quitting} return
1690        set is_quitting 1
1691
1692        if {[winfo exists $ui_comm]} {
1693                # -- Stash our current commit buffer.
1694                #
1695                set save [gitdir GITGUI_MSG]
1696                if {$GITGUI_BCK_exists && ![$ui_comm edit modified]} {
1697                        file rename -force [gitdir GITGUI_BCK] $save
1698                        set GITGUI_BCK_exists 0
1699                } else {
1700                        set msg [string trim [$ui_comm get 0.0 end]]
1701                        regsub -all -line {[ \r\t]+$} $msg {} msg
1702                        if {(![string match amend* $commit_type]
1703                                || [$ui_comm edit modified])
1704                                && $msg ne {}} {
1705                                catch {
1706                                        set fd [open $save w]
1707                                        puts -nonewline $fd $msg
1708                                        close $fd
1709                                }
1710                        } else {
1711                                catch {file delete $save}
1712                        }
1713                }
1714
1715                # -- Cancel our spellchecker if its running.
1716                #
1717                if {[info exists ui_comm_spell]} {
1718                        $ui_comm_spell stop
1719                }
1720
1721                # -- Remove our editor backup, its not needed.
1722                #
1723                after cancel $GITGUI_BCK_i
1724                if {$GITGUI_BCK_exists} {
1725                        catch {file delete [gitdir GITGUI_BCK]}
1726                }
1727
1728                # -- Stash our current window geometry into this repository.
1729                #
1730                set cfg_geometry [list]
1731                lappend cfg_geometry [wm geometry .]
1732                lappend cfg_geometry [lindex [.vpane sash coord 0] 0]
1733                lappend cfg_geometry [lindex [.vpane.files sash coord 0] 1]
1734                if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
1735                        set rc_geometry {}
1736                }
1737                if {$cfg_geometry ne $rc_geometry} {
1738                        catch {git config gui.geometry $cfg_geometry}
1739                }
1740        }
1741
1742        destroy .
1743}
1744
1745proc do_rescan {} {
1746        rescan ui_ready
1747}
1748
1749proc do_commit {} {
1750        commit_tree
1751}
1752
1753proc toggle_or_diff {w x y} {
1754        global file_states file_lists current_diff_path ui_index ui_workdir
1755        global last_clicked selected_paths
1756
1757        set pos [split [$w index @$x,$y] .]
1758        set lno [lindex $pos 0]
1759        set col [lindex $pos 1]
1760        set path [lindex $file_lists($w) [expr {$lno - 1}]]
1761        if {$path eq {}} {
1762                set last_clicked {}
1763                return
1764        }
1765
1766        set last_clicked [list $w $lno]
1767        array unset selected_paths
1768        $ui_index tag remove in_sel 0.0 end
1769        $ui_workdir tag remove in_sel 0.0 end
1770
1771        if {$col == 0} {
1772                if {$current_diff_path eq $path} {
1773                        set after {reshow_diff;}
1774                } else {
1775                        set after {}
1776                }
1777                if {$w eq $ui_index} {
1778                        update_indexinfo \
1779                                "Unstaging [short_path $path] from commit" \
1780                                [list $path] \
1781                                [concat $after [list ui_ready]]
1782                } elseif {$w eq $ui_workdir} {
1783                        update_index \
1784                                "Adding [short_path $path]" \
1785                                [list $path] \
1786                                [concat $after [list ui_ready]]
1787                }
1788        } else {
1789                show_diff $path $w $lno
1790        }
1791}
1792
1793proc add_one_to_selection {w x y} {
1794        global file_lists last_clicked selected_paths
1795
1796        set lno [lindex [split [$w index @$x,$y] .] 0]
1797        set path [lindex $file_lists($w) [expr {$lno - 1}]]
1798        if {$path eq {}} {
1799                set last_clicked {}
1800                return
1801        }
1802
1803        if {$last_clicked ne {}
1804                && [lindex $last_clicked 0] ne $w} {
1805                array unset selected_paths
1806                [lindex $last_clicked 0] tag remove in_sel 0.0 end
1807        }
1808
1809        set last_clicked [list $w $lno]
1810        if {[catch {set in_sel $selected_paths($path)}]} {
1811                set in_sel 0
1812        }
1813        if {$in_sel} {
1814                unset selected_paths($path)
1815                $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
1816        } else {
1817                set selected_paths($path) 1
1818                $w tag add in_sel $lno.0 [expr {$lno + 1}].0
1819        }
1820}
1821
1822proc add_range_to_selection {w x y} {
1823        global file_lists last_clicked selected_paths
1824
1825        if {[lindex $last_clicked 0] ne $w} {
1826                toggle_or_diff $w $x $y
1827                return
1828        }
1829
1830        set lno [lindex [split [$w index @$x,$y] .] 0]
1831        set lc [lindex $last_clicked 1]
1832        if {$lc < $lno} {
1833                set begin $lc
1834                set end $lno
1835        } else {
1836                set begin $lno
1837                set end $lc
1838        }
1839
1840        foreach path [lrange $file_lists($w) \
1841                [expr {$begin - 1}] \
1842                [expr {$end - 1}]] {
1843                set selected_paths($path) 1
1844        }
1845        $w tag add in_sel $begin.0 [expr {$end + 1}].0
1846}
1847
1848######################################################################
1849##
1850## ui construction
1851
1852load_config 0
1853apply_config
1854set ui_comm {}
1855
1856# -- Menu Bar
1857#
1858menu .mbar -tearoff 0
1859.mbar add cascade -label [mc Repository] -menu .mbar.repository
1860.mbar add cascade -label [mc Edit] -menu .mbar.edit
1861if {[is_enabled branch]} {
1862        .mbar add cascade -label [mc Branch] -menu .mbar.branch
1863}
1864if {[is_enabled multicommit] || [is_enabled singlecommit]} {
1865        .mbar add cascade -label [mc Commit@@noun] -menu .mbar.commit
1866}
1867if {[is_enabled transport]} {
1868        .mbar add cascade -label [mc Merge] -menu .mbar.merge
1869        .mbar add cascade -label [mc Remote] -menu .mbar.remote
1870}
1871. configure -menu .mbar
1872
1873# -- Repository Menu
1874#
1875menu .mbar.repository
1876
1877.mbar.repository add command \
1878        -label [mc "Browse Current Branch's Files"] \
1879        -command {browser::new $current_branch}
1880set ui_browse_current [.mbar.repository index last]
1881.mbar.repository add command \
1882        -label [mc "Browse Branch Files..."] \
1883        -command browser_open::dialog
1884.mbar.repository add separator
1885
1886.mbar.repository add command \
1887        -label [mc "Visualize Current Branch's History"] \
1888        -command {do_gitk $current_branch}
1889set ui_visualize_current [.mbar.repository index last]
1890.mbar.repository add command \
1891        -label [mc "Visualize All Branch History"] \
1892        -command {do_gitk --all}
1893.mbar.repository add separator
1894
1895proc current_branch_write {args} {
1896        global current_branch
1897        .mbar.repository entryconf $::ui_browse_current \
1898                -label [mc "Browse %s's Files" $current_branch]
1899        .mbar.repository entryconf $::ui_visualize_current \
1900                -label [mc "Visualize %s's History" $current_branch]
1901}
1902trace add variable current_branch write current_branch_write
1903
1904if {[is_enabled multicommit]} {
1905        .mbar.repository add command -label [mc "Database Statistics"] \
1906                -command do_stats
1907
1908        .mbar.repository add command -label [mc "Compress Database"] \
1909                -command do_gc
1910
1911        .mbar.repository add command -label [mc "Verify Database"] \
1912                -command do_fsck_objects
1913
1914        .mbar.repository add separator
1915
1916        if {[is_Cygwin]} {
1917                .mbar.repository add command \
1918                        -label [mc "Create Desktop Icon"] \
1919                        -command do_cygwin_shortcut
1920        } elseif {[is_Windows]} {
1921                .mbar.repository add command \
1922                        -label [mc "Create Desktop Icon"] \
1923                        -command do_windows_shortcut
1924        } elseif {[is_MacOSX]} {
1925                .mbar.repository add command \
1926                        -label [mc "Create Desktop Icon"] \
1927                        -command do_macosx_app
1928        }
1929}
1930
1931.mbar.repository add command -label [mc Quit] \
1932        -command do_quit \
1933        -accelerator $M1T-Q
1934
1935# -- Edit Menu
1936#
1937menu .mbar.edit
1938.mbar.edit add command -label [mc Undo] \
1939        -command {catch {[focus] edit undo}} \
1940        -accelerator $M1T-Z
1941.mbar.edit add command -label [mc Redo] \
1942        -command {catch {[focus] edit redo}} \
1943        -accelerator $M1T-Y
1944.mbar.edit add separator
1945.mbar.edit add command -label [mc Cut] \
1946        -command {catch {tk_textCut [focus]}} \
1947        -accelerator $M1T-X
1948.mbar.edit add command -label [mc Copy] \
1949        -command {catch {tk_textCopy [focus]}} \
1950        -accelerator $M1T-C
1951.mbar.edit add command -label [mc Paste] \
1952        -command {catch {tk_textPaste [focus]; [focus] see insert}} \
1953        -accelerator $M1T-V
1954.mbar.edit add command -label [mc Delete] \
1955        -command {catch {[focus] delete sel.first sel.last}} \
1956        -accelerator Del
1957.mbar.edit add separator
1958.mbar.edit add command -label [mc "Select All"] \
1959        -command {catch {[focus] tag add sel 0.0 end}} \
1960        -accelerator $M1T-A
1961
1962# -- Branch Menu
1963#
1964if {[is_enabled branch]} {
1965        menu .mbar.branch
1966
1967        .mbar.branch add command -label [mc "Create..."] \
1968                -command branch_create::dialog \
1969                -accelerator $M1T-N
1970        lappend disable_on_lock [list .mbar.branch entryconf \
1971                [.mbar.branch index last] -state]
1972
1973        .mbar.branch add command -label [mc "Checkout..."] \
1974                -command branch_checkout::dialog \
1975                -accelerator $M1T-O
1976        lappend disable_on_lock [list .mbar.branch entryconf \
1977                [.mbar.branch index last] -state]
1978
1979        .mbar.branch add command -label [mc "Rename..."] \
1980                -command branch_rename::dialog
1981        lappend disable_on_lock [list .mbar.branch entryconf \
1982                [.mbar.branch index last] -state]
1983
1984        .mbar.branch add command -label [mc "Delete..."] \
1985                -command branch_delete::dialog
1986        lappend disable_on_lock [list .mbar.branch entryconf \
1987                [.mbar.branch index last] -state]
1988
1989        .mbar.branch add command -label [mc "Reset..."] \
1990                -command merge::reset_hard
1991        lappend disable_on_lock [list .mbar.branch entryconf \
1992                [.mbar.branch index last] -state]
1993}
1994
1995# -- Commit Menu
1996#
1997if {[is_enabled multicommit] || [is_enabled singlecommit]} {
1998        menu .mbar.commit
1999
2000        .mbar.commit add radiobutton \
2001                -label [mc "New Commit"] \
2002                -command do_select_commit_type \
2003                -variable selected_commit_type \
2004                -value new
2005        lappend disable_on_lock \
2006                [list .mbar.commit entryconf [.mbar.commit index last] -state]
2007
2008        .mbar.commit add radiobutton \
2009                -label [mc "Amend Last Commit"] \
2010                -command do_select_commit_type \
2011                -variable selected_commit_type \
2012                -value amend
2013        lappend disable_on_lock \
2014                [list .mbar.commit entryconf [.mbar.commit index last] -state]
2015
2016        .mbar.commit add separator
2017
2018        .mbar.commit add command -label [mc Rescan] \
2019                -command do_rescan \
2020                -accelerator F5
2021        lappend disable_on_lock \
2022                [list .mbar.commit entryconf [.mbar.commit index last] -state]
2023
2024        .mbar.commit add command -label [mc "Stage To Commit"] \
2025                -command do_add_selection \
2026                -accelerator $M1T-T
2027        lappend disable_on_lock \
2028                [list .mbar.commit entryconf [.mbar.commit index last] -state]
2029
2030        .mbar.commit add command -label [mc "Stage Changed Files To Commit"] \
2031                -command do_add_all \
2032                -accelerator $M1T-I
2033        lappend disable_on_lock \
2034                [list .mbar.commit entryconf [.mbar.commit index last] -state]
2035
2036        .mbar.commit add command -label [mc "Unstage From Commit"] \
2037                -command do_unstage_selection
2038        lappend disable_on_lock \
2039                [list .mbar.commit entryconf [.mbar.commit index last] -state]
2040
2041        .mbar.commit add command -label [mc "Revert Changes"] \
2042                -command do_revert_selection
2043        lappend disable_on_lock \
2044                [list .mbar.commit entryconf [.mbar.commit index last] -state]
2045
2046        .mbar.commit add separator
2047
2048        .mbar.commit add command -label [mc "Sign Off"] \
2049                -command do_signoff \
2050                -accelerator $M1T-S
2051
2052        .mbar.commit add command -label [mc Commit@@verb] \
2053                -command do_commit \
2054                -accelerator $M1T-Return
2055        lappend disable_on_lock \
2056                [list .mbar.commit entryconf [.mbar.commit index last] -state]
2057}
2058
2059# -- Merge Menu
2060#
2061if {[is_enabled branch]} {
2062        menu .mbar.merge
2063        .mbar.merge add command -label [mc "Local Merge..."] \
2064                -command merge::dialog \
2065                -accelerator $M1T-M
2066        lappend disable_on_lock \
2067                [list .mbar.merge entryconf [.mbar.merge index last] -state]
2068        .mbar.merge add command -label [mc "Abort Merge..."] \
2069                -command merge::reset_hard
2070        lappend disable_on_lock \
2071                [list .mbar.merge entryconf [.mbar.merge index last] -state]
2072}
2073
2074# -- Transport Menu
2075#
2076if {[is_enabled transport]} {
2077        menu .mbar.remote
2078
2079        .mbar.remote add command \
2080                -label [mc "Push..."] \
2081                -command do_push_anywhere \
2082                -accelerator $M1T-P
2083        .mbar.remote add command \
2084                -label [mc "Delete..."] \
2085                -command remote_branch_delete::dialog
2086}
2087
2088if {[is_MacOSX]} {
2089        # -- Apple Menu (Mac OS X only)
2090        #
2091        .mbar add cascade -label [mc Apple] -menu .mbar.apple
2092        menu .mbar.apple
2093
2094        .mbar.apple add command -label [mc "About %s" [appname]] \
2095                -command do_about
2096        .mbar.apple add separator
2097        .mbar.apple add command \
2098                -label [mc "Preferences..."] \
2099                -command do_options \
2100                -accelerator $M1T-,
2101        bind . <$M1B-,> do_options
2102} else {
2103        # -- Edit Menu
2104        #
2105        .mbar.edit add separator
2106        .mbar.edit add command -label [mc "Options..."] \
2107                -command do_options
2108}
2109
2110# -- Help Menu
2111#
2112.mbar add cascade -label [mc Help] -menu .mbar.help
2113menu .mbar.help
2114
2115if {![is_MacOSX]} {
2116        .mbar.help add command -label [mc "About %s" [appname]] \
2117                -command do_about
2118}
2119
2120set browser {}
2121catch {set browser $repo_config(instaweb.browser)}
2122set doc_path [file dirname [gitexec]]
2123set doc_path [file join $doc_path Documentation index.html]
2124
2125if {[is_Cygwin]} {
2126        set doc_path [exec cygpath --mixed $doc_path]
2127}
2128
2129if {$browser eq {}} {
2130        if {[is_MacOSX]} {
2131                set browser open
2132        } elseif {[is_Cygwin]} {
2133                set program_files [file dirname [exec cygpath --windir]]
2134                set program_files [file join $program_files {Program Files}]
2135                set firefox [file join $program_files {Mozilla Firefox} firefox.exe]
2136                set ie [file join $program_files {Internet Explorer} IEXPLORE.EXE]
2137                if {[file exists $firefox]} {
2138                        set browser $firefox
2139                } elseif {[file exists $ie]} {
2140                        set browser $ie
2141                }
2142                unset program_files firefox ie
2143        }
2144}
2145
2146if {[file isfile $doc_path]} {
2147        set doc_url "file:$doc_path"
2148} else {
2149        set doc_url {http://www.kernel.org/pub/software/scm/git/docs/}
2150}
2151
2152if {$browser ne {}} {
2153        .mbar.help add command -label [mc "Online Documentation"] \
2154                -command [list exec $browser $doc_url &]
2155}
2156unset browser doc_path doc_url
2157
2158# -- Standard bindings
2159#
2160wm protocol . WM_DELETE_WINDOW do_quit
2161bind all <$M1B-Key-q> do_quit
2162bind all <$M1B-Key-Q> do_quit
2163bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
2164bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
2165
2166set subcommand_args {}
2167proc usage {} {
2168        puts stderr "usage: $::argv0 $::subcommand $::subcommand_args"
2169        exit 1
2170}
2171
2172# -- Not a normal commit type invocation?  Do that instead!
2173#
2174switch -- $subcommand {
2175browser -
2176blame {
2177        set subcommand_args {rev? path}
2178        if {$argv eq {}} usage
2179        set head {}
2180        set path {}
2181        set is_path 0
2182        foreach a $argv {
2183                if {$is_path || [file exists $_prefix$a]} {
2184                        if {$path ne {}} usage
2185                        set path $_prefix$a
2186                        break
2187                } elseif {$a eq {--}} {
2188                        if {$path ne {}} {
2189                                if {$head ne {}} usage
2190                                set head $path
2191                                set path {}
2192                        }
2193                        set is_path 1
2194                } elseif {$head eq {}} {
2195                        if {$head ne {}} usage
2196                        set head $a
2197                        set is_path 1
2198                } else {
2199                        usage
2200                }
2201        }
2202        unset is_path
2203
2204        if {$head ne {} && $path eq {}} {
2205                set path $_prefix$head
2206                set head {}
2207        }
2208
2209        if {$head eq {}} {
2210                load_current_branch
2211        } else {
2212                if {[regexp {^[0-9a-f]{1,39}$} $head]} {
2213                        if {[catch {
2214                                        set head [git rev-parse --verify $head]
2215                                } err]} {
2216                                puts stderr $err
2217                                exit 1
2218                        }
2219                }
2220                set current_branch $head
2221        }
2222
2223        switch -- $subcommand {
2224        browser {
2225                if {$head eq {}} {
2226                        if {$path ne {} && [file isdirectory $path]} {
2227                                set head $current_branch
2228                        } else {
2229                                set head $path
2230                                set path {}
2231                        }
2232                }
2233                browser::new $head $path
2234        }
2235        blame   {
2236                if {$head eq {} && ![file exists $path]} {
2237                        puts stderr [mc "fatal: cannot stat path %s: No such file or directory" $path]
2238                        exit 1
2239                }
2240                blame::new $head $path
2241        }
2242        }
2243        return
2244}
2245citool -
2246gui {
2247        if {[llength $argv] != 0} {
2248                puts -nonewline stderr "usage: $argv0"
2249                if {$subcommand ne {gui}
2250                        && [file tail $argv0] ne "git-$subcommand"} {
2251                        puts -nonewline stderr " $subcommand"
2252                }
2253                puts stderr {}
2254                exit 1
2255        }
2256        # fall through to setup UI for commits
2257}
2258default {
2259        puts stderr "usage: $argv0 \[{blame|browser|citool}\]"
2260        exit 1
2261}
2262}
2263
2264# -- Branch Control
2265#
2266frame .branch \
2267        -borderwidth 1 \
2268        -relief sunken
2269label .branch.l1 \
2270        -text [mc "Current Branch:"] \
2271        -anchor w \
2272        -justify left
2273label .branch.cb \
2274        -textvariable current_branch \
2275        -anchor w \
2276        -justify left
2277pack .branch.l1 -side left
2278pack .branch.cb -side left -fill x
2279pack .branch -side top -fill x
2280
2281# -- Main Window Layout
2282#
2283panedwindow .vpane -orient horizontal
2284panedwindow .vpane.files -orient vertical
2285.vpane add .vpane.files -sticky nsew -height 100 -width 200
2286pack .vpane -anchor n -side top -fill both -expand 1
2287
2288# -- Index File List
2289#
2290frame .vpane.files.index -height 100 -width 200
2291label .vpane.files.index.title -text [mc "Staged Changes (Will Commit)"] \
2292        -background lightgreen
2293text $ui_index -background white -borderwidth 0 \
2294        -width 20 -height 10 \
2295        -wrap none \
2296        -cursor $cursor_ptr \
2297        -xscrollcommand {.vpane.files.index.sx set} \
2298        -yscrollcommand {.vpane.files.index.sy set} \
2299        -state disabled
2300scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview]
2301scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview]
2302pack .vpane.files.index.title -side top -fill x
2303pack .vpane.files.index.sx -side bottom -fill x
2304pack .vpane.files.index.sy -side right -fill y
2305pack $ui_index -side left -fill both -expand 1
2306
2307# -- Working Directory File List
2308#
2309frame .vpane.files.workdir -height 100 -width 200
2310label .vpane.files.workdir.title -text [mc "Unstaged Changes"] \
2311        -background lightsalmon
2312text $ui_workdir -background white -borderwidth 0 \
2313        -width 20 -height 10 \
2314        -wrap none \
2315        -cursor $cursor_ptr \
2316        -xscrollcommand {.vpane.files.workdir.sx set} \
2317        -yscrollcommand {.vpane.files.workdir.sy set} \
2318        -state disabled
2319scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview]
2320scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview]
2321pack .vpane.files.workdir.title -side top -fill x
2322pack .vpane.files.workdir.sx -side bottom -fill x
2323pack .vpane.files.workdir.sy -side right -fill y
2324pack $ui_workdir -side left -fill both -expand 1
2325
2326.vpane.files add .vpane.files.workdir -sticky nsew
2327.vpane.files add .vpane.files.index -sticky nsew
2328
2329foreach i [list $ui_index $ui_workdir] {
2330        rmsel_tag $i
2331        $i tag conf in_diff -background [$i tag cget in_sel -background]
2332}
2333unset i
2334
2335# -- Diff and Commit Area
2336#
2337frame .vpane.lower -height 300 -width 400
2338frame .vpane.lower.commarea
2339frame .vpane.lower.diff -relief sunken -borderwidth 1
2340pack .vpane.lower.diff -fill both -expand 1
2341pack .vpane.lower.commarea -side bottom -fill x
2342.vpane add .vpane.lower -sticky nsew
2343
2344# -- Commit Area Buttons
2345#
2346frame .vpane.lower.commarea.buttons
2347label .vpane.lower.commarea.buttons.l -text {} \
2348        -anchor w \
2349        -justify left
2350pack .vpane.lower.commarea.buttons.l -side top -fill x
2351pack .vpane.lower.commarea.buttons -side left -fill y
2352
2353button .vpane.lower.commarea.buttons.rescan -text [mc Rescan] \
2354        -command do_rescan
2355pack .vpane.lower.commarea.buttons.rescan -side top -fill x
2356lappend disable_on_lock \
2357        {.vpane.lower.commarea.buttons.rescan conf -state}
2358
2359button .vpane.lower.commarea.buttons.incall -text [mc "Stage Changed"] \
2360        -command do_add_all
2361pack .vpane.lower.commarea.buttons.incall -side top -fill x
2362lappend disable_on_lock \
2363        {.vpane.lower.commarea.buttons.incall conf -state}
2364
2365button .vpane.lower.commarea.buttons.signoff -text [mc "Sign Off"] \
2366        -command do_signoff
2367pack .vpane.lower.commarea.buttons.signoff -side top -fill x
2368
2369button .vpane.lower.commarea.buttons.commit -text [mc Commit@@verb] \
2370        -command do_commit
2371pack .vpane.lower.commarea.buttons.commit -side top -fill x
2372lappend disable_on_lock \
2373        {.vpane.lower.commarea.buttons.commit conf -state}
2374
2375button .vpane.lower.commarea.buttons.push -text [mc Push] \
2376        -command do_push_anywhere
2377pack .vpane.lower.commarea.buttons.push -side top -fill x
2378
2379# -- Commit Message Buffer
2380#
2381frame .vpane.lower.commarea.buffer
2382frame .vpane.lower.commarea.buffer.header
2383set ui_comm .vpane.lower.commarea.buffer.t
2384set ui_coml .vpane.lower.commarea.buffer.header.l
2385radiobutton .vpane.lower.commarea.buffer.header.new \
2386        -text [mc "New Commit"] \
2387        -command do_select_commit_type \
2388        -variable selected_commit_type \
2389        -value new
2390lappend disable_on_lock \
2391        [list .vpane.lower.commarea.buffer.header.new conf -state]
2392radiobutton .vpane.lower.commarea.buffer.header.amend \
2393        -text [mc "Amend Last Commit"] \
2394        -command do_select_commit_type \
2395        -variable selected_commit_type \
2396        -value amend
2397lappend disable_on_lock \
2398        [list .vpane.lower.commarea.buffer.header.amend conf -state]
2399label $ui_coml \
2400        -anchor w \
2401        -justify left
2402proc trace_commit_type {varname args} {
2403        global ui_coml commit_type
2404        switch -glob -- $commit_type {
2405        initial       {set txt [mc "Initial Commit Message:"]}
2406        amend         {set txt [mc "Amended Commit Message:"]}
2407        amend-initial {set txt [mc "Amended Initial Commit Message:"]}
2408        amend-merge   {set txt [mc "Amended Merge Commit Message:"]}
2409        merge         {set txt [mc "Merge Commit Message:"]}
2410        *             {set txt [mc "Commit Message:"]}
2411        }
2412        $ui_coml conf -text $txt
2413}
2414trace add variable commit_type write trace_commit_type
2415pack $ui_coml -side left -fill x
2416pack .vpane.lower.commarea.buffer.header.amend -side right
2417pack .vpane.lower.commarea.buffer.header.new -side right
2418
2419text $ui_comm -background white -borderwidth 1 \
2420        -undo true \
2421        -maxundo 20 \
2422        -autoseparators true \
2423        -relief sunken \
2424        -width 75 -height 9 -wrap none \
2425        -font font_diff \
2426        -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
2427scrollbar .vpane.lower.commarea.buffer.sby \
2428        -command [list $ui_comm yview]
2429pack .vpane.lower.commarea.buffer.header -side top -fill x
2430pack .vpane.lower.commarea.buffer.sby -side right -fill y
2431pack $ui_comm -side left -fill y
2432pack .vpane.lower.commarea.buffer -side left -fill y
2433
2434# -- Commit Message Buffer Context Menu
2435#
2436set ctxm .vpane.lower.commarea.buffer.ctxm
2437menu $ctxm -tearoff 0
2438$ctxm add command \
2439        -label [mc Cut] \
2440        -command {tk_textCut $ui_comm}
2441$ctxm add command \
2442        -label [mc Copy] \
2443        -command {tk_textCopy $ui_comm}
2444$ctxm add command \
2445        -label [mc Paste] \
2446        -command {tk_textPaste $ui_comm}
2447$ctxm add command \
2448        -label [mc Delete] \
2449        -command {$ui_comm delete sel.first sel.last}
2450$ctxm add separator
2451$ctxm add command \
2452        -label [mc "Select All"] \
2453        -command {focus $ui_comm;$ui_comm tag add sel 0.0 end}
2454$ctxm add command \
2455        -label [mc "Copy All"] \
2456        -command {
2457                $ui_comm tag add sel 0.0 end
2458                tk_textCopy $ui_comm
2459                $ui_comm tag remove sel 0.0 end
2460        }
2461$ctxm add separator
2462$ctxm add command \
2463        -label [mc "Sign Off"] \
2464        -command do_signoff
2465set ui_comm_ctxm $ctxm
2466
2467# -- Diff Header
2468#
2469proc trace_current_diff_path {varname args} {
2470        global current_diff_path diff_actions file_states
2471        if {$current_diff_path eq {}} {
2472                set s {}
2473                set f {}
2474                set p {}
2475                set o disabled
2476        } else {
2477                set p $current_diff_path
2478                set s [mapdesc [lindex $file_states($p) 0] $p]
2479                set f [mc "File:"]
2480                set p [escape_path $p]
2481                set o normal
2482        }
2483
2484        .vpane.lower.diff.header.status configure -text $s
2485        .vpane.lower.diff.header.file configure -text $f
2486        .vpane.lower.diff.header.path configure -text $p
2487        foreach w $diff_actions {
2488                uplevel #0 $w $o
2489        }
2490}
2491trace add variable current_diff_path write trace_current_diff_path
2492
2493frame .vpane.lower.diff.header -background gold
2494label .vpane.lower.diff.header.status \
2495        -background gold \
2496        -width $max_status_desc \
2497        -anchor w \
2498        -justify left
2499label .vpane.lower.diff.header.file \
2500        -background gold \
2501        -anchor w \
2502        -justify left
2503label .vpane.lower.diff.header.path \
2504        -background gold \
2505        -anchor w \
2506        -justify left
2507pack .vpane.lower.diff.header.status -side left
2508pack .vpane.lower.diff.header.file -side left
2509pack .vpane.lower.diff.header.path -fill x
2510set ctxm .vpane.lower.diff.header.ctxm
2511menu $ctxm -tearoff 0
2512$ctxm add command \
2513        -label [mc Copy] \
2514        -command {
2515                clipboard clear
2516                clipboard append \
2517                        -format STRING \
2518                        -type STRING \
2519                        -- $current_diff_path
2520        }
2521lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2522bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
2523
2524# -- Diff Body
2525#
2526frame .vpane.lower.diff.body
2527set ui_diff .vpane.lower.diff.body.t
2528text $ui_diff -background white -borderwidth 0 \
2529        -width 80 -height 15 -wrap none \
2530        -font font_diff \
2531        -xscrollcommand {.vpane.lower.diff.body.sbx set} \
2532        -yscrollcommand {.vpane.lower.diff.body.sby set} \
2533        -state disabled
2534scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
2535        -command [list $ui_diff xview]
2536scrollbar .vpane.lower.diff.body.sby -orient vertical \
2537        -command [list $ui_diff yview]
2538pack .vpane.lower.diff.body.sbx -side bottom -fill x
2539pack .vpane.lower.diff.body.sby -side right -fill y
2540pack $ui_diff -side left -fill both -expand 1
2541pack .vpane.lower.diff.header -side top -fill x
2542pack .vpane.lower.diff.body -side bottom -fill both -expand 1
2543
2544$ui_diff tag conf d_cr -elide true
2545$ui_diff tag conf d_@ -foreground blue -font font_diffbold
2546$ui_diff tag conf d_+ -foreground {#00a000}
2547$ui_diff tag conf d_- -foreground red
2548
2549$ui_diff tag conf d_++ -foreground {#00a000}
2550$ui_diff tag conf d_-- -foreground red
2551$ui_diff tag conf d_+s \
2552        -foreground {#00a000} \
2553        -background {#e2effa}
2554$ui_diff tag conf d_-s \
2555        -foreground red \
2556        -background {#e2effa}
2557$ui_diff tag conf d_s+ \
2558        -foreground {#00a000} \
2559        -background ivory1
2560$ui_diff tag conf d_s- \
2561        -foreground red \
2562        -background ivory1
2563
2564$ui_diff tag conf d<<<<<<< \
2565        -foreground orange \
2566        -font font_diffbold
2567$ui_diff tag conf d======= \
2568        -foreground orange \
2569        -font font_diffbold
2570$ui_diff tag conf d>>>>>>> \
2571        -foreground orange \
2572        -font font_diffbold
2573
2574$ui_diff tag raise sel
2575
2576# -- Diff Body Context Menu
2577#
2578set ctxm .vpane.lower.diff.body.ctxm
2579menu $ctxm -tearoff 0
2580$ctxm add command \
2581        -label [mc "Apply/Reverse Hunk"] \
2582        -command {apply_hunk $cursorX $cursorY}
2583set ui_diff_applyhunk [$ctxm index last]
2584lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
2585$ctxm add separator
2586$ctxm add command \
2587        -label [mc "Show Less Context"] \
2588        -command {if {$repo_config(gui.diffcontext) >= 1} {
2589                incr repo_config(gui.diffcontext) -1
2590                reshow_diff
2591        }}
2592lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2593$ctxm add command \
2594        -label [mc "Show More Context"] \
2595        -command {if {$repo_config(gui.diffcontext) < 99} {
2596                incr repo_config(gui.diffcontext)
2597                reshow_diff
2598        }}
2599lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2600$ctxm add separator
2601$ctxm add command \
2602        -label [mc Refresh] \
2603        -command reshow_diff
2604lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2605$ctxm add command \
2606        -label [mc Copy] \
2607        -command {tk_textCopy $ui_diff}
2608lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2609$ctxm add command \
2610        -label [mc "Select All"] \
2611        -command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
2612lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2613$ctxm add command \
2614        -label [mc "Copy All"] \
2615        -command {
2616                $ui_diff tag add sel 0.0 end
2617                tk_textCopy $ui_diff
2618                $ui_diff tag remove sel 0.0 end
2619        }
2620lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2621$ctxm add separator
2622$ctxm add command \
2623        -label [mc "Decrease Font Size"] \
2624        -command {incr_font_size font_diff -1}
2625lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2626$ctxm add command \
2627        -label [mc "Increase Font Size"] \
2628        -command {incr_font_size font_diff 1}
2629lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2630$ctxm add separator
2631$ctxm add command -label [mc "Options..."] \
2632        -command do_options
2633proc popup_diff_menu {ctxm x y X Y} {
2634        global current_diff_path file_states
2635        set ::cursorX $x
2636        set ::cursorY $y
2637        if {$::ui_index eq $::current_diff_side} {
2638                set l [mc "Unstage Hunk From Commit"]
2639        } else {
2640                set l [mc "Stage Hunk For Commit"]
2641        }
2642        if {$::is_3way_diff
2643                || $current_diff_path eq {}
2644                || ![info exists file_states($current_diff_path)]
2645                || {_O} eq [lindex $file_states($current_diff_path) 0]} {
2646                set s disabled
2647        } else {
2648                set s normal
2649        }
2650        $ctxm entryconf $::ui_diff_applyhunk -state $s -label $l
2651        tk_popup $ctxm $X $Y
2652}
2653bind_button3 $ui_diff [list popup_diff_menu $ctxm %x %y %X %Y]
2654
2655# -- Status Bar
2656#
2657set main_status [::status_bar::new .status]
2658pack .status -anchor w -side bottom -fill x
2659$main_status show [mc "Initializing..."]
2660
2661# -- Load geometry
2662#
2663catch {
2664set gm $repo_config(gui.geometry)
2665wm geometry . [lindex $gm 0]
2666.vpane sash place 0 \
2667        [lindex $gm 1] \
2668        [lindex [.vpane sash coord 0] 1]
2669.vpane.files sash place 0 \
2670        [lindex [.vpane.files sash coord 0] 0] \
2671        [lindex $gm 2]
2672unset gm
2673}
2674
2675# -- Key Bindings
2676#
2677bind $ui_comm <$M1B-Key-Return> {do_commit;break}
2678bind $ui_comm <$M1B-Key-t> {do_add_selection;break}
2679bind $ui_comm <$M1B-Key-T> {do_add_selection;break}
2680bind $ui_comm <$M1B-Key-i> {do_add_all;break}
2681bind $ui_comm <$M1B-Key-I> {do_add_all;break}
2682bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
2683bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
2684bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
2685bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
2686bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
2687bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
2688bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2689bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2690
2691bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
2692bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
2693bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
2694bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
2695bind $ui_diff <$M1B-Key-v> {break}
2696bind $ui_diff <$M1B-Key-V> {break}
2697bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2698bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2699bind $ui_diff <Key-Up>     {catch {%W yview scroll -1 units};break}
2700bind $ui_diff <Key-Down>   {catch {%W yview scroll  1 units};break}
2701bind $ui_diff <Key-Left>   {catch {%W xview scroll -1 units};break}
2702bind $ui_diff <Key-Right>  {catch {%W xview scroll  1 units};break}
2703bind $ui_diff <Key-k>         {catch {%W yview scroll -1 units};break}
2704bind $ui_diff <Key-j>         {catch {%W yview scroll  1 units};break}
2705bind $ui_diff <Key-h>         {catch {%W xview scroll -1 units};break}
2706bind $ui_diff <Key-l>         {catch {%W xview scroll  1 units};break}
2707bind $ui_diff <Control-Key-b> {catch {%W yview scroll -1 pages};break}
2708bind $ui_diff <Control-Key-f> {catch {%W yview scroll  1 pages};break}
2709bind $ui_diff <Button-1>   {focus %W}
2710
2711if {[is_enabled branch]} {
2712        bind . <$M1B-Key-n> branch_create::dialog
2713        bind . <$M1B-Key-N> branch_create::dialog
2714        bind . <$M1B-Key-o> branch_checkout::dialog
2715        bind . <$M1B-Key-O> branch_checkout::dialog
2716        bind . <$M1B-Key-m> merge::dialog
2717        bind . <$M1B-Key-M> merge::dialog
2718}
2719if {[is_enabled transport]} {
2720        bind . <$M1B-Key-p> do_push_anywhere
2721        bind . <$M1B-Key-P> do_push_anywhere
2722}
2723
2724bind .   <Key-F5>     do_rescan
2725bind .   <$M1B-Key-r> do_rescan
2726bind .   <$M1B-Key-R> do_rescan
2727bind .   <$M1B-Key-s> do_signoff
2728bind .   <$M1B-Key-S> do_signoff
2729bind .   <$M1B-Key-t> do_add_selection
2730bind .   <$M1B-Key-T> do_add_selection
2731bind .   <$M1B-Key-i> do_add_all
2732bind .   <$M1B-Key-I> do_add_all
2733bind .   <$M1B-Key-Return> do_commit
2734foreach i [list $ui_index $ui_workdir] {
2735        bind $i <Button-1>       "toggle_or_diff         $i %x %y; break"
2736        bind $i <$M1B-Button-1>  "add_one_to_selection   $i %x %y; break"
2737        bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break"
2738}
2739unset i
2740
2741set file_lists($ui_index) [list]
2742set file_lists($ui_workdir) [list]
2743
2744wm title . "[appname] ([reponame]) [file normalize [file dirname [gitdir]]]"
2745focus -force $ui_comm
2746
2747# -- Warn the user about environmental problems.  Cygwin's Tcl
2748#    does *not* pass its env array onto any processes it spawns.
2749#    This means that git processes get none of our environment.
2750#
2751if {[is_Cygwin]} {
2752        set ignored_env 0
2753        set suggest_user {}
2754        set msg [mc "Possible environment issues exist.
2755
2756The following environment variables are probably
2757going to be ignored by any Git subprocess run
2758by %s:
2759
2760" [appname]]
2761        foreach name [array names env] {
2762                switch -regexp -- $name {
2763                {^GIT_INDEX_FILE$} -
2764                {^GIT_OBJECT_DIRECTORY$} -
2765                {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} -
2766                {^GIT_DIFF_OPTS$} -
2767                {^GIT_EXTERNAL_DIFF$} -
2768                {^GIT_PAGER$} -
2769                {^GIT_TRACE$} -
2770                {^GIT_CONFIG$} -
2771                {^GIT_CONFIG_LOCAL$} -
2772                {^GIT_(AUTHOR|COMMITTER)_DATE$} {
2773                        append msg " - $name\n"
2774                        incr ignored_env
2775                }
2776                {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} {
2777                        append msg " - $name\n"
2778                        incr ignored_env
2779                        set suggest_user $name
2780                }
2781                }
2782        }
2783        if {$ignored_env > 0} {
2784                append msg [mc "
2785This is due to a known issue with the
2786Tcl binary distributed by Cygwin."]
2787
2788                if {$suggest_user ne {}} {
2789                        append msg [mc "
2790
2791A good replacement for %s
2792is placing values for the user.name and
2793user.email settings into your personal
2794~/.gitconfig file.
2795" $suggest_user]
2796                }
2797                warn_popup $msg
2798        }
2799        unset ignored_env msg suggest_user name
2800}
2801
2802# -- Only initialize complex UI if we are going to stay running.
2803#
2804if {[is_enabled transport]} {
2805        load_all_remotes
2806
2807        set n [.mbar.remote index end]
2808        populate_push_menu
2809        populate_fetch_menu
2810        set n [expr {[.mbar.remote index end] - $n}]
2811        if {$n > 0} {
2812                .mbar.remote insert $n separator
2813        }
2814        unset n
2815}
2816
2817if {[winfo exists $ui_comm]} {
2818        set GITGUI_BCK_exists [load_message GITGUI_BCK]
2819
2820        # -- If both our backup and message files exist use the
2821        #    newer of the two files to initialize the buffer.
2822        #
2823        if {$GITGUI_BCK_exists} {
2824                set m [gitdir GITGUI_MSG]
2825                if {[file isfile $m]} {
2826                        if {[file mtime [gitdir GITGUI_BCK]] > [file mtime $m]} {
2827                                catch {file delete [gitdir GITGUI_MSG]}
2828                        } else {
2829                                $ui_comm delete 0.0 end
2830                                $ui_comm edit reset
2831                                $ui_comm edit modified false
2832                                catch {file delete [gitdir GITGUI_BCK]}
2833                                set GITGUI_BCK_exists 0
2834                        }
2835                }
2836                unset m
2837        }
2838
2839        proc backup_commit_buffer {} {
2840                global ui_comm GITGUI_BCK_exists
2841
2842                set m [$ui_comm edit modified]
2843                if {$m || $GITGUI_BCK_exists} {
2844                        set msg [string trim [$ui_comm get 0.0 end]]
2845                        regsub -all -line {[ \r\t]+$} $msg {} msg
2846
2847                        if {$msg eq {}} {
2848                                if {$GITGUI_BCK_exists} {
2849                                        catch {file delete [gitdir GITGUI_BCK]}
2850                                        set GITGUI_BCK_exists 0
2851                                }
2852                        } elseif {$m} {
2853                                catch {
2854                                        set fd [open [gitdir GITGUI_BCK] w]
2855                                        puts -nonewline $fd $msg
2856                                        close $fd
2857                                        set GITGUI_BCK_exists 1
2858                                }
2859                        }
2860
2861                        $ui_comm edit modified false
2862                }
2863
2864                set ::GITGUI_BCK_i [after 2000 backup_commit_buffer]
2865        }
2866
2867        backup_commit_buffer
2868
2869        # -- If the user has aspell available we can drive it
2870        #    in pipe mode to spellcheck the commit message.
2871        #
2872        set spell_cmd [list |]
2873        set spell_dict [get_config gui.spellingdictionary]
2874        lappend spell_cmd aspell
2875        if {$spell_dict ne {}} {
2876                lappend spell_cmd --master=$spell_dict
2877        }
2878        lappend spell_cmd --mode=none
2879        lappend spell_cmd --encoding=utf-8
2880        lappend spell_cmd pipe
2881        if {$spell_dict eq {none}
2882         || [catch {set spell_fd [open $spell_cmd r+]} spell_err]} {
2883                bind_button3 $ui_comm [list tk_popup $ui_comm_ctxm %X %Y]
2884        } else {
2885                set ui_comm_spell [spellcheck::init \
2886                        $spell_fd \
2887                        $ui_comm \
2888                        $ui_comm_ctxm \
2889                ]
2890        }
2891        unset -nocomplain spell_cmd spell_fd spell_err spell_dict
2892}
2893
2894lock_index begin-read
2895if {![winfo ismapped .]} {
2896        wm deiconify .
2897}
2898after 1 do_rescan
2899if {[is_enabled multicommit]} {
2900        after 1000 hint_gc
2901}