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