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