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