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