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