b1ed0ec5681ea78fb5ece8393604760fb302214f
   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 Windows [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_Windows]} {
 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.fastcopyblame) false
 646set default_config(gui.copyblamethreshold) 40
 647set default_config(gui.diffcontext) 5
 648set default_config(gui.commitmsgwidth) 75
 649set default_config(gui.newbranchtemplate) {}
 650set default_config(gui.spellingdictionary) {}
 651set default_config(gui.fontui) [font configure font_ui]
 652set default_config(gui.fontdiff) [font configure font_diff]
 653set font_descs {
 654        {fontui   font_ui   {mc "Main Font"}}
 655        {fontdiff font_diff {mc "Diff/Console Font"}}
 656}
 657
 658######################################################################
 659##
 660## find git
 661
 662set _git  [_which git]
 663if {$_git eq {}} {
 664        catch {wm withdraw .}
 665        tk_messageBox \
 666                -icon error \
 667                -type ok \
 668                -title [mc "git-gui: fatal error"] \
 669                -message [mc "Cannot find git in PATH."]
 670        exit 1
 671}
 672
 673######################################################################
 674##
 675## version check
 676
 677if {[catch {set _git_version [git --version]} err]} {
 678        catch {wm withdraw .}
 679        tk_messageBox \
 680                -icon error \
 681                -type ok \
 682                -title [mc "git-gui: fatal error"] \
 683                -message "Cannot determine Git version:
 684
 685$err
 686
 687[appname] requires Git 1.5.0 or later."
 688        exit 1
 689}
 690if {![regsub {^git version } $_git_version {} _git_version]} {
 691        catch {wm withdraw .}
 692        tk_messageBox \
 693                -icon error \
 694                -type ok \
 695                -title [mc "git-gui: fatal error"] \
 696                -message [strcat [mc "Cannot parse Git version string:"] "\n\n$_git_version"]
 697        exit 1
 698}
 699
 700set _real_git_version $_git_version
 701regsub -- {[\-\.]dirty$} $_git_version {} _git_version
 702regsub {\.[0-9]+\.g[0-9a-f]+$} $_git_version {} _git_version
 703regsub {\.rc[0-9]+$} $_git_version {} _git_version
 704regsub {\.GIT$} $_git_version {} _git_version
 705regsub {\.[a-zA-Z]+\.[0-9]+$} $_git_version {} _git_version
 706
 707if {![regexp {^[1-9]+(\.[0-9]+)+$} $_git_version]} {
 708        catch {wm withdraw .}
 709        if {[tk_messageBox \
 710                -icon warning \
 711                -type yesno \
 712                -default no \
 713                -title "[appname]: warning" \
 714                 -message [mc "Git version cannot be determined.
 715
 716%s claims it is version '%s'.
 717
 718%s requires at least Git 1.5.0 or later.
 719
 720Assume '%s' is version 1.5.0?
 721" $_git $_real_git_version [appname] $_real_git_version]] eq {yes}} {
 722                set _git_version 1.5.0
 723        } else {
 724                exit 1
 725        }
 726}
 727unset _real_git_version
 728
 729proc git-version {args} {
 730        global _git_version
 731
 732        switch [llength $args] {
 733        0 {
 734                return $_git_version
 735        }
 736
 737        2 {
 738                set op [lindex $args 0]
 739                set vr [lindex $args 1]
 740                set cm [package vcompare $_git_version $vr]
 741                return [expr $cm $op 0]
 742        }
 743
 744        4 {
 745                set type [lindex $args 0]
 746                set name [lindex $args 1]
 747                set parm [lindex $args 2]
 748                set body [lindex $args 3]
 749
 750                if {($type ne {proc} && $type ne {method})} {
 751                        error "Invalid arguments to git-version"
 752                }
 753                if {[llength $body] < 2 || [lindex $body end-1] ne {default}} {
 754                        error "Last arm of $type $name must be default"
 755                }
 756
 757                foreach {op vr cb} [lrange $body 0 end-2] {
 758                        if {[git-version $op $vr]} {
 759                                return [uplevel [list $type $name $parm $cb]]
 760                        }
 761                }
 762
 763                return [uplevel [list $type $name $parm [lindex $body end]]]
 764        }
 765
 766        default {
 767                error "git-version >= x"
 768        }
 769
 770        }
 771}
 772
 773if {[git-version < 1.5]} {
 774        catch {wm withdraw .}
 775        tk_messageBox \
 776                -icon error \
 777                -type ok \
 778                -title [mc "git-gui: fatal error"] \
 779                -message "[appname] requires Git 1.5.0 or later.
 780
 781You are using [git-version]:
 782
 783[git --version]"
 784        exit 1
 785}
 786
 787######################################################################
 788##
 789## configure our library
 790
 791set idx [file join $oguilib tclIndex]
 792if {[catch {set fd [open $idx r]} err]} {
 793        catch {wm withdraw .}
 794        tk_messageBox \
 795                -icon error \
 796                -type ok \
 797                -title [mc "git-gui: fatal error"] \
 798                -message $err
 799        exit 1
 800}
 801if {[gets $fd] eq {# Autogenerated by git-gui Makefile}} {
 802        set idx [list]
 803        while {[gets $fd n] >= 0} {
 804                if {$n ne {} && ![string match #* $n]} {
 805                        lappend idx $n
 806                }
 807        }
 808} else {
 809        set idx {}
 810}
 811close $fd
 812
 813if {$idx ne {}} {
 814        set loaded [list]
 815        foreach p $idx {
 816                if {[lsearch -exact $loaded $p] >= 0} continue
 817                source [file join $oguilib $p]
 818                lappend loaded $p
 819        }
 820        unset loaded p
 821} else {
 822        set auto_path [concat [list $oguilib] $auto_path]
 823}
 824unset -nocomplain idx fd
 825
 826######################################################################
 827##
 828## config file parsing
 829
 830git-version proc _parse_config {arr_name args} {
 831        >= 1.5.3 {
 832                upvar $arr_name arr
 833                array unset arr
 834                set buf {}
 835                catch {
 836                        set fd_rc [eval \
 837                                [list git_read config] \
 838                                $args \
 839                                [list --null --list]]
 840                        fconfigure $fd_rc -translation binary
 841                        set buf [read $fd_rc]
 842                        close $fd_rc
 843                }
 844                foreach line [split $buf "\0"] {
 845                        if {[regexp {^([^\n]+)\n(.*)$} $line line name value]} {
 846                                if {[is_many_config $name]} {
 847                                        lappend arr($name) $value
 848                                } else {
 849                                        set arr($name) $value
 850                                }
 851                        }
 852                }
 853        }
 854        default {
 855                upvar $arr_name arr
 856                array unset arr
 857                catch {
 858                        set fd_rc [eval [list git_read config --list] $args]
 859                        while {[gets $fd_rc line] >= 0} {
 860                                if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
 861                                        if {[is_many_config $name]} {
 862                                                lappend arr($name) $value
 863                                        } else {
 864                                                set arr($name) $value
 865                                        }
 866                                }
 867                        }
 868                        close $fd_rc
 869                }
 870        }
 871}
 872
 873proc load_config {include_global} {
 874        global repo_config global_config default_config
 875
 876        if {$include_global} {
 877                _parse_config global_config --global
 878        }
 879        _parse_config repo_config
 880
 881        foreach name [array names default_config] {
 882                if {[catch {set v $global_config($name)}]} {
 883                        set global_config($name) $default_config($name)
 884                }
 885                if {[catch {set v $repo_config($name)}]} {
 886                        set repo_config($name) $default_config($name)
 887                }
 888        }
 889}
 890
 891######################################################################
 892##
 893## feature option selection
 894
 895if {[regexp {^git-(.+)$} [file tail $argv0] _junk subcommand]} {
 896        unset _junk
 897} else {
 898        set subcommand gui
 899}
 900if {$subcommand eq {gui.sh}} {
 901        set subcommand gui
 902}
 903if {$subcommand eq {gui} && [llength $argv] > 0} {
 904        set subcommand [lindex $argv 0]
 905        set argv [lrange $argv 1 end]
 906}
 907
 908enable_option multicommit
 909enable_option branch
 910enable_option transport
 911disable_option bare
 912
 913switch -- $subcommand {
 914browser -
 915blame {
 916        enable_option bare
 917
 918        disable_option multicommit
 919        disable_option branch
 920        disable_option transport
 921}
 922citool {
 923        enable_option singlecommit
 924
 925        disable_option multicommit
 926        disable_option branch
 927        disable_option transport
 928}
 929}
 930
 931######################################################################
 932##
 933## repository setup
 934
 935if {[catch {
 936                set _gitdir $env(GIT_DIR)
 937                set _prefix {}
 938                }]
 939        && [catch {
 940                set _gitdir [git rev-parse --git-dir]
 941                set _prefix [git rev-parse --show-prefix]
 942        } err]} {
 943        load_config 1
 944        apply_config
 945        choose_repository::pick
 946}
 947if {![file isdirectory $_gitdir] && [is_Cygwin]} {
 948        catch {set _gitdir [exec cygpath --windows $_gitdir]}
 949}
 950if {![file isdirectory $_gitdir]} {
 951        catch {wm withdraw .}
 952        error_popup [strcat [mc "Git directory not found:"] "\n\n$_gitdir"]
 953        exit 1
 954}
 955if {$_prefix ne {}} {
 956        regsub -all {[^/]+/} $_prefix ../ cdup
 957        if {[catch {cd $cdup} err]} {
 958                catch {wm withdraw .}
 959                error_popup [strcat [mc "Cannot move to top of working directory:"] "\n\n$err"]
 960                exit 1
 961        }
 962        unset cdup
 963} elseif {![is_enabled bare]} {
 964        if {[lindex [file split $_gitdir] end] ne {.git}} {
 965                catch {wm withdraw .}
 966                error_popup [strcat [mc "Cannot use funny .git directory:"] "\n\n$_gitdir"]
 967                exit 1
 968        }
 969        if {[catch {cd [file dirname $_gitdir]} err]} {
 970                catch {wm withdraw .}
 971                error_popup [strcat [mc "No working directory"] " [file dirname $_gitdir]:\n\n$err"]
 972                exit 1
 973        }
 974}
 975set _reponame [file split [file normalize $_gitdir]]
 976if {[lindex $_reponame end] eq {.git}} {
 977        set _reponame [lindex $_reponame end-1]
 978} else {
 979        set _reponame [lindex $_reponame end]
 980}
 981
 982######################################################################
 983##
 984## global init
 985
 986set current_diff_path {}
 987set current_diff_side {}
 988set diff_actions [list]
 989
 990set HEAD {}
 991set PARENT {}
 992set MERGE_HEAD [list]
 993set commit_type {}
 994set empty_tree {}
 995set current_branch {}
 996set is_detached 0
 997set current_diff_path {}
 998set is_3way_diff 0
 999set selected_commit_type new
1000
1001######################################################################
1002##
1003## task management
1004
1005set rescan_active 0
1006set diff_active 0
1007set last_clicked {}
1008
1009set disable_on_lock [list]
1010set index_lock_type none
1011
1012proc lock_index {type} {
1013        global index_lock_type disable_on_lock
1014
1015        if {$index_lock_type eq {none}} {
1016                set index_lock_type $type
1017                foreach w $disable_on_lock {
1018                        uplevel #0 $w disabled
1019                }
1020                return 1
1021        } elseif {$index_lock_type eq "begin-$type"} {
1022                set index_lock_type $type
1023                return 1
1024        }
1025        return 0
1026}
1027
1028proc unlock_index {} {
1029        global index_lock_type disable_on_lock
1030
1031        set index_lock_type none
1032        foreach w $disable_on_lock {
1033                uplevel #0 $w normal
1034        }
1035}
1036
1037######################################################################
1038##
1039## status
1040
1041proc repository_state {ctvar hdvar mhvar} {
1042        global current_branch
1043        upvar $ctvar ct $hdvar hd $mhvar mh
1044
1045        set mh [list]
1046
1047        load_current_branch
1048        if {[catch {set hd [git rev-parse --verify HEAD]}]} {
1049                set hd {}
1050                set ct initial
1051                return
1052        }
1053
1054        set merge_head [gitdir MERGE_HEAD]
1055        if {[file exists $merge_head]} {
1056                set ct merge
1057                set fd_mh [open $merge_head r]
1058                while {[gets $fd_mh line] >= 0} {
1059                        lappend mh $line
1060                }
1061                close $fd_mh
1062                return
1063        }
1064
1065        set ct normal
1066}
1067
1068proc PARENT {} {
1069        global PARENT empty_tree
1070
1071        set p [lindex $PARENT 0]
1072        if {$p ne {}} {
1073                return $p
1074        }
1075        if {$empty_tree eq {}} {
1076                set empty_tree [git mktree << {}]
1077        }
1078        return $empty_tree
1079}
1080
1081proc rescan {after {honor_trustmtime 1}} {
1082        global HEAD PARENT MERGE_HEAD commit_type
1083        global ui_index ui_workdir ui_comm
1084        global rescan_active file_states
1085        global repo_config
1086
1087        if {$rescan_active > 0 || ![lock_index read]} return
1088
1089        repository_state newType newHEAD newMERGE_HEAD
1090        if {[string match amend* $commit_type]
1091                && $newType eq {normal}
1092                && $newHEAD eq $HEAD} {
1093        } else {
1094                set HEAD $newHEAD
1095                set PARENT $newHEAD
1096                set MERGE_HEAD $newMERGE_HEAD
1097                set commit_type $newType
1098        }
1099
1100        array unset file_states
1101
1102        if {!$::GITGUI_BCK_exists &&
1103                (![$ui_comm edit modified]
1104                || [string trim [$ui_comm get 0.0 end]] eq {})} {
1105                if {[string match amend* $commit_type]} {
1106                } elseif {[load_message GITGUI_MSG]} {
1107                } elseif {[load_message MERGE_MSG]} {
1108                } elseif {[load_message SQUASH_MSG]} {
1109                }
1110                $ui_comm edit reset
1111                $ui_comm edit modified false
1112        }
1113
1114        if {$honor_trustmtime && $repo_config(gui.trustmtime) eq {true}} {
1115                rescan_stage2 {} $after
1116        } else {
1117                set rescan_active 1
1118                ui_status [mc "Refreshing file status..."]
1119                set fd_rf [git_read update-index \
1120                        -q \
1121                        --unmerged \
1122                        --ignore-missing \
1123                        --refresh \
1124                        ]
1125                fconfigure $fd_rf -blocking 0 -translation binary
1126                fileevent $fd_rf readable \
1127                        [list rescan_stage2 $fd_rf $after]
1128        }
1129}
1130
1131if {[is_Cygwin]} {
1132        set is_git_info_exclude {}
1133        proc have_info_exclude {} {
1134                global is_git_info_exclude
1135
1136                if {$is_git_info_exclude eq {}} {
1137                        if {[catch {exec test -f [gitdir info exclude]}]} {
1138                                set is_git_info_exclude 0
1139                        } else {
1140                                set is_git_info_exclude 1
1141                        }
1142                }
1143                return $is_git_info_exclude
1144        }
1145} else {
1146        proc have_info_exclude {} {
1147                return [file readable [gitdir info exclude]]
1148        }
1149}
1150
1151proc rescan_stage2 {fd after} {
1152        global rescan_active buf_rdi buf_rdf buf_rlo
1153
1154        if {$fd ne {}} {
1155                read $fd
1156                if {![eof $fd]} return
1157                close $fd
1158        }
1159
1160        set ls_others [list --exclude-per-directory=.gitignore]
1161        if {[have_info_exclude]} {
1162                lappend ls_others "--exclude-from=[gitdir info exclude]"
1163        }
1164        set user_exclude [get_config core.excludesfile]
1165        if {$user_exclude ne {} && [file readable $user_exclude]} {
1166                lappend ls_others "--exclude-from=$user_exclude"
1167        }
1168
1169        set buf_rdi {}
1170        set buf_rdf {}
1171        set buf_rlo {}
1172
1173        set rescan_active 3
1174        ui_status [mc "Scanning for modified files ..."]
1175        set fd_di [git_read diff-index --cached -z [PARENT]]
1176        set fd_df [git_read diff-files -z]
1177        set fd_lo [eval git_read ls-files --others -z $ls_others]
1178
1179        fconfigure $fd_di -blocking 0 -translation binary -encoding binary
1180        fconfigure $fd_df -blocking 0 -translation binary -encoding binary
1181        fconfigure $fd_lo -blocking 0 -translation binary -encoding binary
1182        fileevent $fd_di readable [list read_diff_index $fd_di $after]
1183        fileevent $fd_df readable [list read_diff_files $fd_df $after]
1184        fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
1185}
1186
1187proc load_message {file} {
1188        global ui_comm
1189
1190        set f [gitdir $file]
1191        if {[file isfile $f]} {
1192                if {[catch {set fd [open $f r]}]} {
1193                        return 0
1194                }
1195                fconfigure $fd -eofchar {}
1196                set content [string trim [read $fd]]
1197                close $fd
1198                regsub -all -line {[ \r\t]+$} $content {} content
1199                $ui_comm delete 0.0 end
1200                $ui_comm insert end $content
1201                return 1
1202        }
1203        return 0
1204}
1205
1206proc read_diff_index {fd after} {
1207        global buf_rdi
1208
1209        append buf_rdi [read $fd]
1210        set c 0
1211        set n [string length $buf_rdi]
1212        while {$c < $n} {
1213                set z1 [string first "\0" $buf_rdi $c]
1214                if {$z1 == -1} break
1215                incr z1
1216                set z2 [string first "\0" $buf_rdi $z1]
1217                if {$z2 == -1} break
1218
1219                incr c
1220                set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }]
1221                set p [string range $buf_rdi $z1 [expr {$z2 - 1}]]
1222                merge_state \
1223                        [encoding convertfrom $p] \
1224                        [lindex $i 4]? \
1225                        [list [lindex $i 0] [lindex $i 2]] \
1226                        [list]
1227                set c $z2
1228                incr c
1229        }
1230        if {$c < $n} {
1231                set buf_rdi [string range $buf_rdi $c end]
1232        } else {
1233                set buf_rdi {}
1234        }
1235
1236        rescan_done $fd buf_rdi $after
1237}
1238
1239proc read_diff_files {fd after} {
1240        global buf_rdf
1241
1242        append buf_rdf [read $fd]
1243        set c 0
1244        set n [string length $buf_rdf]
1245        while {$c < $n} {
1246                set z1 [string first "\0" $buf_rdf $c]
1247                if {$z1 == -1} break
1248                incr z1
1249                set z2 [string first "\0" $buf_rdf $z1]
1250                if {$z2 == -1} break
1251
1252                incr c
1253                set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }]
1254                set p [string range $buf_rdf $z1 [expr {$z2 - 1}]]
1255                merge_state \
1256                        [encoding convertfrom $p] \
1257                        ?[lindex $i 4] \
1258                        [list] \
1259                        [list [lindex $i 0] [lindex $i 2]]
1260                set c $z2
1261                incr c
1262        }
1263        if {$c < $n} {
1264                set buf_rdf [string range $buf_rdf $c end]
1265        } else {
1266                set buf_rdf {}
1267        }
1268
1269        rescan_done $fd buf_rdf $after
1270}
1271
1272proc read_ls_others {fd after} {
1273        global buf_rlo
1274
1275        append buf_rlo [read $fd]
1276        set pck [split $buf_rlo "\0"]
1277        set buf_rlo [lindex $pck end]
1278        foreach p [lrange $pck 0 end-1] {
1279                set p [encoding convertfrom $p]
1280                if {[string index $p end] eq {/}} {
1281                        set p [string range $p 0 end-1]
1282                }
1283                merge_state $p ?O
1284        }
1285        rescan_done $fd buf_rlo $after
1286}
1287
1288proc rescan_done {fd buf after} {
1289        global rescan_active current_diff_path
1290        global file_states repo_config
1291        upvar $buf to_clear
1292
1293        if {![eof $fd]} return
1294        set to_clear {}
1295        close $fd
1296        if {[incr rescan_active -1] > 0} return
1297
1298        prune_selection
1299        unlock_index
1300        display_all_files
1301        if {$current_diff_path ne {}} reshow_diff
1302        uplevel #0 $after
1303}
1304
1305proc prune_selection {} {
1306        global file_states selected_paths
1307
1308        foreach path [array names selected_paths] {
1309                if {[catch {set still_here $file_states($path)}]} {
1310                        unset selected_paths($path)
1311                }
1312        }
1313}
1314
1315######################################################################
1316##
1317## ui helpers
1318
1319proc mapicon {w state path} {
1320        global all_icons
1321
1322        if {[catch {set r $all_icons($state$w)}]} {
1323                puts "error: no icon for $w state={$state} $path"
1324                return file_plain
1325        }
1326        return $r
1327}
1328
1329proc mapdesc {state path} {
1330        global all_descs
1331
1332        if {[catch {set r $all_descs($state)}]} {
1333                puts "error: no desc for state={$state} $path"
1334                return $state
1335        }
1336        return $r
1337}
1338
1339proc ui_status {msg} {
1340        global main_status
1341        if {[info exists main_status]} {
1342                $main_status show $msg
1343        }
1344}
1345
1346proc ui_ready {{test {}}} {
1347        global main_status
1348        if {[info exists main_status]} {
1349                $main_status show [mc "Ready."] $test
1350        }
1351}
1352
1353proc escape_path {path} {
1354        regsub -all {\\} $path "\\\\" path
1355        regsub -all "\n" $path "\\n" path
1356        return $path
1357}
1358
1359proc short_path {path} {
1360        return [escape_path [lindex [file split $path] end]]
1361}
1362
1363set next_icon_id 0
1364set null_sha1 [string repeat 0 40]
1365
1366proc merge_state {path new_state {head_info {}} {index_info {}}} {
1367        global file_states next_icon_id null_sha1
1368
1369        set s0 [string index $new_state 0]
1370        set s1 [string index $new_state 1]
1371
1372        if {[catch {set info $file_states($path)}]} {
1373                set state __
1374                set icon n[incr next_icon_id]
1375        } else {
1376                set state [lindex $info 0]
1377                set icon [lindex $info 1]
1378                if {$head_info eq {}}  {set head_info  [lindex $info 2]}
1379                if {$index_info eq {}} {set index_info [lindex $info 3]}
1380        }
1381
1382        if     {$s0 eq {?}} {set s0 [string index $state 0]} \
1383        elseif {$s0 eq {_}} {set s0 _}
1384
1385        if     {$s1 eq {?}} {set s1 [string index $state 1]} \
1386        elseif {$s1 eq {_}} {set s1 _}
1387
1388        if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} {
1389                set head_info [list 0 $null_sha1]
1390        } elseif {$s0 ne {_} && [string index $state 0] eq {_}
1391                && $head_info eq {}} {
1392                set head_info $index_info
1393        }
1394
1395        set file_states($path) [list $s0$s1 $icon \
1396                $head_info $index_info \
1397                ]
1398        return $state
1399}
1400
1401proc display_file_helper {w path icon_name old_m new_m} {
1402        global file_lists
1403
1404        if {$new_m eq {_}} {
1405                set lno [lsearch -sorted -exact $file_lists($w) $path]
1406                if {$lno >= 0} {
1407                        set file_lists($w) [lreplace $file_lists($w) $lno $lno]
1408                        incr lno
1409                        $w conf -state normal
1410                        $w delete $lno.0 [expr {$lno + 1}].0
1411                        $w conf -state disabled
1412                }
1413        } elseif {$old_m eq {_} && $new_m ne {_}} {
1414                lappend file_lists($w) $path
1415                set file_lists($w) [lsort -unique $file_lists($w)]
1416                set lno [lsearch -sorted -exact $file_lists($w) $path]
1417                incr lno
1418                $w conf -state normal
1419                $w image create $lno.0 \
1420                        -align center -padx 5 -pady 1 \
1421                        -name $icon_name \
1422                        -image [mapicon $w $new_m $path]
1423                $w insert $lno.1 "[escape_path $path]\n"
1424                $w conf -state disabled
1425        } elseif {$old_m ne $new_m} {
1426                $w conf -state normal
1427                $w image conf $icon_name -image [mapicon $w $new_m $path]
1428                $w conf -state disabled
1429        }
1430}
1431
1432proc display_file {path state} {
1433        global file_states selected_paths
1434        global ui_index ui_workdir
1435
1436        set old_m [merge_state $path $state]
1437        set s $file_states($path)
1438        set new_m [lindex $s 0]
1439        set icon_name [lindex $s 1]
1440
1441        set o [string index $old_m 0]
1442        set n [string index $new_m 0]
1443        if {$o eq {U}} {
1444                set o _
1445        }
1446        if {$n eq {U}} {
1447                set n _
1448        }
1449        display_file_helper     $ui_index $path $icon_name $o $n
1450
1451        if {[string index $old_m 0] eq {U}} {
1452                set o U
1453        } else {
1454                set o [string index $old_m 1]
1455        }
1456        if {[string index $new_m 0] eq {U}} {
1457                set n U
1458        } else {
1459                set n [string index $new_m 1]
1460        }
1461        display_file_helper     $ui_workdir $path $icon_name $o $n
1462
1463        if {$new_m eq {__}} {
1464                unset file_states($path)
1465                catch {unset selected_paths($path)}
1466        }
1467}
1468
1469proc display_all_files_helper {w path icon_name m} {
1470        global file_lists
1471
1472        lappend file_lists($w) $path
1473        set lno [expr {[lindex [split [$w index end] .] 0] - 1}]
1474        $w image create end \
1475                -align center -padx 5 -pady 1 \
1476                -name $icon_name \
1477                -image [mapicon $w $m $path]
1478        $w insert end "[escape_path $path]\n"
1479}
1480
1481proc display_all_files {} {
1482        global ui_index ui_workdir
1483        global file_states file_lists
1484        global last_clicked
1485
1486        $ui_index conf -state normal
1487        $ui_workdir conf -state normal
1488
1489        $ui_index delete 0.0 end
1490        $ui_workdir delete 0.0 end
1491        set last_clicked {}
1492
1493        set file_lists($ui_index) [list]
1494        set file_lists($ui_workdir) [list]
1495
1496        foreach path [lsort [array names file_states]] {
1497                set s $file_states($path)
1498                set m [lindex $s 0]
1499                set icon_name [lindex $s 1]
1500
1501                set s [string index $m 0]
1502                if {$s ne {U} && $s ne {_}} {
1503                        display_all_files_helper $ui_index $path \
1504                                $icon_name $s
1505                }
1506
1507                if {[string index $m 0] eq {U}} {
1508                        set s U
1509                } else {
1510                        set s [string index $m 1]
1511                }
1512                if {$s ne {_}} {
1513                        display_all_files_helper $ui_workdir $path \
1514                                $icon_name $s
1515                }
1516        }
1517
1518        $ui_index conf -state disabled
1519        $ui_workdir conf -state disabled
1520}
1521
1522######################################################################
1523##
1524## icons
1525
1526set filemask {
1527#define mask_width 14
1528#define mask_height 15
1529static unsigned char mask_bits[] = {
1530   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1531   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1532   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
1533}
1534
1535image create bitmap file_plain -background white -foreground black -data {
1536#define plain_width 14
1537#define plain_height 15
1538static unsigned char plain_bits[] = {
1539   0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1540   0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
1541   0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1542} -maskdata $filemask
1543
1544image create bitmap file_mod -background white -foreground blue -data {
1545#define mod_width 14
1546#define mod_height 15
1547static unsigned char mod_bits[] = {
1548   0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1549   0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1550   0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1551} -maskdata $filemask
1552
1553image create bitmap file_fulltick -background white -foreground "#007000" -data {
1554#define file_fulltick_width 14
1555#define file_fulltick_height 15
1556static unsigned char file_fulltick_bits[] = {
1557   0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
1558   0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
1559   0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1560} -maskdata $filemask
1561
1562image create bitmap file_parttick -background white -foreground "#005050" -data {
1563#define parttick_width 14
1564#define parttick_height 15
1565static unsigned char parttick_bits[] = {
1566   0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1567   0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
1568   0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1569} -maskdata $filemask
1570
1571image create bitmap file_question -background white -foreground black -data {
1572#define file_question_width 14
1573#define file_question_height 15
1574static unsigned char file_question_bits[] = {
1575   0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
1576   0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
1577   0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1578} -maskdata $filemask
1579
1580image create bitmap file_removed -background white -foreground red -data {
1581#define file_removed_width 14
1582#define file_removed_height 15
1583static unsigned char file_removed_bits[] = {
1584   0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1585   0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
1586   0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
1587} -maskdata $filemask
1588
1589image create bitmap file_merge -background white -foreground blue -data {
1590#define file_merge_width 14
1591#define file_merge_height 15
1592static unsigned char file_merge_bits[] = {
1593   0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
1594   0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1595   0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1596} -maskdata $filemask
1597
1598set ui_index .vpane.files.index.list
1599set ui_workdir .vpane.files.workdir.list
1600
1601set all_icons(_$ui_index)   file_plain
1602set all_icons(A$ui_index)   file_fulltick
1603set all_icons(M$ui_index)   file_fulltick
1604set all_icons(D$ui_index)   file_removed
1605set all_icons(U$ui_index)   file_merge
1606
1607set all_icons(_$ui_workdir) file_plain
1608set all_icons(M$ui_workdir) file_mod
1609set all_icons(D$ui_workdir) file_question
1610set all_icons(U$ui_workdir) file_merge
1611set all_icons(O$ui_workdir) file_plain
1612
1613set max_status_desc 0
1614foreach i {
1615                {__ {mc "Unmodified"}}
1616
1617                {_M {mc "Modified, not staged"}}
1618                {M_ {mc "Staged for commit"}}
1619                {MM {mc "Portions staged for commit"}}
1620                {MD {mc "Staged for commit, missing"}}
1621
1622                {_O {mc "Untracked, not staged"}}
1623                {A_ {mc "Staged for commit"}}
1624                {AM {mc "Portions staged for commit"}}
1625                {AD {mc "Staged for commit, missing"}}
1626
1627                {_D {mc "Missing"}}
1628                {D_ {mc "Staged for removal"}}
1629                {DO {mc "Staged for removal, still present"}}
1630
1631                {U_ {mc "Requires merge resolution"}}
1632                {UU {mc "Requires merge resolution"}}
1633                {UM {mc "Requires merge resolution"}}
1634                {UD {mc "Requires merge resolution"}}
1635        } {
1636        set text [eval [lindex $i 1]]
1637        if {$max_status_desc < [string length $text]} {
1638                set max_status_desc [string length $text]
1639        }
1640        set all_descs([lindex $i 0]) $text
1641}
1642unset i
1643
1644######################################################################
1645##
1646## util
1647
1648proc scrollbar2many {list mode args} {
1649        foreach w $list {eval $w $mode $args}
1650}
1651
1652proc many2scrollbar {list mode sb top bottom} {
1653        $sb set $top $bottom
1654        foreach w $list {$w $mode moveto $top}
1655}
1656
1657proc incr_font_size {font {amt 1}} {
1658        set sz [font configure $font -size]
1659        incr sz $amt
1660        font configure $font -size $sz
1661        font configure ${font}bold -size $sz
1662        font configure ${font}italic -size $sz
1663}
1664
1665######################################################################
1666##
1667## ui commands
1668
1669set starting_gitk_msg [mc "Starting gitk... please wait..."]
1670
1671proc do_gitk {revs} {
1672        # -- Always start gitk through whatever we were loaded with.  This
1673        #    lets us bypass using shell process on Windows systems.
1674        #
1675        set exe [file join [file dirname $::_git] gitk]
1676        set cmd [list [info nameofexecutable] $exe]
1677        if {! [file exists $exe]} {
1678                error_popup [mc "Unable to start gitk:\n\n%s does not exist" $exe]
1679        } else {
1680                global env
1681
1682                if {[info exists env(GIT_DIR)]} {
1683                        set old_GIT_DIR $env(GIT_DIR)
1684                } else {
1685                        set old_GIT_DIR {}
1686                }
1687
1688                set pwd [pwd]
1689                cd [file dirname [gitdir]]
1690                set env(GIT_DIR) [file tail [gitdir]]
1691
1692                eval exec $cmd $revs &
1693
1694                if {$old_GIT_DIR eq {}} {
1695                        unset env(GIT_DIR)
1696                } else {
1697                        set env(GIT_DIR) $old_GIT_DIR
1698                }
1699                cd $pwd
1700
1701                ui_status $::starting_gitk_msg
1702                after 10000 {
1703                        ui_ready $starting_gitk_msg
1704                }
1705        }
1706}
1707
1708set is_quitting 0
1709
1710proc do_quit {} {
1711        global ui_comm is_quitting repo_config commit_type
1712        global GITGUI_BCK_exists GITGUI_BCK_i
1713        global ui_comm_spell
1714
1715        if {$is_quitting} return
1716        set is_quitting 1
1717
1718        if {[winfo exists $ui_comm]} {
1719                # -- Stash our current commit buffer.
1720                #
1721                set save [gitdir GITGUI_MSG]
1722                if {$GITGUI_BCK_exists && ![$ui_comm edit modified]} {
1723                        file rename -force [gitdir GITGUI_BCK] $save
1724                        set GITGUI_BCK_exists 0
1725                } else {
1726                        set msg [string trim [$ui_comm get 0.0 end]]
1727                        regsub -all -line {[ \r\t]+$} $msg {} msg
1728                        if {(![string match amend* $commit_type]
1729                                || [$ui_comm edit modified])
1730                                && $msg ne {}} {
1731                                catch {
1732                                        set fd [open $save w]
1733                                        puts -nonewline $fd $msg
1734                                        close $fd
1735                                }
1736                        } else {
1737                                catch {file delete $save}
1738                        }
1739                }
1740
1741                # -- Cancel our spellchecker if its running.
1742                #
1743                if {[info exists ui_comm_spell]} {
1744                        $ui_comm_spell stop
1745                }
1746
1747                # -- Remove our editor backup, its not needed.
1748                #
1749                after cancel $GITGUI_BCK_i
1750                if {$GITGUI_BCK_exists} {
1751                        catch {file delete [gitdir GITGUI_BCK]}
1752                }
1753
1754                # -- Stash our current window geometry into this repository.
1755                #
1756                set cfg_geometry [list]
1757                lappend cfg_geometry [wm geometry .]
1758                lappend cfg_geometry [lindex [.vpane sash coord 0] 0]
1759                lappend cfg_geometry [lindex [.vpane.files sash coord 0] 1]
1760                if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
1761                        set rc_geometry {}
1762                }
1763                if {$cfg_geometry ne $rc_geometry} {
1764                        catch {git config gui.geometry $cfg_geometry}
1765                }
1766        }
1767
1768        destroy .
1769}
1770
1771proc do_rescan {} {
1772        rescan ui_ready
1773}
1774
1775proc do_commit {} {
1776        commit_tree
1777}
1778
1779proc next_diff {} {
1780        global next_diff_p next_diff_w next_diff_i
1781        show_diff $next_diff_p $next_diff_w $next_diff_i
1782}
1783
1784proc toggle_or_diff {w x y} {
1785        global file_states file_lists current_diff_path ui_index ui_workdir
1786        global last_clicked selected_paths
1787
1788        set pos [split [$w index @$x,$y] .]
1789        set lno [lindex $pos 0]
1790        set col [lindex $pos 1]
1791        set path [lindex $file_lists($w) [expr {$lno - 1}]]
1792        if {$path eq {}} {
1793                set last_clicked {}
1794                return
1795        }
1796
1797        set last_clicked [list $w $lno]
1798        array unset selected_paths
1799        $ui_index tag remove in_sel 0.0 end
1800        $ui_workdir tag remove in_sel 0.0 end
1801
1802        if {$col == 0 && $y > 1} {
1803                set i [expr {$lno-1}]
1804                set ll [expr {[llength $file_lists($w)]-1}]
1805
1806                if {$i == $ll && $i == 0} {
1807                        set after {reshow_diff;}
1808                } else {
1809                        global next_diff_p next_diff_w next_diff_i
1810
1811                        set next_diff_w $w
1812
1813                        if {$i < $ll} {
1814                                set i [expr {$i + 1}]
1815                                set next_diff_i $i
1816                        } else {
1817                                set next_diff_i $i
1818                                set i [expr {$i - 1}]
1819                        }
1820
1821                        set next_diff_p [lindex $file_lists($w) $i]
1822
1823                        if {$next_diff_p ne {} && $current_diff_path ne {}} {
1824                                set after {next_diff;}
1825                        } else {
1826                                set after {}
1827                        }
1828                }
1829
1830                if {$w eq $ui_index} {
1831                        update_indexinfo \
1832                                "Unstaging [short_path $path] from commit" \
1833                                [list $path] \
1834                                [concat $after [list ui_ready]]
1835                } elseif {$w eq $ui_workdir} {
1836                        update_index \
1837                                "Adding [short_path $path]" \
1838                                [list $path] \
1839                                [concat $after [list ui_ready]]
1840                }
1841        } else {
1842                show_diff $path $w $lno
1843        }
1844}
1845
1846proc add_one_to_selection {w x y} {
1847        global file_lists last_clicked selected_paths
1848
1849        set lno [lindex [split [$w index @$x,$y] .] 0]
1850        set path [lindex $file_lists($w) [expr {$lno - 1}]]
1851        if {$path eq {}} {
1852                set last_clicked {}
1853                return
1854        }
1855
1856        if {$last_clicked ne {}
1857                && [lindex $last_clicked 0] ne $w} {
1858                array unset selected_paths
1859                [lindex $last_clicked 0] tag remove in_sel 0.0 end
1860        }
1861
1862        set last_clicked [list $w $lno]
1863        if {[catch {set in_sel $selected_paths($path)}]} {
1864                set in_sel 0
1865        }
1866        if {$in_sel} {
1867                unset selected_paths($path)
1868                $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
1869        } else {
1870                set selected_paths($path) 1
1871                $w tag add in_sel $lno.0 [expr {$lno + 1}].0
1872        }
1873}
1874
1875proc add_range_to_selection {w x y} {
1876        global file_lists last_clicked selected_paths
1877
1878        if {[lindex $last_clicked 0] ne $w} {
1879                toggle_or_diff $w $x $y
1880                return
1881        }
1882
1883        set lno [lindex [split [$w index @$x,$y] .] 0]
1884        set lc [lindex $last_clicked 1]
1885        if {$lc < $lno} {
1886                set begin $lc
1887                set end $lno
1888        } else {
1889                set begin $lno
1890                set end $lc
1891        }
1892
1893        foreach path [lrange $file_lists($w) \
1894                [expr {$begin - 1}] \
1895                [expr {$end - 1}]] {
1896                set selected_paths($path) 1
1897        }
1898        $w tag add in_sel $begin.0 [expr {$end + 1}].0
1899}
1900
1901proc show_more_context {} {
1902        global repo_config
1903        if {$repo_config(gui.diffcontext) < 99} {
1904                incr repo_config(gui.diffcontext)
1905                reshow_diff
1906        }
1907}
1908
1909proc show_less_context {} {
1910        global repo_config
1911        if {$repo_config(gui.diffcontext) >= 1} {
1912                incr repo_config(gui.diffcontext) -1
1913                reshow_diff
1914        }
1915}
1916
1917######################################################################
1918##
1919## ui construction
1920
1921load_config 0
1922apply_config
1923set ui_comm {}
1924
1925# -- Menu Bar
1926#
1927menu .mbar -tearoff 0
1928.mbar add cascade -label [mc Repository] -menu .mbar.repository
1929.mbar add cascade -label [mc Edit] -menu .mbar.edit
1930if {[is_enabled branch]} {
1931        .mbar add cascade -label [mc Branch] -menu .mbar.branch
1932}
1933if {[is_enabled multicommit] || [is_enabled singlecommit]} {
1934        .mbar add cascade -label [mc Commit@@noun] -menu .mbar.commit
1935}
1936if {[is_enabled transport]} {
1937        .mbar add cascade -label [mc Merge] -menu .mbar.merge
1938        .mbar add cascade -label [mc Remote] -menu .mbar.remote
1939}
1940. configure -menu .mbar
1941
1942# -- Repository Menu
1943#
1944menu .mbar.repository
1945
1946.mbar.repository add command \
1947        -label [mc "Browse Current Branch's Files"] \
1948        -command {browser::new $current_branch}
1949set ui_browse_current [.mbar.repository index last]
1950.mbar.repository add command \
1951        -label [mc "Browse Branch Files..."] \
1952        -command browser_open::dialog
1953.mbar.repository add separator
1954
1955.mbar.repository add command \
1956        -label [mc "Visualize Current Branch's History"] \
1957        -command {do_gitk $current_branch}
1958set ui_visualize_current [.mbar.repository index last]
1959.mbar.repository add command \
1960        -label [mc "Visualize All Branch History"] \
1961        -command {do_gitk --all}
1962.mbar.repository add separator
1963
1964proc current_branch_write {args} {
1965        global current_branch
1966        .mbar.repository entryconf $::ui_browse_current \
1967                -label [mc "Browse %s's Files" $current_branch]
1968        .mbar.repository entryconf $::ui_visualize_current \
1969                -label [mc "Visualize %s's History" $current_branch]
1970}
1971trace add variable current_branch write current_branch_write
1972
1973if {[is_enabled multicommit]} {
1974        .mbar.repository add command -label [mc "Database Statistics"] \
1975                -command do_stats
1976
1977        .mbar.repository add command -label [mc "Compress Database"] \
1978                -command do_gc
1979
1980        .mbar.repository add command -label [mc "Verify Database"] \
1981                -command do_fsck_objects
1982
1983        .mbar.repository add separator
1984
1985        if {[is_Cygwin]} {
1986                .mbar.repository add command \
1987                        -label [mc "Create Desktop Icon"] \
1988                        -command do_cygwin_shortcut
1989        } elseif {[is_Windows]} {
1990                .mbar.repository add command \
1991                        -label [mc "Create Desktop Icon"] \
1992                        -command do_windows_shortcut
1993        } elseif {[is_MacOSX]} {
1994                .mbar.repository add command \
1995                        -label [mc "Create Desktop Icon"] \
1996                        -command do_macosx_app
1997        }
1998}
1999
2000if {[is_MacOSX]} {
2001        proc ::tk::mac::Quit {args} { do_quit }
2002} else {
2003        .mbar.repository add command -label [mc Quit] \
2004                -command do_quit \
2005                -accelerator $M1T-Q
2006}
2007
2008# -- Edit Menu
2009#
2010menu .mbar.edit
2011.mbar.edit add command -label [mc Undo] \
2012        -command {catch {[focus] edit undo}} \
2013        -accelerator $M1T-Z
2014.mbar.edit add command -label [mc Redo] \
2015        -command {catch {[focus] edit redo}} \
2016        -accelerator $M1T-Y
2017.mbar.edit add separator
2018.mbar.edit add command -label [mc Cut] \
2019        -command {catch {tk_textCut [focus]}} \
2020        -accelerator $M1T-X
2021.mbar.edit add command -label [mc Copy] \
2022        -command {catch {tk_textCopy [focus]}} \
2023        -accelerator $M1T-C
2024.mbar.edit add command -label [mc Paste] \
2025        -command {catch {tk_textPaste [focus]; [focus] see insert}} \
2026        -accelerator $M1T-V
2027.mbar.edit add command -label [mc Delete] \
2028        -command {catch {[focus] delete sel.first sel.last}} \
2029        -accelerator Del
2030.mbar.edit add separator
2031.mbar.edit add command -label [mc "Select All"] \
2032        -command {catch {[focus] tag add sel 0.0 end}} \
2033        -accelerator $M1T-A
2034
2035# -- Branch Menu
2036#
2037if {[is_enabled branch]} {
2038        menu .mbar.branch
2039
2040        .mbar.branch add command -label [mc "Create..."] \
2041                -command branch_create::dialog \
2042                -accelerator $M1T-N
2043        lappend disable_on_lock [list .mbar.branch entryconf \
2044                [.mbar.branch index last] -state]
2045
2046        .mbar.branch add command -label [mc "Checkout..."] \
2047                -command branch_checkout::dialog \
2048                -accelerator $M1T-O
2049        lappend disable_on_lock [list .mbar.branch entryconf \
2050                [.mbar.branch index last] -state]
2051
2052        .mbar.branch add command -label [mc "Rename..."] \
2053                -command branch_rename::dialog
2054        lappend disable_on_lock [list .mbar.branch entryconf \
2055                [.mbar.branch index last] -state]
2056
2057        .mbar.branch add command -label [mc "Delete..."] \
2058                -command branch_delete::dialog
2059        lappend disable_on_lock [list .mbar.branch entryconf \
2060                [.mbar.branch index last] -state]
2061
2062        .mbar.branch add command -label [mc "Reset..."] \
2063                -command merge::reset_hard
2064        lappend disable_on_lock [list .mbar.branch entryconf \
2065                [.mbar.branch index last] -state]
2066}
2067
2068# -- Commit Menu
2069#
2070if {[is_enabled multicommit] || [is_enabled singlecommit]} {
2071        menu .mbar.commit
2072
2073        .mbar.commit add radiobutton \
2074                -label [mc "New Commit"] \
2075                -command do_select_commit_type \
2076                -variable selected_commit_type \
2077                -value new
2078        lappend disable_on_lock \
2079                [list .mbar.commit entryconf [.mbar.commit index last] -state]
2080
2081        .mbar.commit add radiobutton \
2082                -label [mc "Amend Last Commit"] \
2083                -command do_select_commit_type \
2084                -variable selected_commit_type \
2085                -value amend
2086        lappend disable_on_lock \
2087                [list .mbar.commit entryconf [.mbar.commit index last] -state]
2088
2089        .mbar.commit add separator
2090
2091        .mbar.commit add command -label [mc Rescan] \
2092                -command do_rescan \
2093                -accelerator F5
2094        lappend disable_on_lock \
2095                [list .mbar.commit entryconf [.mbar.commit index last] -state]
2096
2097        .mbar.commit add command -label [mc "Stage To Commit"] \
2098                -command do_add_selection \
2099                -accelerator $M1T-T
2100        lappend disable_on_lock \
2101                [list .mbar.commit entryconf [.mbar.commit index last] -state]
2102
2103        .mbar.commit add command -label [mc "Stage Changed Files To Commit"] \
2104                -command do_add_all \
2105                -accelerator $M1T-I
2106        lappend disable_on_lock \
2107                [list .mbar.commit entryconf [.mbar.commit index last] -state]
2108
2109        .mbar.commit add command -label [mc "Unstage From Commit"] \
2110                -command do_unstage_selection
2111        lappend disable_on_lock \
2112                [list .mbar.commit entryconf [.mbar.commit index last] -state]
2113
2114        .mbar.commit add command -label [mc "Revert Changes"] \
2115                -command do_revert_selection
2116        lappend disable_on_lock \
2117                [list .mbar.commit entryconf [.mbar.commit index last] -state]
2118
2119        .mbar.commit add separator
2120
2121        .mbar.commit add command -label [mc "Show Less Context"] \
2122                -command show_less_context \
2123                -accelerator $M1T-\-
2124
2125        .mbar.commit add command -label [mc "Show More Context"] \
2126                -command show_more_context \
2127                -accelerator $M1T-=
2128
2129        .mbar.commit add separator
2130
2131        .mbar.commit add command -label [mc "Sign Off"] \
2132                -command do_signoff \
2133                -accelerator $M1T-S
2134
2135        .mbar.commit add command -label [mc Commit@@verb] \
2136                -command do_commit \
2137                -accelerator $M1T-Return
2138        lappend disable_on_lock \
2139                [list .mbar.commit entryconf [.mbar.commit index last] -state]
2140}
2141
2142# -- Merge Menu
2143#
2144if {[is_enabled branch]} {
2145        menu .mbar.merge
2146        .mbar.merge add command -label [mc "Local Merge..."] \
2147                -command merge::dialog \
2148                -accelerator $M1T-M
2149        lappend disable_on_lock \
2150                [list .mbar.merge entryconf [.mbar.merge index last] -state]
2151        .mbar.merge add command -label [mc "Abort Merge..."] \
2152                -command merge::reset_hard
2153        lappend disable_on_lock \
2154                [list .mbar.merge entryconf [.mbar.merge index last] -state]
2155}
2156
2157# -- Transport Menu
2158#
2159if {[is_enabled transport]} {
2160        menu .mbar.remote
2161
2162        .mbar.remote add command \
2163                -label [mc "Push..."] \
2164                -command do_push_anywhere \
2165                -accelerator $M1T-P
2166        .mbar.remote add command \
2167                -label [mc "Delete..."] \
2168                -command remote_branch_delete::dialog
2169}
2170
2171if {[is_MacOSX]} {
2172        # -- Apple Menu (Mac OS X only)
2173        #
2174        .mbar add cascade -label Apple -menu .mbar.apple
2175        menu .mbar.apple
2176
2177        .mbar.apple add command -label [mc "About %s" [appname]] \
2178                -command do_about
2179        .mbar.apple add separator
2180        .mbar.apple add command \
2181                -label [mc "Preferences..."] \
2182                -command do_options \
2183                -accelerator $M1T-,
2184        bind . <$M1B-,> do_options
2185} else {
2186        # -- Edit Menu
2187        #
2188        .mbar.edit add separator
2189        .mbar.edit add command -label [mc "Options..."] \
2190                -command do_options
2191}
2192
2193# -- Help Menu
2194#
2195.mbar add cascade -label [mc Help] -menu .mbar.help
2196menu .mbar.help
2197
2198if {![is_MacOSX]} {
2199        .mbar.help add command -label [mc "About %s" [appname]] \
2200                -command do_about
2201}
2202
2203set browser {}
2204catch {set browser $repo_config(instaweb.browser)}
2205set doc_path [file dirname [gitexec]]
2206set doc_path [file join $doc_path Documentation index.html]
2207
2208if {[is_Cygwin]} {
2209        set doc_path [exec cygpath --mixed $doc_path]
2210}
2211
2212if {$browser eq {}} {
2213        if {[is_MacOSX]} {
2214                set browser open
2215        } elseif {[is_Cygwin]} {
2216                set program_files [file dirname [exec cygpath --windir]]
2217                set program_files [file join $program_files {Program Files}]
2218                set firefox [file join $program_files {Mozilla Firefox} firefox.exe]
2219                set ie [file join $program_files {Internet Explorer} IEXPLORE.EXE]
2220                if {[file exists $firefox]} {
2221                        set browser $firefox
2222                } elseif {[file exists $ie]} {
2223                        set browser $ie
2224                }
2225                unset program_files firefox ie
2226        }
2227}
2228
2229if {[file isfile $doc_path]} {
2230        set doc_url "file:$doc_path"
2231} else {
2232        set doc_url {http://www.kernel.org/pub/software/scm/git/docs/}
2233}
2234
2235if {$browser ne {}} {
2236        .mbar.help add command -label [mc "Online Documentation"] \
2237                -command [list exec $browser $doc_url &]
2238}
2239unset browser doc_path doc_url
2240
2241# -- Standard bindings
2242#
2243wm protocol . WM_DELETE_WINDOW do_quit
2244bind all <$M1B-Key-q> do_quit
2245bind all <$M1B-Key-Q> do_quit
2246bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
2247bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
2248
2249set subcommand_args {}
2250proc usage {} {
2251        puts stderr "usage: $::argv0 $::subcommand $::subcommand_args"
2252        exit 1
2253}
2254
2255# -- Not a normal commit type invocation?  Do that instead!
2256#
2257switch -- $subcommand {
2258browser -
2259blame {
2260        set subcommand_args {rev? path}
2261        if {$argv eq {}} usage
2262        set head {}
2263        set path {}
2264        set is_path 0
2265        foreach a $argv {
2266                if {$is_path || [file exists $_prefix$a]} {
2267                        if {$path ne {}} usage
2268                        set path $_prefix$a
2269                        break
2270                } elseif {$a eq {--}} {
2271                        if {$path ne {}} {
2272                                if {$head ne {}} usage
2273                                set head $path
2274                                set path {}
2275                        }
2276                        set is_path 1
2277                } elseif {$head eq {}} {
2278                        if {$head ne {}} usage
2279                        set head $a
2280                        set is_path 1
2281                } else {
2282                        usage
2283                }
2284        }
2285        unset is_path
2286
2287        if {$head ne {} && $path eq {}} {
2288                set path $_prefix$head
2289                set head {}
2290        }
2291
2292        if {$head eq {}} {
2293                load_current_branch
2294        } else {
2295                if {[regexp {^[0-9a-f]{1,39}$} $head]} {
2296                        if {[catch {
2297                                        set head [git rev-parse --verify $head]
2298                                } err]} {
2299                                puts stderr $err
2300                                exit 1
2301                        }
2302                }
2303                set current_branch $head
2304        }
2305
2306        switch -- $subcommand {
2307        browser {
2308                if {$head eq {}} {
2309                        if {$path ne {} && [file isdirectory $path]} {
2310                                set head $current_branch
2311                        } else {
2312                                set head $path
2313                                set path {}
2314                        }
2315                }
2316                browser::new $head $path
2317        }
2318        blame   {
2319                if {$head eq {} && ![file exists $path]} {
2320                        puts stderr [mc "fatal: cannot stat path %s: No such file or directory" $path]
2321                        exit 1
2322                }
2323                blame::new $head $path
2324        }
2325        }
2326        return
2327}
2328citool -
2329gui {
2330        if {[llength $argv] != 0} {
2331                puts -nonewline stderr "usage: $argv0"
2332                if {$subcommand ne {gui}
2333                        && [file tail $argv0] ne "git-$subcommand"} {
2334                        puts -nonewline stderr " $subcommand"
2335                }
2336                puts stderr {}
2337                exit 1
2338        }
2339        # fall through to setup UI for commits
2340}
2341default {
2342        puts stderr "usage: $argv0 \[{blame|browser|citool}\]"
2343        exit 1
2344}
2345}
2346
2347# -- Branch Control
2348#
2349frame .branch \
2350        -borderwidth 1 \
2351        -relief sunken
2352label .branch.l1 \
2353        -text [mc "Current Branch:"] \
2354        -anchor w \
2355        -justify left
2356label .branch.cb \
2357        -textvariable current_branch \
2358        -anchor w \
2359        -justify left
2360pack .branch.l1 -side left
2361pack .branch.cb -side left -fill x
2362pack .branch -side top -fill x
2363
2364# -- Main Window Layout
2365#
2366panedwindow .vpane -orient horizontal
2367panedwindow .vpane.files -orient vertical
2368.vpane add .vpane.files -sticky nsew -height 100 -width 200
2369pack .vpane -anchor n -side top -fill both -expand 1
2370
2371# -- Index File List
2372#
2373frame .vpane.files.index -height 100 -width 200
2374label .vpane.files.index.title -text [mc "Staged Changes (Will Commit)"] \
2375        -background lightgreen -foreground black
2376text $ui_index -background white -foreground black \
2377        -borderwidth 0 \
2378        -width 20 -height 10 \
2379        -wrap none \
2380        -cursor $cursor_ptr \
2381        -xscrollcommand {.vpane.files.index.sx set} \
2382        -yscrollcommand {.vpane.files.index.sy set} \
2383        -state disabled
2384scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview]
2385scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview]
2386pack .vpane.files.index.title -side top -fill x
2387pack .vpane.files.index.sx -side bottom -fill x
2388pack .vpane.files.index.sy -side right -fill y
2389pack $ui_index -side left -fill both -expand 1
2390
2391# -- Working Directory File List
2392#
2393frame .vpane.files.workdir -height 100 -width 200
2394label .vpane.files.workdir.title -text [mc "Unstaged Changes"] \
2395        -background lightsalmon -foreground black
2396text $ui_workdir -background white -foreground black \
2397        -borderwidth 0 \
2398        -width 20 -height 10 \
2399        -wrap none \
2400        -cursor $cursor_ptr \
2401        -xscrollcommand {.vpane.files.workdir.sx set} \
2402        -yscrollcommand {.vpane.files.workdir.sy set} \
2403        -state disabled
2404scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview]
2405scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview]
2406pack .vpane.files.workdir.title -side top -fill x
2407pack .vpane.files.workdir.sx -side bottom -fill x
2408pack .vpane.files.workdir.sy -side right -fill y
2409pack $ui_workdir -side left -fill both -expand 1
2410
2411.vpane.files add .vpane.files.workdir -sticky nsew
2412.vpane.files add .vpane.files.index -sticky nsew
2413
2414foreach i [list $ui_index $ui_workdir] {
2415        rmsel_tag $i
2416        $i tag conf in_diff -background [$i tag cget in_sel -background]
2417}
2418unset i
2419
2420# -- Diff and Commit Area
2421#
2422frame .vpane.lower -height 300 -width 400
2423frame .vpane.lower.commarea
2424frame .vpane.lower.diff -relief sunken -borderwidth 1
2425pack .vpane.lower.diff -fill both -expand 1
2426pack .vpane.lower.commarea -side bottom -fill x
2427.vpane add .vpane.lower -sticky nsew
2428
2429# -- Commit Area Buttons
2430#
2431frame .vpane.lower.commarea.buttons
2432label .vpane.lower.commarea.buttons.l -text {} \
2433        -anchor w \
2434        -justify left
2435pack .vpane.lower.commarea.buttons.l -side top -fill x
2436pack .vpane.lower.commarea.buttons -side left -fill y
2437
2438button .vpane.lower.commarea.buttons.rescan -text [mc Rescan] \
2439        -command do_rescan
2440pack .vpane.lower.commarea.buttons.rescan -side top -fill x
2441lappend disable_on_lock \
2442        {.vpane.lower.commarea.buttons.rescan conf -state}
2443
2444button .vpane.lower.commarea.buttons.incall -text [mc "Stage Changed"] \
2445        -command do_add_all
2446pack .vpane.lower.commarea.buttons.incall -side top -fill x
2447lappend disable_on_lock \
2448        {.vpane.lower.commarea.buttons.incall conf -state}
2449
2450button .vpane.lower.commarea.buttons.signoff -text [mc "Sign Off"] \
2451        -command do_signoff
2452pack .vpane.lower.commarea.buttons.signoff -side top -fill x
2453
2454button .vpane.lower.commarea.buttons.commit -text [mc Commit@@verb] \
2455        -command do_commit
2456pack .vpane.lower.commarea.buttons.commit -side top -fill x
2457lappend disable_on_lock \
2458        {.vpane.lower.commarea.buttons.commit conf -state}
2459
2460button .vpane.lower.commarea.buttons.push -text [mc Push] \
2461        -command do_push_anywhere
2462pack .vpane.lower.commarea.buttons.push -side top -fill x
2463
2464# -- Commit Message Buffer
2465#
2466frame .vpane.lower.commarea.buffer
2467frame .vpane.lower.commarea.buffer.header
2468set ui_comm .vpane.lower.commarea.buffer.t
2469set ui_coml .vpane.lower.commarea.buffer.header.l
2470radiobutton .vpane.lower.commarea.buffer.header.new \
2471        -text [mc "New Commit"] \
2472        -command do_select_commit_type \
2473        -variable selected_commit_type \
2474        -value new
2475lappend disable_on_lock \
2476        [list .vpane.lower.commarea.buffer.header.new conf -state]
2477radiobutton .vpane.lower.commarea.buffer.header.amend \
2478        -text [mc "Amend Last Commit"] \
2479        -command do_select_commit_type \
2480        -variable selected_commit_type \
2481        -value amend
2482lappend disable_on_lock \
2483        [list .vpane.lower.commarea.buffer.header.amend conf -state]
2484label $ui_coml \
2485        -anchor w \
2486        -justify left
2487proc trace_commit_type {varname args} {
2488        global ui_coml commit_type
2489        switch -glob -- $commit_type {
2490        initial       {set txt [mc "Initial Commit Message:"]}
2491        amend         {set txt [mc "Amended Commit Message:"]}
2492        amend-initial {set txt [mc "Amended Initial Commit Message:"]}
2493        amend-merge   {set txt [mc "Amended Merge Commit Message:"]}
2494        merge         {set txt [mc "Merge Commit Message:"]}
2495        *             {set txt [mc "Commit Message:"]}
2496        }
2497        $ui_coml conf -text $txt
2498}
2499trace add variable commit_type write trace_commit_type
2500pack $ui_coml -side left -fill x
2501pack .vpane.lower.commarea.buffer.header.amend -side right
2502pack .vpane.lower.commarea.buffer.header.new -side right
2503
2504text $ui_comm -background white -foreground black \
2505        -borderwidth 1 \
2506        -undo true \
2507        -maxundo 20 \
2508        -autoseparators true \
2509        -relief sunken \
2510        -width $repo_config(gui.commitmsgwidth) -height 9 -wrap none \
2511        -font font_diff \
2512        -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
2513scrollbar .vpane.lower.commarea.buffer.sby \
2514        -command [list $ui_comm yview]
2515pack .vpane.lower.commarea.buffer.header -side top -fill x
2516pack .vpane.lower.commarea.buffer.sby -side right -fill y
2517pack $ui_comm -side left -fill y
2518pack .vpane.lower.commarea.buffer -side left -fill y
2519
2520# -- Commit Message Buffer Context Menu
2521#
2522set ctxm .vpane.lower.commarea.buffer.ctxm
2523menu $ctxm -tearoff 0
2524$ctxm add command \
2525        -label [mc Cut] \
2526        -command {tk_textCut $ui_comm}
2527$ctxm add command \
2528        -label [mc Copy] \
2529        -command {tk_textCopy $ui_comm}
2530$ctxm add command \
2531        -label [mc Paste] \
2532        -command {tk_textPaste $ui_comm}
2533$ctxm add command \
2534        -label [mc Delete] \
2535        -command {$ui_comm delete sel.first sel.last}
2536$ctxm add separator
2537$ctxm add command \
2538        -label [mc "Select All"] \
2539        -command {focus $ui_comm;$ui_comm tag add sel 0.0 end}
2540$ctxm add command \
2541        -label [mc "Copy All"] \
2542        -command {
2543                $ui_comm tag add sel 0.0 end
2544                tk_textCopy $ui_comm
2545                $ui_comm tag remove sel 0.0 end
2546        }
2547$ctxm add separator
2548$ctxm add command \
2549        -label [mc "Sign Off"] \
2550        -command do_signoff
2551set ui_comm_ctxm $ctxm
2552
2553# -- Diff Header
2554#
2555proc trace_current_diff_path {varname args} {
2556        global current_diff_path diff_actions file_states
2557        if {$current_diff_path eq {}} {
2558                set s {}
2559                set f {}
2560                set p {}
2561                set o disabled
2562        } else {
2563                set p $current_diff_path
2564                set s [mapdesc [lindex $file_states($p) 0] $p]
2565                set f [mc "File:"]
2566                set p [escape_path $p]
2567                set o normal
2568        }
2569
2570        .vpane.lower.diff.header.status configure -text $s
2571        .vpane.lower.diff.header.file configure -text $f
2572        .vpane.lower.diff.header.path configure -text $p
2573        foreach w $diff_actions {
2574                uplevel #0 $w $o
2575        }
2576}
2577trace add variable current_diff_path write trace_current_diff_path
2578
2579frame .vpane.lower.diff.header -background gold
2580label .vpane.lower.diff.header.status \
2581        -background gold \
2582        -foreground black \
2583        -width $max_status_desc \
2584        -anchor w \
2585        -justify left
2586label .vpane.lower.diff.header.file \
2587        -background gold \
2588        -foreground black \
2589        -anchor w \
2590        -justify left
2591label .vpane.lower.diff.header.path \
2592        -background gold \
2593        -foreground black \
2594        -anchor w \
2595        -justify left
2596pack .vpane.lower.diff.header.status -side left
2597pack .vpane.lower.diff.header.file -side left
2598pack .vpane.lower.diff.header.path -fill x
2599set ctxm .vpane.lower.diff.header.ctxm
2600menu $ctxm -tearoff 0
2601$ctxm add command \
2602        -label [mc Copy] \
2603        -command {
2604                clipboard clear
2605                clipboard append \
2606                        -format STRING \
2607                        -type STRING \
2608                        -- $current_diff_path
2609        }
2610lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2611bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
2612
2613# -- Diff Body
2614#
2615frame .vpane.lower.diff.body
2616set ui_diff .vpane.lower.diff.body.t
2617text $ui_diff -background white -foreground black \
2618        -borderwidth 0 \
2619        -width 80 -height 15 -wrap none \
2620        -font font_diff \
2621        -xscrollcommand {.vpane.lower.diff.body.sbx set} \
2622        -yscrollcommand {.vpane.lower.diff.body.sby set} \
2623        -state disabled
2624scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
2625        -command [list $ui_diff xview]
2626scrollbar .vpane.lower.diff.body.sby -orient vertical \
2627        -command [list $ui_diff yview]
2628pack .vpane.lower.diff.body.sbx -side bottom -fill x
2629pack .vpane.lower.diff.body.sby -side right -fill y
2630pack $ui_diff -side left -fill both -expand 1
2631pack .vpane.lower.diff.header -side top -fill x
2632pack .vpane.lower.diff.body -side bottom -fill both -expand 1
2633
2634$ui_diff tag conf d_cr -elide true
2635$ui_diff tag conf d_@ -foreground blue -font font_diffbold
2636$ui_diff tag conf d_+ -foreground {#00a000}
2637$ui_diff tag conf d_- -foreground red
2638
2639$ui_diff tag conf d_++ -foreground {#00a000}
2640$ui_diff tag conf d_-- -foreground red
2641$ui_diff tag conf d_+s \
2642        -foreground {#00a000} \
2643        -background {#e2effa}
2644$ui_diff tag conf d_-s \
2645        -foreground red \
2646        -background {#e2effa}
2647$ui_diff tag conf d_s+ \
2648        -foreground {#00a000} \
2649        -background ivory1
2650$ui_diff tag conf d_s- \
2651        -foreground red \
2652        -background ivory1
2653
2654$ui_diff tag conf d<<<<<<< \
2655        -foreground orange \
2656        -font font_diffbold
2657$ui_diff tag conf d======= \
2658        -foreground orange \
2659        -font font_diffbold
2660$ui_diff tag conf d>>>>>>> \
2661        -foreground orange \
2662        -font font_diffbold
2663
2664$ui_diff tag raise sel
2665
2666# -- Diff Body Context Menu
2667#
2668set ctxm .vpane.lower.diff.body.ctxm
2669menu $ctxm -tearoff 0
2670$ctxm add command \
2671        -label [mc "Apply/Reverse Hunk"] \
2672        -command {apply_hunk $cursorX $cursorY}
2673set ui_diff_applyhunk [$ctxm index last]
2674lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
2675$ctxm add command \
2676        -label [mc "Apply/Reverse Line"] \
2677        -command {apply_line $cursorX $cursorY; do_rescan}
2678set ui_diff_applyline [$ctxm index last]
2679lappend diff_actions [list $ctxm entryconf $ui_diff_applyline -state]
2680$ctxm add separator
2681$ctxm add command \
2682        -label [mc "Show Less Context"] \
2683        -command show_less_context
2684lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2685$ctxm add command \
2686        -label [mc "Show More Context"] \
2687        -command show_more_context
2688lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2689$ctxm add separator
2690$ctxm add command \
2691        -label [mc Refresh] \
2692        -command reshow_diff
2693lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2694$ctxm add command \
2695        -label [mc Copy] \
2696        -command {tk_textCopy $ui_diff}
2697lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2698$ctxm add command \
2699        -label [mc "Select All"] \
2700        -command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
2701lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2702$ctxm add command \
2703        -label [mc "Copy All"] \
2704        -command {
2705                $ui_diff tag add sel 0.0 end
2706                tk_textCopy $ui_diff
2707                $ui_diff tag remove sel 0.0 end
2708        }
2709lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2710$ctxm add separator
2711$ctxm add command \
2712        -label [mc "Decrease Font Size"] \
2713        -command {incr_font_size font_diff -1}
2714lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2715$ctxm add command \
2716        -label [mc "Increase Font Size"] \
2717        -command {incr_font_size font_diff 1}
2718lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2719$ctxm add separator
2720$ctxm add command -label [mc "Options..."] \
2721        -command do_options
2722proc popup_diff_menu {ctxm x y X Y} {
2723        global current_diff_path file_states
2724        set ::cursorX $x
2725        set ::cursorY $y
2726        if {$::ui_index eq $::current_diff_side} {
2727                set l [mc "Unstage Hunk From Commit"]
2728                set t [mc "Unstage Line From Commit"]
2729        } else {
2730                set l [mc "Stage Hunk For Commit"]
2731                set t [mc "Stage Line For Commit"]
2732        }
2733        if {$::is_3way_diff
2734                || $current_diff_path eq {}
2735                || ![info exists file_states($current_diff_path)]
2736                || {_O} eq [lindex $file_states($current_diff_path) 0]} {
2737                set s disabled
2738        } else {
2739                set s normal
2740        }
2741        $ctxm entryconf $::ui_diff_applyhunk -state $s -label $l
2742        $ctxm entryconf $::ui_diff_applyline -state $s -label $t
2743        tk_popup $ctxm $X $Y
2744}
2745bind_button3 $ui_diff [list popup_diff_menu $ctxm %x %y %X %Y]
2746
2747# -- Status Bar
2748#
2749set main_status [::status_bar::new .status]
2750pack .status -anchor w -side bottom -fill x
2751$main_status show [mc "Initializing..."]
2752
2753# -- Load geometry
2754#
2755catch {
2756set gm $repo_config(gui.geometry)
2757wm geometry . [lindex $gm 0]
2758.vpane sash place 0 \
2759        [lindex $gm 1] \
2760        [lindex [.vpane sash coord 0] 1]
2761.vpane.files sash place 0 \
2762        [lindex [.vpane.files sash coord 0] 0] \
2763        [lindex $gm 2]
2764unset gm
2765}
2766
2767# -- Key Bindings
2768#
2769bind $ui_comm <$M1B-Key-Return> {do_commit;break}
2770bind $ui_comm <$M1B-Key-t> {do_add_selection;break}
2771bind $ui_comm <$M1B-Key-T> {do_add_selection;break}
2772bind $ui_comm <$M1B-Key-i> {do_add_all;break}
2773bind $ui_comm <$M1B-Key-I> {do_add_all;break}
2774bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
2775bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
2776bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
2777bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
2778bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
2779bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
2780bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2781bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2782bind $ui_comm <$M1B-Key-minus> {show_less_context;break}
2783bind $ui_comm <$M1B-Key-KP_Subtract> {show_less_context;break}
2784bind $ui_comm <$M1B-Key-equal> {show_more_context;break}
2785bind $ui_comm <$M1B-Key-plus> {show_more_context;break}
2786bind $ui_comm <$M1B-Key-KP_Add> {show_more_context;break}
2787
2788bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
2789bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
2790bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
2791bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
2792bind $ui_diff <$M1B-Key-v> {break}
2793bind $ui_diff <$M1B-Key-V> {break}
2794bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2795bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2796bind $ui_diff <Key-Up>     {catch {%W yview scroll -1 units};break}
2797bind $ui_diff <Key-Down>   {catch {%W yview scroll  1 units};break}
2798bind $ui_diff <Key-Left>   {catch {%W xview scroll -1 units};break}
2799bind $ui_diff <Key-Right>  {catch {%W xview scroll  1 units};break}
2800bind $ui_diff <Key-k>         {catch {%W yview scroll -1 units};break}
2801bind $ui_diff <Key-j>         {catch {%W yview scroll  1 units};break}
2802bind $ui_diff <Key-h>         {catch {%W xview scroll -1 units};break}
2803bind $ui_diff <Key-l>         {catch {%W xview scroll  1 units};break}
2804bind $ui_diff <Control-Key-b> {catch {%W yview scroll -1 pages};break}
2805bind $ui_diff <Control-Key-f> {catch {%W yview scroll  1 pages};break}
2806bind $ui_diff <Button-1>   {focus %W}
2807
2808if {[is_enabled branch]} {
2809        bind . <$M1B-Key-n> branch_create::dialog
2810        bind . <$M1B-Key-N> branch_create::dialog
2811        bind . <$M1B-Key-o> branch_checkout::dialog
2812        bind . <$M1B-Key-O> branch_checkout::dialog
2813        bind . <$M1B-Key-m> merge::dialog
2814        bind . <$M1B-Key-M> merge::dialog
2815}
2816if {[is_enabled transport]} {
2817        bind . <$M1B-Key-p> do_push_anywhere
2818        bind . <$M1B-Key-P> do_push_anywhere
2819}
2820
2821bind .   <Key-F5>     do_rescan
2822bind .   <$M1B-Key-r> do_rescan
2823bind .   <$M1B-Key-R> do_rescan
2824bind .   <$M1B-Key-s> do_signoff
2825bind .   <$M1B-Key-S> do_signoff
2826bind .   <$M1B-Key-t> do_add_selection
2827bind .   <$M1B-Key-T> do_add_selection
2828bind .   <$M1B-Key-i> do_add_all
2829bind .   <$M1B-Key-I> do_add_all
2830bind .   <$M1B-Key-minus> {show_less_context;break}
2831bind .   <$M1B-Key-KP_Subtract> {show_less_context;break}
2832bind .   <$M1B-Key-equal> {show_more_context;break}
2833bind .   <$M1B-Key-plus> {show_more_context;break}
2834bind .   <$M1B-Key-KP_Add> {show_more_context;break}
2835bind .   <$M1B-Key-Return> do_commit
2836foreach i [list $ui_index $ui_workdir] {
2837        bind $i <Button-1>       "toggle_or_diff         $i %x %y; break"
2838        bind $i <$M1B-Button-1>  "add_one_to_selection   $i %x %y; break"
2839        bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break"
2840}
2841unset i
2842
2843set file_lists($ui_index) [list]
2844set file_lists($ui_workdir) [list]
2845
2846wm title . "[appname] ([reponame]) [file normalize [file dirname [gitdir]]]"
2847focus -force $ui_comm
2848
2849# -- Warn the user about environmental problems.  Cygwin's Tcl
2850#    does *not* pass its env array onto any processes it spawns.
2851#    This means that git processes get none of our environment.
2852#
2853if {[is_Cygwin]} {
2854        set ignored_env 0
2855        set suggest_user {}
2856        set msg [mc "Possible environment issues exist.
2857
2858The following environment variables are probably
2859going to be ignored by any Git subprocess run
2860by %s:
2861
2862" [appname]]
2863        foreach name [array names env] {
2864                switch -regexp -- $name {
2865                {^GIT_INDEX_FILE$} -
2866                {^GIT_OBJECT_DIRECTORY$} -
2867                {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} -
2868                {^GIT_DIFF_OPTS$} -
2869                {^GIT_EXTERNAL_DIFF$} -
2870                {^GIT_PAGER$} -
2871                {^GIT_TRACE$} -
2872                {^GIT_CONFIG$} -
2873                {^GIT_CONFIG_LOCAL$} -
2874                {^GIT_(AUTHOR|COMMITTER)_DATE$} {
2875                        append msg " - $name\n"
2876                        incr ignored_env
2877                }
2878                {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} {
2879                        append msg " - $name\n"
2880                        incr ignored_env
2881                        set suggest_user $name
2882                }
2883                }
2884        }
2885        if {$ignored_env > 0} {
2886                append msg [mc "
2887This is due to a known issue with the
2888Tcl binary distributed by Cygwin."]
2889
2890                if {$suggest_user ne {}} {
2891                        append msg [mc "
2892
2893A good replacement for %s
2894is placing values for the user.name and
2895user.email settings into your personal
2896~/.gitconfig file.
2897" $suggest_user]
2898                }
2899                warn_popup $msg
2900        }
2901        unset ignored_env msg suggest_user name
2902}
2903
2904# -- Only initialize complex UI if we are going to stay running.
2905#
2906if {[is_enabled transport]} {
2907        load_all_remotes
2908
2909        set n [.mbar.remote index end]
2910        populate_push_menu
2911        populate_fetch_menu
2912        set n [expr {[.mbar.remote index end] - $n}]
2913        if {$n > 0} {
2914                .mbar.remote insert $n separator
2915        }
2916        unset n
2917}
2918
2919if {[winfo exists $ui_comm]} {
2920        set GITGUI_BCK_exists [load_message GITGUI_BCK]
2921
2922        # -- If both our backup and message files exist use the
2923        #    newer of the two files to initialize the buffer.
2924        #
2925        if {$GITGUI_BCK_exists} {
2926                set m [gitdir GITGUI_MSG]
2927                if {[file isfile $m]} {
2928                        if {[file mtime [gitdir GITGUI_BCK]] > [file mtime $m]} {
2929                                catch {file delete [gitdir GITGUI_MSG]}
2930                        } else {
2931                                $ui_comm delete 0.0 end
2932                                $ui_comm edit reset
2933                                $ui_comm edit modified false
2934                                catch {file delete [gitdir GITGUI_BCK]}
2935                                set GITGUI_BCK_exists 0
2936                        }
2937                }
2938                unset m
2939        }
2940
2941        proc backup_commit_buffer {} {
2942                global ui_comm GITGUI_BCK_exists
2943
2944                set m [$ui_comm edit modified]
2945                if {$m || $GITGUI_BCK_exists} {
2946                        set msg [string trim [$ui_comm get 0.0 end]]
2947                        regsub -all -line {[ \r\t]+$} $msg {} msg
2948
2949                        if {$msg eq {}} {
2950                                if {$GITGUI_BCK_exists} {
2951                                        catch {file delete [gitdir GITGUI_BCK]}
2952                                        set GITGUI_BCK_exists 0
2953                                }
2954                        } elseif {$m} {
2955                                catch {
2956                                        set fd [open [gitdir GITGUI_BCK] w]
2957                                        puts -nonewline $fd $msg
2958                                        close $fd
2959                                        set GITGUI_BCK_exists 1
2960                                }
2961                        }
2962
2963                        $ui_comm edit modified false
2964                }
2965
2966                set ::GITGUI_BCK_i [after 2000 backup_commit_buffer]
2967        }
2968
2969        backup_commit_buffer
2970
2971        # -- If the user has aspell available we can drive it
2972        #    in pipe mode to spellcheck the commit message.
2973        #
2974        set spell_cmd [list |]
2975        set spell_dict [get_config gui.spellingdictionary]
2976        lappend spell_cmd aspell
2977        if {$spell_dict ne {}} {
2978                lappend spell_cmd --master=$spell_dict
2979        }
2980        lappend spell_cmd --mode=none
2981        lappend spell_cmd --encoding=utf-8
2982        lappend spell_cmd pipe
2983        if {$spell_dict eq {none}
2984         || [catch {set spell_fd [open $spell_cmd r+]} spell_err]} {
2985                bind_button3 $ui_comm [list tk_popup $ui_comm_ctxm %X %Y]
2986        } else {
2987                set ui_comm_spell [spellcheck::init \
2988                        $spell_fd \
2989                        $ui_comm \
2990                        $ui_comm_ctxm \
2991                ]
2992        }
2993        unset -nocomplain spell_cmd spell_fd spell_err spell_dict
2994}
2995
2996lock_index begin-read
2997if {![winfo ismapped .]} {
2998        wm deiconify .
2999}
3000after 1 do_rescan
3001if {[is_enabled multicommit]} {
3002        after 1000 hint_gc
3003}