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