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