git-gui / git-gui.shon commit environment.c: remove unused function (78d0f5d)
   1#!/bin/sh
   2# Tcl ignores the next line -*- tcl -*- \
   3 if test "z$*" = zversion \
   4 || test "z$*" = z--version; \
   5 then \
   6        echo 'git-gui version @@GITGUI_VERSION@@'; \
   7        exit; \
   8 fi; \
   9 argv0=$0; \
  10 exec wish "$argv0" -- "$@"
  11
  12set appvers {@@GITGUI_VERSION@@}
  13set copyright [encoding convertfrom utf-8 {
  14Copyright © 2006, 2007 Shawn Pearce, et. al.
  15
  16This program is free software; you can redistribute it and/or modify
  17it under the terms of the GNU General Public License as published by
  18the Free Software Foundation; either version 2 of the License, or
  19(at your option) any later version.
  20
  21This program is distributed in the hope that it will be useful,
  22but WITHOUT ANY WARRANTY; without even the implied warranty of
  23MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  24GNU General Public License for more details.
  25
  26You should have received a copy of the GNU General Public License
  27along with this program; if not, write to the Free Software
  28Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA}]
  29
  30######################################################################
  31##
  32## Tcl/Tk sanity check
  33
  34if {[catch {package require Tcl 8.4} err]
  35 || [catch {package require Tk  8.4} err]
  36} {
  37        catch {wm withdraw .}
  38        tk_messageBox \
  39                -icon error \
  40                -type ok \
  41                -title [mc "git-gui: fatal error"] \
  42                -message $err
  43        exit 1
  44}
  45
  46catch {rename send {}} ; # What an evil concept...
  47
  48######################################################################
  49##
  50## locate our library
  51
  52set oguilib {@@GITGUI_LIBDIR@@}
  53set oguirel {@@GITGUI_RELATIVE@@}
  54if {$oguirel eq {1}} {
  55        set oguilib [file dirname [file dirname [file normalize $argv0]]]
  56        set oguilib [file join $oguilib share git-gui lib]
  57        set oguimsg [file join $oguilib msgs]
  58} elseif {[string match @@* $oguirel]} {
  59        set oguilib [file join [file dirname [file normalize $argv0]] lib]
  60        set oguimsg [file join [file dirname [file normalize $argv0]] po]
  61} else {
  62        set oguimsg [file join $oguilib msgs]
  63}
  64unset oguirel
  65
  66######################################################################
  67##
  68## enable verbose loading?
  69
  70if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} {
  71        unset _verbose
  72        rename auto_load real__auto_load
  73        proc auto_load {name args} {
  74                puts stderr "auto_load $name"
  75                return [uplevel 1 real__auto_load $name $args]
  76        }
  77        rename source real__source
  78        proc source {name} {
  79                puts stderr "source    $name"
  80                uplevel 1 real__source $name
  81        }
  82}
  83
  84######################################################################
  85##
  86## Internationalization (i18n) through msgcat and gettext. See
  87## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
  88
  89package require msgcat
  90
  91proc _mc_trim {fmt} {
  92        set cmk [string first @@ $fmt]
  93        if {$cmk > 0} {
  94                return [string range $fmt 0 [expr {$cmk - 1}]]
  95        }
  96        return $fmt
  97}
  98
  99proc mc {en_fmt args} {
 100        set fmt [_mc_trim [::msgcat::mc $en_fmt]]
 101        if {[catch {set msg [eval [list format $fmt] $args]} err]} {
 102                set msg [eval [list format [_mc_trim $en_fmt]] $args]
 103        }
 104        return $msg
 105}
 106
 107proc strcat {args} {
 108        return [join $args {}]
 109}
 110
 111::msgcat::mcload $oguimsg
 112unset oguimsg
 113
 114######################################################################
 115##
 116## read only globals
 117
 118set _appname {Git Gui}
 119set _gitdir {}
 120set _gitexec {}
 121set _reponame {}
 122set _iscygwin {}
 123set _search_path {}
 124
 125set _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}
 132
 133proc appname {} {
 134        global _appname
 135        return $_appname
 136}
 137
 138proc gitdir {args} {
 139        global _gitdir
 140        if {$args eq {}} {
 141                return $_gitdir
 142        }
 143        return [eval [list file join $_gitdir] $args]
 144}
 145
 146proc 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}
 166
 167proc reponame {} {
 168        return $::_reponame
 169}
 170
 171proc is_MacOSX {} {
 172        if {[tk windowingsystem] eq {aqua}} {
 173                return 1
 174        }
 175        return 0
 176}
 177
 178proc is_Windows {} {
 179        if {$::tcl_platform(platform) eq {windows}} {
 180                return 1
 181        }
 182        return 0
 183}
 184
 185proc 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}
 200
 201proc is_enabled {option} {
 202        global enabled_options
 203        if {[catch {set on $enabled_options($option)}]} {return 0}
 204        return $on
 205}
 206
 207proc enable_option {option} {
 208        global enabled_options
 209        set enabled_options($option) 1
 210}
 211
 212proc disable_option {option} {
 213        global enabled_options
 214        set enabled_options($option) 0
 215}
 216
 217######################################################################
 218##
 219## config
 220
 221proc 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}
 231
 232proc 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}
 242
 243proc 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
 252######################################################################
 253##
 254## handy utils
 255
 256proc _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}
 270
 271proc _git_cmd {name} {
 272        global _git_cmd_path
 273
 274        if {[catch {set v $_git_cmd_path($name)}]} {
 275                switch -- $name {
 276                  version   -
 277                --version   -
 278                --exec-path { return [list $::_git $name] }
 279                }
 280
 281                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
 294                        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
 301                        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}
 319
 320proc _which {what} {
 321        global env _search_exe _search_path
 322
 323        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
 343        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}
 351
 352proc _lappend_nice {cmd_var} {
 353        global _nice
 354        upvar $cmd_var cmd
 355
 356        if {![info exists _nice]} {
 357                set _nice [_which nice]
 358        }
 359        if {$_nice ne {}} {
 360                lappend cmd $_nice
 361        }
 362}
 363
 364proc git {args} {
 365        set opt [list]
 366
 367        while {1} {
 368                switch -- [lindex $args 0] {
 369                --nice {
 370                        _lappend_nice opt
 371                }
 372
 373                default {
 374                        break
 375                }
 376
 377                }
 378
 379                set args [lrange $args 1 end]
 380        }
 381
 382        set cmdp [_git_cmd [lindex $args 0]]
 383        set args [lrange $args 1 end]
 384
 385        _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}
 392
 393proc _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}
 418
 419proc git_read {args} {
 420        set opt [list]
 421
 422        while {1} {
 423                switch -- [lindex $args 0] {
 424                --nice {
 425                        _lappend_nice opt
 426                }
 427
 428                --stderr {
 429                        lappend args 2>@1
 430                }
 431
 432                default {
 433                        break
 434                }
 435
 436                }
 437
 438                set args [lrange $args 1 end]
 439        }
 440
 441        set cmdp [_git_cmd [lindex $args 0]]
 442        set args [lrange $args 1 end]
 443
 444        return [_open_stdout_stderr [concat $opt $cmdp $args]]
 445}
 446
 447proc git_write {args} {
 448        set opt [list]
 449
 450        while {1} {
 451                switch -- [lindex $args 0] {
 452                --nice {
 453                        _lappend_nice opt
 454                }
 455
 456                default {
 457                        break
 458                }
 459
 460                }
 461
 462                set args [lrange $args 1 end]
 463        }
 464
 465        set cmdp [_git_cmd [lindex $args 0]]
 466        set args [lrange $args 1 end]
 467
 468        _trace_exec [concat $opt $cmdp $args]
 469        return [open [concat [list | ] $opt $cmdp $args] w]
 470}
 471
 472proc githook_read {hook_name args} {
 473        set pchook [gitdir hooks $hook_name]
 474        lappend args 2>@1
 475
 476        # 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
 488                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
 493        if {[file executable $pchook]} {
 494                return [_open_stdout_stderr [concat [list $pchook] $args]]
 495        }
 496
 497        return {}
 498}
 499
 500proc sq {value} {
 501        regsub -all ' $value "'\\''" value
 502        return "'$value'"
 503}
 504
 505proc load_current_branch {} {
 506        global current_branch is_detached
 507
 508        set fd [open [gitdir HEAD] r]
 509        if {[gets $fd ref] < 1} {
 510                set ref {}
 511        }
 512        close $fd
 513
 514        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}
 529
 530auto_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}
 538
 539proc 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}
 548
 549set root_exists 0
 550bind . <Visibility> {
 551        bind . <Visibility> {}
 552        set root_exists 1
 553}
 554
 555if {[is_Windows]} {
 556        wm iconbitmap . -default $oguilib/git-gui.ico
 557}
 558
 559######################################################################
 560##
 561## config defaults
 562
 563set 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}
 571
 572font create font_uiitalic
 573font create font_uibold
 574font create font_diffbold
 575font create font_diffitalic
 576
 577foreach class {Button Checkbutton Entry Label
 578                Labelframe Listbox Menu Message
 579                Radiobutton Spinbox Text} {
 580        option add *$class.font font_ui
 581}
 582unset class
 583
 584if {[is_Windows] || [is_MacOSX]} {
 585        option add *Menu.tearOff 0
 586}
 587
 588if {[is_MacOSX]} {
 589        set M1B M1
 590        set M1T Cmd
 591} else {
 592        set M1B Control
 593        set M1T Ctrl
 594}
 595
 596proc 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}
 605
 606proc apply_config {} {
 607        global repo_config font_descs
 608
 609        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}
 634
 635set 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) {}
 641
 642set 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
 656######################################################################
 657##
 658## find git
 659
 660set _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
 671######################################################################
 672##
 673## version check
 674
 675if {[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
 683$err
 684
 685[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}
 697
 698set _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
 704
 705if {![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
 714%s claims it is version '%s'.
 715
 716%s requires at least Git 1.5.0 or later.
 717
 718Assume '%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
 726
 727proc git-version {args} {
 728        global _git_version
 729
 730        switch [llength $args] {
 731        0 {
 732                return $_git_version
 733        }
 734
 735        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
 742        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
 748                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
 755                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
 761                return [uplevel [list $type $name $parm [lindex $body end]]]
 762        }
 763
 764        default {
 765                error "git-version >= x"
 766        }
 767
 768        }
 769}
 770
 771if {[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.
 778
 779You are using [git-version]:
 780
 781[git --version]"
 782        exit 1
 783}
 784
 785######################################################################
 786##
 787## configure our library
 788
 789set 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
 810
 811if {$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
 824######################################################################
 825##
 826## config file parsing
 827
 828git-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}
 870
 871proc load_config {include_global} {
 872        global repo_config global_config default_config
 873
 874        if {$include_global} {
 875                _parse_config global_config --global
 876        }
 877        _parse_config repo_config
 878
 879        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
 889######################################################################
 890##
 891## feature option selection
 892
 893if {[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}
 905
 906enable_option multicommit
 907enable_option branch
 908enable_option transport
 909disable_option bare
 910
 911switch -- $subcommand {
 912browser -
 913blame {
 914        enable_option bare
 915
 916        disable_option multicommit
 917        disable_option branch
 918        disable_option transport
 919}
 920citool {
 921        enable_option singlecommit
 922
 923        disable_option multicommit
 924        disable_option branch
 925        disable_option transport
 926}
 927}
 928
 929######################################################################
 930##
 931## repository setup
 932
 933if {[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
 980######################################################################
 981##
 982## global init
 983
 984set current_diff_path {}
 985set current_diff_side {}
 986set diff_actions [list]
 987
 988set 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
 999######################################################################
1000##
1001## task management
1002
1003set rescan_active 0
1004set diff_active 0
1005set last_clicked {}
1006
1007set disable_on_lock [list]
1008set index_lock_type none
1009
1010proc lock_index {type} {
1011        global index_lock_type disable_on_lock
1012
1013        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}
1025
1026proc unlock_index {} {
1027        global index_lock_type disable_on_lock
1028
1029        set index_lock_type none
1030        foreach w $disable_on_lock {
1031                uplevel #0 $w normal
1032        }
1033}
1034
1035######################################################################
1036##
1037## status
1038
1039proc repository_state {ctvar hdvar mhvar} {
1040        global current_branch
1041        upvar $ctvar ct $hdvar hd $mhvar mh
1042
1043        set mh [list]
1044
1045        load_current_branch
1046        if {[catch {set hd [git rev-parse --verify HEAD]}]} {
1047                set hd {}
1048                set ct initial
1049                return
1050        }
1051
1052        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
1063        set ct normal
1064}
1065
1066proc PARENT {} {
1067        global PARENT empty_tree
1068
1069        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}
1078
1079proc 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
1085        if {$rescan_active > 0 || ![lock_index read]} return
1086
1087        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
1098        array unset file_states
1099
1100        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
1112        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}
1128
1129if {[is_Cygwin]} {
1130        set is_git_info_exclude {}
1131        proc have_info_exclude {} {
1132                global is_git_info_exclude
1133
1134                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}
1148
1149proc rescan_stage2 {fd after} {
1150        global rescan_active buf_rdi buf_rdf buf_rlo
1151
1152        if {$fd ne {}} {
1153                read $fd
1154                if {![eof $fd]} return
1155                close $fd
1156        }
1157
1158        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
1167        set buf_rdi {}
1168        set buf_rdf {}
1169        set buf_rlo {}
1170
1171        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
1177        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}
1184
1185proc load_message {file} {
1186        global ui_comm
1187
1188        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}
1203
1204proc read_diff_index {fd after} {
1205        global buf_rdi
1206
1207        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
1217                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
1234        rescan_done $fd buf_rdi $after
1235}
1236
1237proc read_diff_files {fd after} {
1238        global buf_rdf
1239
1240        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
1250                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
1267        rescan_done $fd buf_rdf $after
1268}
1269
1270proc read_ls_others {fd after} {
1271        global buf_rlo
1272
1273        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}
1285
1286proc rescan_done {fd buf after} {
1287        global rescan_active current_diff_path
1288        global file_states repo_config
1289        upvar $buf to_clear
1290
1291        if {![eof $fd]} return
1292        set to_clear {}
1293        close $fd
1294        if {[incr rescan_active -1] > 0} return
1295
1296        prune_selection
1297        unlock_index
1298        display_all_files
1299        if {$current_diff_path ne {}} reshow_diff
1300        uplevel #0 $after
1301}
1302
1303proc prune_selection {} {
1304        global file_states selected_paths
1305
1306        foreach path [array names selected_paths] {
1307                if {[catch {set still_here $file_states($path)}]} {
1308                        unset selected_paths($path)
1309                }
1310        }
1311}
1312
1313######################################################################
1314##
1315## ui helpers
1316
1317proc mapicon {w state path} {
1318        global all_icons
1319
1320        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}
1326
1327proc mapdesc {state path} {
1328        global all_descs
1329
1330        if {[catch {set r $all_descs($state)}]} {
1331                puts "error: no desc for state={$state} $path"
1332                return $state
1333        }
1334        return $r
1335}
1336
1337proc ui_status {msg} {
1338        global main_status
1339        if {[info exists main_status]} {
1340                $main_status show $msg
1341        }
1342}
1343
1344proc ui_ready {{test {}}} {
1345        global main_status
1346        if {[info exists main_status]} {
1347                $main_status show [mc "Ready."] $test
1348        }
1349}
1350
1351proc escape_path {path} {
1352        regsub -all {\\} $path "\\\\" path
1353        regsub -all "\n" $path "\\n" path
1354        return $path
1355}
1356
1357proc short_path {path} {
1358        return [escape_path [lindex [file split $path] end]]
1359}
1360
1361set next_icon_id 0
1362set null_sha1 [string repeat 0 40]
1363
1364proc merge_state {path new_state {head_info {}} {index_info {}}} {
1365        global file_states next_icon_id null_sha1
1366
1367        set s0 [string index $new_state 0]
1368        set s1 [string index $new_state 1]
1369
1370        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
1380        if     {$s0 eq {?}} {set s0 [string index $state 0]} \
1381        elseif {$s0 eq {_}} {set s0 _}
1382
1383        if     {$s1 eq {?}} {set s1 [string index $state 1]} \
1384        elseif {$s1 eq {_}} {set s1 _}
1385
1386        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
1393        set file_states($path) [list $s0$s1 $icon \
1394                $head_info $index_info \
1395                ]
1396        return $state
1397}
1398
1399proc display_file_helper {w path icon_name old_m new_m} {
1400        global file_lists
1401
1402        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}
1429
1430proc display_file {path state} {
1431        global file_states selected_paths
1432        global ui_index ui_workdir
1433
1434        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
1439        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
1449        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
1461        if {$new_m eq {__}} {
1462                unset file_states($path)
1463                catch {unset selected_paths($path)}
1464        }
1465}
1466
1467proc display_all_files_helper {w path icon_name m} {
1468        global file_lists
1469
1470        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}
1478
1479proc display_all_files {} {
1480        global ui_index ui_workdir
1481        global file_states file_lists
1482        global last_clicked
1483
1484        $ui_index conf -state normal
1485        $ui_workdir conf -state normal
1486
1487        $ui_index delete 0.0 end
1488        $ui_workdir delete 0.0 end
1489        set last_clicked {}
1490
1491        set file_lists($ui_index) [list]
1492        set file_lists($ui_workdir) [list]
1493
1494        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
1499                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
1505                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
1516        $ui_index conf -state disabled
1517        $ui_workdir conf -state disabled
1518}
1519
1520######################################################################
1521##
1522## icons
1523
1524set 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}
1532
1533image 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
1541
1542image 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
1550
1551image 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
1559
1560image 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
1568
1569image 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
1577
1578image 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
1586
1587image 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
1595
1596set ui_index .vpane.files.index.list
1597set ui_workdir .vpane.files.workdir.list
1598
1599set 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
1604
1605set 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
1610
1611set max_status_desc 0
1612foreach i {
1613                {__ {mc "Unmodified"}}
1614
1615                {_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
1620                {_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
1625                {_D {mc "Missing"}}
1626                {D_ {mc "Staged for removal"}}
1627                {DO {mc "Staged for removal, still present"}}
1628
1629                {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
1642######################################################################
1643##
1644## util
1645
1646proc scrollbar2many {list mode args} {
1647        foreach w $list {eval $w $mode $args}
1648}
1649
1650proc many2scrollbar {list mode sb top bottom} {
1651        $sb set $top $bottom
1652        foreach w $list {$w $mode moveto $top}
1653}
1654
1655proc 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
1663######################################################################
1664##
1665## ui commands
1666
1667set starting_gitk_msg [mc "Starting gitk... please wait..."]
1668
1669proc 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
1680                if {[info exists env(GIT_DIR)]} {
1681                        set old_GIT_DIR $env(GIT_DIR)
1682                } else {
1683                        set old_GIT_DIR {}
1684                }
1685
1686                set pwd [pwd]
1687                cd [file dirname [gitdir]]
1688                set env(GIT_DIR) [file tail [gitdir]]
1689
1690                eval exec $cmd $revs &
1691
1692                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
1699                ui_status $::starting_gitk_msg
1700                after 10000 {
1701                        ui_ready $starting_gitk_msg
1702                }
1703        }
1704}
1705
1706set is_quitting 0
1707
1708proc 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
1713        if {$is_quitting} return
1714        set is_quitting 1
1715
1716        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
1739                # -- Cancel our spellchecker if its running.
1740                #
1741                if {[info exists ui_comm_spell]} {
1742                        $ui_comm_spell stop
1743                }
1744
1745                # -- 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
1752                # -- 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
1766        destroy .
1767}
1768
1769proc do_rescan {} {
1770        rescan ui_ready
1771}
1772
1773proc do_commit {} {
1774        commit_tree
1775}
1776
1777proc 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
1781        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
1790        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
1795        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}
1816
1817proc add_one_to_selection {w x y} {
1818        global file_lists last_clicked selected_paths
1819
1820        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
1827        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
1833        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}
1845
1846proc add_range_to_selection {w x y} {
1847        global file_lists last_clicked selected_paths
1848
1849        if {[lindex $last_clicked 0] ne $w} {
1850                toggle_or_diff $w $x $y
1851                return
1852        }
1853
1854        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
1864        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}
1871
1872proc 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}
1879
1880proc 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
1888######################################################################
1889##
1890## ui construction
1891
1892load_config 0
1893apply_config
1894set ui_comm {}
1895
1896# -- 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
1913# -- Repository Menu
1914#
1915menu .mbar.repository
1916
1917.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
1926.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
1934
1935proc 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
1943
1944if {[is_enabled multicommit]} {
1945        .mbar.repository add command -label [mc "Database Statistics"] \
1946                -command do_stats
1947
1948        .mbar.repository add command -label [mc "Compress Database"] \
1949                -command do_gc
1950
1951        .mbar.repository add command -label [mc "Verify Database"] \
1952                -command do_fsck_objects
1953
1954        .mbar.repository add separator
1955
1956        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
1971.mbar.repository add command -label [mc Quit] \
1972        -command do_quit \
1973        -accelerator $M1T-Q
1974
1975# -- 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
2002# -- Branch Menu
2003#
2004if {[is_enabled branch]} {
2005        menu .mbar.branch
2006
2007        .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
2013        .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
2019        .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
2024        .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
2029        .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
2035# -- Commit Menu
2036#
2037if {[is_enabled multicommit] || [is_enabled singlecommit]} {
2038        menu .mbar.commit
2039
2040        .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
2048        .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
2056        .mbar.commit add separator
2057
2058        .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
2064        .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
2070        .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
2076        .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
2081        .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
2086        .mbar.commit add separator
2087
2088        .mbar.commit add command -label [mc "Show Less Context"] \
2089                -command show_less_context \
2090                -accelerator $M1T-\-
2091
2092        .mbar.commit add command -label [mc "Show More Context"] \
2093                -command show_more_context \
2094                -accelerator $M1T-=
2095
2096        .mbar.commit add separator
2097
2098        .mbar.commit add command -label [mc "Sign Off"] \
2099                -command do_signoff \
2100                -accelerator $M1T-S
2101
2102        .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
2109# -- 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
2124# -- Transport Menu
2125#
2126if {[is_enabled transport]} {
2127        menu .mbar.remote
2128
2129        .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}
2137
2138if {[is_MacOSX]} {
2139        # -- Apple Menu (Mac OS X only)
2140        #
2141        .mbar add cascade -label Apple -menu .mbar.apple
2142        menu .mbar.apple
2143
2144        .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
2160# -- Help Menu
2161#
2162.mbar add cascade -label [mc Help] -menu .mbar.help
2163menu .mbar.help
2164
2165if {![is_MacOSX]} {
2166        .mbar.help add command -label [mc "About %s" [appname]] \
2167                -command do_about
2168}
2169
2170set browser {}
2171catch {set browser $repo_config(instaweb.browser)}
2172set doc_path [file dirname [gitexec]]
2173set doc_path [file join $doc_path Documentation index.html]
2174
2175if {[is_Cygwin]} {
2176        set doc_path [exec cygpath --mixed $doc_path]
2177}
2178
2179if {$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}
2195
2196if {[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}
2201
2202if {$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
2208# -- 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]}
2215
2216set subcommand_args {}
2217proc usage {} {
2218        puts stderr "usage: $::argv0 $::subcommand $::subcommand_args"
2219        exit 1
2220}
2221
2222# -- 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
2254        if {$head ne {} && $path eq {}} {
2255                set path $_prefix$head
2256                set head {}
2257        }
2258
2259        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
2273        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
2314# -- 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
2331# -- 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
2338# -- 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
2358# -- 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
2378.vpane.files add .vpane.files.workdir -sticky nsew
2379.vpane.files add .vpane.files.index -sticky nsew
2380
2381foreach 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
2387# -- 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
2396# -- 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
2404
2405button .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}
2410
2411button .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}
2416
2417button .vpane.lower.commarea.buttons.signoff -text [mc "Sign Off"] \
2418        -command do_signoff
2419pack .vpane.lower.commarea.buttons.signoff -side top -fill x
2420
2421button .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}
2426
2427button .vpane.lower.commarea.buttons.push -text [mc Push] \
2428        -command do_push_anywhere
2429pack .vpane.lower.commarea.buttons.push -side top -fill x
2430
2431# -- 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
2470
2471text $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
2487# -- 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
2520# -- 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
2537        .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
2545
2546frame .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
2580# -- 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
2601$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
2606$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
2621$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
2631$ui_diff tag raise sel
2632
2633# -- 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
2706# -- 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
2712# -- 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
2726# -- 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}
2746
2747bind $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}
2766
2767if {[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}
2779
2780bind .   <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
2801
2802set file_lists($ui_index) [list]
2803set file_lists($ui_workdir) [list]
2804
2805wm title . "[appname] ([reponame]) [file normalize [file dirname [gitdir]]]"
2806focus -force $ui_comm
2807
2808# -- 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.
2816
2817The following environment variables are probably
2818going to be ignored by any Git subprocess run
2819by %s:
2820
2821" [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
2849                if {$suggest_user ne {}} {
2850                        append msg [mc "
2851
2852A 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
2863# -- Only initialize complex UI if we are going to stay running.
2864#
2865if {[is_enabled transport]} {
2866        load_all_remotes
2867
2868        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}
2877
2878if {[winfo exists $ui_comm]} {
2879        set GITGUI_BCK_exists [load_message GITGUI_BCK]
2880
2881        # -- 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
2900        proc backup_commit_buffer {} {
2901                global ui_comm GITGUI_BCK_exists
2902
2903                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
2908                        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
2922                        $ui_comm edit modified false
2923                }
2924
2925                set ::GITGUI_BCK_i [after 2000 backup_commit_buffer]
2926        }
2927
2928        backup_commit_buffer
2929
2930        # -- 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}
2954
2955lock_index begin-read
2956if {![winfo ismapped .]} {
2957        wm deiconify .
2958}
2959after 1 do_rescan
2960if {[is_enabled multicommit]} {
2961        after 1000 hint_gc
2962}