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