git-gui.shon commit git-gui: Don't select the wrong file if the last listed file is staged. (f531e46)
   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 next_diff {} {
1778        global next_diff_p next_diff_w next_diff_i
1779        show_diff $next_diff_p $next_diff_w $next_diff_i
1780}
1781
1782proc toggle_or_diff {w x y} {
1783        global file_states file_lists current_diff_path ui_index ui_workdir
1784        global last_clicked selected_paths
1785
1786        set pos [split [$w index @$x,$y] .]
1787        set lno [lindex $pos 0]
1788        set col [lindex $pos 1]
1789        set path [lindex $file_lists($w) [expr {$lno - 1}]]
1790        if {$path eq {}} {
1791                set last_clicked {}
1792                return
1793        }
1794
1795        set last_clicked [list $w $lno]
1796        array unset selected_paths
1797        $ui_index tag remove in_sel 0.0 end
1798        $ui_workdir tag remove in_sel 0.0 end
1799
1800        if {$col == 0 && $y > 1} {
1801                set i [expr {$lno-1}]
1802                set ll [expr {[llength $file_lists($w)]-1}]
1803
1804                if {$i == $ll && $i == 0} {
1805                        set after {reshow_diff;}
1806                } else {
1807                        global next_diff_p next_diff_w next_diff_i
1808
1809                        set next_diff_w $w
1810
1811                        if {$i < $ll} {
1812                                set i [expr {$i + 1}]
1813                                set next_diff_i $i
1814                        } else {
1815                                set next_diff_i $i
1816                                set i [expr {$i - 1}]
1817                        }
1818
1819                        set next_diff_p [lindex $file_lists($w) $i]
1820
1821                        if {$next_diff_p ne {} && $current_diff_path ne {}} {
1822                                set after {next_diff;}
1823                        } else {
1824                                set after {}
1825                        }
1826                }
1827
1828                if {$w eq $ui_index} {
1829                        update_indexinfo \
1830                                "Unstaging [short_path $path] from commit" \
1831                                [list $path] \
1832                                [concat $after [list ui_ready]]
1833                } elseif {$w eq $ui_workdir} {
1834                        update_index \
1835                                "Adding [short_path $path]" \
1836                                [list $path] \
1837                                [concat $after [list ui_ready]]
1838                }
1839        } else {
1840                show_diff $path $w $lno
1841        }
1842}
1843
1844proc add_one_to_selection {w x y} {
1845        global file_lists last_clicked selected_paths
1846
1847        set lno [lindex [split [$w index @$x,$y] .] 0]
1848        set path [lindex $file_lists($w) [expr {$lno - 1}]]
1849        if {$path eq {}} {
1850                set last_clicked {}
1851                return
1852        }
1853
1854        if {$last_clicked ne {}
1855                && [lindex $last_clicked 0] ne $w} {
1856                array unset selected_paths
1857                [lindex $last_clicked 0] tag remove in_sel 0.0 end
1858        }
1859
1860        set last_clicked [list $w $lno]
1861        if {[catch {set in_sel $selected_paths($path)}]} {
1862                set in_sel 0
1863        }
1864        if {$in_sel} {
1865                unset selected_paths($path)
1866                $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
1867        } else {
1868                set selected_paths($path) 1
1869                $w tag add in_sel $lno.0 [expr {$lno + 1}].0
1870        }
1871}
1872
1873proc add_range_to_selection {w x y} {
1874        global file_lists last_clicked selected_paths
1875
1876        if {[lindex $last_clicked 0] ne $w} {
1877                toggle_or_diff $w $x $y
1878                return
1879        }
1880
1881        set lno [lindex [split [$w index @$x,$y] .] 0]
1882        set lc [lindex $last_clicked 1]
1883        if {$lc < $lno} {
1884                set begin $lc
1885                set end $lno
1886        } else {
1887                set begin $lno
1888                set end $lc
1889        }
1890
1891        foreach path [lrange $file_lists($w) \
1892                [expr {$begin - 1}] \
1893                [expr {$end - 1}]] {
1894                set selected_paths($path) 1
1895        }
1896        $w tag add in_sel $begin.0 [expr {$end + 1}].0
1897}
1898
1899proc show_more_context {} {
1900        global repo_config
1901        if {$repo_config(gui.diffcontext) < 99} {
1902                incr repo_config(gui.diffcontext)
1903                reshow_diff
1904        }
1905}
1906
1907proc show_less_context {} {
1908        global repo_config
1909        if {$repo_config(gui.diffcontext) >= 1} {
1910                incr repo_config(gui.diffcontext) -1
1911                reshow_diff
1912        }
1913}
1914
1915######################################################################
1916##
1917## ui construction
1918
1919load_config 0
1920apply_config
1921set ui_comm {}
1922
1923# -- Menu Bar
1924#
1925menu .mbar -tearoff 0
1926.mbar add cascade -label [mc Repository] -menu .mbar.repository
1927.mbar add cascade -label [mc Edit] -menu .mbar.edit
1928if {[is_enabled branch]} {
1929        .mbar add cascade -label [mc Branch] -menu .mbar.branch
1930}
1931if {[is_enabled multicommit] || [is_enabled singlecommit]} {
1932        .mbar add cascade -label [mc Commit@@noun] -menu .mbar.commit
1933}
1934if {[is_enabled transport]} {
1935        .mbar add cascade -label [mc Merge] -menu .mbar.merge
1936        .mbar add cascade -label [mc Remote] -menu .mbar.remote
1937}
1938. configure -menu .mbar
1939
1940# -- Repository Menu
1941#
1942menu .mbar.repository
1943
1944.mbar.repository add command \
1945        -label [mc "Browse Current Branch's Files"] \
1946        -command {browser::new $current_branch}
1947set ui_browse_current [.mbar.repository index last]
1948.mbar.repository add command \
1949        -label [mc "Browse Branch Files..."] \
1950        -command browser_open::dialog
1951.mbar.repository add separator
1952
1953.mbar.repository add command \
1954        -label [mc "Visualize Current Branch's History"] \
1955        -command {do_gitk $current_branch}
1956set ui_visualize_current [.mbar.repository index last]
1957.mbar.repository add command \
1958        -label [mc "Visualize All Branch History"] \
1959        -command {do_gitk --all}
1960.mbar.repository add separator
1961
1962proc current_branch_write {args} {
1963        global current_branch
1964        .mbar.repository entryconf $::ui_browse_current \
1965                -label [mc "Browse %s's Files" $current_branch]
1966        .mbar.repository entryconf $::ui_visualize_current \
1967                -label [mc "Visualize %s's History" $current_branch]
1968}
1969trace add variable current_branch write current_branch_write
1970
1971if {[is_enabled multicommit]} {
1972        .mbar.repository add command -label [mc "Database Statistics"] \
1973                -command do_stats
1974
1975        .mbar.repository add command -label [mc "Compress Database"] \
1976                -command do_gc
1977
1978        .mbar.repository add command -label [mc "Verify Database"] \
1979                -command do_fsck_objects
1980
1981        .mbar.repository add separator
1982
1983        if {[is_Cygwin]} {
1984                .mbar.repository add command \
1985                        -label [mc "Create Desktop Icon"] \
1986                        -command do_cygwin_shortcut
1987        } elseif {[is_Windows]} {
1988                .mbar.repository add command \
1989                        -label [mc "Create Desktop Icon"] \
1990                        -command do_windows_shortcut
1991        } elseif {[is_MacOSX]} {
1992                .mbar.repository add command \
1993                        -label [mc "Create Desktop Icon"] \
1994                        -command do_macosx_app
1995        }
1996}
1997
1998.mbar.repository add command -label [mc Quit] \
1999        -command do_quit \
2000        -accelerator $M1T-Q
2001
2002# -- Edit Menu
2003#
2004menu .mbar.edit
2005.mbar.edit add command -label [mc Undo] \
2006        -command {catch {[focus] edit undo}} \
2007        -accelerator $M1T-Z
2008.mbar.edit add command -label [mc Redo] \
2009        -command {catch {[focus] edit redo}} \
2010        -accelerator $M1T-Y
2011.mbar.edit add separator
2012.mbar.edit add command -label [mc Cut] \
2013        -command {catch {tk_textCut [focus]}} \
2014        -accelerator $M1T-X
2015.mbar.edit add command -label [mc Copy] \
2016        -command {catch {tk_textCopy [focus]}} \
2017        -accelerator $M1T-C
2018.mbar.edit add command -label [mc Paste] \
2019        -command {catch {tk_textPaste [focus]; [focus] see insert}} \
2020        -accelerator $M1T-V
2021.mbar.edit add command -label [mc Delete] \
2022        -command {catch {[focus] delete sel.first sel.last}} \
2023        -accelerator Del
2024.mbar.edit add separator
2025.mbar.edit add command -label [mc "Select All"] \
2026        -command {catch {[focus] tag add sel 0.0 end}} \
2027        -accelerator $M1T-A
2028
2029# -- Branch Menu
2030#
2031if {[is_enabled branch]} {
2032        menu .mbar.branch
2033
2034        .mbar.branch add command -label [mc "Create..."] \
2035                -command branch_create::dialog \
2036                -accelerator $M1T-N
2037        lappend disable_on_lock [list .mbar.branch entryconf \
2038                [.mbar.branch index last] -state]
2039
2040        .mbar.branch add command -label [mc "Checkout..."] \
2041                -command branch_checkout::dialog \
2042                -accelerator $M1T-O
2043        lappend disable_on_lock [list .mbar.branch entryconf \
2044                [.mbar.branch index last] -state]
2045
2046        .mbar.branch add command -label [mc "Rename..."] \
2047                -command branch_rename::dialog
2048        lappend disable_on_lock [list .mbar.branch entryconf \
2049                [.mbar.branch index last] -state]
2050
2051        .mbar.branch add command -label [mc "Delete..."] \
2052                -command branch_delete::dialog
2053        lappend disable_on_lock [list .mbar.branch entryconf \
2054                [.mbar.branch index last] -state]
2055
2056        .mbar.branch add command -label [mc "Reset..."] \
2057                -command merge::reset_hard
2058        lappend disable_on_lock [list .mbar.branch entryconf \
2059                [.mbar.branch index last] -state]
2060}
2061
2062# -- Commit Menu
2063#
2064if {[is_enabled multicommit] || [is_enabled singlecommit]} {
2065        menu .mbar.commit
2066
2067        .mbar.commit add radiobutton \
2068                -label [mc "New Commit"] \
2069                -command do_select_commit_type \
2070                -variable selected_commit_type \
2071                -value new
2072        lappend disable_on_lock \
2073                [list .mbar.commit entryconf [.mbar.commit index last] -state]
2074
2075        .mbar.commit add radiobutton \
2076                -label [mc "Amend Last Commit"] \
2077                -command do_select_commit_type \
2078                -variable selected_commit_type \
2079                -value amend
2080        lappend disable_on_lock \
2081                [list .mbar.commit entryconf [.mbar.commit index last] -state]
2082
2083        .mbar.commit add separator
2084
2085        .mbar.commit add command -label [mc Rescan] \
2086                -command do_rescan \
2087                -accelerator F5
2088        lappend disable_on_lock \
2089                [list .mbar.commit entryconf [.mbar.commit index last] -state]
2090
2091        .mbar.commit add command -label [mc "Stage To Commit"] \
2092                -command do_add_selection \
2093                -accelerator $M1T-T
2094        lappend disable_on_lock \
2095                [list .mbar.commit entryconf [.mbar.commit index last] -state]
2096
2097        .mbar.commit add command -label [mc "Stage Changed Files To Commit"] \
2098                -command do_add_all \
2099                -accelerator $M1T-I
2100        lappend disable_on_lock \
2101                [list .mbar.commit entryconf [.mbar.commit index last] -state]
2102
2103        .mbar.commit add command -label [mc "Unstage From Commit"] \
2104                -command do_unstage_selection
2105        lappend disable_on_lock \
2106                [list .mbar.commit entryconf [.mbar.commit index last] -state]
2107
2108        .mbar.commit add command -label [mc "Revert Changes"] \
2109                -command do_revert_selection
2110        lappend disable_on_lock \
2111                [list .mbar.commit entryconf [.mbar.commit index last] -state]
2112
2113        .mbar.commit add separator
2114
2115        .mbar.commit add command -label [mc "Show Less Context"] \
2116                -command show_less_context \
2117                -accelerator $M1T-\-
2118
2119        .mbar.commit add command -label [mc "Show More Context"] \
2120                -command show_more_context \
2121                -accelerator $M1T-=
2122
2123        .mbar.commit add separator
2124
2125        .mbar.commit add command -label [mc "Sign Off"] \
2126                -command do_signoff \
2127                -accelerator $M1T-S
2128
2129        .mbar.commit add command -label [mc Commit@@verb] \
2130                -command do_commit \
2131                -accelerator $M1T-Return
2132        lappend disable_on_lock \
2133                [list .mbar.commit entryconf [.mbar.commit index last] -state]
2134}
2135
2136# -- Merge Menu
2137#
2138if {[is_enabled branch]} {
2139        menu .mbar.merge
2140        .mbar.merge add command -label [mc "Local Merge..."] \
2141                -command merge::dialog \
2142                -accelerator $M1T-M
2143        lappend disable_on_lock \
2144                [list .mbar.merge entryconf [.mbar.merge index last] -state]
2145        .mbar.merge add command -label [mc "Abort Merge..."] \
2146                -command merge::reset_hard
2147        lappend disable_on_lock \
2148                [list .mbar.merge entryconf [.mbar.merge index last] -state]
2149}
2150
2151# -- Transport Menu
2152#
2153if {[is_enabled transport]} {
2154        menu .mbar.remote
2155
2156        .mbar.remote add command \
2157                -label [mc "Push..."] \
2158                -command do_push_anywhere \
2159                -accelerator $M1T-P
2160        .mbar.remote add command \
2161                -label [mc "Delete..."] \
2162                -command remote_branch_delete::dialog
2163}
2164
2165if {[is_MacOSX]} {
2166        # -- Apple Menu (Mac OS X only)
2167        #
2168        .mbar add cascade -label Apple -menu .mbar.apple
2169        menu .mbar.apple
2170
2171        .mbar.apple add command -label [mc "About %s" [appname]] \
2172                -command do_about
2173        .mbar.apple add separator
2174        .mbar.apple add command \
2175                -label [mc "Preferences..."] \
2176                -command do_options \
2177                -accelerator $M1T-,
2178        bind . <$M1B-,> do_options
2179} else {
2180        # -- Edit Menu
2181        #
2182        .mbar.edit add separator
2183        .mbar.edit add command -label [mc "Options..."] \
2184                -command do_options
2185}
2186
2187# -- Help Menu
2188#
2189.mbar add cascade -label [mc Help] -menu .mbar.help
2190menu .mbar.help
2191
2192if {![is_MacOSX]} {
2193        .mbar.help add command -label [mc "About %s" [appname]] \
2194                -command do_about
2195}
2196
2197set browser {}
2198catch {set browser $repo_config(instaweb.browser)}
2199set doc_path [file dirname [gitexec]]
2200set doc_path [file join $doc_path Documentation index.html]
2201
2202if {[is_Cygwin]} {
2203        set doc_path [exec cygpath --mixed $doc_path]
2204}
2205
2206if {$browser eq {}} {
2207        if {[is_MacOSX]} {
2208                set browser open
2209        } elseif {[is_Cygwin]} {
2210                set program_files [file dirname [exec cygpath --windir]]
2211                set program_files [file join $program_files {Program Files}]
2212                set firefox [file join $program_files {Mozilla Firefox} firefox.exe]
2213                set ie [file join $program_files {Internet Explorer} IEXPLORE.EXE]
2214                if {[file exists $firefox]} {
2215                        set browser $firefox
2216                } elseif {[file exists $ie]} {
2217                        set browser $ie
2218                }
2219                unset program_files firefox ie
2220        }
2221}
2222
2223if {[file isfile $doc_path]} {
2224        set doc_url "file:$doc_path"
2225} else {
2226        set doc_url {http://www.kernel.org/pub/software/scm/git/docs/}
2227}
2228
2229if {$browser ne {}} {
2230        .mbar.help add command -label [mc "Online Documentation"] \
2231                -command [list exec $browser $doc_url &]
2232}
2233unset browser doc_path doc_url
2234
2235# -- Standard bindings
2236#
2237wm protocol . WM_DELETE_WINDOW do_quit
2238bind all <$M1B-Key-q> do_quit
2239bind all <$M1B-Key-Q> do_quit
2240bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
2241bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
2242
2243set subcommand_args {}
2244proc usage {} {
2245        puts stderr "usage: $::argv0 $::subcommand $::subcommand_args"
2246        exit 1
2247}
2248
2249# -- Not a normal commit type invocation?  Do that instead!
2250#
2251switch -- $subcommand {
2252browser -
2253blame {
2254        set subcommand_args {rev? path}
2255        if {$argv eq {}} usage
2256        set head {}
2257        set path {}
2258        set is_path 0
2259        foreach a $argv {
2260                if {$is_path || [file exists $_prefix$a]} {
2261                        if {$path ne {}} usage
2262                        set path $_prefix$a
2263                        break
2264                } elseif {$a eq {--}} {
2265                        if {$path ne {}} {
2266                                if {$head ne {}} usage
2267                                set head $path
2268                                set path {}
2269                        }
2270                        set is_path 1
2271                } elseif {$head eq {}} {
2272                        if {$head ne {}} usage
2273                        set head $a
2274                        set is_path 1
2275                } else {
2276                        usage
2277                }
2278        }
2279        unset is_path
2280
2281        if {$head ne {} && $path eq {}} {
2282                set path $_prefix$head
2283                set head {}
2284        }
2285
2286        if {$head eq {}} {
2287                load_current_branch
2288        } else {
2289                if {[regexp {^[0-9a-f]{1,39}$} $head]} {
2290                        if {[catch {
2291                                        set head [git rev-parse --verify $head]
2292                                } err]} {
2293                                puts stderr $err
2294                                exit 1
2295                        }
2296                }
2297                set current_branch $head
2298        }
2299
2300        switch -- $subcommand {
2301        browser {
2302                if {$head eq {}} {
2303                        if {$path ne {} && [file isdirectory $path]} {
2304                                set head $current_branch
2305                        } else {
2306                                set head $path
2307                                set path {}
2308                        }
2309                }
2310                browser::new $head $path
2311        }
2312        blame   {
2313                if {$head eq {} && ![file exists $path]} {
2314                        puts stderr [mc "fatal: cannot stat path %s: No such file or directory" $path]
2315                        exit 1
2316                }
2317                blame::new $head $path
2318        }
2319        }
2320        return
2321}
2322citool -
2323gui {
2324        if {[llength $argv] != 0} {
2325                puts -nonewline stderr "usage: $argv0"
2326                if {$subcommand ne {gui}
2327                        && [file tail $argv0] ne "git-$subcommand"} {
2328                        puts -nonewline stderr " $subcommand"
2329                }
2330                puts stderr {}
2331                exit 1
2332        }
2333        # fall through to setup UI for commits
2334}
2335default {
2336        puts stderr "usage: $argv0 \[{blame|browser|citool}\]"
2337        exit 1
2338}
2339}
2340
2341# -- Branch Control
2342#
2343frame .branch \
2344        -borderwidth 1 \
2345        -relief sunken
2346label .branch.l1 \
2347        -text [mc "Current Branch:"] \
2348        -anchor w \
2349        -justify left
2350label .branch.cb \
2351        -textvariable current_branch \
2352        -anchor w \
2353        -justify left
2354pack .branch.l1 -side left
2355pack .branch.cb -side left -fill x
2356pack .branch -side top -fill x
2357
2358# -- Main Window Layout
2359#
2360panedwindow .vpane -orient horizontal
2361panedwindow .vpane.files -orient vertical
2362.vpane add .vpane.files -sticky nsew -height 100 -width 200
2363pack .vpane -anchor n -side top -fill both -expand 1
2364
2365# -- Index File List
2366#
2367frame .vpane.files.index -height 100 -width 200
2368label .vpane.files.index.title -text [mc "Staged Changes (Will Commit)"] \
2369        -background lightgreen -foreground black
2370text $ui_index -background white -foreground black \
2371        -borderwidth 0 \
2372        -width 20 -height 10 \
2373        -wrap none \
2374        -cursor $cursor_ptr \
2375        -xscrollcommand {.vpane.files.index.sx set} \
2376        -yscrollcommand {.vpane.files.index.sy set} \
2377        -state disabled
2378scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview]
2379scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview]
2380pack .vpane.files.index.title -side top -fill x
2381pack .vpane.files.index.sx -side bottom -fill x
2382pack .vpane.files.index.sy -side right -fill y
2383pack $ui_index -side left -fill both -expand 1
2384
2385# -- Working Directory File List
2386#
2387frame .vpane.files.workdir -height 100 -width 200
2388label .vpane.files.workdir.title -text [mc "Unstaged Changes"] \
2389        -background lightsalmon -foreground black
2390text $ui_workdir -background white -foreground black \
2391        -borderwidth 0 \
2392        -width 20 -height 10 \
2393        -wrap none \
2394        -cursor $cursor_ptr \
2395        -xscrollcommand {.vpane.files.workdir.sx set} \
2396        -yscrollcommand {.vpane.files.workdir.sy set} \
2397        -state disabled
2398scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview]
2399scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview]
2400pack .vpane.files.workdir.title -side top -fill x
2401pack .vpane.files.workdir.sx -side bottom -fill x
2402pack .vpane.files.workdir.sy -side right -fill y
2403pack $ui_workdir -side left -fill both -expand 1
2404
2405.vpane.files add .vpane.files.workdir -sticky nsew
2406.vpane.files add .vpane.files.index -sticky nsew
2407
2408foreach i [list $ui_index $ui_workdir] {
2409        rmsel_tag $i
2410        $i tag conf in_diff -background [$i tag cget in_sel -background]
2411}
2412unset i
2413
2414# -- Diff and Commit Area
2415#
2416frame .vpane.lower -height 300 -width 400
2417frame .vpane.lower.commarea
2418frame .vpane.lower.diff -relief sunken -borderwidth 1
2419pack .vpane.lower.diff -fill both -expand 1
2420pack .vpane.lower.commarea -side bottom -fill x
2421.vpane add .vpane.lower -sticky nsew
2422
2423# -- Commit Area Buttons
2424#
2425frame .vpane.lower.commarea.buttons
2426label .vpane.lower.commarea.buttons.l -text {} \
2427        -anchor w \
2428        -justify left
2429pack .vpane.lower.commarea.buttons.l -side top -fill x
2430pack .vpane.lower.commarea.buttons -side left -fill y
2431
2432button .vpane.lower.commarea.buttons.rescan -text [mc Rescan] \
2433        -command do_rescan
2434pack .vpane.lower.commarea.buttons.rescan -side top -fill x
2435lappend disable_on_lock \
2436        {.vpane.lower.commarea.buttons.rescan conf -state}
2437
2438button .vpane.lower.commarea.buttons.incall -text [mc "Stage Changed"] \
2439        -command do_add_all
2440pack .vpane.lower.commarea.buttons.incall -side top -fill x
2441lappend disable_on_lock \
2442        {.vpane.lower.commarea.buttons.incall conf -state}
2443
2444button .vpane.lower.commarea.buttons.signoff -text [mc "Sign Off"] \
2445        -command do_signoff
2446pack .vpane.lower.commarea.buttons.signoff -side top -fill x
2447
2448button .vpane.lower.commarea.buttons.commit -text [mc Commit@@verb] \
2449        -command do_commit
2450pack .vpane.lower.commarea.buttons.commit -side top -fill x
2451lappend disable_on_lock \
2452        {.vpane.lower.commarea.buttons.commit conf -state}
2453
2454button .vpane.lower.commarea.buttons.push -text [mc Push] \
2455        -command do_push_anywhere
2456pack .vpane.lower.commarea.buttons.push -side top -fill x
2457
2458# -- Commit Message Buffer
2459#
2460frame .vpane.lower.commarea.buffer
2461frame .vpane.lower.commarea.buffer.header
2462set ui_comm .vpane.lower.commarea.buffer.t
2463set ui_coml .vpane.lower.commarea.buffer.header.l
2464radiobutton .vpane.lower.commarea.buffer.header.new \
2465        -text [mc "New Commit"] \
2466        -command do_select_commit_type \
2467        -variable selected_commit_type \
2468        -value new
2469lappend disable_on_lock \
2470        [list .vpane.lower.commarea.buffer.header.new conf -state]
2471radiobutton .vpane.lower.commarea.buffer.header.amend \
2472        -text [mc "Amend Last Commit"] \
2473        -command do_select_commit_type \
2474        -variable selected_commit_type \
2475        -value amend
2476lappend disable_on_lock \
2477        [list .vpane.lower.commarea.buffer.header.amend conf -state]
2478label $ui_coml \
2479        -anchor w \
2480        -justify left
2481proc trace_commit_type {varname args} {
2482        global ui_coml commit_type
2483        switch -glob -- $commit_type {
2484        initial       {set txt [mc "Initial Commit Message:"]}
2485        amend         {set txt [mc "Amended Commit Message:"]}
2486        amend-initial {set txt [mc "Amended Initial Commit Message:"]}
2487        amend-merge   {set txt [mc "Amended Merge Commit Message:"]}
2488        merge         {set txt [mc "Merge Commit Message:"]}
2489        *             {set txt [mc "Commit Message:"]}
2490        }
2491        $ui_coml conf -text $txt
2492}
2493trace add variable commit_type write trace_commit_type
2494pack $ui_coml -side left -fill x
2495pack .vpane.lower.commarea.buffer.header.amend -side right
2496pack .vpane.lower.commarea.buffer.header.new -side right
2497
2498text $ui_comm -background white -foreground black \
2499        -borderwidth 1 \
2500        -undo true \
2501        -maxundo 20 \
2502        -autoseparators true \
2503        -relief sunken \
2504        -width $repo_config(gui.commitmsgwidth) -height 9 -wrap none \
2505        -font font_diff \
2506        -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
2507scrollbar .vpane.lower.commarea.buffer.sby \
2508        -command [list $ui_comm yview]
2509pack .vpane.lower.commarea.buffer.header -side top -fill x
2510pack .vpane.lower.commarea.buffer.sby -side right -fill y
2511pack $ui_comm -side left -fill y
2512pack .vpane.lower.commarea.buffer -side left -fill y
2513
2514# -- Commit Message Buffer Context Menu
2515#
2516set ctxm .vpane.lower.commarea.buffer.ctxm
2517menu $ctxm -tearoff 0
2518$ctxm add command \
2519        -label [mc Cut] \
2520        -command {tk_textCut $ui_comm}
2521$ctxm add command \
2522        -label [mc Copy] \
2523        -command {tk_textCopy $ui_comm}
2524$ctxm add command \
2525        -label [mc Paste] \
2526        -command {tk_textPaste $ui_comm}
2527$ctxm add command \
2528        -label [mc Delete] \
2529        -command {$ui_comm delete sel.first sel.last}
2530$ctxm add separator
2531$ctxm add command \
2532        -label [mc "Select All"] \
2533        -command {focus $ui_comm;$ui_comm tag add sel 0.0 end}
2534$ctxm add command \
2535        -label [mc "Copy All"] \
2536        -command {
2537                $ui_comm tag add sel 0.0 end
2538                tk_textCopy $ui_comm
2539                $ui_comm tag remove sel 0.0 end
2540        }
2541$ctxm add separator
2542$ctxm add command \
2543        -label [mc "Sign Off"] \
2544        -command do_signoff
2545set ui_comm_ctxm $ctxm
2546
2547# -- Diff Header
2548#
2549proc trace_current_diff_path {varname args} {
2550        global current_diff_path diff_actions file_states
2551        if {$current_diff_path eq {}} {
2552                set s {}
2553                set f {}
2554                set p {}
2555                set o disabled
2556        } else {
2557                set p $current_diff_path
2558                set s [mapdesc [lindex $file_states($p) 0] $p]
2559                set f [mc "File:"]
2560                set p [escape_path $p]
2561                set o normal
2562        }
2563
2564        .vpane.lower.diff.header.status configure -text $s
2565        .vpane.lower.diff.header.file configure -text $f
2566        .vpane.lower.diff.header.path configure -text $p
2567        foreach w $diff_actions {
2568                uplevel #0 $w $o
2569        }
2570}
2571trace add variable current_diff_path write trace_current_diff_path
2572
2573frame .vpane.lower.diff.header -background gold
2574label .vpane.lower.diff.header.status \
2575        -background gold \
2576        -foreground black \
2577        -width $max_status_desc \
2578        -anchor w \
2579        -justify left
2580label .vpane.lower.diff.header.file \
2581        -background gold \
2582        -foreground black \
2583        -anchor w \
2584        -justify left
2585label .vpane.lower.diff.header.path \
2586        -background gold \
2587        -foreground black \
2588        -anchor w \
2589        -justify left
2590pack .vpane.lower.diff.header.status -side left
2591pack .vpane.lower.diff.header.file -side left
2592pack .vpane.lower.diff.header.path -fill x
2593set ctxm .vpane.lower.diff.header.ctxm
2594menu $ctxm -tearoff 0
2595$ctxm add command \
2596        -label [mc Copy] \
2597        -command {
2598                clipboard clear
2599                clipboard append \
2600                        -format STRING \
2601                        -type STRING \
2602                        -- $current_diff_path
2603        }
2604lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2605bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
2606
2607# -- Diff Body
2608#
2609frame .vpane.lower.diff.body
2610set ui_diff .vpane.lower.diff.body.t
2611text $ui_diff -background white -foreground black \
2612        -borderwidth 0 \
2613        -width 80 -height 15 -wrap none \
2614        -font font_diff \
2615        -xscrollcommand {.vpane.lower.diff.body.sbx set} \
2616        -yscrollcommand {.vpane.lower.diff.body.sby set} \
2617        -state disabled
2618scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
2619        -command [list $ui_diff xview]
2620scrollbar .vpane.lower.diff.body.sby -orient vertical \
2621        -command [list $ui_diff yview]
2622pack .vpane.lower.diff.body.sbx -side bottom -fill x
2623pack .vpane.lower.diff.body.sby -side right -fill y
2624pack $ui_diff -side left -fill both -expand 1
2625pack .vpane.lower.diff.header -side top -fill x
2626pack .vpane.lower.diff.body -side bottom -fill both -expand 1
2627
2628$ui_diff tag conf d_cr -elide true
2629$ui_diff tag conf d_@ -foreground blue -font font_diffbold
2630$ui_diff tag conf d_+ -foreground {#00a000}
2631$ui_diff tag conf d_- -foreground red
2632
2633$ui_diff tag conf d_++ -foreground {#00a000}
2634$ui_diff tag conf d_-- -foreground red
2635$ui_diff tag conf d_+s \
2636        -foreground {#00a000} \
2637        -background {#e2effa}
2638$ui_diff tag conf d_-s \
2639        -foreground red \
2640        -background {#e2effa}
2641$ui_diff tag conf d_s+ \
2642        -foreground {#00a000} \
2643        -background ivory1
2644$ui_diff tag conf d_s- \
2645        -foreground red \
2646        -background ivory1
2647
2648$ui_diff tag conf d<<<<<<< \
2649        -foreground orange \
2650        -font font_diffbold
2651$ui_diff tag conf d======= \
2652        -foreground orange \
2653        -font font_diffbold
2654$ui_diff tag conf d>>>>>>> \
2655        -foreground orange \
2656        -font font_diffbold
2657
2658$ui_diff tag raise sel
2659
2660# -- Diff Body Context Menu
2661#
2662set ctxm .vpane.lower.diff.body.ctxm
2663menu $ctxm -tearoff 0
2664$ctxm add command \
2665        -label [mc "Apply/Reverse Hunk"] \
2666        -command {apply_hunk $cursorX $cursorY}
2667set ui_diff_applyhunk [$ctxm index last]
2668lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
2669$ctxm add separator
2670$ctxm add command \
2671        -label [mc "Show Less Context"] \
2672        -command show_less_context
2673lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2674$ctxm add command \
2675        -label [mc "Show More Context"] \
2676        -command show_more_context
2677lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2678$ctxm add separator
2679$ctxm add command \
2680        -label [mc Refresh] \
2681        -command reshow_diff
2682lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2683$ctxm add command \
2684        -label [mc Copy] \
2685        -command {tk_textCopy $ui_diff}
2686lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2687$ctxm add command \
2688        -label [mc "Select All"] \
2689        -command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
2690lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2691$ctxm add command \
2692        -label [mc "Copy All"] \
2693        -command {
2694                $ui_diff tag add sel 0.0 end
2695                tk_textCopy $ui_diff
2696                $ui_diff tag remove sel 0.0 end
2697        }
2698lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2699$ctxm add separator
2700$ctxm add command \
2701        -label [mc "Decrease Font Size"] \
2702        -command {incr_font_size font_diff -1}
2703lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2704$ctxm add command \
2705        -label [mc "Increase Font Size"] \
2706        -command {incr_font_size font_diff 1}
2707lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2708$ctxm add separator
2709$ctxm add command -label [mc "Options..."] \
2710        -command do_options
2711proc popup_diff_menu {ctxm x y X Y} {
2712        global current_diff_path file_states
2713        set ::cursorX $x
2714        set ::cursorY $y
2715        if {$::ui_index eq $::current_diff_side} {
2716                set l [mc "Unstage Hunk From Commit"]
2717        } else {
2718                set l [mc "Stage Hunk For Commit"]
2719        }
2720        if {$::is_3way_diff
2721                || $current_diff_path eq {}
2722                || ![info exists file_states($current_diff_path)]
2723                || {_O} eq [lindex $file_states($current_diff_path) 0]} {
2724                set s disabled
2725        } else {
2726                set s normal
2727        }
2728        $ctxm entryconf $::ui_diff_applyhunk -state $s -label $l
2729        tk_popup $ctxm $X $Y
2730}
2731bind_button3 $ui_diff [list popup_diff_menu $ctxm %x %y %X %Y]
2732
2733# -- Status Bar
2734#
2735set main_status [::status_bar::new .status]
2736pack .status -anchor w -side bottom -fill x
2737$main_status show [mc "Initializing..."]
2738
2739# -- Load geometry
2740#
2741catch {
2742set gm $repo_config(gui.geometry)
2743wm geometry . [lindex $gm 0]
2744.vpane sash place 0 \
2745        [lindex $gm 1] \
2746        [lindex [.vpane sash coord 0] 1]
2747.vpane.files sash place 0 \
2748        [lindex [.vpane.files sash coord 0] 0] \
2749        [lindex $gm 2]
2750unset gm
2751}
2752
2753# -- Key Bindings
2754#
2755bind $ui_comm <$M1B-Key-Return> {do_commit;break}
2756bind $ui_comm <$M1B-Key-t> {do_add_selection;break}
2757bind $ui_comm <$M1B-Key-T> {do_add_selection;break}
2758bind $ui_comm <$M1B-Key-i> {do_add_all;break}
2759bind $ui_comm <$M1B-Key-I> {do_add_all;break}
2760bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
2761bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
2762bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
2763bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
2764bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
2765bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
2766bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2767bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2768bind $ui_comm <$M1B-Key-minus> {show_less_context;break}
2769bind $ui_comm <$M1B-Key-KP_Subtract> {show_less_context;break}
2770bind $ui_comm <$M1B-Key-equal> {show_more_context;break}
2771bind $ui_comm <$M1B-Key-plus> {show_more_context;break}
2772bind $ui_comm <$M1B-Key-KP_Add> {show_more_context;break}
2773
2774bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
2775bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
2776bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
2777bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
2778bind $ui_diff <$M1B-Key-v> {break}
2779bind $ui_diff <$M1B-Key-V> {break}
2780bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2781bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2782bind $ui_diff <Key-Up>     {catch {%W yview scroll -1 units};break}
2783bind $ui_diff <Key-Down>   {catch {%W yview scroll  1 units};break}
2784bind $ui_diff <Key-Left>   {catch {%W xview scroll -1 units};break}
2785bind $ui_diff <Key-Right>  {catch {%W xview scroll  1 units};break}
2786bind $ui_diff <Key-k>         {catch {%W yview scroll -1 units};break}
2787bind $ui_diff <Key-j>         {catch {%W yview scroll  1 units};break}
2788bind $ui_diff <Key-h>         {catch {%W xview scroll -1 units};break}
2789bind $ui_diff <Key-l>         {catch {%W xview scroll  1 units};break}
2790bind $ui_diff <Control-Key-b> {catch {%W yview scroll -1 pages};break}
2791bind $ui_diff <Control-Key-f> {catch {%W yview scroll  1 pages};break}
2792bind $ui_diff <Button-1>   {focus %W}
2793
2794if {[is_enabled branch]} {
2795        bind . <$M1B-Key-n> branch_create::dialog
2796        bind . <$M1B-Key-N> branch_create::dialog
2797        bind . <$M1B-Key-o> branch_checkout::dialog
2798        bind . <$M1B-Key-O> branch_checkout::dialog
2799        bind . <$M1B-Key-m> merge::dialog
2800        bind . <$M1B-Key-M> merge::dialog
2801}
2802if {[is_enabled transport]} {
2803        bind . <$M1B-Key-p> do_push_anywhere
2804        bind . <$M1B-Key-P> do_push_anywhere
2805}
2806
2807bind .   <Key-F5>     do_rescan
2808bind .   <$M1B-Key-r> do_rescan
2809bind .   <$M1B-Key-R> do_rescan
2810bind .   <$M1B-Key-s> do_signoff
2811bind .   <$M1B-Key-S> do_signoff
2812bind .   <$M1B-Key-t> do_add_selection
2813bind .   <$M1B-Key-T> do_add_selection
2814bind .   <$M1B-Key-i> do_add_all
2815bind .   <$M1B-Key-I> do_add_all
2816bind .   <$M1B-Key-minus> {show_less_context;break}
2817bind .   <$M1B-Key-KP_Subtract> {show_less_context;break}
2818bind .   <$M1B-Key-equal> {show_more_context;break}
2819bind .   <$M1B-Key-plus> {show_more_context;break}
2820bind .   <$M1B-Key-KP_Add> {show_more_context;break}
2821bind .   <$M1B-Key-Return> do_commit
2822foreach i [list $ui_index $ui_workdir] {
2823        bind $i <Button-1>       "toggle_or_diff         $i %x %y; break"
2824        bind $i <$M1B-Button-1>  "add_one_to_selection   $i %x %y; break"
2825        bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break"
2826}
2827unset i
2828
2829set file_lists($ui_index) [list]
2830set file_lists($ui_workdir) [list]
2831
2832wm title . "[appname] ([reponame]) [file normalize [file dirname [gitdir]]]"
2833focus -force $ui_comm
2834
2835# -- Warn the user about environmental problems.  Cygwin's Tcl
2836#    does *not* pass its env array onto any processes it spawns.
2837#    This means that git processes get none of our environment.
2838#
2839if {[is_Cygwin]} {
2840        set ignored_env 0
2841        set suggest_user {}
2842        set msg [mc "Possible environment issues exist.
2843
2844The following environment variables are probably
2845going to be ignored by any Git subprocess run
2846by %s:
2847
2848" [appname]]
2849        foreach name [array names env] {
2850                switch -regexp -- $name {
2851                {^GIT_INDEX_FILE$} -
2852                {^GIT_OBJECT_DIRECTORY$} -
2853                {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} -
2854                {^GIT_DIFF_OPTS$} -
2855                {^GIT_EXTERNAL_DIFF$} -
2856                {^GIT_PAGER$} -
2857                {^GIT_TRACE$} -
2858                {^GIT_CONFIG$} -
2859                {^GIT_CONFIG_LOCAL$} -
2860                {^GIT_(AUTHOR|COMMITTER)_DATE$} {
2861                        append msg " - $name\n"
2862                        incr ignored_env
2863                }
2864                {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} {
2865                        append msg " - $name\n"
2866                        incr ignored_env
2867                        set suggest_user $name
2868                }
2869                }
2870        }
2871        if {$ignored_env > 0} {
2872                append msg [mc "
2873This is due to a known issue with the
2874Tcl binary distributed by Cygwin."]
2875
2876                if {$suggest_user ne {}} {
2877                        append msg [mc "
2878
2879A good replacement for %s
2880is placing values for the user.name and
2881user.email settings into your personal
2882~/.gitconfig file.
2883" $suggest_user]
2884                }
2885                warn_popup $msg
2886        }
2887        unset ignored_env msg suggest_user name
2888}
2889
2890# -- Only initialize complex UI if we are going to stay running.
2891#
2892if {[is_enabled transport]} {
2893        load_all_remotes
2894
2895        set n [.mbar.remote index end]
2896        populate_push_menu
2897        populate_fetch_menu
2898        set n [expr {[.mbar.remote index end] - $n}]
2899        if {$n > 0} {
2900                .mbar.remote insert $n separator
2901        }
2902        unset n
2903}
2904
2905if {[winfo exists $ui_comm]} {
2906        set GITGUI_BCK_exists [load_message GITGUI_BCK]
2907
2908        # -- If both our backup and message files exist use the
2909        #    newer of the two files to initialize the buffer.
2910        #
2911        if {$GITGUI_BCK_exists} {
2912                set m [gitdir GITGUI_MSG]
2913                if {[file isfile $m]} {
2914                        if {[file mtime [gitdir GITGUI_BCK]] > [file mtime $m]} {
2915                                catch {file delete [gitdir GITGUI_MSG]}
2916                        } else {
2917                                $ui_comm delete 0.0 end
2918                                $ui_comm edit reset
2919                                $ui_comm edit modified false
2920                                catch {file delete [gitdir GITGUI_BCK]}
2921                                set GITGUI_BCK_exists 0
2922                        }
2923                }
2924                unset m
2925        }
2926
2927        proc backup_commit_buffer {} {
2928                global ui_comm GITGUI_BCK_exists
2929
2930                set m [$ui_comm edit modified]
2931                if {$m || $GITGUI_BCK_exists} {
2932                        set msg [string trim [$ui_comm get 0.0 end]]
2933                        regsub -all -line {[ \r\t]+$} $msg {} msg
2934
2935                        if {$msg eq {}} {
2936                                if {$GITGUI_BCK_exists} {
2937                                        catch {file delete [gitdir GITGUI_BCK]}
2938                                        set GITGUI_BCK_exists 0
2939                                }
2940                        } elseif {$m} {
2941                                catch {
2942                                        set fd [open [gitdir GITGUI_BCK] w]
2943                                        puts -nonewline $fd $msg
2944                                        close $fd
2945                                        set GITGUI_BCK_exists 1
2946                                }
2947                        }
2948
2949                        $ui_comm edit modified false
2950                }
2951
2952                set ::GITGUI_BCK_i [after 2000 backup_commit_buffer]
2953        }
2954
2955        backup_commit_buffer
2956
2957        # -- If the user has aspell available we can drive it
2958        #    in pipe mode to spellcheck the commit message.
2959        #
2960        set spell_cmd [list |]
2961        set spell_dict [get_config gui.spellingdictionary]
2962        lappend spell_cmd aspell
2963        if {$spell_dict ne {}} {
2964                lappend spell_cmd --master=$spell_dict
2965        }
2966        lappend spell_cmd --mode=none
2967        lappend spell_cmd --encoding=utf-8
2968        lappend spell_cmd pipe
2969        if {$spell_dict eq {none}
2970         || [catch {set spell_fd [open $spell_cmd r+]} spell_err]} {
2971                bind_button3 $ui_comm [list tk_popup $ui_comm_ctxm %X %Y]
2972        } else {
2973                set ui_comm_spell [spellcheck::init \
2974                        $spell_fd \
2975                        $ui_comm \
2976                        $ui_comm_ctxm \
2977                ]
2978        }
2979        unset -nocomplain spell_cmd spell_fd spell_err spell_dict
2980}
2981
2982lock_index begin-read
2983if {![winfo ismapped .]} {
2984        wm deiconify .
2985}
2986after 1 do_rescan
2987if {[is_enabled multicommit]} {
2988        after 1000 hint_gc
2989}