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