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