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